carray-jit 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +917 -3
- data/README.md +14 -12
- data/carray-jit.gemspec +1 -3
- data/docs/00_Introduction.md +5 -3
- data/docs/01_GettingStarted.md +1 -1
- data/docs/02_KernelShapes.md +94 -15
- data/docs/03_SupportedFeatures.md +665 -32
- data/docs/04_Compiling.md +34 -7
- data/docs/05_DesignNotes.md +3 -3
- data/docs/06_Cheatsheet.md +200 -7
- data/docs/07_StepByStep.ja.md +533 -0
- data/docs/07_StepByStep.md +534 -0
- data/docs/08_LanguageReference.md +427 -0
- data/examples/README.md +12 -0
- data/examples/applications/alarm.rb +121 -0
- data/examples/applications/collatz.rb +105 -0
- data/examples/applications/cubic_spline.rb +331 -0
- data/examples/applications/dithering.rb +144 -0
- data/examples/applications/group_stats.rb +115 -0
- data/examples/applications/lookup.rb +126 -0
- data/examples/applications/median_filter.rb +153 -0
- data/examples/applications/parcel_ascent.rb +220 -0
- data/examples/applications/point_in_polygon.rb +111 -0
- data/examples/applications/random_walk.rb +98 -0
- data/examples/applications/van_der_pol.rb +186 -0
- data/examples/applications/wet_bulb.rb +140 -0
- data/examples/features/10_complex.rb +14 -4
- data/examples/features/15_loops.rb +7 -1
- data/lib/carray/jit/access.rb +14 -0
- data/lib/carray/jit/analyzer.rb +2218 -140
- data/lib/carray/jit/block_reader.rb +37 -6
- data/lib/carray/jit/c_function.rb +961 -86
- data/lib/carray/jit/c_generator.rb +1970 -191
- data/lib/carray/jit/call.rb +68 -0
- data/lib/carray/jit/compiler.rb +102 -14
- data/lib/carray/jit/expression.rb +271 -33
- data/lib/carray/jit/kernel.rb +382 -32
- data/lib/carray/jit/node.rb +374 -9
- data/lib/carray/jit/sorting_networks.rb +182 -0
- data/lib/carray/jit/type_assignment.rb +424 -35
- data/lib/carray/jit/version.rb +1 -1
- data/lib/carray/jit.rb +547 -77
- metadata +23 -8
- data/ext/carray_jit_access/carray_jit_access.c +0 -460
- data/ext/carray_jit_access/extconf.rb +0 -8
data/lib/carray/jit/kernel.rb
CHANGED
|
@@ -19,7 +19,13 @@ class CArray
|
|
|
19
19
|
# @return [Boolean] whether this call invoked the compiler, rather
|
|
20
20
|
# than reusing a cached object.
|
|
21
21
|
attr_reader :source, :c_source, :arrays, :storage_types, :reals,
|
|
22
|
-
:integers, :complexes, :
|
|
22
|
+
:integers, :complexes, :unsigned_integers,
|
|
23
|
+
:rank, :index_names, :written_arrays,
|
|
24
|
+
# The arrays handed to a C function whole, by address,
|
|
25
|
+
# rather than walked a cell at a time. A caller that lines
|
|
26
|
+
# operands up has to leave these alone: their shape is the
|
|
27
|
+
# C function's business, not the expression's.
|
|
28
|
+
:address_arrays,
|
|
23
29
|
:compiled, :masked, :directions, :contracted_names,
|
|
24
30
|
:index_axes,
|
|
25
31
|
# How far a stencil's windows reach on each axis, as
|
|
@@ -38,6 +44,8 @@ class CArray
|
|
|
38
44
|
@reals = generator.reals
|
|
39
45
|
@integers = generator.integers
|
|
40
46
|
@complexes = generator.complexes
|
|
47
|
+
@unsigned_integers = generator.unsigned_integers
|
|
48
|
+
@unsigned_captures = generator.unsigned_captures
|
|
41
49
|
# Per array, the axes read at an index only the running kernel knows,
|
|
42
50
|
# and the extents it checks them against.
|
|
43
51
|
@extent_slots = generator.extent_slots
|
|
@@ -48,6 +56,10 @@ class CArray
|
|
|
48
56
|
@written_arrays = analyzer.written_arrays
|
|
49
57
|
@array_ranks = analyzer.array_ranks
|
|
50
58
|
@inner_ranges = analyzer.inner_ranges
|
|
59
|
+
@unbounded_indices = analyzer.unbounded_indices
|
|
60
|
+
# An index the block wrote twice has two identifiers here; the
|
|
61
|
+
# messages speak the name it was written with.
|
|
62
|
+
@index_sources = analyzer.index_sources
|
|
51
63
|
@axis_uses = @arrays.to_h { |array|
|
|
52
64
|
[array, (0...array_rank(array)).map { |axis| analyzer.axis_use(array, axis) }]
|
|
53
65
|
}
|
|
@@ -61,6 +73,17 @@ class CArray
|
|
|
61
73
|
end
|
|
62
74
|
end
|
|
63
75
|
|
|
76
|
+
# Which captured scalars decide where the kernel reaches, rather than
|
|
77
|
+
# what it computes. A pinned subscript, an offset and an inner loop's
|
|
78
|
+
# range are worked out here, in Ruby, before the first cell; every
|
|
79
|
+
# other captured number is packed into a buffer and read by the C. So
|
|
80
|
+
# these are the ones a prepared call has to be keyed on -- and a
|
|
81
|
+
# coefficient or a clock that changes every step is not one of them,
|
|
82
|
+
# which is what keeps a time-stepping caller on the prepared path.
|
|
83
|
+
@shape_scalar_names = shape_scalar_names
|
|
84
|
+
# Calls prepared earlier, most recently used first.
|
|
85
|
+
@plans = []
|
|
86
|
+
|
|
64
87
|
@masked = generator.masked
|
|
65
88
|
@window_reach = analyzer.window_reach
|
|
66
89
|
@window_reaches = analyzer.window_reaches
|
|
@@ -68,6 +91,15 @@ class CArray
|
|
|
68
91
|
# writes into the error slot. The message does not travel: C has
|
|
69
92
|
# nothing to carry it in, and it was known when this was compiled.
|
|
70
93
|
@raise_messages = generator.raise_messages
|
|
94
|
+
# The arrays this kernel allocates at its entry, as
|
|
95
|
+
# [name, storage, shape] -- so that a shape it could not use, or an
|
|
96
|
+
# allocation that failed, can be named with the lengths it came to on
|
|
97
|
+
# this call. The shapes are worked out here as they are there: from
|
|
98
|
+
# the same expressions over the same captured integers.
|
|
99
|
+
@heap_arrays = analyzer.local_array_declarations.filter_map { |entry|
|
|
100
|
+
_scope, name, storage, shape, heap = entry
|
|
101
|
+
[name, storage, shape] if heap
|
|
102
|
+
}
|
|
71
103
|
# Kept so the sweep entry point can be built from it if one is ever
|
|
72
104
|
# asked for. Compiling it eagerly would pay for a road most kernels
|
|
73
105
|
# never take.
|
|
@@ -109,16 +141,15 @@ class CArray
|
|
|
109
141
|
def call (array_values, scalar_values, bounds, c_function_values = {},
|
|
110
142
|
border: false)
|
|
111
143
|
arrays = @arrays.map { |name| array_values.fetch(name) }
|
|
112
|
-
|
|
113
|
-
verify(arrays, bounds, ranges, scalar_values, reach: !border)
|
|
144
|
+
plan = prepared_call(arrays, array_values, bounds, scalar_values, border)
|
|
114
145
|
|
|
115
|
-
writable =
|
|
146
|
+
writable = plan[:writable]
|
|
116
147
|
error = [0].pack("l")
|
|
117
|
-
packed_bounds = bounds
|
|
148
|
+
packed_bounds = plan[:bounds]
|
|
118
149
|
reals = packed_reals(scalar_values)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
150
|
+
# The captured integers change from call to call and are packed here;
|
|
151
|
+
# the extents behind them are the arrays' own and travel in the plan.
|
|
152
|
+
integers = packed_scalar_integers(scalar_values) + plan[:extents]
|
|
122
153
|
functions = @c_function_names.map { |name|
|
|
123
154
|
c_function_values.fetch(name).pointer.to_i
|
|
124
155
|
}.pack("Q*")
|
|
@@ -136,14 +167,12 @@ class CArray
|
|
|
136
167
|
}
|
|
137
168
|
}.pack("Q*")
|
|
138
169
|
|
|
139
|
-
box =
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
basis[:mask_strides] || Array.new(@rank, 0)
|
|
146
|
-
}.pack("q*")
|
|
170
|
+
box = plan[:box]
|
|
171
|
+
# Opened for a kernel rather than for a reader: the four buffers come
|
|
172
|
+
# back packed, instead of a hash and two arrays per operand that this
|
|
173
|
+
# would pack back into bytes and throw away.
|
|
174
|
+
Access.open(arrays, writable, box[0], box[1], true) do
|
|
175
|
+
|pointers, strides, mask_pointers, mask_strides|
|
|
147
176
|
entry = border ? border_function : @function
|
|
148
177
|
entry.call(buffer(pointers), buffer(strides), buffer(packed_bounds),
|
|
149
178
|
buffer(reals), buffer(integers), buffer(functions),
|
|
@@ -157,9 +186,17 @@ class CArray
|
|
|
157
186
|
array[] = buffer
|
|
158
187
|
end
|
|
159
188
|
|
|
160
|
-
report(error)
|
|
189
|
+
report(error, scalar_values)
|
|
161
190
|
end
|
|
162
191
|
|
|
192
|
+
# How many prepared calls one kernel keeps. A stencil asks under its
|
|
193
|
+
# interior's bounds and one pair per axis of frame, so a two-dimensional
|
|
194
|
+
# one needs five before it repeats; a caller alternating between two
|
|
195
|
+
# model states needs two of whatever it was already using. Past this
|
|
196
|
+
# the oldest goes and the call prepares itself again, which is what
|
|
197
|
+
# every call did before.
|
|
198
|
+
PLAN_LIMIT = 8
|
|
199
|
+
|
|
163
200
|
# @private
|
|
164
201
|
SLAB_NAME = "carray_jit_slab"
|
|
165
202
|
|
|
@@ -178,10 +215,7 @@ class CArray
|
|
|
178
215
|
|
|
179
216
|
error = [0].pack("l")
|
|
180
217
|
reals = packed_reals(scalar_values)
|
|
181
|
-
integers = (
|
|
182
|
-
@extent_slots.map { |name, axis|
|
|
183
|
-
array_values.fetch(name).dim[axis]
|
|
184
|
-
}).pack("q*")
|
|
218
|
+
integers = packed_integers(scalar_values, array_values)
|
|
185
219
|
functions = @c_function_names.map { |name|
|
|
186
220
|
c_function_values.fetch(name).pointer.to_i
|
|
187
221
|
}.pack("Q*")
|
|
@@ -211,7 +245,7 @@ class CArray
|
|
|
211
245
|
array[] = item
|
|
212
246
|
end
|
|
213
247
|
|
|
214
|
-
report(error)
|
|
248
|
+
report(error, scalar_values)
|
|
215
249
|
end
|
|
216
250
|
|
|
217
251
|
# The wrapper CArray's sweep calls, for the same reason #c_source is
|
|
@@ -230,16 +264,138 @@ class CArray
|
|
|
230
264
|
|
|
231
265
|
private
|
|
232
266
|
|
|
267
|
+
# The parts of a call that the operands' shapes, the bounds and the
|
|
268
|
+
# kernel's own scalars decide, rather than the data or the views.
|
|
269
|
+
#
|
|
270
|
+
# Everything here answers the same for two calls whose key agrees, so it
|
|
271
|
+
# is worked out once and kept: the bounds check, the box the xfer tier
|
|
272
|
+
# transfers, the extents the kernel is handed, and which operands it
|
|
273
|
+
# writes. What is deliberately *not* here is anything read off a view:
|
|
274
|
+
# two arrays of one shape and one type can be laid out differently, and
|
|
275
|
+
# a stride read from the one is not a stride into the other. Those come
|
|
276
|
+
# from Access.open on every call, as they always did.
|
|
277
|
+
#
|
|
278
|
+
# Nor is the refusal of a masked array handed whole to a C function
|
|
279
|
+
# (#address_buffer): a mask is runtime state that leaves the shape and
|
|
280
|
+
# the type alone, so no key here would notice one appearing. It is
|
|
281
|
+
# asked on every call, which costs a predicate and keeps the refusal.
|
|
282
|
+
def prepared_call (arrays, array_values, bounds, scalars, border)
|
|
283
|
+
key = plan_key(arrays, bounds, scalars, border)
|
|
284
|
+
if key
|
|
285
|
+
found = @plans.assoc(key)
|
|
286
|
+
if found
|
|
287
|
+
unless @plans.first.equal?(found)
|
|
288
|
+
@plans.unshift(@plans.delete(found))
|
|
289
|
+
end
|
|
290
|
+
return found[1]
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
ranges = index_ranges(bounds, scalars)
|
|
294
|
+
verify(arrays, bounds, ranges, scalars, reach: !border)
|
|
295
|
+
plan = {
|
|
296
|
+
:writable => @arrays.map { |name| @written_arrays.include?(name) },
|
|
297
|
+
:bounds => bounds.flatten.pack("q*"),
|
|
298
|
+
:extents => packed_extents(array_values),
|
|
299
|
+
:box => region_box(ranges, scalars, array_values),
|
|
300
|
+
}
|
|
301
|
+
freeze_throughout(plan)
|
|
302
|
+
if key
|
|
303
|
+
@plans.unshift([key, plan])
|
|
304
|
+
@plans.pop if @plans.size > PLAN_LIMIT
|
|
305
|
+
end
|
|
306
|
+
plan
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# What two calls have to agree on for one to be prepared like the other.
|
|
310
|
+
#
|
|
311
|
+
# Shapes and types, because that is all `verify` and `region_box` and
|
|
312
|
+
# the extents read of an array; the bounds and the border, because they
|
|
313
|
+
# are what the check is against; and the scalars that decide where the
|
|
314
|
+
# kernel reaches, but not the ones it merely computes with.
|
|
315
|
+
#
|
|
316
|
+
# No identity: two arrays of one shape and type prepare the same way, so
|
|
317
|
+
# keying on which object it was would only make a caller that rebuilds
|
|
318
|
+
# its fields every step miss every time -- and would have this kernel,
|
|
319
|
+
# which lives as long as the process, hold their memory.
|
|
320
|
+
#
|
|
321
|
+
# nil where an operand is not a CArray at all, so that the call falls
|
|
322
|
+
# through to `verify` and is refused by name rather than by whatever
|
|
323
|
+
# the next method call happens to raise.
|
|
324
|
+
def plan_key (arrays, bounds, scalars, border)
|
|
325
|
+
shapes = []
|
|
326
|
+
arrays.each do |array|
|
|
327
|
+
return nil unless array.is_a?(CArray)
|
|
328
|
+
shapes << array.data_type << array.dim
|
|
329
|
+
end
|
|
330
|
+
[shapes, bounds, border,
|
|
331
|
+
@shape_scalar_names.map { |name| scalars[name] }]
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# A kernel is shared, and Fiddle lets go of the GVL for the call, so two
|
|
335
|
+
# threads can be reading one plan while a third prepares another. That
|
|
336
|
+
# is sound only while nothing in a plan is written after it is published
|
|
337
|
+
# -- and a box is arrays inside an array, so freezing the top of it
|
|
338
|
+
# would leave the parts a later edit could still reach.
|
|
339
|
+
def freeze_throughout (value)
|
|
340
|
+
case value
|
|
341
|
+
when Hash then value.each_value { |item| freeze_throughout(item) }
|
|
342
|
+
when Array then value.each { |item| freeze_throughout(item) }
|
|
343
|
+
end
|
|
344
|
+
value.freeze
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# The captured scalars a pinned subscript, an offset or an inner loop's
|
|
348
|
+
# range is worked out from.
|
|
349
|
+
def shape_scalar_names
|
|
350
|
+
names = []
|
|
351
|
+
@axis_uses.each_value do |uses|
|
|
352
|
+
uses.each do |walkers, pinned|
|
|
353
|
+
pinned.each { |node| collect_captures(node, names) }
|
|
354
|
+
walkers.each { |walker| walker[1].each { |node|
|
|
355
|
+
collect_captures(node, names)
|
|
356
|
+
} }
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
@inner_ranges.each_value do |from, to, _step|
|
|
360
|
+
collect_captures(from, names)
|
|
361
|
+
collect_captures(to, names)
|
|
362
|
+
end
|
|
363
|
+
names.uniq.freeze
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def collect_captures (node, names)
|
|
367
|
+
return names unless node.is_a?(Node)
|
|
368
|
+
names << node.name if node.is_a?(CaptureRead)
|
|
369
|
+
node.children.each { |child| collect_captures(child, names) }
|
|
370
|
+
names
|
|
371
|
+
end
|
|
372
|
+
|
|
233
373
|
# What the cell that stopped said, raised now that Ruby has control
|
|
234
374
|
# back. C cannot raise, so a failure is a code in a slot and a loop
|
|
235
375
|
# that stops; this is where it becomes the exception the same body run
|
|
236
376
|
# in Ruby would have raised.
|
|
237
|
-
def report (error)
|
|
377
|
+
def report (error, scalars = {})
|
|
238
378
|
code = error.unpack1("l")
|
|
379
|
+
if (failure = CGenerator::FIXED_FAILURES[code])
|
|
380
|
+
raise failure[0], failure[1]
|
|
381
|
+
end
|
|
239
382
|
case code
|
|
240
383
|
when 0 then nil
|
|
384
|
+
when CGenerator::SHAPE_CODE then raise_a_bad_shape(scalars)
|
|
385
|
+
when CGenerator::MEMORY_CODE then raise_out_of_memory(scalars)
|
|
241
386
|
when 1 then raise ZeroDivisionError, "divided by 0"
|
|
242
387
|
when 2 then raise IndexError, "index out of range"
|
|
388
|
+
when 3 then raise ArgumentError,
|
|
389
|
+
"min argument must be less than or equal to " \
|
|
390
|
+
"max argument"
|
|
391
|
+
# Ruby's own wording names the value it could not compare, and the
|
|
392
|
+
# slot carries a code rather than a number, so the reason is named
|
|
393
|
+
# instead. The class and the failure are Ruby's.
|
|
394
|
+
when 4 then raise ArgumentError,
|
|
395
|
+
"comparison with a NaN failed, so `clamp` has " \
|
|
396
|
+
"no answer"
|
|
397
|
+
when 5 then raise Math::DomainError,
|
|
398
|
+
"Numerical argument is out of domain - gamma"
|
|
243
399
|
else
|
|
244
400
|
message = @raise_messages[code]
|
|
245
401
|
# A code with no message behind it is this compiler's bug, not the
|
|
@@ -251,6 +407,53 @@ class CArray
|
|
|
251
407
|
end
|
|
252
408
|
end
|
|
253
409
|
|
|
410
|
+
# A length the kernel worked out that is no length: zero cells, a
|
|
411
|
+
# negative count, or so many that the bytes would not be a `size_t`.
|
|
412
|
+
# The kernel reports which of its arrays it was by reporting at all --
|
|
413
|
+
# it stops before the first cell -- and the lengths are worked out
|
|
414
|
+
# again here, where there is a message to put them in.
|
|
415
|
+
def raise_a_bad_shape (scalars)
|
|
416
|
+
offending = @heap_arrays.filter_map { |name, storage, shape|
|
|
417
|
+
lengths = shape_lengths(shape, scalars)
|
|
418
|
+
next unless lengths&.any? { |length| length < 1 }
|
|
419
|
+
"`#{name}` (#{storage}) asks for " \
|
|
420
|
+
"#{lengths.join(' x ')} #{lengths.size == 1 ? 'cells' : 'cells per axis'}"
|
|
421
|
+
}
|
|
422
|
+
raise ArgumentError,
|
|
423
|
+
"a local array holds at least one cell: " \
|
|
424
|
+
"#{offending.join('; ')}" unless offending.empty?
|
|
425
|
+
raise ArgumentError,
|
|
426
|
+
"a local array of this kernel asks for more cells than its " \
|
|
427
|
+
"bytes could be counted in"
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def raise_out_of_memory (scalars)
|
|
431
|
+
asked = @heap_arrays.map { |name, storage, shape|
|
|
432
|
+
lengths = shape_lengths(shape, scalars)
|
|
433
|
+
cells = lengths&.inject(1, :*)
|
|
434
|
+
bytes = cells && cells * LOCAL_ARRAY_STORAGE_BYTES.fetch(storage, 1)
|
|
435
|
+
"`#{name}` (#{storage}, #{lengths ? lengths.join(' x ') : 'shape'}" +
|
|
436
|
+
(bytes ? ", #{bytes} bytes" : "") + ")"
|
|
437
|
+
}
|
|
438
|
+
raise NoMemoryError,
|
|
439
|
+
"a kernel could not allocate the local arrays it works in: " \
|
|
440
|
+
"#{asked.join(', ')}"
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
# @private
|
|
444
|
+
LOCAL_ARRAY_STORAGE_BYTES = Analyzer::LOCAL_ARRAY_STORAGE_BYTES
|
|
445
|
+
|
|
446
|
+
# The shape as numbers, or nil where one of its axes cannot be worked
|
|
447
|
+
# out here -- which is not a failure worth a second exception, the one
|
|
448
|
+
# being raised having happened already.
|
|
449
|
+
def shape_lengths (shape, scalars)
|
|
450
|
+
shape.map { |extent|
|
|
451
|
+
extent.is_a?(Integer) ? extent : evaluate(extent.node, scalars)
|
|
452
|
+
}
|
|
453
|
+
rescue StandardError, Unsupported
|
|
454
|
+
nil
|
|
455
|
+
end
|
|
456
|
+
|
|
254
457
|
def slab_pointer
|
|
255
458
|
@slab_pointer ||= @handle[SLAB_NAME]
|
|
256
459
|
end
|
|
@@ -280,6 +483,43 @@ class CArray
|
|
|
280
483
|
}).pack("d*")
|
|
281
484
|
end
|
|
282
485
|
|
|
486
|
+
# The `integers` slots the kernel reads: the signed captures, then the
|
|
487
|
+
# unsigned ones, then the extents an index is checked against. A uint64
|
|
488
|
+
# is packed as the bits it is -- `pack("q")` would take 2**63 modulo the
|
|
489
|
+
# width and say nothing -- and the kernel casts the slot back, so the
|
|
490
|
+
# two directives are what makes one buffer carry both widths.
|
|
491
|
+
#
|
|
492
|
+
# One method because both loops pack it, and the same buffer packed in
|
|
493
|
+
# two places is what `packed_reals` is one method about.
|
|
494
|
+
def packed_integers (scalar_values, array_values)
|
|
495
|
+
packed_scalar_integers(scalar_values) + packed_extents(array_values)
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
# The captured half, which is different every time a caller steps a
|
|
499
|
+
# counter, and the arrays' half, which is the shape and travels in the
|
|
500
|
+
# plan. Two methods because `#call` packs the first and takes the
|
|
501
|
+
# second from what it prepared earlier; `#sweep` still wants both.
|
|
502
|
+
# A captured Integer that meets a uint64 takes that width, and a
|
|
503
|
+
# negative one has none to take: it would compute as 2**64-1. CArray
|
|
504
|
+
# refuses the same operand on a uint64 array, and so does this.
|
|
505
|
+
def packed_scalar_integers (scalar_values)
|
|
506
|
+
@integers.map { |name|
|
|
507
|
+
value = Integer(scalar_values.fetch(name))
|
|
508
|
+
if value.negative? && @unsigned_captures.include?(name)
|
|
509
|
+
raise RangeError, "#{value} is out of range for uint64 " \
|
|
510
|
+
"(`#{name}` meets a uint64)"
|
|
511
|
+
end
|
|
512
|
+
value
|
|
513
|
+
}.pack("q*") +
|
|
514
|
+
@unsigned_integers.map { |name| Integer(scalar_values.fetch(name)) }.pack("Q*")
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def packed_extents (array_values)
|
|
518
|
+
@extent_slots.map { |name, axis|
|
|
519
|
+
array_values.fetch(name).dim[axis]
|
|
520
|
+
}.pack("q*")
|
|
521
|
+
end
|
|
522
|
+
|
|
283
523
|
def address_buffer (name, array)
|
|
284
524
|
@address_parameters.fetch(name).each do |parameter|
|
|
285
525
|
wanted = CDeclaration::DATA_TYPES.fetch(parameter.element.fiddle)
|
|
@@ -318,7 +558,7 @@ class CArray
|
|
|
318
558
|
packed.empty? ? "\0" * 8 : packed
|
|
319
559
|
end
|
|
320
560
|
|
|
321
|
-
# The
|
|
561
|
+
# The xfer tier transfers a box rather than the whole view. The box
|
|
322
562
|
# is the loop range grown by how far the kernel reaches from the cell it
|
|
323
563
|
# is on -- per array, because two arrays in one kernel need not be read
|
|
324
564
|
# at the same offsets, and per axis, because they need not be read at
|
|
@@ -376,13 +616,120 @@ class CArray
|
|
|
376
616
|
ranges[name] = covered_span(bounds[axis])
|
|
377
617
|
end
|
|
378
618
|
flat = bounds.flatten
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
619
|
+
# In source order, outermost first, so an index a range is written
|
|
620
|
+
# over has its own range settled before it is read.
|
|
621
|
+
@inner_ranges.each do |name, (from, to, step)|
|
|
622
|
+
# A range read from a local or a cell has no span before the loop
|
|
623
|
+
# is reached; the subscripts it walks are checked at each access.
|
|
624
|
+
next if @unbounded_indices.include?(name)
|
|
625
|
+
step ||= 1
|
|
626
|
+
first = span_of(from, scalar_values, flat, ranges, name)
|
|
627
|
+
last = span_of(to, scalar_values, flat, ranges, name)
|
|
628
|
+
# The widest pass the range could describe: counting up, that is the
|
|
629
|
+
# earliest start against the latest end, and counting down it is the
|
|
630
|
+
# other way round.
|
|
631
|
+
start, limit = step.positive? ? [first.first, last.last]
|
|
632
|
+
: [first.last, last.first]
|
|
633
|
+
# A step that skips cells lands on the ones a pass started on, and
|
|
634
|
+
# where the start moves so do they: `(p...8).step(3)` visits 0, 3, 6
|
|
635
|
+
# from one start and 1, 4, 7 from the next. Stepped out from the
|
|
636
|
+
# earliest start alone, the reach stopped at 6 and a pass wrote at
|
|
637
|
+
# 7. So with a start that moves, the span is every cell between
|
|
638
|
+
# the widest start and the widest end, which every pass lies within.
|
|
639
|
+
step = step <=> 0 if first.first != first.last
|
|
640
|
+
ranges[name] = covered_span([start, limit, step])
|
|
382
641
|
end
|
|
383
642
|
ranges
|
|
384
643
|
end
|
|
385
644
|
|
|
645
|
+
# A range written over another index is read at its widest (see
|
|
646
|
+
# #span_of), so the number in the message above can be one no pass
|
|
647
|
+
# actually starts or ends at. Saying which index it was written over is
|
|
648
|
+
# what tells a reader whether to narrow the range or the array's use --
|
|
649
|
+
# without it the message names a bound the loop never reaches.
|
|
650
|
+
def widest_reading (index, spelled)
|
|
651
|
+
over = range_indices(index)
|
|
652
|
+
return "" if over.empty?
|
|
653
|
+
". The range on `#{spelled}` is written over " \
|
|
654
|
+
"#{over.map { |name| "`#{name}`" }.join(' and ')}, and a range over " \
|
|
655
|
+
"another index is read here at its widest -- as far as any value of " \
|
|
656
|
+
"#{over.size == 1 ? 'it' : 'them'} could take it, which may be " \
|
|
657
|
+
"further than it goes on any one pass"
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
# The indices an inner loop's range mentions, by the name the block
|
|
661
|
+
# wrote for each.
|
|
662
|
+
def range_indices (index)
|
|
663
|
+
range = @inner_ranges[index]
|
|
664
|
+
return [] unless range
|
|
665
|
+
from, to, = range
|
|
666
|
+
[from, to].flat_map { |node| mentioned_indices(node) }.uniq
|
|
667
|
+
.map { |name| @index_sources.fetch(name, name) }
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
def mentioned_indices (node)
|
|
671
|
+
return [] unless node.is_a?(Node)
|
|
672
|
+
return [node.name] if node.is_a?(IndexVariable)
|
|
673
|
+
node.children.flat_map { |child| mentioned_indices(child) }
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
# What an index range covers, as [lowest, highest] inclusive.
|
|
677
|
+
#
|
|
678
|
+
# A range written over another index -- `(p+1...3)`, the shape a
|
|
679
|
+
# triangular loop takes -- has no single pair of numbers to be: `k`
|
|
680
|
+
# starts somewhere else for every `p`. What the two callers of this
|
|
681
|
+
# want is where the index can reach, not what it is on a given pass:
|
|
682
|
+
# `verify_bounds` asks whether a subscript can leave its array, and
|
|
683
|
+
# `region_box` asks what a stencil touches. So the answer is an
|
|
684
|
+
# interval, and a range over another index is read at its widest.
|
|
685
|
+
#
|
|
686
|
+
# Widest is the safe direction. It covers every pass the loop can take
|
|
687
|
+
# and then some, so a subscript this says is inside really is; what it
|
|
688
|
+
# costs is that a reach nothing actually makes can still be refused.
|
|
689
|
+
#
|
|
690
|
+
# A range that mentions no index gives an interval of one number, which
|
|
691
|
+
# is what `evaluate` gave before, so those kernels are unchanged.
|
|
692
|
+
def span_of (node, scalars, flat_bounds, ranges, index)
|
|
693
|
+
case node
|
|
694
|
+
when IntegerLiteral then [node.value, node.value]
|
|
695
|
+
when CaptureRead then value = Integer(scalars.fetch(node.name))
|
|
696
|
+
[value, value]
|
|
697
|
+
when BoundsValue then value = flat_bounds.fetch(node.slot)
|
|
698
|
+
[value, value]
|
|
699
|
+
when IndexVariable
|
|
700
|
+
# Held half-open, as the loops are; the last index it visits is one
|
|
701
|
+
# below the end.
|
|
702
|
+
low, high = ranges.fetch(node.name) do
|
|
703
|
+
raise Unsupported,
|
|
704
|
+
"the range on `#{index}` is written over `#{node.name}`, " \
|
|
705
|
+
"which is not an index around it"
|
|
706
|
+
end
|
|
707
|
+
[low, high - 1]
|
|
708
|
+
when UnaryMinus
|
|
709
|
+
low, high = span_of(node.operand, scalars, flat_bounds, ranges, index)
|
|
710
|
+
[-high, -low]
|
|
711
|
+
when BinaryOperation
|
|
712
|
+
left = span_of(node.left, scalars, flat_bounds, ranges, index)
|
|
713
|
+
right = span_of(node.right, scalars, flat_bounds, ranges, index)
|
|
714
|
+
case node.operator
|
|
715
|
+
when :+ then [left.first + right.first, left.last + right.last]
|
|
716
|
+
when :- then [left.first - right.last, left.last - right.first]
|
|
717
|
+
when :*
|
|
718
|
+
# Four corners, because either interval may straddle zero.
|
|
719
|
+
corners = [left.first * right.first, left.first * right.last,
|
|
720
|
+
left.last * right.first, left.last * right.last]
|
|
721
|
+
[corners.min, corners.max]
|
|
722
|
+
else
|
|
723
|
+
raise Unsupported,
|
|
724
|
+
"an inner loop's range is built from `+`, `-` and `*` only"
|
|
725
|
+
end
|
|
726
|
+
else
|
|
727
|
+
raise Unsupported,
|
|
728
|
+
"an inner loop's range is an integer expression over " \
|
|
729
|
+
"literals, captured scalars and the indices around it"
|
|
730
|
+
end
|
|
731
|
+
end
|
|
732
|
+
|
|
386
733
|
# The half-open span an axis actually touches. A step may skip cells,
|
|
387
734
|
# but every cell it lands on has to exist, so the span runs from the
|
|
388
735
|
# first index to the last one visited.
|
|
@@ -463,19 +810,22 @@ class CArray
|
|
|
463
810
|
walkers.each do |index, offsets|
|
|
464
811
|
low, high = ranges.fetch(index)
|
|
465
812
|
next if low >= high
|
|
813
|
+
spelled = @index_sources.fetch(index, index)
|
|
466
814
|
minimum, maximum = offset_span(offsets, scalars)
|
|
467
815
|
if low + minimum < 0
|
|
468
816
|
raise Unsupported,
|
|
469
817
|
"`#{name}` is indexed at " \
|
|
470
|
-
"#{offset_text(name,
|
|
471
|
-
"the range on `#{
|
|
818
|
+
"#{offset_text(name, spelled, minimum)}, so " \
|
|
819
|
+
"the range on `#{spelled}` cannot start at #{low}" +
|
|
820
|
+
widest_reading(index, spelled)
|
|
472
821
|
end
|
|
473
822
|
if high - 1 + maximum > extent - 1
|
|
474
823
|
raise Unsupported,
|
|
475
824
|
"`#{name}` is indexed at " \
|
|
476
|
-
"#{offset_text(name,
|
|
477
|
-
"the range on `#{
|
|
478
|
-
"for an extent of #{extent}"
|
|
825
|
+
"#{offset_text(name, spelled, maximum)}, so " \
|
|
826
|
+
"the range on `#{spelled}` cannot end at #{high} " \
|
|
827
|
+
"for an extent of #{extent}" +
|
|
828
|
+
widest_reading(index, spelled)
|
|
479
829
|
end
|
|
480
830
|
end
|
|
481
831
|
end
|