carray-jit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +10 -0
  3. data/CHANGELOG.md +84 -0
  4. data/LICENSE +21 -0
  5. data/README.md +88 -0
  6. data/bin/carray-jit +194 -0
  7. data/carray-jit.gemspec +41 -0
  8. data/docs/00_Introduction.md +40 -0
  9. data/docs/01_GettingStarted.md +80 -0
  10. data/docs/02_KernelShapes.md +397 -0
  11. data/docs/03_SupportedFeatures.md +595 -0
  12. data/docs/04_Compiling.md +234 -0
  13. data/docs/05_DesignNotes.md +136 -0
  14. data/docs/06_Cheatsheet.md +177 -0
  15. data/examples/README.md +56 -0
  16. data/examples/applications/game_of_life.rb +161 -0
  17. data/examples/applications/heat_equation.rb +117 -0
  18. data/examples/applications/kepler.rb +178 -0
  19. data/examples/applications/mandelbrot.rb +151 -0
  20. data/examples/applications/moving_average.rb +124 -0
  21. data/examples/applications/partial_sums.rb +141 -0
  22. data/examples/applications/point_cloud.rb +110 -0
  23. data/examples/applications/quicksort.rb +118 -0
  24. data/examples/applications/recursion.rb +121 -0
  25. data/examples/applications/relaxation.rb +115 -0
  26. data/examples/applications/sensor_gaps.rb +118 -0
  27. data/examples/applications/sieve.rb +95 -0
  28. data/examples/applications/sobel_edges.rb +80 -0
  29. data/examples/features/01_element_wise.rb +69 -0
  30. data/examples/features/02_stencil.rb +40 -0
  31. data/examples/features/03_recurrence.rb +50 -0
  32. data/examples/features/04_thomas.rb +81 -0
  33. data/examples/features/05_reduction.rb +90 -0
  34. data/examples/features/06_jit_contract.rb +58 -0
  35. data/examples/features/07_masks.rb +55 -0
  36. data/examples/features/08_views.rb +46 -0
  37. data/examples/features/09_inspecting.rb +55 -0
  38. data/examples/features/10_complex.rb +107 -0
  39. data/examples/features/11_c_functions.rb +260 -0
  40. data/examples/features/12_sweep.rb +139 -0
  41. data/examples/features/13_cscalar.rb +80 -0
  42. data/examples/features/14_stencil_window.rb +106 -0
  43. data/examples/features/15_loops.rb +148 -0
  44. data/examples/features/16_raising.rb +69 -0
  45. data/ext/carray_jit_access/carray_jit_access.c +460 -0
  46. data/ext/carray_jit_access/extconf.rb +8 -0
  47. data/lib/carray/jit/analyzer.rb +1847 -0
  48. data/lib/carray/jit/block_reader.rb +139 -0
  49. data/lib/carray/jit/c_function.rb +777 -0
  50. data/lib/carray/jit/c_generator.rb +2305 -0
  51. data/lib/carray/jit/compiler.rb +468 -0
  52. data/lib/carray/jit/errors.rb +37 -0
  53. data/lib/carray/jit/expression.rb +202 -0
  54. data/lib/carray/jit/kernel.rb +509 -0
  55. data/lib/carray/jit/node.rb +573 -0
  56. data/lib/carray/jit/sweep.rb +97 -0
  57. data/lib/carray/jit/type_assignment.rb +811 -0
  58. data/lib/carray/jit/version.rb +5 -0
  59. data/lib/carray/jit.rb +1210 -0
  60. metadata +139 -0
