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,1847 @@
|
|
|
1
|
+
require "prism"
|
|
2
|
+
|
|
3
|
+
class CArray
|
|
4
|
+
module JIT
|
|
5
|
+
|
|
6
|
+
# Turns the source of a kernel block into an untyped IR tree, rejecting
|
|
7
|
+
# anything outside the recognized subset.
|
|
8
|
+
#
|
|
9
|
+
# The block's parameters are the loop indices and nothing else; arrays and
|
|
10
|
+
# scalars alike are reached as variables the block closed over. Which of
|
|
11
|
+
# those names are arrays has to be settled before the tree can be built,
|
|
12
|
+
# so the caller passes them in -- it knows, because it has the values.
|
|
13
|
+
class Analyzer
|
|
14
|
+
|
|
15
|
+
# Ruby Math methods that correspond 1:1 to a math.h function.
|
|
16
|
+
#
|
|
17
|
+
# Deliberately excludes anything whose C counterpart disagrees on
|
|
18
|
+
# semantics. `%` is the cautionary case: CArray floors it to agree with
|
|
19
|
+
# Ruby, while C's fmod truncates, so `%` is NOT lowered to fmod.
|
|
20
|
+
MATH_FUNCTIONS = {
|
|
21
|
+
:sqrt => "sqrt",
|
|
22
|
+
:cbrt => "cbrt",
|
|
23
|
+
:exp => "exp",
|
|
24
|
+
:log => "log",
|
|
25
|
+
:log2 => "log2",
|
|
26
|
+
:log10 => "log10",
|
|
27
|
+
:sin => "sin",
|
|
28
|
+
:cos => "cos",
|
|
29
|
+
:tan => "tan",
|
|
30
|
+
:asin => "asin",
|
|
31
|
+
:acos => "acos",
|
|
32
|
+
:atan => "atan",
|
|
33
|
+
:atan2 => "atan2",
|
|
34
|
+
:sinh => "sinh",
|
|
35
|
+
:cosh => "cosh",
|
|
36
|
+
:tanh => "tanh",
|
|
37
|
+
:hypot => "hypot",
|
|
38
|
+
:asinh => "asinh",
|
|
39
|
+
:acosh => "acosh",
|
|
40
|
+
:atanh => "atanh",
|
|
41
|
+
}.freeze
|
|
42
|
+
|
|
43
|
+
# The C99 complex form of each math.h function that has one, by the
|
|
44
|
+
# real name this IR carries. Which functions appear here is not a
|
|
45
|
+
# choice made here: it is the set CArray itself computes on a complex
|
|
46
|
+
# array, so that a formula gives the same answer whether it is applied
|
|
47
|
+
# to the array or compiled cell by cell.
|
|
48
|
+
#
|
|
49
|
+
# Absent, because a complex CArray raises CArray::DataTypeError for
|
|
50
|
+
# them: log10 and log2 (C99 has no clog10 or clog2), cbrt (no ccbrt),
|
|
51
|
+
# and the two-argument atan2 and hypot, which are about the plane a
|
|
52
|
+
# complex number already lives in.
|
|
53
|
+
COMPLEX_MATH_FUNCTIONS = {
|
|
54
|
+
"sqrt" => "csqrt",
|
|
55
|
+
"exp" => "cexp",
|
|
56
|
+
"log" => "clog",
|
|
57
|
+
"sin" => "csin",
|
|
58
|
+
"cos" => "ccos",
|
|
59
|
+
"tan" => "ctan",
|
|
60
|
+
"asin" => "casin",
|
|
61
|
+
"acos" => "cacos",
|
|
62
|
+
"atan" => "catan",
|
|
63
|
+
"sinh" => "csinh",
|
|
64
|
+
"cosh" => "ccosh",
|
|
65
|
+
"tanh" => "ctanh",
|
|
66
|
+
"asinh" => "casinh",
|
|
67
|
+
"acosh" => "cacosh",
|
|
68
|
+
"atanh" => "catanh",
|
|
69
|
+
}.freeze
|
|
70
|
+
|
|
71
|
+
# The postfix spelling `x.sqrt`, which CArray::CoreExtensions puts on
|
|
72
|
+
# Float and Integer so that one formula reads the same whether it is
|
|
73
|
+
# applied to a scalar or to a whole array. A per-cell kernel works on
|
|
74
|
+
# scalars pulled out of arrays, so a formula already written that way
|
|
75
|
+
# should not have to be rewritten to be compiled.
|
|
76
|
+
#
|
|
77
|
+
# Only the names that are 1:1 with math.h; the rest of the refinement
|
|
78
|
+
# is refused by name below.
|
|
79
|
+
POSTFIX_NAMES = %i[sqrt exp log log10 sin cos tan sinh cosh tanh
|
|
80
|
+
asin acos atan asinh acosh atanh].freeze
|
|
81
|
+
|
|
82
|
+
# Names the same refinement provides that are NOT 1:1 with math.h.
|
|
83
|
+
# `expm1` and `log1p` are the ones worth naming: C has functions by
|
|
84
|
+
# those names, and they exist precisely because `exp(x) - 1` and
|
|
85
|
+
# `log(1 + x)` lose precision for small x -- which is what the Ruby
|
|
86
|
+
# side computes. Lowering them to the C functions would silently
|
|
87
|
+
# produce different numbers.
|
|
88
|
+
REFUSED_POSTFIX = {
|
|
89
|
+
:expm1 => "the refinement computes exp(x) - 1, which is not what " \
|
|
90
|
+
"C's expm1 computes",
|
|
91
|
+
:log1p => "the refinement computes log(1 + x), which is not what " \
|
|
92
|
+
"C's log1p computes",
|
|
93
|
+
:rad => "write the multiplication out",
|
|
94
|
+
:deg => "write the multiplication out",
|
|
95
|
+
:square => "write x * x",
|
|
96
|
+
:rsqrt => "write 1.0 / Math.sqrt(x)",
|
|
97
|
+
:signbit => "write x < 0",
|
|
98
|
+
:deg_360 => "no math.h counterpart",
|
|
99
|
+
:deg_180 => "no math.h counterpart",
|
|
100
|
+
:rad_2pi => "no math.h counterpart",
|
|
101
|
+
:rad_pi => "no math.h counterpart",
|
|
102
|
+
}.freeze
|
|
103
|
+
|
|
104
|
+
ARITHMETIC_OPERATORS = [:+, :-, :*, :/, :%].freeze
|
|
105
|
+
# Ruby's bit operators on Integers, which C has too. What they do at
|
|
106
|
+
# the edges is C's answer rather than Ruby's -- a shift wraps and takes
|
|
107
|
+
# its count modulo the width, because CArray's own `<<` compiles to the
|
|
108
|
+
# same C shift (`ext/mkkernel.rb`, :bit_lshift) and this has to agree
|
|
109
|
+
# with CArray.
|
|
110
|
+
BIT_OPERATORS = [:&, :|, :^, :<<, :>>].freeze
|
|
111
|
+
|
|
112
|
+
# Methods whose result type is a property of the method: Float#floor and
|
|
113
|
+
# friends hand back an Integer in Ruby, and so must here.
|
|
114
|
+
CONVERSIONS = {
|
|
115
|
+
:floor => [:int64, "floor"],
|
|
116
|
+
:ceil => [:int64, "ceil"],
|
|
117
|
+
:round => [:int64, "round"],
|
|
118
|
+
:truncate => [:int64, "trunc"],
|
|
119
|
+
:to_i => [:int64, "trunc"],
|
|
120
|
+
:to_int => [:int64, "trunc"],
|
|
121
|
+
:to_f => [:double, nil],
|
|
122
|
+
}.freeze
|
|
123
|
+
|
|
124
|
+
# The parts of a complex number, by every spelling Ruby gives them.
|
|
125
|
+
# `imaginary` and `imag` are one method in Ruby and one node here.
|
|
126
|
+
COMPLEX_PARTS = {
|
|
127
|
+
:real => :real,
|
|
128
|
+
:imaginary => :imaginary,
|
|
129
|
+
:imag => :imaginary,
|
|
130
|
+
:conjugate => :conjugate,
|
|
131
|
+
:conj => :conjugate,
|
|
132
|
+
:arg => :arg,
|
|
133
|
+
:angle => :arg,
|
|
134
|
+
:phase => :arg,
|
|
135
|
+
}.freeze
|
|
136
|
+
|
|
137
|
+
# Constants under Math, emitted as literals so that the C sees exactly
|
|
138
|
+
# the double Ruby would have used.
|
|
139
|
+
MATH_CONSTANTS = { :PI => Math::PI, :E => Math::E }.freeze
|
|
140
|
+
COMPARISON_OPERATORS = [:<, :<=, :>, :>=, :==, :!=].freeze
|
|
141
|
+
|
|
142
|
+
attr_reader :parameter_names, :pointer_names, :address_arrays,
|
|
143
|
+
:address_parameters
|
|
144
|
+
# An offset that is an integer here rather than when the kernel runs.
|
|
145
|
+
# A literal is one; so is arithmetic over literals, which is the same
|
|
146
|
+
# number written a way that says where it came from -- `w[-RADIUS-1]`
|
|
147
|
+
# cannot be written, but `w[-2-1]` can, and a stencil drawn from a
|
|
148
|
+
# formula is usually written the second way.
|
|
149
|
+
#
|
|
150
|
+
# Nothing that reads a value: a captured integer arrives with the call,
|
|
151
|
+
# and one kernel serves every value of it, so a window built from one
|
|
152
|
+
# would have a radius the compiled loop does not know. The radius is
|
|
153
|
+
# what lets the interior be walked without asking, cell by cell,
|
|
154
|
+
# whether it is still inside.
|
|
155
|
+
LITERAL_OPERATORS = { :+ => true, :- => true, :* => true }.freeze
|
|
156
|
+
|
|
157
|
+
def literal_integer (node)
|
|
158
|
+
node = unwrap(node)
|
|
159
|
+
case node
|
|
160
|
+
when Prism::IntegerNode
|
|
161
|
+
node.value
|
|
162
|
+
when Prism::CallNode
|
|
163
|
+
return nil unless node.receiver
|
|
164
|
+
value = literal_integer(node.receiver)
|
|
165
|
+
return nil unless value
|
|
166
|
+
arguments = node.arguments&.arguments || []
|
|
167
|
+
if arguments.empty?
|
|
168
|
+
return -value if node.name == :-@
|
|
169
|
+
return value if node.name == :+@
|
|
170
|
+
return nil
|
|
171
|
+
end
|
|
172
|
+
return nil unless arguments.size == 1 && LITERAL_OPERATORS[node.name]
|
|
173
|
+
right = literal_integer(arguments.first)
|
|
174
|
+
right && value.public_send(node.name, right)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# How far the windows reach on each axis, as [lowest, highest] -- the
|
|
179
|
+
# radius, kept per side because a window need not be symmetric. It is
|
|
180
|
+
# what the caller walks the interior by.
|
|
181
|
+
attr_reader :window_reach
|
|
182
|
+
# The names the block gave its windows, which are the arrays a border
|
|
183
|
+
# rule applies to: the ones the block reaches away from the cell in.
|
|
184
|
+
attr_reader :windows
|
|
185
|
+
attr_reader :index_names, :array_names, :scalar_names, :c_function_names, :body,
|
|
186
|
+
:written_arrays, :array_ranks, :subscripts, :inner_ranges,
|
|
187
|
+
:contracted_names
|
|
188
|
+
|
|
189
|
+
# A kernel that mentions UNDEF is a masked kernel whatever its arrays
|
|
190
|
+
# happen to carry: it asks about masks, or makes them.
|
|
191
|
+
def uses_undef?
|
|
192
|
+
@uses_undef
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Collects the names a block reaches for without assigning them, so the
|
|
196
|
+
# caller can look up their values and say which are arrays.
|
|
197
|
+
# Whether the source names UNDEF anywhere, which decides that the kernel
|
|
198
|
+
# is a masked one before anything else is known about it.
|
|
199
|
+
def self.mentions_undef? (source, node: nil)
|
|
200
|
+
analyzer = allocate
|
|
201
|
+
block = node || analyzer.send(:parse_block, source)
|
|
202
|
+
analyzer.send(:names_constants, block).include?(:UNDEF)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# The names the block reaches for, and the names it assigns. The
|
|
206
|
+
# second set is not a subset of the first -- a name the block assigns
|
|
207
|
+
# is not free in it -- but in the whole-array spelling an assignment
|
|
208
|
+
# may still land in an array outside, so the caller looks both up.
|
|
209
|
+
def self.free_names (source, node: nil)
|
|
210
|
+
allocate.send(:scan_free_names, source, node).first
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def self.free_and_assigned_names (source, node: nil)
|
|
214
|
+
allocate.send(:scan_free_names, source, node)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# `rank` is given only for a block that takes no parameters and writes
|
|
218
|
+
# `out[] = a[] + b[]`. There the rank is a property of the arrays
|
|
219
|
+
# rather than of the block, so it arrives from the caller and the loop
|
|
220
|
+
# indices are named here.
|
|
221
|
+
# `steps` is the stride each outer index advances by, which decides
|
|
222
|
+
# whether an offset is a dependency at all: with a step of two, reading
|
|
223
|
+
# `a[i-1]` touches a cell this loop never writes.
|
|
224
|
+
# `contract` puts the block in the contraction convention: every parameter
|
|
225
|
+
# is an index, the ones that do not appear on the left are summed over, and
|
|
226
|
+
# every extent comes from the arrays' own shapes.
|
|
227
|
+
# `contract` is true for a contraction, or :probe to stop before the
|
|
228
|
+
# rewrite -- which is how the returned form learns the summand's type
|
|
229
|
+
# and the free indices' extents before it has an array to put them in.
|
|
230
|
+
# `result` names the array a returned contraction writes into.
|
|
231
|
+
# `function` puts the block in the third mode: its parameters are values
|
|
232
|
+
# rather than loop indices, its body is an expression whose value is
|
|
233
|
+
# returned, and it reaches no array. It is the smallest of the three --
|
|
234
|
+
# with no cell to address there is no extent, no direction and no mask,
|
|
235
|
+
# so most of what follows never runs.
|
|
236
|
+
def initialize (source, node: nil, array_names: [], c_functions: {}, rank: nil,
|
|
237
|
+
steps: nil, contract: false, result: nil, function: false,
|
|
238
|
+
pointers: {}, map: false, cell_names: [],
|
|
239
|
+
recursion: nil, windows: [], returns: true)
|
|
240
|
+
@source = source
|
|
241
|
+
@node = node
|
|
242
|
+
@array_names = array_names
|
|
243
|
+
# Arrays with one cell and no axis to walk -- a CScalar. There is no
|
|
244
|
+
# index to write for one, which is the whole of what distinguishes it
|
|
245
|
+
# from the one-cell CArray it otherwise is, so it is spelled `s[]` or
|
|
246
|
+
# named bare and the loop reads its cell at every iteration.
|
|
247
|
+
@cell_names = cell_names
|
|
248
|
+
# `jit_stencil`: the block's parameters are windows onto the arrays it
|
|
249
|
+
# was given, rather than the loop's indices. `a[-1, 1]` is then an
|
|
250
|
+
# offset from the cell the loop is on -- the same reach `a[i-1, j+1]`
|
|
251
|
+
# writes with the indices named, which is what it becomes here. The
|
|
252
|
+
# indices are this analyzer's, as they are for a block that names
|
|
253
|
+
# none, because a window has nowhere to write one.
|
|
254
|
+
@windows = windows
|
|
255
|
+
@c_functions = c_functions
|
|
256
|
+
@function = function
|
|
257
|
+
# A function declared `void` ends in a statement like any other; one
|
|
258
|
+
# that returns ends in the expression it returns. Which it is comes
|
|
259
|
+
# from the declaration, as everything else about a signature does.
|
|
260
|
+
@returns = returns
|
|
261
|
+
# `map` is jit_map rather than jit_each: the same block, read the
|
|
262
|
+
# same way, except that its last statement is a value and every cell
|
|
263
|
+
# of the result gets it. An assignment may be that statement, since
|
|
264
|
+
# in Ruby an assignment has the value it assigned.
|
|
265
|
+
@map = map
|
|
266
|
+
# In a function, a parameter declared `const double *` is reached the
|
|
267
|
+
# way C reaches it. The value says what may be done with it: true to
|
|
268
|
+
# read and write, false to read only (= const), nil for one that
|
|
269
|
+
# points at nothing in particular and so cannot be reached at all.
|
|
270
|
+
@pointers = pointers
|
|
271
|
+
# What the function being compiled is called, what it takes and what
|
|
272
|
+
# it returns -- so its own body can call it. C puts a declarator's
|
|
273
|
+
# name in scope inside the body it heads, and this is that. Nil for
|
|
274
|
+
# a kernel, and for a function declared without a name, which has
|
|
275
|
+
# nothing to call itself by.
|
|
276
|
+
@recursion = recursion
|
|
277
|
+
@whole_array = false
|
|
278
|
+
# How far the windows reach on each axis, filled in as they are read.
|
|
279
|
+
@window_reach = Array.new(rank.to_i) { [0, 0] }
|
|
280
|
+
@uses_undef = false
|
|
281
|
+
@calls_for_effect = false
|
|
282
|
+
@outer_names = []
|
|
283
|
+
@inner_names = []
|
|
284
|
+
# How many loops the statement being built stands inside. `break`
|
|
285
|
+
# needs a loop to leave and does not care which kind: an inner loop
|
|
286
|
+
# brings an index and a `while` brings none, but both are loops in C
|
|
287
|
+
# and in the Ruby they stand for.
|
|
288
|
+
@loop_depth = 0
|
|
289
|
+
@inner_names_seen = []
|
|
290
|
+
@array_ranks = {}
|
|
291
|
+
@inner_ranges = {}
|
|
292
|
+
@subscripts = Hash.new { |hash, key| hash[key] = [] }
|
|
293
|
+
@given_rank = rank
|
|
294
|
+
@steps = steps
|
|
295
|
+
@contract = contract
|
|
296
|
+
@result = result
|
|
297
|
+
@contracted_names = []
|
|
298
|
+
@free_names = []
|
|
299
|
+
@index_names = []
|
|
300
|
+
@local_names = []
|
|
301
|
+
@assigned_names = []
|
|
302
|
+
@scalar_names = []
|
|
303
|
+
@c_function_names = []
|
|
304
|
+
@parameter_names = []
|
|
305
|
+
@pointer_names = []
|
|
306
|
+
@address_arrays = []
|
|
307
|
+
@address_parameters = {}
|
|
308
|
+
@read_offsets = Hash.new { |hash, key| hash[key] = [] }
|
|
309
|
+
@written_arrays = []
|
|
310
|
+
analyze
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def rank
|
|
314
|
+
@index_names.size
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# For one axis of one array: which indices walk it and how far the kernel
|
|
318
|
+
# reaches along it with each, plus any fixed positions it is also read
|
|
319
|
+
# at. Returns [[[index, minimum, maximum], ...], constants].
|
|
320
|
+
#
|
|
321
|
+
# An axis may be walked by more than one index, which is how a covariance
|
|
322
|
+
# is written -- `c[p,a] * c[p,b]` reads the same axis at two independent
|
|
323
|
+
# positions. A fixed subscript sits alongside them, so a kernel may read
|
|
324
|
+
# `a[i, 0]` and `a[i, j]` on the same axis too.
|
|
325
|
+
#
|
|
326
|
+
# This holds for an array the kernel writes as well. In `v[a,b] =
|
|
327
|
+
# v[a,a]` the cell being read is one this loop also writes, at b == a,
|
|
328
|
+
# so cells reached before that read the old value and cells reached
|
|
329
|
+
# after it read the new one -- and the answer depends on the order. It
|
|
330
|
+
# is not an ambiguity, though: the extent states the order, so the
|
|
331
|
+
# kernel runs the order it was given and means what the same Ruby loop
|
|
332
|
+
# means. What is lost is only the check, since there is no fixed offset
|
|
333
|
+
# here to derive a direction from and compare the extent against.
|
|
334
|
+
def axis_use (array, axis)
|
|
335
|
+
uses = @subscripts[array].map { |per_axis| per_axis[axis] }.compact
|
|
336
|
+
walked = uses.reject { |index, _| index.nil? }
|
|
337
|
+
constants = uses.select { |index, _| index.nil? }.map(&:last)
|
|
338
|
+
.select { |node|
|
|
339
|
+
!node.is_a?(Node) || Analyzer.fixed_subscript?(node)
|
|
340
|
+
}
|
|
341
|
+
names = walked.map(&:first).uniq
|
|
342
|
+
walkers = names.map { |name|
|
|
343
|
+
[name, walked.select { |index, _| index == name }.map(&:last)]
|
|
344
|
+
}
|
|
345
|
+
[walkers, constants]
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Per array: the axes it is read at an index only the running kernel
|
|
349
|
+
# knows. Those axes are bounds-checked as they are reached.
|
|
350
|
+
def dynamic_axes (array)
|
|
351
|
+
axes = []
|
|
352
|
+
@subscripts[array].each do |per_axis|
|
|
353
|
+
per_axis.each_with_index do |(index, offset), axis|
|
|
354
|
+
next unless index.nil? && offset.is_a?(Node)
|
|
355
|
+
axes << axis unless Analyzer.fixed_subscript?(offset)
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
axes.uniq.sort
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# A write is addressed the way a read is: every axis either walks with an
|
|
362
|
+
# outer index, at whatever offset, or is pinned at a position known
|
|
363
|
+
# before the first cell.
|
|
364
|
+
#
|
|
365
|
+
# Two iterations may land on the same cell -- `box[0]` puts every one of
|
|
366
|
+
# them there -- and that is not an ambiguity: the extent states the
|
|
367
|
+
# order, so what the array holds afterwards is what the same Ruby loop
|
|
368
|
+
# would leave in it. An inner loop already rests on exactly that, an
|
|
369
|
+
# accumulator being one cell written once per pass.
|
|
370
|
+
def walking_subscripts? (subscripts)
|
|
371
|
+
subscripts.all? { |index, offset|
|
|
372
|
+
if index
|
|
373
|
+
@outer_names.include?(index)
|
|
374
|
+
else
|
|
375
|
+
!offset.is_a?(Node) || Analyzer.fixed_subscript?(offset)
|
|
376
|
+
end
|
|
377
|
+
}
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# A write is a scatter when some axis of it is addressed by a value only
|
|
381
|
+
# the running kernel knows. The axes that are not may still be the
|
|
382
|
+
# loop's own indices.
|
|
383
|
+
def computed_subscripts? (subscripts)
|
|
384
|
+
subscripts.any? { |index, offset|
|
|
385
|
+
index.nil? && offset.is_a?(Node) && !Analyzer.fixed_subscript?(offset)
|
|
386
|
+
} && subscripts.all? { |index, offset|
|
|
387
|
+
(index && offset.zero?) ||
|
|
388
|
+
(index.nil? && offset.is_a?(Node) && !Analyzer.fixed_subscript?(offset))
|
|
389
|
+
}
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def arrays_used
|
|
393
|
+
(@subscripts.keys + @written_arrays).uniq
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def inner_index? (name)
|
|
397
|
+
@inner_names.include?(name)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
private
|
|
401
|
+
|
|
402
|
+
def scan_free_names (source, node)
|
|
403
|
+
block = node || parse_block(source)
|
|
404
|
+
parameters = block.parameters
|
|
405
|
+
list = parameters && parameters.parameters
|
|
406
|
+
indices = list ? list.requireds.map(&:name) : []
|
|
407
|
+
assigned = collect_assigned_names(block.body)
|
|
408
|
+
# An inner loop names an index too, and it is no more a captured
|
|
409
|
+
# variable than the outer ones are.
|
|
410
|
+
inner = collect_block_parameters(block.body)
|
|
411
|
+
constants = names_constants(block.body).reject { |name| resolved_here?(name) }
|
|
412
|
+
free = (collect_names(block.body) + constants).uniq -
|
|
413
|
+
indices - assigned - inner
|
|
414
|
+
[free, assigned.uniq - indices - inner]
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# Names this analyzer answers for itself, so they are never captured:
|
|
418
|
+
# UNDEF is a mark rather than a value, and `Math` is written out in C
|
|
419
|
+
# (see build_math_call and build_constant_path).
|
|
420
|
+
NAMES_RESOLVED_HERE = [:UNDEF, :Math].freeze
|
|
421
|
+
|
|
422
|
+
# `Math::PI` and the rest are written out in C, so they are not among
|
|
423
|
+
# the names captured either.
|
|
424
|
+
def resolved_here? (name)
|
|
425
|
+
NAMES_RESOLVED_HERE.include?(name) || name.to_s.start_with?("Math::")
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# `raise "..."` says what it says: the class is not named and the
|
|
429
|
+
# message is a literal, so nothing under it is a name the block reached
|
|
430
|
+
# for. Both scans stop here, or `raise ArgumentError, "x"` would be
|
|
431
|
+
# refused for capturing a Class and never reach the reason it is
|
|
432
|
+
# actually refused for.
|
|
433
|
+
def raise_call? (node)
|
|
434
|
+
node.is_a?(Prism::CallNode) && node.receiver.nil? && node.name == :raise
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def names_constants (node)
|
|
438
|
+
return [] unless node
|
|
439
|
+
return [] if raise_call?(node)
|
|
440
|
+
# `Foo::TABLE` names one thing, and `Foo` on its own names none of
|
|
441
|
+
# it, so a path is read whole and not descended into.
|
|
442
|
+
return [node.slice.to_sym] if node.is_a?(Prism::ConstantPathNode)
|
|
443
|
+
names = []
|
|
444
|
+
names << node.name if node.is_a?(Prism::ConstantReadNode)
|
|
445
|
+
node.compact_child_nodes.each { |child| names.concat(names_constants(child)) }
|
|
446
|
+
names
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def collect_block_parameters (node)
|
|
450
|
+
return [] unless node
|
|
451
|
+
names = []
|
|
452
|
+
if node.is_a?(Prism::BlockParametersNode) && node.parameters
|
|
453
|
+
names.concat(node.parameters.requireds.map(&:name))
|
|
454
|
+
end
|
|
455
|
+
node.compact_child_nodes.each do |child|
|
|
456
|
+
names.concat(collect_block_parameters(child))
|
|
457
|
+
end
|
|
458
|
+
names
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def collect_names (node)
|
|
462
|
+
return [] unless node
|
|
463
|
+
return [] if raise_call?(node)
|
|
464
|
+
names = []
|
|
465
|
+
case node
|
|
466
|
+
when Prism::LocalVariableReadNode
|
|
467
|
+
names << node.name
|
|
468
|
+
when Prism::CallNode
|
|
469
|
+
if node.receiver.nil? && node.arguments.nil? && node.block.nil?
|
|
470
|
+
names << node.name
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
node.compact_child_nodes.each { |child| names.concat(collect_names(child)) }
|
|
474
|
+
names
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def analyze
|
|
478
|
+
block = @node || parse_block(@source)
|
|
479
|
+
read_parameters(block)
|
|
480
|
+
|
|
481
|
+
statements = block.body ? block.body.body : []
|
|
482
|
+
if statements.empty?
|
|
483
|
+
raise Unsupported.new("kernel body is empty")
|
|
484
|
+
end
|
|
485
|
+
@assigned_names = collect_assigned_names(block.body)
|
|
486
|
+
|
|
487
|
+
# A contraction may end in a bare expression rather than an
|
|
488
|
+
# assignment; that is the form that allocates its result and returns.
|
|
489
|
+
# A function always ends in one -- that expression is what it returns.
|
|
490
|
+
last = statements.last
|
|
491
|
+
# A block that ends in an assignment still has that assignment's
|
|
492
|
+
# value, which is what Ruby says it has -- so jit_map keeps the
|
|
493
|
+
# statement and reads the cell back for the value.
|
|
494
|
+
map_assignment = @map && last.is_a?(Prism::LocalVariableWriteNode)
|
|
495
|
+
returns_value = (@function && @returns) || (@map && !map_assignment) ||
|
|
496
|
+
(@contract &&
|
|
497
|
+
!(last.is_a?(Prism::CallNode) && last.name == :[]=))
|
|
498
|
+
built = statements[0..-2].map { |node| build_statement(node) }
|
|
499
|
+
if map_assignment
|
|
500
|
+
built << build_statement(last)
|
|
501
|
+
@map_value = build_name_read(last.name, last.location)
|
|
502
|
+
else
|
|
503
|
+
built << (returns_value ? build(last) : build_statement(last))
|
|
504
|
+
@map_value = built.last if @map
|
|
505
|
+
end
|
|
506
|
+
@body = KernelBody.new(built)
|
|
507
|
+
|
|
508
|
+
return if @contract == :probe || @map == :probe
|
|
509
|
+
return if @function
|
|
510
|
+
|
|
511
|
+
map_body if @map
|
|
512
|
+
contract_body if @contract
|
|
513
|
+
verify_written_arrays_are_not_read_through_inner_indices
|
|
514
|
+
|
|
515
|
+
# A kernel that only calls a C function still does something: what
|
|
516
|
+
# it did is wherever the addresses it handed over pointed. Only a
|
|
517
|
+
# kernel that neither wrote nor called has put its work nowhere.
|
|
518
|
+
if @written_arrays.empty? && !@calls_for_effect
|
|
519
|
+
raise Unsupported.new(
|
|
520
|
+
@whole_array ?
|
|
521
|
+
"this block computes a value and puts it nowhere; assign it to " \
|
|
522
|
+
"an array, as in `out = ...`, or ask for the value back with " \
|
|
523
|
+
"`CArray.jit_map`" :
|
|
524
|
+
"the kernel writes to no array; assign to one, as in " \
|
|
525
|
+
"`out[i] = ...`")
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
# An array the kernel writes is written once per outer cell; reading it
|
|
530
|
+
# through an inner index would reach cells other outer iterations own,
|
|
531
|
+
# and no evaluation order settles that.
|
|
532
|
+
def verify_written_arrays_are_not_read_through_inner_indices
|
|
533
|
+
@written_arrays.each do |array|
|
|
534
|
+
@subscripts[array].each do |per_axis|
|
|
535
|
+
per_axis.each do |index, _|
|
|
536
|
+
next unless @inner_names_seen.include?(index)
|
|
537
|
+
raise Unsupported.new(
|
|
538
|
+
"`#{array}` is written by this kernel, so it cannot also be " \
|
|
539
|
+
"read through the inner index `#{index}`")
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
# Turns `c[i,j] = a[i,k] * b[k,j]` into the loops it stands for.
|
|
546
|
+
#
|
|
547
|
+
# Which indices are summed is not the assignment's business: an index
|
|
548
|
+
# that appears twice in the term is summed, and that repetition is the
|
|
549
|
+
# notation -- it is what stands in for the sigma. An index appearing
|
|
550
|
+
# once is free. The left-hand side says where the result goes and in
|
|
551
|
+
# what order its axes lie; it cannot make an index disappear.
|
|
552
|
+
# The block's value is what every cell of the result gets, so the last
|
|
553
|
+
# expression becomes a write into the result at the cell the loop is on.
|
|
554
|
+
# jit_map writes its value into a result of its own, at the cell the
|
|
555
|
+
# loop is on, and that array is what comes back. Where the block ended
|
|
556
|
+
# in an assignment the assignment stays, and the value is the cell it
|
|
557
|
+
# just wrote -- which is the value Ruby gives that statement.
|
|
558
|
+
def map_body
|
|
559
|
+
statements = @body.statements
|
|
560
|
+
statements = statements[0..-2] if statements.last.equal?(@map_value)
|
|
561
|
+
subscripts = @outer_names.map { |name| [name, 0] }
|
|
562
|
+
write = ElementWrite.new(@result, @map_value, @map_value.location,
|
|
563
|
+
subscripts)
|
|
564
|
+
@body = KernelBody.new(statements + [write])
|
|
565
|
+
record_array_rank(@result, subscripts.size)
|
|
566
|
+
record_subscripts(@result, subscripts)
|
|
567
|
+
@written_arrays << @result unless @written_arrays.include?(@result)
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
def contract_body
|
|
571
|
+
statements = @body.statements
|
|
572
|
+
writes = statements.grep(ElementWrite)
|
|
573
|
+
unless writes.size <= 1 && (writes.empty? || statements.last.equal?(writes.first))
|
|
574
|
+
raise Unsupported.new(
|
|
575
|
+
"a contraction is one expression, optionally assigned into an " \
|
|
576
|
+
"array of your own")
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
write = writes.first
|
|
580
|
+
unless write
|
|
581
|
+
# The returned form: the free indices, in the order the block named
|
|
582
|
+
# them, are the result's axes.
|
|
583
|
+
_, summed = classify_indices(statements, statements.last)
|
|
584
|
+
free = @index_names - summed
|
|
585
|
+
# With every index summed the result is a single number, which lives
|
|
586
|
+
# in a one-cell array at a fixed subscript.
|
|
587
|
+
subscripts = free.empty? ? [[nil, 0]] : free.map { |name| [name, 0] }
|
|
588
|
+
write = ElementWrite.new(@result, statements.last, statements.last.location,
|
|
589
|
+
subscripts)
|
|
590
|
+
statements = statements[0..-2] + [write]
|
|
591
|
+
@body = KernelBody.new(statements)
|
|
592
|
+
record_array_rank(@result, subscripts.size)
|
|
593
|
+
record_subscripts(@result, subscripts)
|
|
594
|
+
@written_arrays << @result
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
written = write.subscripts.reject { |index, _| index.nil? }.map(&:first)
|
|
598
|
+
if written.uniq.size != written.size
|
|
599
|
+
raise Unsupported.new(
|
|
600
|
+
"the left-hand side names #{written.join(', ')}; an index can walk " \
|
|
601
|
+
"one of its axes only")
|
|
602
|
+
end
|
|
603
|
+
unless write.subscripts.all? { |index, offset| index.nil? || offset.zero? }
|
|
604
|
+
raise Unsupported.new(
|
|
605
|
+
"a contraction writes the cell it is on, with no offset")
|
|
606
|
+
end
|
|
607
|
+
if @subscripts[write.array].size > 1
|
|
608
|
+
raise Unsupported.new(
|
|
609
|
+
"`#{write.array}` is both written and read here, which is a " \
|
|
610
|
+
"recurrence rather than a contraction; write it with jit_for")
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
free, summed = classify_indices(statements, write)
|
|
614
|
+
unless written.sort == free.sort
|
|
615
|
+
raise Unsupported.new(describe_index_mismatch(written, free, summed))
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
@free_names = free
|
|
619
|
+
@contracted_names = summed
|
|
620
|
+
@outer_names = written
|
|
621
|
+
@index_names = written
|
|
622
|
+
|
|
623
|
+
accumulator = free_local_name
|
|
624
|
+
summand = statements[0..-2] + [Assignment.new(accumulator,
|
|
625
|
+
BinaryOperation.new(:+,
|
|
626
|
+
LocalRead.new(accumulator),
|
|
627
|
+
write.expression))]
|
|
628
|
+
inner = @contracted_names.each_with_index.reverse_each.inject(summand) {
|
|
629
|
+
|body, (name, position)|
|
|
630
|
+
slot = @outer_names.size + position
|
|
631
|
+
from = BoundsValue.new(3 * slot)
|
|
632
|
+
to = BoundsValue.new(3 * slot + 1)
|
|
633
|
+
@inner_ranges[name] = [from, to]
|
|
634
|
+
[InnerLoop.new(name, from, to, body)]
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
@body = KernelBody.new(
|
|
638
|
+
[Assignment.new(accumulator, zero_for(write))] + inner +
|
|
639
|
+
[ElementWrite.new(write.array, LocalRead.new(accumulator), write.location,
|
|
640
|
+
write.subscripts)])
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
# Counts where each index sits on a tensor, on the right-hand side only.
|
|
644
|
+
# Once is free, twice is summed, and more than twice is not the
|
|
645
|
+
# convention -- it says nothing about which pair to sum.
|
|
646
|
+
def classify_indices (statements, write)
|
|
647
|
+
counts = Hash.new(0)
|
|
648
|
+
subscripts_of(statements, write).each { |index, _| counts[index] += 1 }
|
|
649
|
+
|
|
650
|
+
missing = @index_names - counts.keys
|
|
651
|
+
unless missing.empty?
|
|
652
|
+
raise Unsupported.new(
|
|
653
|
+
"#{missing.map { |name| "`#{name}`" }.join(', ')} " \
|
|
654
|
+
"#{missing.size == 1 ? 'names no axis' : 'name no axis'} here")
|
|
655
|
+
end
|
|
656
|
+
crowded = counts.select { |_, count| count > 2 }.keys
|
|
657
|
+
unless crowded.empty?
|
|
658
|
+
raise Unsupported.new(
|
|
659
|
+
"#{crowded.map { |name| "`#{name}`" }.join(', ')} appears more " \
|
|
660
|
+
"than twice; a contraction sums a pair, and there is no pair to " \
|
|
661
|
+
"choose")
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
[@index_names.select { |name| counts[name] == 1 },
|
|
665
|
+
@index_names.select { |name| counts[name] == 2 }]
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
# Every subscript on the right-hand side: the summand, and whatever the
|
|
669
|
+
# locals before it read.
|
|
670
|
+
def subscripts_of (statements, write)
|
|
671
|
+
summand = write.is_a?(ElementWrite) ? write.expression : write
|
|
672
|
+
collected = []
|
|
673
|
+
walk = lambda do |node|
|
|
674
|
+
return unless node.is_a?(Node)
|
|
675
|
+
collected.concat(node.subscripts) if node.is_a?(ElementRead)
|
|
676
|
+
collected.concat(node.subscripts) if node.is_a?(MaskTest)
|
|
677
|
+
node.children.each { |child| walk.call(child) }
|
|
678
|
+
end
|
|
679
|
+
statements[0..-2].each { |statement| walk.call(statement) }
|
|
680
|
+
walk.call(summand)
|
|
681
|
+
collected.reject { |index, _| index.nil? }
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
def describe_index_mismatch (written, free, summed)
|
|
685
|
+
summed_on_left = written & summed
|
|
686
|
+
unless summed_on_left.empty?
|
|
687
|
+
return "#{summed_on_left.map { |name| "`#{name}`" }.join(', ')} " \
|
|
688
|
+
"#{summed_on_left.size == 1 ? 'appears' : 'appear'} twice on " \
|
|
689
|
+
"the right, so #{summed_on_left.size == 1 ? 'it is' : 'they are'} " \
|
|
690
|
+
"summed over and cannot also be free"
|
|
691
|
+
end
|
|
692
|
+
dropped = free - written
|
|
693
|
+
"#{dropped.map { |name| "`#{name}`" }.join(', ')} " \
|
|
694
|
+
"#{dropped.size == 1 ? 'appears' : 'appear'} once, so " \
|
|
695
|
+
"#{dropped.size == 1 ? 'it is' : 'they are'} free and must be on the " \
|
|
696
|
+
"left. A contraction sums the indices that appear twice; to sum one " \
|
|
697
|
+
"that does not, write the loop with jit_for, or use sum(axis:)"
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
# The sum starts from zero of whatever the summand is; which zero that
|
|
701
|
+
# is falls out of the type assignment.
|
|
702
|
+
def zero_for (write)
|
|
703
|
+
ZeroLike.new(write.expression, write.location)
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
public
|
|
707
|
+
|
|
708
|
+
# For the returned form, before the rewrite: which indices stay free, so
|
|
709
|
+
# the caller can size the result.
|
|
710
|
+
def probe_free_names
|
|
711
|
+
_, summed = classify_indices(@body.statements, @body.statements.last)
|
|
712
|
+
@index_names - summed
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
private
|
|
716
|
+
|
|
717
|
+
def free_local_name
|
|
718
|
+
name = :contraction
|
|
719
|
+
name = :"#{name}_" while @local_names.include?(name)
|
|
720
|
+
name
|
|
721
|
+
end
|
|
722
|
+
|
|
723
|
+
def parse_block (source)
|
|
724
|
+
result = Prism.parse(source)
|
|
725
|
+
unless result.success?
|
|
726
|
+
message = result.errors.map { |error| error.message }.join("; ")
|
|
727
|
+
raise Unsupported.new("kernel source does not parse: #{message}")
|
|
728
|
+
end
|
|
729
|
+
extract_block(result.value)
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
def collect_assigned_names (node)
|
|
733
|
+
return [] unless node
|
|
734
|
+
names = []
|
|
735
|
+
names << node.name if node.is_a?(Prism::LocalVariableWriteNode)
|
|
736
|
+
node.compact_child_nodes.each do |child|
|
|
737
|
+
names.concat(collect_assigned_names(child))
|
|
738
|
+
end
|
|
739
|
+
names.uniq
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
# Accepts `->(i) { ... }`, `proc { |i| ... }` and `lambda { |i| ... }`.
|
|
743
|
+
def extract_block (program)
|
|
744
|
+
statements = program.statements.body
|
|
745
|
+
unless statements.size == 1
|
|
746
|
+
raise Unsupported.new("kernel source must be a single block literal, " \
|
|
747
|
+
"got #{statements.size} statements")
|
|
748
|
+
end
|
|
749
|
+
node = statements.first
|
|
750
|
+
|
|
751
|
+
case node
|
|
752
|
+
when Prism::LambdaNode
|
|
753
|
+
node
|
|
754
|
+
when Prism::CallNode
|
|
755
|
+
unless [:proc, :lambda].include?(node.name) && node.block
|
|
756
|
+
raise Unsupported.new("expected a block literal", node.location)
|
|
757
|
+
end
|
|
758
|
+
node.block
|
|
759
|
+
else
|
|
760
|
+
raise Unsupported.new("expected a block literal such as `{ |i| ... }`, " \
|
|
761
|
+
"got #{node_name(node)}", node.location)
|
|
762
|
+
end
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
def read_parameters (block)
|
|
766
|
+
parameters = block.parameters
|
|
767
|
+
list = parameters && parameters.parameters
|
|
768
|
+
requireds = list ? list.requireds : []
|
|
769
|
+
if list && (list.optionals.any? || list.rest || list.keywords.any? || list.block)
|
|
770
|
+
raise Unsupported.new("the kernel block takes only required parameters")
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
if @function
|
|
774
|
+
@parameter_names = requireds.map(&:name)
|
|
775
|
+
@index_names = []
|
|
776
|
+
@outer_names = []
|
|
777
|
+
return
|
|
778
|
+
end
|
|
779
|
+
|
|
780
|
+
if @windows.any?
|
|
781
|
+
# The parameters were read before this analyzer was built -- the
|
|
782
|
+
# caller had to, to know which array each window is onto -- so what
|
|
783
|
+
# is left here is to check that the block agrees with what it was
|
|
784
|
+
# given, and to name the indices it does not name.
|
|
785
|
+
unless requireds.map(&:name) == @windows
|
|
786
|
+
raise Unsupported.new(
|
|
787
|
+
"the block's parameters are the windows onto the arrays it was " \
|
|
788
|
+
"given, in that order")
|
|
789
|
+
end
|
|
790
|
+
@index_names = Array.new(@given_rank) { |axis| :"index#{axis}" }
|
|
791
|
+
elsif requireds.empty?
|
|
792
|
+
unless @given_rank
|
|
793
|
+
raise Unsupported.new("the kernel block takes the loop indices as " \
|
|
794
|
+
"its parameters, and was given none")
|
|
795
|
+
end
|
|
796
|
+
@whole_array = true
|
|
797
|
+
@index_names = Array.new(@given_rank) { |axis| :"index#{axis}" }
|
|
798
|
+
else
|
|
799
|
+
@index_names = requireds.map(&:name)
|
|
800
|
+
end
|
|
801
|
+
@outer_names = @index_names.dup
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
# `printf` writes to the terminal from inside the loop, which is what
|
|
805
|
+
# it is for: the kernel is otherwise silent until it finishes. The
|
|
806
|
+
# format has to be a literal, since C reads it at compile time.
|
|
807
|
+
def build_print (node)
|
|
808
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
809
|
+
template = arguments.first
|
|
810
|
+
unless template.is_a?(Prism::StringNode)
|
|
811
|
+
raise Unsupported.new(
|
|
812
|
+
"printf's format has to be written out, as in " \
|
|
813
|
+
"`printf(\"x = %g\\n\", x)`",
|
|
814
|
+
node.location)
|
|
815
|
+
end
|
|
816
|
+
Print.new(template.unescaped,
|
|
817
|
+
arguments.drop(1).map { |argument| build(argument) },
|
|
818
|
+
node.location)
|
|
819
|
+
end
|
|
820
|
+
|
|
821
|
+
# `raise "the message"`, and nothing else in that shape: the class is
|
|
822
|
+
# not named -- what comes back is the RuntimeError `raise "..."` gives
|
|
823
|
+
# in Ruby -- and the message is a literal, because it is registered when
|
|
824
|
+
# the body is compiled and only its code travels out of a cell.
|
|
825
|
+
#
|
|
826
|
+
# A compiled function may raise as a kernel may. It reports into the
|
|
827
|
+
# flag it already reports a division by zero into -- its own when it
|
|
828
|
+
# stands alone, the caller's when it is pasted into a kernel -- and the
|
|
829
|
+
# message behind the code travels with the function, so whichever way
|
|
830
|
+
# the body is reached, the same line raises the same thing.
|
|
831
|
+
def build_raise (node)
|
|
832
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
833
|
+
if arguments.size != 1
|
|
834
|
+
raise Unsupported.new(
|
|
835
|
+
arguments.empty? ?
|
|
836
|
+
"`raise` in a kernel takes the message, as in `raise \"x is 0\"`" :
|
|
837
|
+
"`raise` in a kernel takes the message alone; the class is not " \
|
|
838
|
+
"named, and what comes back is a RuntimeError",
|
|
839
|
+
node.location)
|
|
840
|
+
end
|
|
841
|
+
message = arguments.first
|
|
842
|
+
unless message.is_a?(Prism::StringNode)
|
|
843
|
+
raise Unsupported.new(
|
|
844
|
+
"a kernel's `raise` message is written out rather than built: it " \
|
|
845
|
+
"is registered when the kernel is compiled, and the cell reports " \
|
|
846
|
+
"which one it was",
|
|
847
|
+
node.location)
|
|
848
|
+
end
|
|
849
|
+
Raise.new(message.unescaped, node.location)
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
def build_statement (node)
|
|
853
|
+
case node
|
|
854
|
+
when Prism::LocalVariableWriteNode
|
|
855
|
+
# `out = UNDEF` marks the cell missing, the same as `out[i] = UNDEF`
|
|
856
|
+
# does where the indices are written out. It is read before the
|
|
857
|
+
# right-hand side is built, since UNDEF is a mark and not a value.
|
|
858
|
+
if @whole_array && @array_names.include?(node.name) &&
|
|
859
|
+
undef_constant?(node.value)
|
|
860
|
+
record_array_rank(node.name, rank, node.location)
|
|
861
|
+
record_subscripts(node.name,
|
|
862
|
+
Array.new(rank) { |axis| [@outer_names[axis], 0] })
|
|
863
|
+
@written_arrays << node.name unless @written_arrays.include?(node.name)
|
|
864
|
+
@uses_undef = true
|
|
865
|
+
return MaskWrite.new(node.name, node.location)
|
|
866
|
+
end
|
|
867
|
+
expression = build(node.value)
|
|
868
|
+
# In the whole-array spelling every name in the block is a cell, so
|
|
869
|
+
# an assignment to a name that is an array outside writes that
|
|
870
|
+
# array's cell -- the one the loop is on, the same cell every read
|
|
871
|
+
# in the block is at. A name that is not an array is a local, as
|
|
872
|
+
# it is anywhere else.
|
|
873
|
+
if @whole_array && @array_names.include?(node.name)
|
|
874
|
+
return whole_array_write(node.name, expression, node.location)
|
|
875
|
+
end
|
|
876
|
+
@local_names << node.name unless @local_names.include?(node.name)
|
|
877
|
+
Assignment.new(node.name, expression, node.location)
|
|
878
|
+
when Prism::CallNode
|
|
879
|
+
if node.receiver.nil? && node.name == :printf
|
|
880
|
+
return build_print(node)
|
|
881
|
+
end
|
|
882
|
+
return build_raise(node) if node.receiver.nil? && node.name == :raise
|
|
883
|
+
# A call to a C function may stand alone. Its value is dropped, as
|
|
884
|
+
# Ruby drops it, and what it did is wherever its pointer parameters
|
|
885
|
+
# pointed -- which is the whole reason C has statements that are
|
|
886
|
+
# calls. Everything else keeps the refusal below: a computation
|
|
887
|
+
# standing where a statement stands is a line that does nothing.
|
|
888
|
+
if (call = recursive_call(node) || c_function_call(node))
|
|
889
|
+
@calls_for_effect = true
|
|
890
|
+
return CallStatement.new(call, node.location)
|
|
891
|
+
end
|
|
892
|
+
build_element_write(node)
|
|
893
|
+
when Prism::IfNode
|
|
894
|
+
build_branch(node)
|
|
895
|
+
when Prism::WhileNode
|
|
896
|
+
build_while(node)
|
|
897
|
+
when Prism::NextNode
|
|
898
|
+
build_loop_jump(node, LoopSkip, "next")
|
|
899
|
+
when Prism::BreakNode
|
|
900
|
+
build_loop_jump(node, LoopStop, "break")
|
|
901
|
+
else
|
|
902
|
+
raise Unsupported.new(
|
|
903
|
+
"a kernel body holds assignments, `if`, `while`, `(a...b).each` " \
|
|
904
|
+
"and `n.times` only, got #{node_name(node)}",
|
|
905
|
+
node.location)
|
|
906
|
+
end
|
|
907
|
+
end
|
|
908
|
+
|
|
909
|
+
# `next` skips the rest of this iteration, `break` leaves the loop. Both
|
|
910
|
+
# mean in the generated loop what they mean in the Ruby loop it replaces
|
|
911
|
+
# -- with one exception: Ruby's `break` in the kernel block is not a
|
|
912
|
+
# loop exit at all but a return from `jit_for` with a value, and the
|
|
913
|
+
# value is one this cannot produce. So `break` belongs to an inner
|
|
914
|
+
# loop, which is a loop in both languages.
|
|
915
|
+
def build_loop_jump (node, kind, word)
|
|
916
|
+
if node.arguments
|
|
917
|
+
raise Unsupported.new(
|
|
918
|
+
"`#{word}` here takes no value: the loop's own value is never " \
|
|
919
|
+
"used, so a value would be dropped",
|
|
920
|
+
node.location)
|
|
921
|
+
end
|
|
922
|
+
if kind == LoopStop && @loop_depth.zero?
|
|
923
|
+
raise Unsupported.new(
|
|
924
|
+
"`break` in the kernel block returns from `jit_for` in Ruby, " \
|
|
925
|
+
"with a value this cannot produce; it works inside a loop -- an " \
|
|
926
|
+
"`(a...b).each`, an `n.times` or a `while` -- and `next` skips " \
|
|
927
|
+
"the cell here",
|
|
928
|
+
node.location)
|
|
929
|
+
end
|
|
930
|
+
kind.new(node.location)
|
|
931
|
+
end
|
|
932
|
+
|
|
933
|
+
# `while cond ... end`. The loop whose end is not written down.
|
|
934
|
+
#
|
|
935
|
+
# It is the one construct here that can keep a kernel from returning,
|
|
936
|
+
# and there is no honest way to stop it from that: a bound the compiler
|
|
937
|
+
# invented would be a number nobody could choose -- the loops whose
|
|
938
|
+
# bound is knowable are already the bounded loop's, spelled
|
|
939
|
+
# `(0...cap).each` with a `break`. So what is offered is C's own
|
|
940
|
+
# bargain, the same one a `jit_function` that recurses too deep already
|
|
941
|
+
# takes: the loop does what it is written to do, and a condition that
|
|
942
|
+
# never goes false does not return. Worth knowing before writing one:
|
|
943
|
+
# the C loop has no interrupt check in it, so `Ctrl-C` does not reach a
|
|
944
|
+
# kernel that is running -- whether or not it holds the GVL -- and a
|
|
945
|
+
# runaway pass ends with a signal from another terminal.
|
|
946
|
+
#
|
|
947
|
+
# `begin ... end while` is refused rather than compiled as a do-while.
|
|
948
|
+
# Ruby's is the one loop in the language that tests after the body, and
|
|
949
|
+
# a reader who missed the `begin` would read the body's first pass as
|
|
950
|
+
# conditional when it is not. `while` with the test written first says
|
|
951
|
+
# the same thing where it can be seen.
|
|
952
|
+
def build_while (node)
|
|
953
|
+
if node.begin_modifier?
|
|
954
|
+
raise Unsupported.new(
|
|
955
|
+
"`begin ... end while` runs its body before the condition is " \
|
|
956
|
+
"ever read, which is not what `while` says anywhere else in " \
|
|
957
|
+
"Ruby; write the test at the top",
|
|
958
|
+
node.location)
|
|
959
|
+
end
|
|
960
|
+
condition = build(node.predicate)
|
|
961
|
+
@loop_depth += 1
|
|
962
|
+
statements = statements_of(node.statements).map { |inner|
|
|
963
|
+
build_statement(inner)
|
|
964
|
+
}
|
|
965
|
+
@loop_depth -= 1
|
|
966
|
+
# Whether a loop returns is not a question that can be answered in
|
|
967
|
+
# general, and nothing here pretends to answer it. This is the one
|
|
968
|
+
# case where it does not have to be: a condition written `true` is
|
|
969
|
+
# never going to be false, so the only way out is a `break` or a
|
|
970
|
+
# `raise`, and a body with neither cannot end. That is not a guess
|
|
971
|
+
# about the data -- it is the loop read as written -- so refusing it
|
|
972
|
+
# costs no legitimate program. `while true` with a `break` in it is
|
|
973
|
+
# a normal thing to write, and is left alone.
|
|
974
|
+
if literal_true?(node.predicate) && !can_leave?(statements)
|
|
975
|
+
raise Unsupported.new(
|
|
976
|
+
"this `while true` holds no `break` and no `raise`, so it cannot " \
|
|
977
|
+
"end; a kernel that does not return cannot be interrupted either, " \
|
|
978
|
+
"since `Ctrl-C` does not reach a running kernel",
|
|
979
|
+
node.location)
|
|
980
|
+
end
|
|
981
|
+
While.new(condition, statements, node.location)
|
|
982
|
+
end
|
|
983
|
+
|
|
984
|
+
def literal_true? (node)
|
|
985
|
+
unwrap(node).is_a?(Prism::TrueNode)
|
|
986
|
+
end
|
|
987
|
+
|
|
988
|
+
# Whether any statement of this loop's own body could leave it. A
|
|
989
|
+
# `break` in a loop nested inside leaves that one, not this one, so the
|
|
990
|
+
# walk goes into branches and stops at loops.
|
|
991
|
+
def can_leave? (statements)
|
|
992
|
+
statements.any? do |statement|
|
|
993
|
+
case statement
|
|
994
|
+
when LoopStop, Raise then true
|
|
995
|
+
when Branch
|
|
996
|
+
can_leave?(statement.consequent) || can_leave?(statement.alternative)
|
|
997
|
+
else false
|
|
998
|
+
end
|
|
999
|
+
end
|
|
1000
|
+
end
|
|
1001
|
+
|
|
1002
|
+
# `if` in statement position, where the branches write cells rather than
|
|
1003
|
+
# produce a value. Unlike the expression form, this one does not need an
|
|
1004
|
+
# `else`: a cell the kernel does not write keeps what it had.
|
|
1005
|
+
# `(from...to).each { |j| ... }` -- the loop a reduction runs in -- and
|
|
1006
|
+
# `n.times { |j| ... }`, which is that loop with both ends implied.
|
|
1007
|
+
#
|
|
1008
|
+
# `each` rather than `for` because its scoping is the one being
|
|
1009
|
+
# compiled: `for` would assign an enclosing variable of the same name
|
|
1010
|
+
# and leave the index bound afterwards, neither of which the generated
|
|
1011
|
+
# loop does. `times` has the scoping of `each` and needs no exception.
|
|
1012
|
+
def build_inner_loop (node)
|
|
1013
|
+
counted = node.name == :times
|
|
1014
|
+
range = unwrap(node.receiver)
|
|
1015
|
+
if !counted && !range.is_a?(Prism::RangeNode)
|
|
1016
|
+
raise Unsupported.new(
|
|
1017
|
+
"an inner loop runs over a range, as in `(0...n).each { |j| ... }`",
|
|
1018
|
+
node.location)
|
|
1019
|
+
end
|
|
1020
|
+
unless node.block && node.block.parameters
|
|
1021
|
+
raise Unsupported.new("an inner loop names its index", node.location)
|
|
1022
|
+
end
|
|
1023
|
+
requireds = node.block.parameters.parameters&.requireds || []
|
|
1024
|
+
unless requireds.size == 1
|
|
1025
|
+
raise Unsupported.new("an inner loop names one index", node.location)
|
|
1026
|
+
end
|
|
1027
|
+
index = requireds.first.name
|
|
1028
|
+
if index_in_scope?(index)
|
|
1029
|
+
raise Unsupported.new("`#{index}` is already an index here", node.location)
|
|
1030
|
+
end
|
|
1031
|
+
unless counted || (range.left && range.right)
|
|
1032
|
+
raise Unsupported.new("an inner loop needs both ends of its range",
|
|
1033
|
+
node.location)
|
|
1034
|
+
end
|
|
1035
|
+
|
|
1036
|
+
# `n.times` is `(0...n).each`: the count is the exclusive end, and it
|
|
1037
|
+
# is built from the same vocabulary a range end is, so a literal and a
|
|
1038
|
+
# captured integer both serve.
|
|
1039
|
+
if counted
|
|
1040
|
+
from = IntegerLiteral.new(0, node.location)
|
|
1041
|
+
to = build(node.receiver)
|
|
1042
|
+
else
|
|
1043
|
+
from = build(range.left)
|
|
1044
|
+
to = build(range.right)
|
|
1045
|
+
to = BinaryOperation.new(:+, to, IntegerLiteral.new(1, node.location),
|
|
1046
|
+
node.location) unless range.exclude_end?
|
|
1047
|
+
end
|
|
1048
|
+
|
|
1049
|
+
@inner_names << index
|
|
1050
|
+
@inner_names_seen << index
|
|
1051
|
+
@inner_ranges[index] = [from, to]
|
|
1052
|
+
@loop_depth += 1
|
|
1053
|
+
statements = statements_of(node.block.body).map { |inner|
|
|
1054
|
+
build_statement(inner)
|
|
1055
|
+
}
|
|
1056
|
+
@loop_depth -= 1
|
|
1057
|
+
@inner_names.pop
|
|
1058
|
+
InnerLoop.new(index, from, to, statements, node.location)
|
|
1059
|
+
end
|
|
1060
|
+
|
|
1061
|
+
def unwrap (node)
|
|
1062
|
+
return node unless node.is_a?(Prism::ParenthesesNode)
|
|
1063
|
+
body = node.body ? node.body.body : []
|
|
1064
|
+
body.size == 1 ? unwrap(body.first) : node
|
|
1065
|
+
end
|
|
1066
|
+
|
|
1067
|
+
def build_branch (node)
|
|
1068
|
+
alternative =
|
|
1069
|
+
case node.subsequent
|
|
1070
|
+
when nil then []
|
|
1071
|
+
when Prism::ElseNode
|
|
1072
|
+
statements_of(node.subsequent.statements).map { |inner|
|
|
1073
|
+
build_statement(inner)
|
|
1074
|
+
}
|
|
1075
|
+
when Prism::IfNode then [build_branch(node.subsequent)]
|
|
1076
|
+
else
|
|
1077
|
+
raise Unsupported.new("unsupported `if` continuation", node.location)
|
|
1078
|
+
end
|
|
1079
|
+
Branch.new(build(node.predicate),
|
|
1080
|
+
statements_of(node.statements).map { |inner|
|
|
1081
|
+
build_statement(inner)
|
|
1082
|
+
},
|
|
1083
|
+
alternative,
|
|
1084
|
+
node.location)
|
|
1085
|
+
end
|
|
1086
|
+
|
|
1087
|
+
def statements_of (node)
|
|
1088
|
+
node ? node.body : []
|
|
1089
|
+
end
|
|
1090
|
+
|
|
1091
|
+
def build_element_write (node)
|
|
1092
|
+
return build_inner_loop(node) if node.name == :each || node.name == :times
|
|
1093
|
+
unless node.name == :[]=
|
|
1094
|
+
# An expression standing alone in this spelling is a computation
|
|
1095
|
+
# nobody can see: there is no index to have written it against, and
|
|
1096
|
+
# nothing took its value.
|
|
1097
|
+
if @whole_array && !@map
|
|
1098
|
+
raise Unsupported.new(
|
|
1099
|
+
"this computes a value and puts it nowhere; assign it to an " \
|
|
1100
|
+
"array, as in `out = ...`, or ask for the value back with " \
|
|
1101
|
+
"`CArray.jit_map`",
|
|
1102
|
+
node.location)
|
|
1103
|
+
end
|
|
1104
|
+
raise Unsupported.new(
|
|
1105
|
+
"a kernel body holds assignments, `if`, `while`, `(a...b).each` " \
|
|
1106
|
+
"and `n.times` only, got a call to `#{node.name}`",
|
|
1107
|
+
node.location)
|
|
1108
|
+
end
|
|
1109
|
+
if (pointer = pointer_subscript(node, write: true))
|
|
1110
|
+
name, index = pointer
|
|
1111
|
+
arguments = node.arguments.arguments
|
|
1112
|
+
return PointerWrite.new(name, index, build(arguments.last),
|
|
1113
|
+
node.location)
|
|
1114
|
+
end
|
|
1115
|
+
array = array_name(node.receiver, node.location)
|
|
1116
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
1117
|
+
# `out[] = ...` was how a block that had to run as Ruby said "the
|
|
1118
|
+
# whole array", `[]=` being the only spelling Ruby has for it. The
|
|
1119
|
+
# block is read rather than run, and every name in it is a cell, so
|
|
1120
|
+
# the assignment is Ruby's own: `out = ...`.
|
|
1121
|
+
if @whole_array && arguments.size == 1
|
|
1122
|
+
raise Unsupported.new(
|
|
1123
|
+
"`#{array}[] = ...` writes the cell the loop is on, which is what " \
|
|
1124
|
+
"`#{array} = ...` says; the block is read rather than run, so the " \
|
|
1125
|
+
"assignment is an ordinary one",
|
|
1126
|
+
node.location)
|
|
1127
|
+
end
|
|
1128
|
+
subscripts = read_subscripts(array, arguments[0..-2], node.location)
|
|
1129
|
+
# A kernel writes a cell its own indices reach -- or one it works out,
|
|
1130
|
+
# which is a scatter and is checked as it is reached rather than in
|
|
1131
|
+
# advance. What it may not do is write through an index that is not
|
|
1132
|
+
# the loop's: an inner index addresses reads only.
|
|
1133
|
+
#
|
|
1134
|
+
# In a contraction the left-hand side names which indices are free and
|
|
1135
|
+
# which are summed over, so it is checked there instead.
|
|
1136
|
+
unless @contract || walking_subscripts?(subscripts) ||
|
|
1137
|
+
computed_subscripts?(subscripts)
|
|
1138
|
+
raise Unsupported.new(
|
|
1139
|
+
"a kernel writes through its own indices, so `#{array}[...]` on " \
|
|
1140
|
+
"the left of `=` is addressed by #{@outer_names.join(', ')}, by a " \
|
|
1141
|
+
"position fixed before the loop runs -- or by a value the kernel " \
|
|
1142
|
+
"works out, which is a scatter",
|
|
1143
|
+
node.location)
|
|
1144
|
+
end
|
|
1145
|
+
record_subscripts(array, subscripts)
|
|
1146
|
+
@written_arrays << array unless @written_arrays.include?(array)
|
|
1147
|
+
if undef_constant?(arguments.last)
|
|
1148
|
+
@uses_undef = true
|
|
1149
|
+
return MaskWrite.new(array, node.location)
|
|
1150
|
+
end
|
|
1151
|
+
ElementWrite.new(array, build(arguments.last), node.location, subscripts)
|
|
1152
|
+
end
|
|
1153
|
+
|
|
1154
|
+
def build (node)
|
|
1155
|
+
case node
|
|
1156
|
+
when Prism::IntegerNode
|
|
1157
|
+
IntegerLiteral.new(node.value, node.location)
|
|
1158
|
+
when Prism::FloatNode
|
|
1159
|
+
FloatLiteral.new(node.value, node.location)
|
|
1160
|
+
when Prism::ImaginaryNode
|
|
1161
|
+
# `2i` is Complex(0, 2); its imaginary part is what the literal
|
|
1162
|
+
# spells, and may be an Integer or a Float.
|
|
1163
|
+
ImaginaryLiteral.new(node.value.imaginary, node.location)
|
|
1164
|
+
when Prism::TrueNode
|
|
1165
|
+
BooleanLiteral.new(true, node.location)
|
|
1166
|
+
when Prism::FalseNode
|
|
1167
|
+
BooleanLiteral.new(false, node.location)
|
|
1168
|
+
when Prism::ParenthesesNode
|
|
1169
|
+
inner = node.body ? node.body.body : []
|
|
1170
|
+
unless inner.size == 1
|
|
1171
|
+
raise Unsupported.new("parentheses must hold one expression", node.location)
|
|
1172
|
+
end
|
|
1173
|
+
build(inner.first)
|
|
1174
|
+
when Prism::LocalVariableReadNode
|
|
1175
|
+
build_name_read(node.name, node.location)
|
|
1176
|
+
when Prism::IfNode
|
|
1177
|
+
build_conditional(node)
|
|
1178
|
+
when Prism::AndNode
|
|
1179
|
+
LogicalOperation.new(:"&&", build(node.left), build(node.right),
|
|
1180
|
+
node.location)
|
|
1181
|
+
when Prism::OrNode
|
|
1182
|
+
LogicalOperation.new(:"||", build(node.left), build(node.right),
|
|
1183
|
+
node.location)
|
|
1184
|
+
when Prism::ConstantPathNode
|
|
1185
|
+
build_constant_path(node)
|
|
1186
|
+
when Prism::CallNode
|
|
1187
|
+
build_call(node)
|
|
1188
|
+
when Prism::ConstantReadNode
|
|
1189
|
+
if node.name == :UNDEF
|
|
1190
|
+
raise Unsupported.new(
|
|
1191
|
+
"UNDEF is not a number; write it as `a[i] == UNDEF` to test a " \
|
|
1192
|
+
"cell, or `a[i] = UNDEF` to mark one",
|
|
1193
|
+
node.location)
|
|
1194
|
+
end
|
|
1195
|
+
# A constant holds an array, a number or a function like any other
|
|
1196
|
+
# name -- and is the only name a method body can reach, since `def`
|
|
1197
|
+
# closes over nothing.
|
|
1198
|
+
build_name_read(node.name, node.location)
|
|
1199
|
+
else
|
|
1200
|
+
raise Unsupported.new("unsupported expression #{node_name(node)}",
|
|
1201
|
+
node.location)
|
|
1202
|
+
end
|
|
1203
|
+
end
|
|
1204
|
+
|
|
1205
|
+
# A name is an index, a local once assigned, an error if the block
|
|
1206
|
+
# assigns it only later, an array, or a captured scalar. The same rule
|
|
1207
|
+
# serves both entry points: parsed on its own a captured name arrives as
|
|
1208
|
+
# a receiverless call, while in the file it was written in Prism
|
|
1209
|
+
# resolves it to the enclosing scope's local.
|
|
1210
|
+
def build_name_read (name, location)
|
|
1211
|
+
axis = @outer_names.index(name)
|
|
1212
|
+
return IndexVariable.new(name, axis, location) if axis
|
|
1213
|
+
return IndexVariable.new(name, nil, location) if @inner_names.include?(name)
|
|
1214
|
+
if @local_names.include?(name)
|
|
1215
|
+
return LocalRead.new(name, location)
|
|
1216
|
+
end
|
|
1217
|
+
if @cell_names.include?(name)
|
|
1218
|
+
return cell_read(name, location)
|
|
1219
|
+
end
|
|
1220
|
+
if @array_names.include?(name)
|
|
1221
|
+
# Every name in this spelling is a cell: the loop is the compiler's
|
|
1222
|
+
# and is not written in the block, so `a + b * c` reads three cells
|
|
1223
|
+
# and computes one. What the same expression means in CArray is
|
|
1224
|
+
# what it adds up to over the whole array -- in one pass here,
|
|
1225
|
+
# instead of three with two arrays in between.
|
|
1226
|
+
# And a stencil reads a captured array the same way: the windows
|
|
1227
|
+
# are what reach, so a name that is not one is the cell the loop is
|
|
1228
|
+
# on -- `a[0, 0]` spelled the way a block with no indices spells it.
|
|
1229
|
+
if @whole_array || @windows.any?
|
|
1230
|
+
subscripts = Array.new(rank) { |axis| [@outer_names[axis], 0] }
|
|
1231
|
+
record_array_rank(name, rank, location)
|
|
1232
|
+
record_subscripts(name, subscripts)
|
|
1233
|
+
return ElementRead.new(name, subscripts, location)
|
|
1234
|
+
end
|
|
1235
|
+
raise Unsupported.new(
|
|
1236
|
+
"`#{name}` is an array; index it, as in `#{name}[#{@index_names.first}]`",
|
|
1237
|
+
location)
|
|
1238
|
+
end
|
|
1239
|
+
if @assigned_names.include?(name)
|
|
1240
|
+
raise Unsupported.new("`#{name}` is read before it is assigned", location)
|
|
1241
|
+
end
|
|
1242
|
+
@scalar_names << name unless @scalar_names.include?(name)
|
|
1243
|
+
CaptureRead.new(name, location)
|
|
1244
|
+
end
|
|
1245
|
+
|
|
1246
|
+
def build_conditional (node)
|
|
1247
|
+
unless node.subsequent
|
|
1248
|
+
raise Unsupported.new("`if` without `else` has no value for every cell",
|
|
1249
|
+
node.location)
|
|
1250
|
+
end
|
|
1251
|
+
unless node.subsequent.is_a?(Prism::ElseNode)
|
|
1252
|
+
raise Unsupported.new("`elsif` is not supported", node.location)
|
|
1253
|
+
end
|
|
1254
|
+
Conditional.new(build(node.predicate),
|
|
1255
|
+
build_single(node.statements, node.location, "if"),
|
|
1256
|
+
build_single(node.subsequent.statements, node.location, "else"),
|
|
1257
|
+
node.location)
|
|
1258
|
+
end
|
|
1259
|
+
|
|
1260
|
+
def build_single (statements, location, what)
|
|
1261
|
+
body = statements ? statements.body : []
|
|
1262
|
+
unless body.size == 1
|
|
1263
|
+
raise Unsupported.new("the `#{what}` branch must be a single expression",
|
|
1264
|
+
location)
|
|
1265
|
+
end
|
|
1266
|
+
build(body.first)
|
|
1267
|
+
end
|
|
1268
|
+
|
|
1269
|
+
# `f.call(x)`, `f.(x)` -- which Prism also spells `call` -- and `f[x]`,
|
|
1270
|
+
# after Proc. All three are real Ruby that computes the same thing when
|
|
1271
|
+
# the block is run rather than compiled, which is the property the rest
|
|
1272
|
+
# of the kernel grammar keeps too.
|
|
1273
|
+
C_FUNCTION_CALL_NAMES = [:call, :[]].freeze
|
|
1274
|
+
|
|
1275
|
+
def build_call (node)
|
|
1276
|
+
if node.receiver.nil? && node.arguments.nil? && node.block.nil?
|
|
1277
|
+
return build_name_read(node.name, node.location)
|
|
1278
|
+
end
|
|
1279
|
+
if (recursive = recursive_call(node))
|
|
1280
|
+
return recursive
|
|
1281
|
+
end
|
|
1282
|
+
if (c_function = c_function_call(node))
|
|
1283
|
+
return c_function
|
|
1284
|
+
end
|
|
1285
|
+
return build_complex(node) if node.receiver.nil? && node.name == :Complex
|
|
1286
|
+
if node.block
|
|
1287
|
+
raise Unsupported.new("block arguments are not supported", node.location)
|
|
1288
|
+
end
|
|
1289
|
+
return build_element_read(node) if node.name == :[]
|
|
1290
|
+
|
|
1291
|
+
if node.receiver.is_a?(Prism::ConstantReadNode) && node.receiver.name == :Math
|
|
1292
|
+
return build_math_call(node)
|
|
1293
|
+
end
|
|
1294
|
+
|
|
1295
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
1296
|
+
|
|
1297
|
+
if node.name == :-@ && arguments.empty?
|
|
1298
|
+
return UnaryMinus.new(build(node.receiver), node.location)
|
|
1299
|
+
end
|
|
1300
|
+
if node.name == :+@ && arguments.empty?
|
|
1301
|
+
return build(node.receiver)
|
|
1302
|
+
end
|
|
1303
|
+
if node.name == :! && arguments.empty?
|
|
1304
|
+
return LogicalNot.new(build(node.receiver), node.location)
|
|
1305
|
+
end
|
|
1306
|
+
if node.name == :abs && arguments.empty?
|
|
1307
|
+
return AbsoluteValue.new(build(node.receiver), node.location)
|
|
1308
|
+
end
|
|
1309
|
+
if arguments.empty? && (part = COMPLEX_PARTS[node.name])
|
|
1310
|
+
return ComplexPart.new(part, node.name, build(node.receiver),
|
|
1311
|
+
node.location)
|
|
1312
|
+
end
|
|
1313
|
+
if arguments.empty? && (conversion = CONVERSIONS[node.name])
|
|
1314
|
+
type, function = conversion
|
|
1315
|
+
return Conversion.new(function, build(node.receiver), type, node.location)
|
|
1316
|
+
end
|
|
1317
|
+
if node.name == :** && arguments.size == 1
|
|
1318
|
+
return build_power(node.receiver, arguments.first, node.location)
|
|
1319
|
+
end
|
|
1320
|
+
if arguments.empty?
|
|
1321
|
+
if (function = postfix_math(node.name))
|
|
1322
|
+
return MathCall.new(function, [build(node.receiver)], node.location)
|
|
1323
|
+
end
|
|
1324
|
+
if (reason = REFUSED_POSTFIX[node.name])
|
|
1325
|
+
raise Unsupported.new("`.#{node.name}` is not compiled: #{reason}",
|
|
1326
|
+
node.location)
|
|
1327
|
+
end
|
|
1328
|
+
end
|
|
1329
|
+
if [:==, :!=].include?(node.name) && arguments.size == 1
|
|
1330
|
+
# Either way round: `a[i] == UNDEF` and `UNDEF == a[i]` ask the same
|
|
1331
|
+
# question, and Ruby lets both be written.
|
|
1332
|
+
if undef_constant?(arguments.first)
|
|
1333
|
+
return build_mask_test(node.receiver, node.name == :!=, node.location)
|
|
1334
|
+
end
|
|
1335
|
+
if undef_constant?(node.receiver)
|
|
1336
|
+
return build_mask_test(arguments.first, node.name == :!=, node.location)
|
|
1337
|
+
end
|
|
1338
|
+
end
|
|
1339
|
+
if node.name == :~ && arguments.empty?
|
|
1340
|
+
return BitwiseNot.new(build(node.receiver), node.location)
|
|
1341
|
+
end
|
|
1342
|
+
if (ARITHMETIC_OPERATORS + COMPARISON_OPERATORS +
|
|
1343
|
+
BIT_OPERATORS).include?(node.name)
|
|
1344
|
+
unless arguments.size == 1
|
|
1345
|
+
raise Unsupported.new("`#{node.name}` takes one operand", node.location)
|
|
1346
|
+
end
|
|
1347
|
+
return BinaryOperation.new(node.name, build(node.receiver),
|
|
1348
|
+
build(arguments.first), node.location)
|
|
1349
|
+
end
|
|
1350
|
+
|
|
1351
|
+
if raise_call?(node)
|
|
1352
|
+
raise Unsupported.new(
|
|
1353
|
+
"`raise` is a statement here, not a value: write it on its own, " \
|
|
1354
|
+
"as in `raise \"x is 0\" if x == 0`",
|
|
1355
|
+
node.location)
|
|
1356
|
+
end
|
|
1357
|
+
raise Unsupported.new("unsupported method `#{node.name}`", node.location)
|
|
1358
|
+
end
|
|
1359
|
+
|
|
1360
|
+
def build_constant_path (node)
|
|
1361
|
+
# Math is answered here, whether or not the answer exists: a name it
|
|
1362
|
+
# does not have is not something to go looking for outside.
|
|
1363
|
+
if node.parent.is_a?(Prism::ConstantReadNode) && node.parent.name == :Math
|
|
1364
|
+
if MATH_CONSTANTS.key?(node.name)
|
|
1365
|
+
return FloatLiteral.new(MATH_CONSTANTS.fetch(node.name), node.location)
|
|
1366
|
+
end
|
|
1367
|
+
raise Unsupported.new("unsupported constant #{node.slice}", node.location)
|
|
1368
|
+
end
|
|
1369
|
+
# Any other path names one thing, the way a plain constant does.
|
|
1370
|
+
build_name_read(node.slice.to_sym, node.location)
|
|
1371
|
+
end
|
|
1372
|
+
|
|
1373
|
+
def subscripted? (node)
|
|
1374
|
+
node.is_a?(Prism::CallNode) && node.name == :[]
|
|
1375
|
+
end
|
|
1376
|
+
|
|
1377
|
+
def undef_constant? (node)
|
|
1378
|
+
node.is_a?(Prism::ConstantReadNode) && node.name == :UNDEF
|
|
1379
|
+
end
|
|
1380
|
+
|
|
1381
|
+
# `a[i] == UNDEF` asks whether the cell is missing, which is a question
|
|
1382
|
+
# about the mask. Only a cell can be asked.
|
|
1383
|
+
def build_mask_test (receiver, negated, location)
|
|
1384
|
+
# In an element kernel the name is already the cell -- the loop is
|
|
1385
|
+
# the compiler's and is not written in the block -- so `a == UNDEF`
|
|
1386
|
+
# asks about the same cell `a` reads. `a[] == UNDEF` says it the
|
|
1387
|
+
# longer way and is read below, with the indexed spellings.
|
|
1388
|
+
if @whole_array && !subscripted?(receiver)
|
|
1389
|
+
name = captured_name(receiver)
|
|
1390
|
+
unless name && @array_names.include?(name)
|
|
1391
|
+
raise Unsupported.new(
|
|
1392
|
+
"only a cell can be compared with UNDEF, as in `a == UNDEF`",
|
|
1393
|
+
location)
|
|
1394
|
+
end
|
|
1395
|
+
subscripts = Array.new(rank) { |axis| [@outer_names[axis], 0] }
|
|
1396
|
+
record_array_rank(name, rank, location)
|
|
1397
|
+
record_subscripts(name, subscripts)
|
|
1398
|
+
@uses_undef = true
|
|
1399
|
+
return MaskTest.new(name, subscripts, negated, location)
|
|
1400
|
+
end
|
|
1401
|
+
unless subscripted?(receiver)
|
|
1402
|
+
raise Unsupported.new(
|
|
1403
|
+
"only a cell can be compared with UNDEF, as in `a[i] == UNDEF`",
|
|
1404
|
+
location)
|
|
1405
|
+
end
|
|
1406
|
+
array = array_name(receiver.receiver, location)
|
|
1407
|
+
array_for_test = array
|
|
1408
|
+
subscripts = read_subscripts(array_for_test,
|
|
1409
|
+
receiver.arguments ? receiver.arguments.arguments : [],
|
|
1410
|
+
location)
|
|
1411
|
+
# Recorded like any other read, because the cell still has to exist
|
|
1412
|
+
# and still has to have been settled before it is asked about. What
|
|
1413
|
+
# it does not do is feed the value-mask propagation, which is decided
|
|
1414
|
+
# separately: a mask test never enters the value's mask.
|
|
1415
|
+
record_subscripts(array, subscripts)
|
|
1416
|
+
@uses_undef = true
|
|
1417
|
+
MaskTest.new(array, subscripts, negated, location)
|
|
1418
|
+
end
|
|
1419
|
+
|
|
1420
|
+
# `[name, index]` when this subscripts a pointer parameter, nil when it
|
|
1421
|
+
# does not. A pointer declared const is refused on the left rather than
|
|
1422
|
+
# silently written through: the declaration is a promise to the caller,
|
|
1423
|
+
# not decoration.
|
|
1424
|
+
def pointer_subscript (node, write: false)
|
|
1425
|
+
name = captured_name(node.receiver)
|
|
1426
|
+
return nil unless name && @pointers.key?(name)
|
|
1427
|
+
case @pointers.fetch(name)
|
|
1428
|
+
when nil
|
|
1429
|
+
raise Unsupported.new(
|
|
1430
|
+
"`#{name}` points at nothing in particular, so there is no cell " \
|
|
1431
|
+
"for `#{name}[...]` to reach; declare what it points at",
|
|
1432
|
+
node.location)
|
|
1433
|
+
when false
|
|
1434
|
+
if write
|
|
1435
|
+
raise Unsupported.new(
|
|
1436
|
+
"`#{name}` is declared const, so the function may read it but " \
|
|
1437
|
+
"not write through it", node.location)
|
|
1438
|
+
end
|
|
1439
|
+
end
|
|
1440
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
1441
|
+
arguments = arguments[0..-2] if write
|
|
1442
|
+
unless arguments.size == 1
|
|
1443
|
+
raise Unsupported.new(
|
|
1444
|
+
"`#{name}` is a pointer, so it takes one index", node.location)
|
|
1445
|
+
end
|
|
1446
|
+
@pointer_names << name unless @pointer_names.include?(name)
|
|
1447
|
+
[name, build(arguments.first)]
|
|
1448
|
+
end
|
|
1449
|
+
|
|
1450
|
+
# `fact(n - 1)` inside the body of `double fact(double)`, and
|
|
1451
|
+
# `fact.call(n - 1)` for whoever prefers the spelling a captured
|
|
1452
|
+
# function takes. Returns nil when this is not that call.
|
|
1453
|
+
#
|
|
1454
|
+
# It is not a capture: nothing outside the function is reached, and the
|
|
1455
|
+
# compiled object still references no Ruby value. The name is in the
|
|
1456
|
+
# declaration the caller wrote, which is where C would have put it too.
|
|
1457
|
+
def recursive_call (node)
|
|
1458
|
+
return nil unless @recursion
|
|
1459
|
+
name, parameters, result_type = @recursion
|
|
1460
|
+
# `fact.call(n - 1)`, the spelling every other C function takes. A
|
|
1461
|
+
# bare `fact(n - 1)` would read better as C and is refused all the
|
|
1462
|
+
# same: it is not Ruby, and the block has to stay runnable, since
|
|
1463
|
+
# running it beside the compiled function is how the two are checked
|
|
1464
|
+
# against each other.
|
|
1465
|
+
if node.receiver.nil? && node.name == name && node.arguments
|
|
1466
|
+
raise Unsupported.new(
|
|
1467
|
+
"`#{name}` calls itself the way any C function is called here, " \
|
|
1468
|
+
"as `#{name}.call(...)` -- a bare `#{name}(...)` is not Ruby, and " \
|
|
1469
|
+
"the block has to stay runnable",
|
|
1470
|
+
node.location)
|
|
1471
|
+
end
|
|
1472
|
+
return nil unless C_FUNCTION_CALL_NAMES.include?(node.name) &&
|
|
1473
|
+
captured_name(node.receiver) == name
|
|
1474
|
+
return nil if node.block
|
|
1475
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
1476
|
+
unless arguments.size == parameters.size
|
|
1477
|
+
raise Unsupported.new(
|
|
1478
|
+
"`#{name}` takes #{parameters.size} " \
|
|
1479
|
+
"#{parameters.size == 1 ? 'argument' : 'arguments'}; " \
|
|
1480
|
+
"#{arguments.size} #{arguments.size == 1 ? 'was' : 'were'} given",
|
|
1481
|
+
node.location)
|
|
1482
|
+
end
|
|
1483
|
+
built = arguments.each_with_index.map { |argument, position|
|
|
1484
|
+
build_c_function_argument(argument, parameters[position], name)
|
|
1485
|
+
}
|
|
1486
|
+
RecursiveCall.new(name, built, parameters, result_type, node.location)
|
|
1487
|
+
end
|
|
1488
|
+
|
|
1489
|
+
# Returns the node for a call on a captured C function, or nil when this
|
|
1490
|
+
# is not one.
|
|
1491
|
+
def c_function_call (node)
|
|
1492
|
+
return nil unless C_FUNCTION_CALL_NAMES.include?(node.name)
|
|
1493
|
+
name = captured_name(node.receiver)
|
|
1494
|
+
return nil unless name && @c_functions.key?(name)
|
|
1495
|
+
c_function = @c_functions.fetch(name)
|
|
1496
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
1497
|
+
unless arguments.size == c_function.arity
|
|
1498
|
+
raise Unsupported.new(
|
|
1499
|
+
"`#{name}` is `#{c_function}`, so it takes #{c_function.arity} " \
|
|
1500
|
+
"#{c_function.arity == 1 ? 'argument' : 'arguments'}; " \
|
|
1501
|
+
"#{arguments.size} #{arguments.size == 1 ? 'was' : 'were'} given",
|
|
1502
|
+
node.location)
|
|
1503
|
+
end
|
|
1504
|
+
@c_function_names << name unless @c_function_names.include?(name)
|
|
1505
|
+
built = arguments.each_with_index.map { |argument, position|
|
|
1506
|
+
build_c_function_argument(argument, c_function.parameters[position], name)
|
|
1507
|
+
}
|
|
1508
|
+
CFunctionCall.new(name, built, node.location)
|
|
1509
|
+
end
|
|
1510
|
+
|
|
1511
|
+
# A parameter that points at numbers takes an array, not a cell: the
|
|
1512
|
+
# kernel hands over the address and the function reaches the cells
|
|
1513
|
+
# itself. Which it is comes from the declaration, so the same captured
|
|
1514
|
+
# name means a cell in one argument position and the whole array in
|
|
1515
|
+
# another -- `poly.call(x[i], coef)` says both.
|
|
1516
|
+
def build_c_function_argument (argument, parameter, c_function)
|
|
1517
|
+
# Inside a function, one of its own pointer parameters is already the
|
|
1518
|
+
# address the callee wants, and handing it on is what C does -- for a
|
|
1519
|
+
# `void *` slot as much as for a run of numbers, since neither is
|
|
1520
|
+
# read here. It is emitted as the bare name, which is what the C
|
|
1521
|
+
# parameter is called.
|
|
1522
|
+
if parameter&.pointer
|
|
1523
|
+
name = captured_name(argument)
|
|
1524
|
+
if name && @pointers.key?(name)
|
|
1525
|
+
@pointer_names << name unless @pointer_names.include?(name)
|
|
1526
|
+
return ArrayAddress.new(name, argument.location)
|
|
1527
|
+
end
|
|
1528
|
+
end
|
|
1529
|
+
return build(argument) unless parameter&.indexable?
|
|
1530
|
+
name = captured_name(argument)
|
|
1531
|
+
unless name && @array_names.include?(name)
|
|
1532
|
+
raise Unsupported.new(
|
|
1533
|
+
"`#{c_function}` takes `#{parameter.text}` there, which is an array; " \
|
|
1534
|
+
"pass one by name",
|
|
1535
|
+
argument.location)
|
|
1536
|
+
end
|
|
1537
|
+
@address_arrays << name unless @address_arrays.include?(name)
|
|
1538
|
+
# What the declaration promised about it, kept so the caller can hold
|
|
1539
|
+
# the array to it: which type it points at, whether it may be written
|
|
1540
|
+
# through, and how long it said it was.
|
|
1541
|
+
(@address_parameters[name] ||= []) << parameter
|
|
1542
|
+
ArrayAddress.new(name, argument.location)
|
|
1543
|
+
end
|
|
1544
|
+
|
|
1545
|
+
# A free name, however Prism spelled it: a block parsed on its own sees
|
|
1546
|
+
# a local it does not know as a method call with no receiver.
|
|
1547
|
+
def captured_name (receiver)
|
|
1548
|
+
case receiver
|
|
1549
|
+
when Prism::LocalVariableReadNode then receiver.name
|
|
1550
|
+
when Prism::ConstantReadNode then receiver.name
|
|
1551
|
+
when Prism::ConstantPathNode then receiver.slice.to_sym
|
|
1552
|
+
when Prism::CallNode
|
|
1553
|
+
receiver.name if receiver.receiver.nil? && receiver.arguments.nil? &&
|
|
1554
|
+
receiver.block.nil?
|
|
1555
|
+
end
|
|
1556
|
+
end
|
|
1557
|
+
|
|
1558
|
+
# `out = ...` in the whole-array spelling. The cell is the loop's own,
|
|
1559
|
+
# which is what makes this an assignment rather than a subscript: there
|
|
1560
|
+
# is no other cell it could mean.
|
|
1561
|
+
def whole_array_write (name, expression, location)
|
|
1562
|
+
subscripts = Array.new(rank) { |axis| [@outer_names[axis], 0] }
|
|
1563
|
+
record_array_rank(name, rank, location)
|
|
1564
|
+
record_subscripts(name, subscripts)
|
|
1565
|
+
@written_arrays << name unless @written_arrays.include?(name)
|
|
1566
|
+
ElementWrite.new(name, expression, location, subscripts)
|
|
1567
|
+
end
|
|
1568
|
+
|
|
1569
|
+
def build_element_read (node)
|
|
1570
|
+
if (pointer = pointer_subscript(node))
|
|
1571
|
+
return PointerRead.new(pointer.first, pointer.last, node.location)
|
|
1572
|
+
end
|
|
1573
|
+
array = array_name(node.receiver, node.location)
|
|
1574
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
1575
|
+
subscripts = read_subscripts(array, arguments, node.location)
|
|
1576
|
+
record_subscripts(array, subscripts)
|
|
1577
|
+
ElementRead.new(array, subscripts, node.location)
|
|
1578
|
+
end
|
|
1579
|
+
|
|
1580
|
+
def record_subscripts (array, subscripts)
|
|
1581
|
+
@subscripts[array] << subscripts unless @subscripts[array].include?(subscripts)
|
|
1582
|
+
end
|
|
1583
|
+
|
|
1584
|
+
def array_name (receiver, location)
|
|
1585
|
+
name = captured_name(receiver)
|
|
1586
|
+
unless name && @array_names.include?(name)
|
|
1587
|
+
raise Unsupported.new("only a captured CArray may be indexed", location)
|
|
1588
|
+
end
|
|
1589
|
+
name
|
|
1590
|
+
end
|
|
1591
|
+
|
|
1592
|
+
# One [index name, offset] per axis of the array. Which index addresses
|
|
1593
|
+
# which axis is the caller's choice, so a reduction can run an inner
|
|
1594
|
+
# index down one axis while an outer one holds the others.
|
|
1595
|
+
def read_subscripts (array, arguments, location)
|
|
1596
|
+
return window_subscripts(array, arguments, location) if @windows.include?(array)
|
|
1597
|
+
# An array the block closed over rather than was given is read at the
|
|
1598
|
+
# cell the loop is on, which is what a bare name means everywhere a
|
|
1599
|
+
# loop is this compiler's.
|
|
1600
|
+
if @windows.any? && arguments.empty? && !@cell_names.include?(array)
|
|
1601
|
+
record_array_rank(array, rank, location)
|
|
1602
|
+
return Array.new(rank) { |axis| [@outer_names[axis], 0] }
|
|
1603
|
+
end
|
|
1604
|
+
if @whole_array
|
|
1605
|
+
unless arguments.empty?
|
|
1606
|
+
raise Unsupported.new(
|
|
1607
|
+
"this block takes no indices, so an array is spelled `a[]`",
|
|
1608
|
+
location)
|
|
1609
|
+
end
|
|
1610
|
+
return Array.new(rank) { |axis| [@outer_names[axis], 0] }
|
|
1611
|
+
end
|
|
1612
|
+
if arguments.empty?
|
|
1613
|
+
return cell_subscripts(array, location) if @cell_names.include?(array)
|
|
1614
|
+
raise Unsupported.new("`#{array}` needs an index for each of its axes",
|
|
1615
|
+
location)
|
|
1616
|
+
end
|
|
1617
|
+
record_array_rank(array, arguments.size, location)
|
|
1618
|
+
arguments.map { |argument| read_subscript(argument, location) }
|
|
1619
|
+
end
|
|
1620
|
+
|
|
1621
|
+
# `a[-1, 1]`: one offset per axis, written out. Written out because the
|
|
1622
|
+
# offsets are what the radius is read from, and the radius is what lets
|
|
1623
|
+
# the interior be walked without asking, at every cell, whether it is
|
|
1624
|
+
# still inside. A computed subscript is a different thing and has a
|
|
1625
|
+
# different spelling -- it is `jit_for`'s.
|
|
1626
|
+
def window_subscripts (array, arguments, location)
|
|
1627
|
+
if arguments.size != rank
|
|
1628
|
+
raise Unsupported.new(
|
|
1629
|
+
"`#{array}` is a window onto a rank-#{rank} array, so it takes " \
|
|
1630
|
+
"#{rank} #{rank == 1 ? 'offset' : 'offsets'}: `#{array}[0" \
|
|
1631
|
+
"#{', 0' * (rank - 1)}]` is the cell itself",
|
|
1632
|
+
location)
|
|
1633
|
+
end
|
|
1634
|
+
record_array_rank(array, arguments.size, location)
|
|
1635
|
+
arguments.each_with_index.map { |argument, axis|
|
|
1636
|
+
offset = literal_integer(argument)
|
|
1637
|
+
unless offset
|
|
1638
|
+
raise Unsupported.new(
|
|
1639
|
+
"a window's offsets are written out, as in `#{array}[-1, 1]`; " \
|
|
1640
|
+
"a subscript the kernel works out is `jit_for`'s",
|
|
1641
|
+
location)
|
|
1642
|
+
end
|
|
1643
|
+
@window_reach[axis] = [[@window_reach[axis][0], offset].min,
|
|
1644
|
+
[@window_reach[axis][1], offset].max]
|
|
1645
|
+
[@outer_names[axis], offset]
|
|
1646
|
+
}
|
|
1647
|
+
end
|
|
1648
|
+
|
|
1649
|
+
def record_array_rank (array, count, location = nil)
|
|
1650
|
+
known = @array_ranks[array]
|
|
1651
|
+
if known && known != count
|
|
1652
|
+
raise Unsupported.new(
|
|
1653
|
+
"`#{array}` is indexed with #{known} " \
|
|
1654
|
+
"#{known == 1 ? 'index' : 'indices'} in one place and #{count} in " \
|
|
1655
|
+
"another",
|
|
1656
|
+
location)
|
|
1657
|
+
end
|
|
1658
|
+
@array_ranks[array] = count
|
|
1659
|
+
end
|
|
1660
|
+
|
|
1661
|
+
# An index expression is `j`, `j + c` or `j - c`, where `j` is any index
|
|
1662
|
+
# in scope, so that the offset is a compile-time constant.
|
|
1663
|
+
def read_subscript (node, location)
|
|
1664
|
+
if node.is_a?(Prism::LocalVariableReadNode) && index_in_scope?(node.name)
|
|
1665
|
+
return [node.name, 0]
|
|
1666
|
+
end
|
|
1667
|
+
if node.is_a?(Prism::CallNode) && [:+, :-].include?(node.name) &&
|
|
1668
|
+
node.receiver.is_a?(Prism::LocalVariableReadNode) &&
|
|
1669
|
+
index_in_scope?(node.receiver.name)
|
|
1670
|
+
return walked_subscript(node)
|
|
1671
|
+
end
|
|
1672
|
+
# Anything else pins the axis at a position the loop does not walk:
|
|
1673
|
+
# `a[i, 0]`, or `a[row, k]` where `row` is an integer the block closed
|
|
1674
|
+
# over. It is an argument to the kernel like any other scalar, so one
|
|
1675
|
+
# compiled kernel serves every value of it.
|
|
1676
|
+
[nil, pinned_subscript(node)]
|
|
1677
|
+
end
|
|
1678
|
+
|
|
1679
|
+
def pinned_subscript (node)
|
|
1680
|
+
build(node)
|
|
1681
|
+
end
|
|
1682
|
+
|
|
1683
|
+
# The one cell a CScalar has: position zero on its one axis, which is a
|
|
1684
|
+
# pinned subscript like any other and is checked like one.
|
|
1685
|
+
def cell_subscripts (array, location)
|
|
1686
|
+
record_array_rank(array, 1, location)
|
|
1687
|
+
[[nil, IntegerLiteral.new(0, location)]]
|
|
1688
|
+
end
|
|
1689
|
+
|
|
1690
|
+
def cell_read (array, location)
|
|
1691
|
+
subscripts = cell_subscripts(array, location)
|
|
1692
|
+
record_subscripts(array, subscripts)
|
|
1693
|
+
ElementRead.new(array, subscripts, location)
|
|
1694
|
+
end
|
|
1695
|
+
|
|
1696
|
+
# A subscript that does not walk with the loop is *fixed* when its value
|
|
1697
|
+
# can be worked out before the kernel runs -- a literal, a captured
|
|
1698
|
+
# integer, arithmetic over those. Then it joins the checks that happen
|
|
1699
|
+
# in advance: it is part of the box a view has to transfer, and reaching
|
|
1700
|
+
# outside the array is a message rather than a read.
|
|
1701
|
+
#
|
|
1702
|
+
# Anything else is *dynamic*: `a[b[i]]`, or an index the body computed.
|
|
1703
|
+
# Its value is not knowable until the cell is reached, so the check
|
|
1704
|
+
# moves to the access itself.
|
|
1705
|
+
def self.fixed_subscript? (node)
|
|
1706
|
+
case node
|
|
1707
|
+
when IntegerLiteral, CaptureRead then true
|
|
1708
|
+
when UnaryMinus then fixed_subscript?(node.operand)
|
|
1709
|
+
when BinaryOperation
|
|
1710
|
+
[:+, :-, :*].include?(node.operator) &&
|
|
1711
|
+
fixed_subscript?(node.left) && fixed_subscript?(node.right)
|
|
1712
|
+
else false
|
|
1713
|
+
end
|
|
1714
|
+
end
|
|
1715
|
+
|
|
1716
|
+
|
|
1717
|
+
# `j + c` or `j - c`, where c is a literal or an integer built from
|
|
1718
|
+
# literals and captured integers -- `a[i - window]` for a window the
|
|
1719
|
+
# caller chooses. A captured offset reaches the kernel as an argument,
|
|
1720
|
+
# so one compiled kernel serves every value of it, and the judgements
|
|
1721
|
+
# that need the value (which way the axis runs, and whether the offset
|
|
1722
|
+
# is a dependency at all under this extent's step) are made when the
|
|
1723
|
+
# kernel is called, where the value is known, alongside the bounds check.
|
|
1724
|
+
def walked_subscript (node)
|
|
1725
|
+
receiver = node.receiver
|
|
1726
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
1727
|
+
unless arguments.size == 1
|
|
1728
|
+
raise Unsupported.new("an index offset takes one argument",
|
|
1729
|
+
node.location)
|
|
1730
|
+
end
|
|
1731
|
+
argument = arguments.first
|
|
1732
|
+
if argument.is_a?(Prism::IntegerNode)
|
|
1733
|
+
constant = argument.value
|
|
1734
|
+
if constant < 0
|
|
1735
|
+
raise Unsupported.new("write the offset as `j - c` with c >= 0",
|
|
1736
|
+
node.location)
|
|
1737
|
+
end
|
|
1738
|
+
return [receiver.name, node.name == :+ ? constant : -constant]
|
|
1739
|
+
end
|
|
1740
|
+
offset = pinned_subscript(argument)
|
|
1741
|
+
[receiver.name, node.name == :+ ? offset : UnaryMinus.new(offset)]
|
|
1742
|
+
end
|
|
1743
|
+
|
|
1744
|
+
def index_in_scope? (name)
|
|
1745
|
+
@outer_names.include?(name) || @inner_names.include?(name)
|
|
1746
|
+
end
|
|
1747
|
+
|
|
1748
|
+
def available_indices
|
|
1749
|
+
(@outer_names + @inner_names).map { |name| "`#{name}`" }.join(", ")
|
|
1750
|
+
end
|
|
1751
|
+
|
|
1752
|
+
# Index expressions are limited to `i`, `i + c` and `i - c` so that the
|
|
1753
|
+
# dependency offset is a compile-time constant.
|
|
1754
|
+
def read_offset (node, axis)
|
|
1755
|
+
expected = @index_names[axis]
|
|
1756
|
+
if node.is_a?(Prism::LocalVariableReadNode) && node.name == expected
|
|
1757
|
+
return 0
|
|
1758
|
+
end
|
|
1759
|
+
unless node.is_a?(Prism::CallNode) && [:+, :-].include?(node.name)
|
|
1760
|
+
raise Unsupported.new(
|
|
1761
|
+
"index #{axis} must be `#{expected}`, `#{expected} + c` or " \
|
|
1762
|
+
"`#{expected} - c`", node.location)
|
|
1763
|
+
end
|
|
1764
|
+
receiver = node.receiver
|
|
1765
|
+
unless receiver.is_a?(Prism::LocalVariableReadNode) && receiver.name == expected
|
|
1766
|
+
raise Unsupported.new("index #{axis} must start from `#{expected}`",
|
|
1767
|
+
node.location)
|
|
1768
|
+
end
|
|
1769
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
1770
|
+
unless arguments.size == 1 && arguments.first.is_a?(Prism::IntegerNode)
|
|
1771
|
+
raise Unsupported.new("an index offset must be an integer literal",
|
|
1772
|
+
node.location)
|
|
1773
|
+
end
|
|
1774
|
+
constant = arguments.first.value
|
|
1775
|
+
if constant < 0
|
|
1776
|
+
raise Unsupported.new("write the offset as `#{expected} - c` with c >= 0",
|
|
1777
|
+
node.location)
|
|
1778
|
+
end
|
|
1779
|
+
node.name == :+ ? constant : -constant
|
|
1780
|
+
end
|
|
1781
|
+
|
|
1782
|
+
# `Complex(x, y)`, whose arguments are the parts, and `Complex(x)`,
|
|
1783
|
+
# which is taken for `Complex(x, 0.0)`.
|
|
1784
|
+
#
|
|
1785
|
+
# In Ruby the shorter one is not quite the longer one: its imaginary
|
|
1786
|
+
# part is an exact Integer zero, and `f_add` returns the other operand
|
|
1787
|
+
# untouched rather than adding that zero to it, so
|
|
1788
|
+
# `Complex(1.0) + Complex(2.0, -0.0)` keeps the sign of a zero that
|
|
1789
|
+
# `Complex(1.0, 0.0) + Complex(2.0, -0.0)` loses. Nothing else tells
|
|
1790
|
+
# them apart -- multiplication and division agree, infinities and all --
|
|
1791
|
+
# and carrying an exactly-zero imaginary part through the type lattice
|
|
1792
|
+
# to reproduce that one case is not worth what it would cost to read.
|
|
1793
|
+
def build_complex (node)
|
|
1794
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
1795
|
+
unless (1..2).cover?(arguments.size)
|
|
1796
|
+
raise Unsupported.new(
|
|
1797
|
+
"`Complex` takes one part or two, and got #{arguments.size}",
|
|
1798
|
+
node.location)
|
|
1799
|
+
end
|
|
1800
|
+
imaginary = arguments.size == 2 ? build(arguments.last)
|
|
1801
|
+
: FloatLiteral.new(0.0, node.location)
|
|
1802
|
+
ComplexBuild.new(build(arguments.first), imaginary, node.location)
|
|
1803
|
+
end
|
|
1804
|
+
|
|
1805
|
+
def postfix_math (name)
|
|
1806
|
+
return nil unless POSTFIX_NAMES.include?(name)
|
|
1807
|
+
MATH_FUNCTIONS[name]
|
|
1808
|
+
end
|
|
1809
|
+
|
|
1810
|
+
# Ruby's Integer ** Integer is exact and unbounded, which int64 is not.
|
|
1811
|
+
# A non-negative literal exponent can still be squared out; anything
|
|
1812
|
+
# else has to be asked for in floating point.
|
|
1813
|
+
def build_power (receiver, exponent, location)
|
|
1814
|
+
base = build(receiver)
|
|
1815
|
+
power = build(exponent)
|
|
1816
|
+
if power.is_a?(IntegerLiteral) && power.value < 0
|
|
1817
|
+
raise Unsupported.new(
|
|
1818
|
+
"a negative exponent gives a Rational in Ruby; write `1.0 / x ** n`",
|
|
1819
|
+
location)
|
|
1820
|
+
end
|
|
1821
|
+
Power.new(base, power, location)
|
|
1822
|
+
end
|
|
1823
|
+
|
|
1824
|
+
def build_math_call (node)
|
|
1825
|
+
function = MATH_FUNCTIONS[node.name]
|
|
1826
|
+
unless function
|
|
1827
|
+
raise Unsupported.new("Math.#{node.name} has no math.h counterpart",
|
|
1828
|
+
node.location)
|
|
1829
|
+
end
|
|
1830
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
1831
|
+
expected = [:atan2, :hypot].include?(node.name) ? 2 : 1
|
|
1832
|
+
unless arguments.size == expected
|
|
1833
|
+
raise Unsupported.new("Math.#{node.name} takes #{expected} argument(s)",
|
|
1834
|
+
node.location)
|
|
1835
|
+
end
|
|
1836
|
+
MathCall.new(function, arguments.map { |argument| build(argument) },
|
|
1837
|
+
node.location)
|
|
1838
|
+
end
|
|
1839
|
+
|
|
1840
|
+
def node_name (node)
|
|
1841
|
+
node.class.name.split("::").last.sub(/Node\z/, "")
|
|
1842
|
+
end
|
|
1843
|
+
|
|
1844
|
+
end
|
|
1845
|
+
|
|
1846
|
+
end
|
|
1847
|
+
end
|