data/lib/carray/jit.rb ADDED
@@ -0,0 +1,1210 @@
1
+ require "carray"
2
+
3
+ require "carray/jit/version"
4
+ require "carray/jit/errors"
5
+ require "carray/jit/node"
6
+ require "carray/jit/analyzer"
7
+ require "carray/jit/block_reader"
8
+ require "carray/jit/c_function"
9
+ require "carray/jit/type_assignment"
10
+ require "carray/jit/c_generator"
11
+ require "carray/jit/compiler"
12
+ require "carray/jit/access"
13
+ require "carray/jit/kernel"
14
+
15
+ class CArray
16
+
17
+ # @!group Compiling a block
18
+
19
+ # Returns the kernel compiled from `block` and run over `extents`.
20
+ #
21
+ # The block's parameters are the loop indices, and each extent is a Range or
22
+ # an Integer standing for `0...n`, one per index:
23
+ #
24
+ # CArray.jit_for(2...24) { |i|
25
+ # w = x * leg[i-1]
26
+ # wy = w - leg[i-2]
27
+ # leg[i] = wy + w - wy/i
28
+ # }
29
+ #
30
+ # Naming an index is what lets a kernel reach a neighbouring cell, and
31
+ # reaching a neighbouring cell is what makes the range and the direction
32
+ # matter. A computation that reaches no neighbour names no index and needs
33
+ # no extent, and is written with CArray.jit_each or CArray.jit_map instead.
34
+ #
35
+ # Arrays and scalars are the variables the block closes over, so nothing has
36
+ # to be named twice. Which way each axis runs is derived from the kernel's
37
+ # own dependencies, not chosen: reading a cell the kernel will later write
38
+ # means that cell has to be reached in one particular order.
39
+ #
40
+ # A block outside the compilable subset raises CArray::JIT::Unsupported
41
+ # rather than falling back to a Ruby loop. Nobody calls this method except
42
+ # to make a per-cell computation fast, so quietly doing the slow thing would
43
+ # answer a question that was not asked.
44
+ #
45
+ # The name is CArray's own: carray/lazy.rb defines jit_for to raise
46
+ # NotImplementedError, saying that the block is compiled and that the
47
+ # compiler is this gem. Requiring carray/jit replaces it with this one.
48
+ # So the method is named where the subset is documented, and a program that
49
+ # calls it either compiles or is told why it cannot -- an expression over
50
+ # whole arrays that needs no compiler is CArray.fuse's.
51
+ #
52
+ # `reassociate:` says whether a reduction's accumulator may be split into
53
+ # partial sums. It defaults to CArray::JIT.reassociate, which is true:
54
+ # the answer is then not the one a serial Ruby loop reaches, and is usually
55
+ # the more accurate of the two, because splitting the accumulation is what
56
+ # limits the cancellation. Pass false for the serial order -- for a
57
+ # compensated summation, whose algorithm *is* the order, or to check a
58
+ # kernel against the loop it replaces.
59
+ #
60
+ # Returns the compiled kernel, whose {CArray::JIT::CompiledKernel#c_source} is the
61
+ # C that ran.
62
+ #
63
+ # The block is read and compiled, never called, so it is not yielded to.
64
+ #
65
+ # @param extents [Array<Range, Integer>] one per loop index, an Integer
66
+ # standing for `0...n`.
67
+ # @param reassociate [Boolean, nil] whether a reduction's accumulator may be
68
+ # split into partial sums; `nil` defers to {CArray::JIT.reassociate}.
69
+ # @return [CArray::JIT::CompiledKernel]
70
+ # @raise [CArray::JIT::Unsupported] when the block falls outside the
71
+ # recognized subset, or the extents do not match its parameters.
72
+ def self.jit_for (*extents, reassociate: nil, &block)
73
+ unless block
74
+ raise JIT::Unsupported, "jit_for needs a block"
75
+ end
76
+ if block.arity.zero?
77
+ raise JIT::Unsupported,
78
+ "jit_for's block names the cells it is on, so it takes the loop " \
79
+ "indices as its parameters; a block that names none is " \
80
+ "element-wise and belongs to jit_each"
81
+ end
82
+ JIT.run(extents, block, reassociate)
83
+ end
84
+
85
+ # Returns the kernel compiled from `block` and run at every cell.
86
+ #
87
+ # Element-wise means what it means in CArray: every cell is computed from
88
+ # the cells beside it in the other arrays, none reaches a neighbour, and the
89
+ # shapes are broadcast. So there is no index to name and no extent to give,
90
+ # and what changes is not the expression but how it runs -- at the cell, in
91
+ # one pass, instead of one pass per operation with intermediate arrays in
92
+ # between.
93
+ #
94
+ # CArray.jit_each { out = a + b * c }
95
+ #
96
+ # The arrays are the variables the block closes over, and the expression is
97
+ # written the way CArray already writes it. Every name in the block is a
98
+ # cell -- the loop is this compiler's and is not written here -- so the
99
+ # assignment is Ruby's own: `out = ...` writes the cell of the array `out`
100
+ # names outside. A name that is not an array there is a local of the
101
+ # block's, as it is anywhere else.
102
+ #
103
+ # `each` is what CArray means by it: a cell. It is jit_for's sibling and
104
+ # not its special case -- a block that names an index is on a cell and can
105
+ # reach the cells around it, which is what makes a range and a direction
106
+ # matter, while an element-wise block has neither. And it is jit_map's
107
+ # sibling in the other direction: the name says whether a value comes back.
108
+ #
109
+ # Like jit_for, the name is CArray's own and raises there until this gem
110
+ # replaces it. Written without a compiler, the same computation is
111
+ # CArray.fuse's -- the expression itself, one pass per operation, with the
112
+ # intermediates this one does without.
113
+ #
114
+ # Returns the compiled kernel, whose #c_source is the C that ran. The
115
+ # value of the computation is in the
116
+ # arrays the block wrote; ask for it back with {CArray.jit_map} instead.
117
+ #
118
+ # The block is read and compiled, never called, so it is not yielded to.
119
+ #
120
+ # @return [CArray::JIT::CompiledKernel]
121
+ # @raise [CArray::JIT::Unsupported] when the block falls outside the
122
+ # recognized subset, or names any parameter.
123
+ def self.jit_each (&block)
124
+ unless block
125
+ raise JIT::Unsupported, "jit_each needs a block"
126
+ end
127
+ unless block.arity.zero?
128
+ raise JIT::Unsupported,
129
+ "this block names the arrays it reaches, so it takes no " \
130
+ "parameters; for a loop that names its indices, see `jit_for`"
131
+ end
132
+ JIT.run_over_whole_arrays(block)
133
+ end
134
+
135
+ # Returns a new array holding `block`'s value at every cell.
136
+ #
137
+ # The same block as {CArray.jit_each}, with its value asked for:
138
+ #
139
+ # larger = CArray.jit_map { a > b ? a : b }
140
+ #
141
+ # The last statement is the value every cell of the result gets, and the
142
+ # result is allocated here and returned, typed from that value. An
143
+ # assignment may be the last statement -- in Ruby an assignment has the
144
+ # value it assigned -- so a block may write an array of yours and hand the
145
+ # same value back.
146
+ #
147
+ # The block is read and compiled, never called, so it is not yielded to.
148
+ #
149
+ # @return [CArray] a new array, typed from the block's value.
150
+ # @raise [CArray::JIT::Unsupported] when the block falls outside the
151
+ # recognized subset, or names any parameter.
152
+ def self.jit_map (&block)
153
+ unless block
154
+ raise JIT::Unsupported, "jit_map needs a block"
155
+ end
156
+ unless block.arity.zero?
157
+ raise JIT::Unsupported,
158
+ "this block names the arrays it reaches, so it takes no " \
159
+ "parameters; for a loop that names its indices, see `jit_for`"
160
+ end
161
+ JIT.run_over_whole_arrays(block, map: true)
162
+ end
163
+
164
+ # Returns a new array holding `block`'s value at every cell, computed from
165
+ # windows onto `arrays`.
166
+ #
167
+ # A stencil: every cell from the ones around it, with the loop implied.
168
+ #
169
+ # smoothed = CArray.jit_stencil(image) { |a|
170
+ # 0.25 * (a[-1, 0] + a[1, 0] + a[0, -1] + a[0, 1])
171
+ # }
172
+ #
173
+ # The arrays are given rather than closed over, and the block's parameters
174
+ # are windows onto them, in that order: `a[0, 0]` is the cell, `a[-1, 1]`
175
+ # its neighbour. The block's value is what the cell gets, as jit_map's is,
176
+ # and the result comes back -- an array of the same shape.
177
+ #
178
+ # Naming a window rather than an index is what lets the edge be said at the
179
+ # call rather than written into the loop. Where the window falls off the
180
+ # array there is nothing to read, and `border:` says what to do about it:
181
+ #
182
+ # :mask the cell is UNDEF -- it was not computed (the default)
183
+ # :skip the cell is left as it was found
184
+ #
185
+ # The default is `:mask` because CArray can say "not computed", and a
186
+ # border of zeros that means the same thing cannot be told from zeros that
187
+ # were computed.
188
+ #
189
+ # `type:` names the array to collect into; without it the type is the
190
+ # block's value's, as jit_map's result is. `into:` writes into an array of
191
+ # yours instead, and then the type is that array's.
192
+ #
193
+ # The block is read and compiled, never called, so it is not yielded to.
194
+ #
195
+ # @param arrays [Array<CArray>] the arrays the block has windows onto, in
196
+ # the order its parameters name them.
197
+ # @param border [Symbol] `:mask` to leave an uncomputed cell UNDEF,
198
+ # `:skip` to leave it as it was found.
199
+ # @param type [Symbol, nil] the data type to collect into; `nil` takes the
200
+ # block's value's.
201
+ # @param into [CArray, nil] an array of yours to write into, which then
202
+ # decides the type.
203
+ # @return [CArray] `into` when it is given, otherwise a new array.
204
+ # @raise [CArray::JIT::Unsupported] when the block falls outside the
205
+ # recognized subset, when no array is given, or when both `type` and
206
+ # `into` are.
207
+ def self.jit_stencil (*arrays, border: :mask, type: nil, into: nil, &block)
208
+ unless block
209
+ raise JIT::Unsupported, "jit_stencil needs a block"
210
+ end
211
+ if arrays.empty?
212
+ raise JIT::Unsupported,
213
+ "jit_stencil takes the arrays its block has windows onto, as in " \
214
+ "`CArray.jit_stencil(image) { |a| ... }`"
215
+ end
216
+ JIT.run_stencil(arrays, block, border, type, into)
217
+ end
218
+
219
+ # Returns the contraction the block writes: an index that appears twice
220
+ # is summed.
221
+ #
222
+ # CArray.jit_contract { |i, j, k| c[i,j] = a[i,k] * b[k,j] }
223
+ #
224
+ # Every block parameter is an index. The ones that appear on the left are
225
+ # the cells written; the rest -- `k` here -- are summed over.
226
+ #
227
+ # No extent is given, because every index's extent is fixed by the axes it
228
+ # addresses; an index whose axes disagree is an error, which is the shape
229
+ # check a contraction needs.
230
+ #
231
+ # With no assignment the result is allocated and returned, with the free
232
+ # indices as its axes in the order the block named them:
233
+ #
234
+ # c = CArray.jit_contract { |i, j, k| a[i,k] * b[k,j] }
235
+ #
236
+ # Assigning into an array of your own says where to put it, and in what
237
+ # order its axes lie; it does not decide what is summed.
238
+ #
239
+ # The block is read and compiled, never called, so it is not yielded to.
240
+ #
241
+ # @return [CArray, CArray::JIT::CompiledKernel] the allocated result when the block
242
+ # assigns into nothing, otherwise the compiled kernel.
243
+ # @raise [CArray::JIT::Unsupported] when the block falls outside the
244
+ # recognized subset, or an index's axes disagree.
245
+ def self.jit_contract (&block)
246
+ unless block
247
+ raise JIT::Unsupported, "jit_contract needs a block"
248
+ end
249
+ JIT.run_contraction(block)
250
+ end
251
+
252
+ # @!endgroup
253
+
254
+ # @!endgroup
255
+
256
+ # @!group C functions
257
+
258
+ # Returns a callable for a C function someone else compiled, named by
259
+ # quoting its declaration, so that a kernel body -- or Ruby -- can call it:
260
+ #
261
+ # j0 = CArray.jit_extern("double j0(double)", from: "libgsl")
262
+ # CArray.jit_each { out = j0.call(x) }
263
+ #
264
+ # Nothing is compiled here: `extern` is C's word for a body that lives
265
+ # elsewhere, and finding it is Fiddle's job. What this gem adds is that the
266
+ # kernel calls the address directly instead of reaching it per cell through
267
+ # Fiddle.
268
+ #
269
+ # @param prototype [String] the function's C declaration, as C writes it.
270
+ # @param from [String, nil] the library to open; `nil` searches the process.
271
+ # @return [CArray::JIT::CFunction]
272
+ def self.jit_extern (prototype, from: nil, &block)
273
+ JIT.extern(prototype, from: from, &block)
274
+ end
275
+
276
+ # Returns a callable for a C function of your own, written in Ruby and
277
+ # compiled here:
278
+ #
279
+ # smoothstep = CArray.jit_function("double (*)(double)") { |t| t * t }
280
+ #
281
+ # It is called from a kernel like any other, which gives kernels a body that
282
+ # can be factored and named, and its address may be handed to a C library
283
+ # that knows nothing about Ruby.
284
+ #
285
+ # The block is read and compiled, never called, so it is not yielded to.
286
+ #
287
+ # @param prototype [String] the function's C declaration, as C writes it.
288
+ # @return [CArray::JIT::CFunction]
289
+ # @raise [CArray::JIT::Unsupported] when the block falls outside the
290
+ # recognized subset.
291
+ def self.jit_function (prototype, &block)
292
+ JIT.function(prototype, &block)
293
+ end
294
+
295
+ # @!endgroup
296
+
297
+ # The compiler behind `CArray.jit_*`: it reads a block, generates C for it,
298
+ # builds it and calls it. What is public here is the reassociation default
299
+ # and the kernel cache; the rest is the machinery.
300
+ module JIT
301
+
302
+ class << self
303
+
304
+ # Whether a reduction's accumulator may be split into partial sums when
305
+ # the call site does not say. True: a kernel is here to be fast, and
306
+ # splitting the accumulation is usually the more accurate answer as well
307
+ # as the faster one.
308
+ #
309
+ # What it does not give is the order a serial Ruby loop takes, so it is
310
+ # false that says "compute this exactly as the loop would" -- for a
311
+ # compensated summation, or to check one against the other.
312
+ # CARRAY_JIT_REASSOCIATE=0 sets it false for a whole process, which is
313
+ # how this gem's own tests compare against Ruby.
314
+ # @!attribute [w] reassociate
315
+ # Sets whether a reduction's accumulator may be split into partial
316
+ # sums when the call site does not say.
317
+ # @return [Boolean]
318
+ attr_writer :reassociate
319
+
320
+ # @return [Boolean] whether a reduction's accumulator may be split when
321
+ # the call site does not say.
322
+ def reassociate
323
+ return @reassociate unless @reassociate.nil?
324
+ @reassociate = ENV["CARRAY_JIT_REASSOCIATE"] != "0"
325
+ end
326
+
327
+ # @private
328
+ RESULT = :__contraction_result
329
+
330
+ # @private
331
+ def run_contraction (block)
332
+ node, source, origin = read_block(block)
333
+ names = capture_names(source, node)
334
+ arrays, scalars, c_functions = split_captures(names, binding_of(block))
335
+
336
+ result = allocate_result(source, node, arrays, scalars)
337
+ arrays = arrays.merge(RESULT => result) if result
338
+
339
+ kernel = compile(source,
340
+ node: node,
341
+ origin: origin,
342
+ array_names: arrays.keys,
343
+ storage_types: arrays.transform_values(&:data_type_name),
344
+ scalar_values: scalars,
345
+ c_functions: c_functions,
346
+ masked: arrays.each_value.any? { |array| array.has_mask? },
347
+ contract: true,
348
+ result: RESULT,
349
+ cell_names: cell_names(arrays))
350
+
351
+ extents = contraction_extents(kernel, arrays)
352
+ if kernel.masked
353
+ kernel.written_arrays.each do |name|
354
+ array = arrays.fetch(name)
355
+ array.mask = 0 unless array.has_mask?
356
+ end
357
+ end
358
+ kernel.call(arrays, scalars, extents, c_functions)
359
+ result || kernel
360
+ end
361
+
362
+ # A contraction with nothing to assign into needs its result sized and
363
+ # typed before there is a kernel to ask, so the block is analyzed once
364
+ # without being compiled. Returns nil when the block assigns into an
365
+ # array of its own.
366
+ def allocate_result (source, node, arrays, scalars)
367
+ probe = probe_contraction(source, node, arrays, scalars)
368
+ return nil unless probe
369
+ free, index_axes, type = probe
370
+ shape = free.map do |index|
371
+ extents = index_axes.fetch(index).map { |array, axis|
372
+ [arrays.fetch(array).dim[axis], array, axis]
373
+ }
374
+ distinct = extents.map(&:first).uniq
375
+ if distinct.size > 1
376
+ described = extents.map { |extent, array, axis|
377
+ "`#{array}` axis #{axis} is #{extent}"
378
+ }.join(", ")
379
+ raise Unsupported,
380
+ "`#{index}` addresses axes of different extents: #{described}"
381
+ end
382
+ distinct.first
383
+ end
384
+ CArray.send(type, *(shape.empty? ? [1] : shape))
385
+ end
386
+
387
+ # @private
388
+ def probe_contraction (source, node, arrays, scalars)
389
+ key = [source, arrays.transform_values(&:data_type_name),
390
+ scalars.transform_values { |value| TypeAssignment.scalar_type(value) },
391
+ cell_names(arrays)]
392
+ cached = probe_cache[key]
393
+ return cached unless cached.nil?
394
+ probe_cache[key] = build_probe(source, node, arrays, scalars)
395
+ end
396
+
397
+ # @private
398
+ def probe_cache
399
+ @probe_cache ||= {}
400
+ end
401
+
402
+ # @private
403
+ def build_probe (source, node, arrays, scalars)
404
+ storage_types = arrays.transform_values(&:data_type_name)
405
+ analyzer = Analyzer.new(source, node: node, array_names: arrays.keys,
406
+ contract: :probe,
407
+ cell_names: cell_names(arrays))
408
+ return false if analyzer.body.statements.last.is_a?(ElementWrite)
409
+
410
+ TypeAssignment.new(analyzer.body, storage_types, scalars)
411
+ summand = analyzer.body.statements.last
412
+ axes = Hash.new { |hash, key| hash[key] = [] }
413
+ analyzer.subscripts.each do |array, uses|
414
+ uses.each do |per_axis|
415
+ per_axis.each_with_index do |(index, _), axis|
416
+ axes[index] << [array, axis] if index
417
+ end
418
+ end
419
+ end
420
+ [analyzer.probe_free_names, axes,
421
+ { :double => :float64, :complex => :cmplx128 }
422
+ .fetch(summand.type, :int64)]
423
+ end
424
+
425
+ # Each index's extent comes from the axes it addresses. Where it
426
+ # addresses more than one and they differ, the contraction has no shape
427
+ # and says which axes disagreed.
428
+ def contraction_extents (kernel, arrays)
429
+ order = kernel.index_names + kernel.contracted_names
430
+ order.map do |index|
431
+ places = kernel.index_axes[index]
432
+ extents = places.map { |array, axis| [arrays.fetch(array).dim[axis], array, axis] }
433
+ distinct = extents.map(&:first).uniq
434
+ if distinct.size > 1
435
+ described = extents.map { |extent, array, axis|
436
+ "`#{array}` axis #{axis} is #{extent}"
437
+ }.join(", ")
438
+ raise Unsupported,
439
+ "`#{index}` addresses axes of different extents: #{described}"
440
+ end
441
+ [0, distinct.first, 1]
442
+ end
443
+ end
444
+
445
+ # @private
446
+ MAP_RESULT = :__map_result
447
+
448
+ # Whether CArray's sweep can run this pass, decided before there is a
449
+ # kernel -- because the answer changes what is compiled.
450
+ #
451
+ # A chunk is one flat run of cells, and CArray already treats an operand
452
+ # as one: its acquire reads `elements` and the element size and never
453
+ # looks at the shape. So the arrays do not have to be flattened, and
454
+ # are not; what has to be flat is the *kernel*, which is compiled at
455
+ # rank one over the same cells rather than as a nest over the axes.
456
+ #
457
+ # The one thing that cannot be flattened is a stretched operand.
458
+ # Broadcasting arrives as a stride of zero on an axis, and an axis is
459
+ # what a flat run has none of -- so cell k of the output would stop
460
+ # lining up with cell k of the stretched operand. Hence the test is
461
+ # that every operand already has the shape the pass covers, which is
462
+ # stricter than agreeing on the count of cells.
463
+ def sweepable_pass? (arrays, shape, masked)
464
+ return false unless Sweep.available?
465
+ return false if masked
466
+ return false if arrays.empty? || arrays.size > Sweep::MAX_ARITY
467
+ return false unless arrays.each_value.all? { |array|
468
+ array.rank.zero? || array.dim == shape
469
+ }
470
+ walkable_in_place?(arrays.each_value)
471
+ end
472
+
473
+ # Whether the operands settle the question the shape leaves open.
474
+ #
475
+ # The sweep re-gathers what it cannot walk in place, 32KB at a time,
476
+ # and that is a bargain against materialising the same operand whole --
477
+ # but only where the tiers here would have to materialise it. So the
478
+ # tier each operand would be opened at is the rest of the decision:
479
+ #
480
+ # TIER_ATTACH neither can walk it, and the sweep holds 32KB where
481
+ # the tiers hold the whole box: the sweep runs
482
+ # TIER_STRIDE a column, a transpose, every other cell -- the tiers
483
+ # address it in place, and the sweep re-gathers it for
484
+ # nothing: the driver stays here
485
+ # TIER_ENTITY both walk the buffer, and the two are
486
+ # indistinguishable: the sweep runs, as it always did
487
+ #
488
+ # Measured over two million doubles with a strided view as the operand:
489
+ # 0.9 ns/cell with the driver here against 6.1 ns/cell swept, and no
490
+ # scratch either way -- there was nothing the re-gather was buying.
491
+ def walkable_in_place? (arrays)
492
+ tiers = arrays.map { |array| Access.classify(array)[:tier] }
493
+ return true if tiers.include?(Access::TIER_ATTACH)
494
+ tiers.none? { |tier| tier == Access::TIER_STRIDE }
495
+ end
496
+
497
+ # Which loop runs it. Both compute the same thing from the same
498
+ # compiled body -- what differs is who acquires the operands.
499
+ #
500
+ # CArray's chunked sweep re-gathers an operand it cannot walk in place
501
+ # 32KB at a time, where the tiers here move the whole box the kernel
502
+ # touches, and for an element-wise pass that box is the whole array.
503
+ # Measured over two million doubles with a gather view as an operand:
504
+ # 1.3 ms either way, and 32KB of scratch against sixteen megabytes.
505
+ # With an entity operand the two are indistinguishable, so there is
506
+ # nothing to weigh. With a strided view there is: the tiers address it
507
+ # in place and the sweep re-gathers it, which is why #sweepable_pass?
508
+ # asks what tier each operand would be opened at and not only what
509
+ # shape it has.
510
+ #
511
+ # It cannot always. A body that asks about a mask has no per-cell mask
512
+ # in a chunk to ask it of; operands that disagree on their element count
513
+ # are broadcast here and refused there; and a kernel of rank two or more
514
+ # addresses its operands by axis, which one flat run of cells cannot
515
+ # supply.
516
+ def drive (kernel, aligned, scalars, c_functions, shape, sweeping)
517
+ if sweeping
518
+ kernel.sweep(aligned, scalars, c_functions)
519
+ else
520
+ kernel.call(aligned, scalars, shape.map { |extent| [0, extent, 1] },
521
+ c_functions)
522
+ end
523
+ end
524
+
525
+ # What the frame gets. Two kinds: `:mask` and `:skip` are answers about
526
+ # the frame cells themselves -- mark them, or leave them -- and nothing
527
+ # is computed for a cell that was not computed. The other three say
528
+ # what a read outside the array gives, so the frame is computed after
529
+ # all, by a second walk with that rule written into its reads.
530
+ FRAME_BORDERS = [:mask, :skip].freeze
531
+ # @private
532
+ COMPUTED_BORDERS = [:zero, :clamp, :wrap].freeze
533
+
534
+ # @private
535
+ def run_stencil (arrays, block, border, type, into)
536
+ unless FRAME_BORDERS.include?(border) || COMPUTED_BORDERS.include?(border)
537
+ raise Unsupported,
538
+ "`border:` is " \
539
+ "#{(FRAME_BORDERS + COMPUTED_BORDERS).map(&:inspect).join(', ')}, " \
540
+ "got #{border.inspect}"
541
+ end
542
+ if type && into
543
+ raise Unsupported,
544
+ "`into:` names an array, which already says what type it is; " \
545
+ "pass one or the other"
546
+ end
547
+
548
+ node, source, origin = read_block(block)
549
+ windows = block.parameters.map(&:last)
550
+ unless windows.size == arrays.size
551
+ raise Unsupported,
552
+ "the block takes #{windows.size} " \
553
+ "#{windows.size == 1 ? 'window' : 'windows'}, and " \
554
+ "#{arrays.size} #{arrays.size == 1 ? 'array was' : 'arrays were'} " \
555
+ "given"
556
+ end
557
+ arrays.each_with_index do |array, position|
558
+ unless array.is_a?(CArray)
559
+ raise Unsupported,
560
+ "jit_stencil takes arrays; `#{windows[position]}` was given " \
561
+ "#{array.class}"
562
+ end
563
+ end
564
+ shape = arrays.first.dim
565
+ arrays.each_with_index do |array, position|
566
+ next if array.dim == shape
567
+ # Shapes are required to agree rather than broadcast: a stretched
568
+ # axis has no neighbour to reach, so what a window would mean on one
569
+ # is a question this does not have to answer yet.
570
+ raise Unsupported,
571
+ "`#{windows.first}` has shape #{shape.inspect} and " \
572
+ "`#{windows[position]}` #{array.dim.inspect}; a stencil's " \
573
+ "arrays have the same shape"
574
+ end
575
+
576
+ windowed = windows.zip(arrays).to_h
577
+ free, assigned = free_and_assigned_names(source, node)
578
+ captured, scalars, c_functions =
579
+ split_captures(free - windows, binding_of(block))
580
+ unless assigned.empty? || (assigned & captured.keys).empty?
581
+ raise Unsupported,
582
+ "a stencil's value is its block's, and the cells it is over " \
583
+ "are windows; an array it writes belongs to `jit_each`"
584
+ end
585
+ given = windowed.merge(captured)
586
+
587
+ result = stencil_result(source, node, given, scalars, c_functions,
588
+ windows, shape, type, into)
589
+ # `:mask` does not make this a masked kernel. The frame is marked
590
+ # before the loop runs and the loop never reaches it, so what the
591
+ # kernel is asked to carry is what the operands carry -- and a masked
592
+ # kernel reads and ORs a mask byte per cell, which the interior of an
593
+ # unmasked stencil has no reason to pay for.
594
+ masked = given.each_value.any? { |array| array.has_mask? } ||
595
+ mentions_undef(source, node)
596
+ kernel = compile(source,
597
+ node: node,
598
+ origin: origin,
599
+ array_names: given.keys + [MAP_RESULT],
600
+ storage_types: given.merge(MAP_RESULT => result)
601
+ .transform_values(&:data_type_name),
602
+ scalar_values: scalars,
603
+ c_functions: c_functions,
604
+ masked: masked,
605
+ rank: shape.size,
606
+ windows: windows,
607
+ border: COMPUTED_BORDERS.include?(border) ? border : nil,
608
+ map: true,
609
+ result: MAP_RESULT)
610
+
611
+ # The interior is where every window is inside the array; the frame is
612
+ # what is left, and what `border:` answers for.
613
+ reach = kernel.window_reach
614
+ bounds = shape.each_with_index.map { |extent, axis|
615
+ low, high = reach[axis]
616
+ [-low, extent - high, 1]
617
+ }
618
+ if bounds.any? { |from, to, _| from >= to }
619
+ raise Unsupported,
620
+ "the window reaches #{reach.inspect} and the arrays are " \
621
+ "#{shape.inspect}, so there is no cell where the window is " \
622
+ "inside the array"
623
+ end
624
+ result.mask = 0 if (kernel.masked || border == :mask) && !result.has_mask?
625
+ mark_frame(result, bounds, shape) if border == :mask
626
+ operands = given.merge(MAP_RESULT => result)
627
+ kernel.call(operands, scalars, bounds, c_functions)
628
+ if COMPUTED_BORDERS.include?(border)
629
+ frame_boxes(bounds, shape).each do |box|
630
+ kernel.call(operands, scalars, box, c_functions, border: true)
631
+ end
632
+ end
633
+ result
634
+ end
635
+
636
+ # The frame, cut into boxes the loop can walk. Peeling one axis at a
637
+ # time and taking the interior of the axes already peeled is what keeps
638
+ # them from overlapping: a cell in two of them would be computed twice,
639
+ # and a stencil that wrote a cell twice would be a different thing
640
+ # depending on which write landed last.
641
+ #
642
+ # Two boxes per axis, so 2 * rank of them however wide the window is --
643
+ # the corners come with the axis peeled first rather than being cases of
644
+ # their own.
645
+ def frame_boxes (bounds, shape)
646
+ boxes = []
647
+ shape.each_index do |axis|
648
+ from, to, = bounds[axis]
649
+ [[0, from], [to, shape[axis]]].each do |low, high|
650
+ next if low >= high
651
+ box = shape.each_index.map { |other|
652
+ if other == axis then [low, high, 1]
653
+ elsif other < axis then bounds[other]
654
+ else [0, shape[other], 1]
655
+ end
656
+ }
657
+ boxes << box
658
+ end
659
+ end
660
+ boxes
661
+ end
662
+
663
+ # A frame cell is one the loop does not write, so `:mask` marks it
664
+ # before the loop rather than during it: what says "not computed" is a
665
+ # mask byte, and the cells are named by the same bounds the loop is
666
+ # given. This is the whole of `:mask` -- there is nothing to compute
667
+ # for a cell that was not computed.
668
+ def mark_frame (result, bounds, shape)
669
+ shape.each_with_index do |extent, axis|
670
+ from, to, = bounds[axis]
671
+ [(0...from), (to...extent)].each do |span|
672
+ next if span.size.zero?
673
+ index = Array.new(shape.size) { nil }
674
+ index[axis] = span
675
+ result[*index] = UNDEF
676
+ end
677
+ end
678
+ end
679
+
680
+ # Typed from the block's value, as jit_map's result is, unless the
681
+ # caller said otherwise. `into:` is checked against the shape here
682
+ # rather than by the kernel, so that a wrong array is refused before
683
+ # anything is compiled for it.
684
+ def stencil_result (source, node, arrays, scalars, c_functions, windows,
685
+ shape, type, into)
686
+ if into
687
+ unless into.is_a?(CArray) && into.dim == shape
688
+ raise Unsupported,
689
+ "`into:` takes an array of the stencil's own shape " \
690
+ "#{shape.inspect}"
691
+ end
692
+ return into
693
+ end
694
+ chosen = type || probe_stencil(source, node, arrays, scalars,
695
+ c_functions, windows, shape)
696
+ CArray.send(chosen, *shape)
697
+ end
698
+
699
+ # @private
700
+ def probe_stencil (source, node, arrays, scalars, c_functions, windows,
701
+ shape)
702
+ key = [:stencil, source, arrays.transform_values(&:data_type_name),
703
+ scalars.transform_values { |value| TypeAssignment.scalar_type(value) },
704
+ c_functions.transform_values(&:signature), shape.size]
705
+ cached = probe_cache[key]
706
+ return cached if cached
707
+ analyzer = Analyzer.new(source, node: node, array_names: arrays.keys,
708
+ c_functions: c_functions, rank: shape.size,
709
+ windows: windows, map: :probe)
710
+ TypeAssignment.new(analyzer.body, arrays.transform_values(&:data_type_name),
711
+ scalars, c_functions)
712
+ probe_cache[key] =
713
+ TypeAssignment.result_storage_type(analyzer.body.statements.last.type)
714
+ end
715
+
716
+ # The result has to be sized and typed before there is a kernel to ask,
717
+ # so the block is analyzed once without being compiled -- the same thing
718
+ # a returned contraction does, for the same reason.
719
+ def allocate_map_result (source, node, arrays, scalars, shape, c_functions)
720
+ type = probe_map(source, node, arrays, scalars, c_functions)
721
+ CArray.send(type, *(shape.empty? ? [1] : shape))
722
+ end
723
+
724
+ # @private
725
+ def probe_map (source, node, arrays, scalars, c_functions)
726
+ key = [:map, source, arrays.transform_values(&:data_type_name),
727
+ scalars.transform_values { |value| TypeAssignment.scalar_type(value) },
728
+ c_functions.transform_values(&:signature)]
729
+ cached = probe_cache[key]
730
+ return cached if cached
731
+ analyzer = Analyzer.new(source, node: node, array_names: arrays.keys,
732
+ c_functions: c_functions, rank: 1, map: :probe)
733
+ TypeAssignment.new(analyzer.body, arrays.transform_values(&:data_type_name),
734
+ scalars, c_functions)
735
+ value = analyzer.body.statements.last
736
+ probe_cache[key] = TypeAssignment.result_storage_type(value.type)
737
+ end
738
+
739
+ # @private
740
+ def run_over_whole_arrays (block, map: false)
741
+ node, source, origin = read_block(block)
742
+ free, assigned = free_and_assigned_names(source, node)
743
+ arrays, scalars, c_functions = split_captures(free, binding_of(block))
744
+ # `out = a + b` writes the array named `out` where the block was
745
+ # written. A name the block assigns is not free in it, so it is
746
+ # looked up here rather than by split_captures -- and a name that is
747
+ # not an array outside stays what it looks like, a local.
748
+ arrays = arrays.merge(assigned_arrays(assigned, binding_of(block)))
749
+ if arrays.empty?
750
+ raise Unsupported, "the block reaches no array"
751
+ end
752
+
753
+ aligned, shape = broadcast(arrays)
754
+ if map
755
+ result = allocate_map_result(source, node, arrays, scalars, shape,
756
+ c_functions)
757
+ arrays = arrays.merge(MAP_RESULT => result)
758
+ aligned = aligned.merge(MAP_RESULT => result)
759
+ end
760
+ masked = arrays.each_value.any? { |array| array.has_mask? } ||
761
+ mentions_undef(source, node)
762
+ sweeping = sweepable_pass?(arrays, shape, masked)
763
+ kernel = compile(source,
764
+ node: node,
765
+ origin: origin,
766
+ array_names: arrays.keys,
767
+ storage_types: arrays.transform_values(&:data_type_name),
768
+ scalar_values: scalars,
769
+ c_functions: c_functions,
770
+ masked: masked,
771
+ rank: sweeping ? 1 : shape.size,
772
+ map: map,
773
+ result: map ? MAP_RESULT : nil)
774
+
775
+ kernel.written_arrays.each do |name|
776
+ next if name == MAP_RESULT
777
+ written = arrays.fetch(name)
778
+ unless written.dim == shape
779
+ raise Unsupported,
780
+ "`#{name}` has shape #{written.dim.inspect}, but the " \
781
+ "expression covers #{shape.inspect}; a stretched array " \
782
+ "cannot be written to"
783
+ end
784
+ aligned[name] = written
785
+ end
786
+
787
+ if kernel.masked
788
+ kernel.written_arrays.each do |name|
789
+ array = arrays.fetch(name)
790
+ array.mask = 0 unless array.has_mask?
791
+ end
792
+ end
793
+
794
+ result.mask = 0 if map && kernel.masked && !result.has_mask?
795
+ drive(kernel, aligned, scalars, c_functions, shape, sweeping)
796
+ map ? result : kernel
797
+ end
798
+
799
+ # CArray lines the shapes up; a stretched axis comes back as a stride of
800
+ # zero, which the kernel addresses like any other stride.
801
+ # CArray leaves a CScalar as it is, because its own kernels know to read
802
+ # one cell of it for every cell of everything else. This loop does not
803
+ # know that -- it addresses what it is handed -- so the CScalar is first
804
+ # referred to as the one-cell array it already is, and comes back from
805
+ # the broadcast as the stretched view a one-cell CArray comes back as.
806
+ # The referred view is the same memory, so a write still lands.
807
+ #
808
+ # One axis per axis of what it is standing beside, rather than the one
809
+ # axis it would have had on its own: `CArray.broadcast` stretches a
810
+ # size-1 axis but does not invent an axis that is missing, so `[1]`
811
+ # against a `[2, 3]` is an ndim mismatch rather than a scalar. A
812
+ # CScalar has no shape of its own to contradict this -- being shapeless
813
+ # is what it is for -- so it takes the rank of the operands that do,
814
+ # and stretches on every axis, which is what CArray's own operators
815
+ # give for the same expression.
816
+ def broadcast (arrays)
817
+ values = arrays.values
818
+ rank = values.reject { |value| value.is_a?(CScalar) }
819
+ .map(&:rank).max || 1
820
+ values = values.map { |value|
821
+ value.is_a?(CScalar) ? value.refer(value.data_type, [1] * rank) : value
822
+ }
823
+ aligned = values.size == 1 ? values : CArray.broadcast(*values)
824
+ [arrays.keys.zip(aligned).to_h, aligned.first.dim]
825
+ end
826
+
827
+ # An indexed kernel addresses what it is handed, so a CScalar among the
828
+ # captures is named here rather than stretched: it has no axis to walk,
829
+ # and the block says so by writing no index for it. The whole-array
830
+ # spellings have no such name to write, and broadcast instead.
831
+ def cell_names (arrays)
832
+ arrays.select { |_, value| value.is_a?(CScalar) }.keys
833
+ end
834
+
835
+ # @private
836
+ def binding_of (block)
837
+ block.binding
838
+ end
839
+
840
+ # @private
841
+ def run (extents, block, reassociate = nil)
842
+ node, source, origin = read_block(block)
843
+ names = capture_names(source, node)
844
+ arrays, scalars, c_functions = split_captures(names, binding_of(block))
845
+
846
+ # A plain CArray carries no mask; one exists only once a cell has
847
+ # actually been marked. So masks are touched at all only when some
848
+ # array already has one -- and then the arrays being written need one
849
+ # too, which is what CArray's own operators do.
850
+ if extents.empty?
851
+ count = block.arity
852
+ raise Unsupported,
853
+ "this block names #{count == 1 ? 'an index' : "#{count} indices"}, " \
854
+ "so it needs #{count == 1 ? 'an extent' : 'one extent each'}; " \
855
+ "pass a Range, a count or `(high - 1).step(low, -1)`, or drop " \
856
+ "the #{count == 1 ? 'index' : 'indices'} and write the arrays " \
857
+ "whole as `a[]`"
858
+ end
859
+ pairs = bounds(extents, extents.size)
860
+ steps = pairs.map { |triple, _| triple[2] }
861
+
862
+ kernel = compile(source,
863
+ node: node,
864
+ origin: origin,
865
+ array_names: arrays.keys,
866
+ storage_types: arrays.transform_values(&:data_type_name),
867
+ scalar_values: scalars,
868
+ c_functions: c_functions,
869
+ masked: arrays.each_value.any? { |array| array.has_mask? },
870
+ steps: steps,
871
+ cell_names: cell_names(arrays),
872
+ reassociate: reassociate.nil? ? JIT.reassociate : reassociate)
873
+ # The kernel decides, not the caller: mentioning UNDEF makes it a
874
+ # masked kernel even when no array carries a mask yet.
875
+ if kernel.masked
876
+ kernel.written_arrays.each do |name|
877
+ array = arrays.fetch(name)
878
+ array.mask = 0 unless array.has_mask?
879
+ end
880
+ end
881
+ unless pairs.size == kernel.rank
882
+ raise Unsupported,
883
+ "the block names #{kernel.rank} " \
884
+ "#{kernel.rank == 1 ? 'index' : 'indices'}, and " \
885
+ "#{pairs.size} #{pairs.size == 1 ? 'extent was' : 'extents were'} " \
886
+ "given"
887
+ end
888
+ kernel.call(arrays, scalars, pairs.map(&:first), c_functions)
889
+ kernel
890
+ end
891
+
892
+ # Compiles for one set of array data types and one set of scalar types,
893
+ # and returns the same CompiledKernel for every later call with the
894
+ # same ones. Nothing on this path is cheap relative to running the
895
+ # kernel, so all of it is memoized.
896
+ def compile (source, node: nil, origin: nil, array_names:, storage_types:,
897
+ scalar_values:, c_functions: {}, masked: false, rank: nil,
898
+ steps: nil, contract: false, result: nil, map: false,
899
+ reassociate: false, cell_names: [], windows: [], border: nil)
900
+ # A kernel that mentions UNDEF is a masked one whatever its arrays
901
+ # carry, and deciding that here means no caller has to remember it.
902
+ masked ||= mentions_undef(source, node)
903
+ key = [source, storage_types,
904
+ scalar_values.transform_values { |value|
905
+ TypeAssignment.scalar_type(value)
906
+ },
907
+ # The signature, not the address: `j0` and `y0` are the same
908
+ # kernel, and it is compiled once. One written here adds its
909
+ # symbol, which stands for its body -- see `CFunction#kernel_key`.
910
+ c_functions.transform_values(&:kernel_key),
911
+ masked, rank, steps, contract, result, map,
912
+ # Which names are read at their one cell rather than walked:
913
+ # the same source over a CScalar is a different kernel from
914
+ # the same source over a one-cell CArray.
915
+ cell_names,
916
+ # Which names are windows: the same source read as a stencil is
917
+ # not the same kernel as the same source read as a block that
918
+ # named its indices.
919
+ windows,
920
+ # And what a window that falls off the array reads: the rule is
921
+ # emitted into the frame's body, so two rules are two kernels.
922
+ border,
923
+ # The licence is part of the kernel, not of the call: it
924
+ # decides what C is emitted, so the two spellings are two
925
+ # kernels and the cache keeps them apart.
926
+ reassociate]
927
+ found = registry[key]
928
+ return found if found
929
+ registry[key] = build(source, node, array_names, storage_types,
930
+ scalar_values, c_functions, masked, rank, steps,
931
+ contract, result, origin, map, reassociate,
932
+ cell_names, windows, border)
933
+ end
934
+
935
+ # A kernel that mentions UNDEF is a masked kernel whatever its arrays
936
+ # happen to carry: it asks about masks, or makes them. Checked here
937
+ # because the answer is needed before the kernel is built.
938
+ def mentions_undef (source, node = nil)
939
+ cached = undef_cache[source]
940
+ return cached unless cached.nil?
941
+ undef_cache[source] = Analyzer.mentions_undef?(source, node: node)
942
+ end
943
+
944
+ # @private
945
+ def undef_cache
946
+ @undef_cache ||= {}
947
+ end
948
+
949
+ # @private
950
+ def capture_names (source, node = nil)
951
+ free, = free_and_assigned_names(source, node)
952
+ free
953
+ end
954
+
955
+ # Which names the block reaches for, and which it assigns. Both are
956
+ # properties of the source, so both are cached; what each name *is* is
957
+ # a property of the binding, and is settled outside the cache.
958
+ def free_and_assigned_names (source, node = nil)
959
+ cached = capture_name_cache[source]
960
+ return cached if cached
961
+ capture_name_cache[source] =
962
+ Analyzer.free_and_assigned_names(source, node: node)
963
+ end
964
+
965
+ # @private
966
+ def registry
967
+ @registry ||= {}
968
+ end
969
+
970
+ # @private
971
+ def capture_name_cache
972
+ @capture_name_cache ||= {}
973
+ end
974
+
975
+ # Keyed by instruction sequence, which CRuby hands back as the same
976
+ # object for every Proc made from one block literal. That makes it a
977
+ # free identity for the block, and keeps the file from being read and
978
+ # parsed again on each call.
979
+ def block_cache
980
+ @block_cache ||= {}
981
+ end
982
+
983
+ # @!group Kernel cache
984
+
985
+ # Forgets the kernels compiled in this process, so that the next call
986
+ # reaches the cache on disk rather than the one held in memory.
987
+ #
988
+ # @return [void]
989
+ def clear_registry
990
+ @registry = {}
991
+ @capture_name_cache = {}
992
+ @block_cache = {}
993
+ @undef_cache = {}
994
+ end
995
+
996
+ # @return [String] the directory this environment's kernels are kept in,
997
+ # named for the versions and architecture they were built for.
998
+ def cache_directory
999
+ Compiler.cache_directory
1000
+ end
1001
+
1002
+ # @return [String] the directory holding one entry per environment.
1003
+ def cache_root
1004
+ Compiler.cache_root
1005
+ end
1006
+
1007
+ # @return [Array<String>] the environment directories no longer in use --
1008
+ # another version, or another architecture.
1009
+ def stale_cache_environments
1010
+ Compiler.stale_environments
1011
+ end
1012
+
1013
+ # @return [Integer] the number of kernels this environment has cached.
1014
+ def cache_entry_count
1015
+ Compiler.entry_count
1016
+ end
1017
+
1018
+ # @return [Integer] the bytes this environment's cache holds.
1019
+ def cache_byte_size
1020
+ Compiler.byte_size
1021
+ end
1022
+
1023
+ # Removes cached kernels.
1024
+ #
1025
+ # @param everything [Boolean] `true` to clear every environment, not
1026
+ # only this one.
1027
+ # @return [Integer] the number of entries removed.
1028
+ def clear_cache (everything: false)
1029
+ Compiler.clear(:everything => everything)
1030
+ end
1031
+
1032
+ # @!endgroup
1033
+
1034
+ private
1035
+
1036
+ def build (source, node, array_names, storage_types, scalar_values, c_functions,
1037
+ masked, rank = nil, steps = nil, contract = false, result = nil,
1038
+ origin = nil, map = false, reassociate = false,
1039
+ cell_names = [], windows = [], border = nil)
1040
+ analyzer = Analyzer.new(source, node: node, array_names: array_names,
1041
+ c_functions: c_functions,
1042
+ rank: rank, steps: steps, contract: contract,
1043
+ result: result, map: map,
1044
+ cell_names: cell_names, windows: windows)
1045
+ assignment = TypeAssignment.new(analyzer.body, storage_types,
1046
+ scalar_values, c_functions)
1047
+ generator = CGenerator.new(analyzer, storage_types, assignment.scalar_types,
1048
+ c_functions: c_functions,
1049
+ masked: masked, reassociate: reassociate,
1050
+ steps: steps, border: border,
1051
+ origin: origin, block_source: source)
1052
+ CompiledKernel.new(source: source, generator: generator,
1053
+ analyzer: analyzer, storage_types: storage_types)
1054
+ end
1055
+
1056
+ # Recovering a block's source means parsing the whole file it lives in,
1057
+ # which is far too expensive to repeat per call.
1058
+ def read_block (block)
1059
+ sequence = RubyVM::InstructionSequence.of(block) if
1060
+ defined?(RubyVM::InstructionSequence)
1061
+ return BlockReader.read(block) unless sequence
1062
+ cached = block_cache[sequence]
1063
+ return cached if cached
1064
+ block_cache[sequence] = BlockReader.read(block)
1065
+ end
1066
+
1067
+ # What a block closes over is one of three things, and which it is
1068
+ # decides what the name means inside the kernel: an array is addressed
1069
+ # at a cell, a scalar is a constant the kernel is compiled with, and a
1070
+ # C function is something it calls.
1071
+ # Of the names the block assigns, the ones that are arrays where it was
1072
+ # written. Anything else -- a name that holds a number, or no name at
1073
+ # all -- is a local of the block's own.
1074
+ def assigned_arrays (names, binding)
1075
+ names.each_with_object({}) do |name, found|
1076
+ next unless binding.local_variable_defined?(name)
1077
+ value = binding.local_variable_get(name)
1078
+ found[name] = value if value.is_a?(CArray)
1079
+ end
1080
+ end
1081
+
1082
+ def split_captures (names, binding)
1083
+ arrays = {}
1084
+ scalars = {}
1085
+ c_functions = {}
1086
+ names.each do |name|
1087
+ value = capture_value(name, binding)
1088
+ case value
1089
+ when CArray then arrays[name] = value
1090
+ when CFunction then c_functions[name] = value
1091
+ else scalars[name] = value
1092
+ end
1093
+ end
1094
+ [arrays, scalars, c_functions]
1095
+ end
1096
+
1097
+ # A constant is looked up where the block was written, so it means what
1098
+ # it means there -- the enclosing module's, not the top level's. It is
1099
+ # the only name a method body can reach: `def` closes over nothing, so
1100
+ # a compiled function or a table held in a constant is how a method
1101
+ # gets at one.
1102
+ def capture_value (name, binding)
1103
+ if name.to_s.start_with?(/[A-Z]/)
1104
+ begin
1105
+ binding.eval(name.to_s)
1106
+ rescue NameError
1107
+ raise Unsupported,
1108
+ "`#{name}` is not defined where the block was written"
1109
+ end
1110
+ else
1111
+ unless binding.local_variable_defined?(name)
1112
+ refuse_a_draw(name)
1113
+ raise Unsupported,
1114
+ "`#{name}` is not defined where the block was written"
1115
+ end
1116
+ binding.local_variable_get(name)
1117
+ end
1118
+ end
1119
+
1120
+ # `rand` is Kernel's, so "not defined" would be a lie, and the reason it
1121
+ # is not here is worth saying where it is reached for.
1122
+ DRAW_NAMES = [:rand, :srand].freeze
1123
+
1124
+ def refuse_a_draw (name)
1125
+ return unless DRAW_NAMES.include?(name.to_sym)
1126
+ raise Unsupported, draw_message("`#{name}`")
1127
+ end
1128
+
1129
+ # A generator has one state and hands out its numbers in the order it
1130
+ # was asked in, and a kernel does not fix that order: a stencil's border
1131
+ # is a second loop over the frame, a reduction may split its
1132
+ # accumulator, and the loop runs with the GVL released, which is not
1133
+ # where Ruby's Random -- the one `CArray#random!` draws through -- may
1134
+ # be reached at all. An array filled before the call has none of those
1135
+ # questions in it.
1136
+ def draw_message (what)
1137
+ "#{what} draws from a generator, and a kernel does not fix the order " \
1138
+ "it would draw in; fill an array with `CArray#random!` and read a " \
1139
+ "cell of it, as the kernel reads any other array"
1140
+ end
1141
+
1142
+ # An extent is a Range, an Integer standing for `0...n`, or an
1143
+ # Enumerator::ArithmeticSequence -- which is what `(hi-1).step(lo, -1)`
1144
+ # returns, and how a downward loop is written.
1145
+ #
1146
+ # Returns the half-open pair per axis, and the direction the extent
1147
+ # asked for where it said one.
1148
+ def bounds (extents, rank)
1149
+ extents.map { |extent| bounds_of(extent) }
1150
+ end
1151
+
1152
+ def bounds_of (extent)
1153
+ case extent
1154
+ when Range
1155
+ low = extent.begin || 0
1156
+ high = extent.end
1157
+ raise Unsupported, "an endless range has no extent" unless high
1158
+ [[low, extent.exclude_end? ? high : high + 1, 1], nil]
1159
+ when Integer
1160
+ [[0, extent, 1], nil]
1161
+ when Enumerator::ArithmeticSequence
1162
+ arithmetic_bounds(extent)
1163
+ else
1164
+ raise Unsupported,
1165
+ "an extent is a Range, an Integer or an arithmetic sequence, " \
1166
+ "got #{extent.class}"
1167
+ end
1168
+ end
1169
+
1170
+ # A sequence may skip cells. An offset still means what it means in
1171
+ # Ruby -- `a[i-1]` is the cell at index i-1 -- so skipping changes not
1172
+ # the reading but the dependencies: with a step of two, index i-1 is a
1173
+ # cell this loop never writes.
1174
+ def arithmetic_bounds (extent)
1175
+ step = extent.step
1176
+ raise Unsupported, "an extent's step cannot be zero" if step.zero?
1177
+ first = extent.begin
1178
+ last = extent.end
1179
+ raise Unsupported, "an endless sequence has no extent" if last.nil?
1180
+ limit = extent.exclude_end? ? last : last + (step.positive? ? 1 : -1)
1181
+ [[first, limit, step], step.positive? ? :ascending : :descending]
1182
+ end
1183
+
1184
+ # The direction an extent asked for has to be the one the kernel's own
1185
+ # dependencies require -- and where they require one, the extent has to
1186
+ # say so, because that is the only place a reader sees it.
1187
+ end
1188
+
1189
+ end
1190
+
1191
+ end
1192
+
1193
+ # ---------------------------------------------------------------------------
1194
+ # CArray asks about an expression it is about to compute; this answers.
1195
+ #
1196
+ # Registering here rather than being reached for means a program that only
1197
+ # writes `CArray.fuse { ... }` gets the compiled path from having this gem
1198
+ # installed, and the same answer without it.
1199
+ # ---------------------------------------------------------------------------
1200
+
1201
+ # The expression front end needs things CArray gained after 3.0.0 -- the
1202
+ # plan, the kernel bodies as text, the flags they were built with, and
1203
+ # somewhere to register. Without them the Prism front end above is the
1204
+ # whole of this gem, exactly as before.
1205
+ if CArray.respond_to?(:expression_evaluator) &&
1206
+ CArray.respond_to?(:__kernel_body__) &&
1207
+ CArray.const_defined?(:BUILD_FLAGS)
1208
+ require "carray/jit/expression"
1209
+ CArray.expression_evaluator = CArray::JIT::Expression.new
1210
+ end