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,573 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
module JIT
|
|
3
|
+
|
|
4
|
+
# Typed intermediate representation.
|
|
5
|
+
#
|
|
6
|
+
# Nodes are built untyped by Analyzer and annotated by TypeAssignment,
|
|
7
|
+
# which is why #type is writable. Types are one of :int64, :double,
|
|
8
|
+
# :complex or :boolean -- computation types, deliberately distinct from
|
|
9
|
+
# any array's storage type (see TypeAssignment).
|
|
10
|
+
class Node
|
|
11
|
+
|
|
12
|
+
attr_accessor :type
|
|
13
|
+
attr_reader :location
|
|
14
|
+
|
|
15
|
+
def initialize (location = nil)
|
|
16
|
+
@location = location
|
|
17
|
+
@type = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def children
|
|
21
|
+
[]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class IntegerLiteral < Node
|
|
27
|
+
attr_reader :value
|
|
28
|
+
def initialize (value, location = nil)
|
|
29
|
+
super(location)
|
|
30
|
+
@value = value
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class FloatLiteral < Node
|
|
35
|
+
attr_reader :value
|
|
36
|
+
def initialize (value, location = nil)
|
|
37
|
+
super(location)
|
|
38
|
+
@value = value
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# An imaginary literal: `2i`, or the `1i` that turns a real formula
|
|
43
|
+
# complex. Ruby has no literal for a complex number with both parts, so
|
|
44
|
+
# this is always purely imaginary and `1 + 2i` is an addition.
|
|
45
|
+
class ImaginaryLiteral < Node
|
|
46
|
+
attr_reader :value
|
|
47
|
+
def initialize (value, location = nil)
|
|
48
|
+
super(location)
|
|
49
|
+
@value = value
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# One of the loop indices; `axis` is its position in the index list.
|
|
54
|
+
class IndexVariable < Node
|
|
55
|
+
attr_reader :name, :axis
|
|
56
|
+
def initialize (name, axis, location = nil)
|
|
57
|
+
super(location)
|
|
58
|
+
@name = name
|
|
59
|
+
@axis = axis
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class LocalRead < Node
|
|
64
|
+
# The C variable this read resolves to. A Ruby local may hold an
|
|
65
|
+
# Integer at one point in the body and a Float at another; a C variable
|
|
66
|
+
# cannot, so each type gets its own.
|
|
67
|
+
attr_accessor :binding_name
|
|
68
|
+
attr_reader :name
|
|
69
|
+
def initialize (name, location = nil)
|
|
70
|
+
super(location)
|
|
71
|
+
@name = name
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# A scalar the block closed over; passed in as a kernel argument.
|
|
76
|
+
class CaptureRead < Node
|
|
77
|
+
attr_reader :name
|
|
78
|
+
def initialize (name, location = nil)
|
|
79
|
+
super(location)
|
|
80
|
+
@name = name
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# array[i + 1, j], where each axis is addressed by some index in scope.
|
|
85
|
+
# `subscripts` holds one [index name, offset] pair per axis of the array,
|
|
86
|
+
# which need not be the kernel's own indices: an inner loop introduces
|
|
87
|
+
# index names too, and that is what lets a reduction be written.
|
|
88
|
+
class ElementRead < Node
|
|
89
|
+
attr_reader :array, :subscripts
|
|
90
|
+
def initialize (array, subscripts, location = nil)
|
|
91
|
+
super(location)
|
|
92
|
+
@array = array
|
|
93
|
+
@subscripts = subscripts
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# array[i] == UNDEF, or its negation.
|
|
98
|
+
#
|
|
99
|
+
# This reads the mask, not the value, which is what makes it different
|
|
100
|
+
# from every other read: a cell tested this way has not had its garbage
|
|
101
|
+
# looked at, so it does not mask what it decides. That distinction is
|
|
102
|
+
# syntactic, which is what lets it be settled here at all.
|
|
103
|
+
class MaskTest < Node
|
|
104
|
+
attr_reader :array, :subscripts, :negated
|
|
105
|
+
def initialize (array, subscripts, negated, location = nil)
|
|
106
|
+
super(location)
|
|
107
|
+
@array = array
|
|
108
|
+
@subscripts = subscripts
|
|
109
|
+
@negated = negated
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Zero of whatever type another expression has.
|
|
114
|
+
#
|
|
115
|
+
# A sum has to start from a zero of the summand's type: start it from an
|
|
116
|
+
# integer zero and the accumulator is an integer at its first assignment
|
|
117
|
+
# and a float at its second, which is two variables rather than one, and
|
|
118
|
+
# the sum would never accumulate.
|
|
119
|
+
class ZeroLike < Node
|
|
120
|
+
attr_reader :reference
|
|
121
|
+
def initialize (reference, location = nil)
|
|
122
|
+
super(location)
|
|
123
|
+
@reference = reference
|
|
124
|
+
end
|
|
125
|
+
def children
|
|
126
|
+
[@reference]
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# An extent the caller passed in, referred to by slot. A contraction
|
|
131
|
+
# derives its ranges from the arrays' own shapes, so they arrive with the
|
|
132
|
+
# other extents rather than as expressions in the block.
|
|
133
|
+
class BoundsValue < Node
|
|
134
|
+
attr_reader :slot
|
|
135
|
+
def initialize (slot, location = nil)
|
|
136
|
+
super(location)
|
|
137
|
+
@slot = slot
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# (from...to).each { |j| ... }, and n.times { |j| ... }
|
|
142
|
+
#
|
|
143
|
+
# The index it introduces is in scope only inside it, exactly as the block
|
|
144
|
+
# scopes it in Ruby, and can address any axis of any array that is read.
|
|
145
|
+
# Nothing is written through it: a kernel writes the cell its outer
|
|
146
|
+
# indices are on, and the inner loop is what runs within that cell.
|
|
147
|
+
class InnerLoop < Node
|
|
148
|
+
attr_reader :index, :from, :to, :statements
|
|
149
|
+
def initialize (index, from, to, statements, location = nil)
|
|
150
|
+
super(location)
|
|
151
|
+
@index = index
|
|
152
|
+
@from = from
|
|
153
|
+
@to = to
|
|
154
|
+
@statements = statements
|
|
155
|
+
end
|
|
156
|
+
def children
|
|
157
|
+
[@from, @to] + @statements
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# while cond ... end
|
|
162
|
+
#
|
|
163
|
+
# The loop the bounded one is not: it introduces no index, and how many
|
|
164
|
+
# passes it takes is not written anywhere -- it is whatever the condition
|
|
165
|
+
# says, pass by pass. That is the whole of what it adds, and the whole of
|
|
166
|
+
# what it costs: a kernel with one in it may fail to return, and nothing
|
|
167
|
+
# here can tell whether it will.
|
|
168
|
+
#
|
|
169
|
+
# The condition is re-read at the top of every pass, as Ruby's is. A
|
|
170
|
+
# local it reads must already be a local before the loop -- the condition
|
|
171
|
+
# is walked before the body, so a name the body would introduce is not in
|
|
172
|
+
# scope yet, and says so rather than reading whatever C left there.
|
|
173
|
+
class While < Node
|
|
174
|
+
attr_reader :condition, :statements
|
|
175
|
+
def initialize (condition, statements, location = nil)
|
|
176
|
+
super(location)
|
|
177
|
+
@condition = condition
|
|
178
|
+
@statements = statements
|
|
179
|
+
end
|
|
180
|
+
def children
|
|
181
|
+
[@condition] + @statements
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# `next` and `break` inside a loop. Neither carries a value: the inner
|
|
186
|
+
# loop's own value is never used, so `break x` would drop x silently.
|
|
187
|
+
class LoopSkip < Node
|
|
188
|
+
def children
|
|
189
|
+
[]
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
class LoopStop < Node
|
|
194
|
+
def children
|
|
195
|
+
[]
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# array[i] = UNDEF -- marks the cell missing and leaves its bytes alone.
|
|
200
|
+
class MaskWrite < Node
|
|
201
|
+
attr_reader :array
|
|
202
|
+
def initialize (array, location = nil)
|
|
203
|
+
super(location)
|
|
204
|
+
@array = array
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# if/else in statement position, with writes inside the branches.
|
|
209
|
+
class Branch < Node
|
|
210
|
+
attr_reader :condition, :consequent, :alternative
|
|
211
|
+
def initialize (condition, consequent, alternative, location = nil)
|
|
212
|
+
super(location)
|
|
213
|
+
@condition = condition
|
|
214
|
+
@consequent = consequent
|
|
215
|
+
@alternative = alternative
|
|
216
|
+
end
|
|
217
|
+
def children
|
|
218
|
+
[@condition] + @consequent + @alternative
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
class BinaryOperation < Node
|
|
223
|
+
attr_reader :operator, :left, :right
|
|
224
|
+
def initialize (operator, left, right, location = nil)
|
|
225
|
+
super(location)
|
|
226
|
+
@operator = operator
|
|
227
|
+
@left = left
|
|
228
|
+
@right = right
|
|
229
|
+
end
|
|
230
|
+
def children
|
|
231
|
+
[@left, @right]
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# && and ||
|
|
236
|
+
class LogicalOperation < Node
|
|
237
|
+
attr_reader :operator, :left, :right
|
|
238
|
+
def initialize (operator, left, right, location = nil)
|
|
239
|
+
super(location)
|
|
240
|
+
@operator = operator
|
|
241
|
+
@left = left
|
|
242
|
+
@right = right
|
|
243
|
+
end
|
|
244
|
+
def children
|
|
245
|
+
[@left, @right]
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
class LogicalNot < Node
|
|
250
|
+
attr_reader :operand
|
|
251
|
+
def initialize (operand, location = nil)
|
|
252
|
+
super(location)
|
|
253
|
+
@operand = operand
|
|
254
|
+
end
|
|
255
|
+
def children
|
|
256
|
+
[@operand]
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# abs, which unlike the math.h functions keeps the type it was given.
|
|
261
|
+
class AbsoluteValue < Node
|
|
262
|
+
attr_reader :operand
|
|
263
|
+
def initialize (operand, location = nil)
|
|
264
|
+
super(location)
|
|
265
|
+
@operand = operand
|
|
266
|
+
end
|
|
267
|
+
def children
|
|
268
|
+
[@operand]
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Complex(x, y) -- the way into the complex type from two real numbers,
|
|
273
|
+
# and the only one that does not start from a complex array.
|
|
274
|
+
class ComplexBuild < Node
|
|
275
|
+
attr_reader :real, :imaginary
|
|
276
|
+
def initialize (real, imaginary, location = nil)
|
|
277
|
+
super(location)
|
|
278
|
+
@real = real
|
|
279
|
+
@imaginary = imaginary
|
|
280
|
+
end
|
|
281
|
+
def children
|
|
282
|
+
[@real, @imaginary]
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# real / imag / conjugate / arg. Three of the four take a Complex to a
|
|
287
|
+
# Float, which makes them the way out of the complex type: a kernel that
|
|
288
|
+
# writes into a real array has to pass through one of them.
|
|
289
|
+
#
|
|
290
|
+
# `ruby_name` is the spelling the block used, kept only so that a message
|
|
291
|
+
# about it names the method that was actually written.
|
|
292
|
+
class ComplexPart < Node
|
|
293
|
+
attr_reader :name, :ruby_name, :operand
|
|
294
|
+
def initialize (name, ruby_name, operand, location = nil)
|
|
295
|
+
super(location)
|
|
296
|
+
@name = name
|
|
297
|
+
@ruby_name = ruby_name
|
|
298
|
+
@operand = operand
|
|
299
|
+
end
|
|
300
|
+
def children
|
|
301
|
+
[@operand]
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# floor / ceil / round / truncate / to_i / to_f, whose result type is a
|
|
306
|
+
# property of the method rather than of the operand.
|
|
307
|
+
class Conversion < Node
|
|
308
|
+
attr_reader :name, :operand, :result_type
|
|
309
|
+
def initialize (name, operand, result_type, location = nil)
|
|
310
|
+
super(location)
|
|
311
|
+
@name = name
|
|
312
|
+
@operand = operand
|
|
313
|
+
@result_type = result_type
|
|
314
|
+
end
|
|
315
|
+
def children
|
|
316
|
+
[@operand]
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# A captured array handed to a C function whole, as an address.
|
|
321
|
+
#
|
|
322
|
+
# Not an ElementRead: nothing is read here. A kernel addresses an array
|
|
323
|
+
# cell by cell, through a base and a stride; a C function takes the
|
|
324
|
+
# address itself, which does not vary with the cell the kernel is on.
|
|
325
|
+
class ArrayAddress < Node
|
|
326
|
+
attr_reader :array
|
|
327
|
+
def initialize (array, location = nil)
|
|
328
|
+
super(location)
|
|
329
|
+
@array = array
|
|
330
|
+
end
|
|
331
|
+
def children
|
|
332
|
+
[]
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# `p[i]` where `p` is a pointer parameter of a compiled function.
|
|
337
|
+
#
|
|
338
|
+
# Not an ElementRead: an array's cell is reached through a base and a
|
|
339
|
+
# stride the caller supplied, and a pointer parameter is reached the way C
|
|
340
|
+
# reaches it -- contiguous, from the address it was handed. The index is
|
|
341
|
+
# an expression rather than a loop index with an offset, because there is
|
|
342
|
+
# no loop.
|
|
343
|
+
class PointerRead < Node
|
|
344
|
+
attr_reader :name, :index
|
|
345
|
+
def initialize (name, index, location = nil)
|
|
346
|
+
super(location)
|
|
347
|
+
@name = name
|
|
348
|
+
@index = index
|
|
349
|
+
end
|
|
350
|
+
def children
|
|
351
|
+
[@index]
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# `p[i] = value` through a pointer parameter that was not declared const.
|
|
356
|
+
class PointerWrite < Node
|
|
357
|
+
attr_reader :name, :index, :expression
|
|
358
|
+
def initialize (name, index, expression, location = nil)
|
|
359
|
+
super(location)
|
|
360
|
+
@name = name
|
|
361
|
+
@index = index
|
|
362
|
+
@expression = expression
|
|
363
|
+
end
|
|
364
|
+
def children
|
|
365
|
+
[@index, @expression]
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# A call to a C function the block closed over. The name is the local
|
|
370
|
+
# the block used, not the symbol: which library it came from is settled
|
|
371
|
+
# before the kernel is built, and the kernel only knows the signature.
|
|
372
|
+
class CFunctionCall < Node
|
|
373
|
+
attr_reader :name, :arguments
|
|
374
|
+
def initialize (name, arguments, location = nil)
|
|
375
|
+
super(location)
|
|
376
|
+
@name = name
|
|
377
|
+
@arguments = arguments
|
|
378
|
+
end
|
|
379
|
+
def children
|
|
380
|
+
@arguments
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# A call standing where a statement stands, its value dropped.
|
|
385
|
+
#
|
|
386
|
+
# Only a call to a C function may be one. Everything else this compiler
|
|
387
|
+
# can write is a computation, and a computation nobody takes the value of
|
|
388
|
+
# is a line that does nothing -- refused, because writing one is a
|
|
389
|
+
# mistake rather than an intention. A C function is the exception
|
|
390
|
+
# because its parameters can carry an address, so what it did may be
|
|
391
|
+
# somewhere other than in the value it returned.
|
|
392
|
+
class CallStatement < Node
|
|
393
|
+
attr_reader :call
|
|
394
|
+
def initialize (call, location = nil)
|
|
395
|
+
super(location)
|
|
396
|
+
@call = call
|
|
397
|
+
end
|
|
398
|
+
def children
|
|
399
|
+
[@call]
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
# A call to the function being compiled, from inside its own body. The
|
|
404
|
+
# name is the one its declaration gave it, which is how C would spell the
|
|
405
|
+
# call too; the symbol it becomes is the generator's business. What the
|
|
406
|
+
# call takes and returns is the declaration's answer rather than anything
|
|
407
|
+
# inferred, so both travel on the node.
|
|
408
|
+
class RecursiveCall < Node
|
|
409
|
+
attr_reader :name, :arguments, :parameters, :result_type
|
|
410
|
+
def initialize (name, arguments, parameters, result_type, location = nil)
|
|
411
|
+
super(location)
|
|
412
|
+
@name = name
|
|
413
|
+
@arguments = arguments
|
|
414
|
+
@parameters = parameters
|
|
415
|
+
@result_type = result_type
|
|
416
|
+
end
|
|
417
|
+
def children
|
|
418
|
+
@arguments
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
# x ** y
|
|
423
|
+
class Power < Node
|
|
424
|
+
attr_reader :base, :exponent
|
|
425
|
+
def initialize (base, exponent, location = nil)
|
|
426
|
+
super(location)
|
|
427
|
+
@base = base
|
|
428
|
+
@exponent = exponent
|
|
429
|
+
end
|
|
430
|
+
def children
|
|
431
|
+
[@base, @exponent]
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
class BitwiseNot < Node
|
|
436
|
+
attr_reader :operand
|
|
437
|
+
def initialize (operand, location = nil)
|
|
438
|
+
super(location)
|
|
439
|
+
@operand = operand
|
|
440
|
+
end
|
|
441
|
+
def children
|
|
442
|
+
[@operand]
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
class BooleanLiteral < Node
|
|
447
|
+
attr_reader :value
|
|
448
|
+
def initialize (value, location = nil)
|
|
449
|
+
super(location)
|
|
450
|
+
@value = value
|
|
451
|
+
end
|
|
452
|
+
def children
|
|
453
|
+
[]
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
class UnaryMinus < Node
|
|
458
|
+
attr_reader :operand
|
|
459
|
+
def initialize (operand, location = nil)
|
|
460
|
+
super(location)
|
|
461
|
+
@operand = operand
|
|
462
|
+
end
|
|
463
|
+
def children
|
|
464
|
+
[@operand]
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
class MathCall < Node
|
|
469
|
+
attr_reader :name, :arguments
|
|
470
|
+
def initialize (name, arguments, location = nil)
|
|
471
|
+
super(location)
|
|
472
|
+
@name = name
|
|
473
|
+
@arguments = arguments
|
|
474
|
+
end
|
|
475
|
+
def children
|
|
476
|
+
@arguments
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
class Conditional < Node
|
|
481
|
+
attr_reader :condition, :consequent, :alternative
|
|
482
|
+
def initialize (condition, consequent, alternative, location = nil)
|
|
483
|
+
super(location)
|
|
484
|
+
@condition = condition
|
|
485
|
+
@consequent = consequent
|
|
486
|
+
@alternative = alternative
|
|
487
|
+
end
|
|
488
|
+
def children
|
|
489
|
+
[@condition, @consequent, @alternative]
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
# A block-local variable: w = ...
|
|
494
|
+
# `printf` in the block, for looking at what a kernel is doing. The
|
|
495
|
+
# format is carried as it was written and rewritten on the way out: C's
|
|
496
|
+
# directives are not Ruby's, and by then each argument's type is known.
|
|
497
|
+
class Print < Node
|
|
498
|
+
attr_reader :template, :arguments
|
|
499
|
+
def initialize (template, arguments, location = nil)
|
|
500
|
+
super(location)
|
|
501
|
+
@template = template
|
|
502
|
+
@arguments = arguments
|
|
503
|
+
end
|
|
504
|
+
def children
|
|
505
|
+
@arguments
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
# `raise "..."` in the block. C has no exception to throw, so the
|
|
510
|
+
# message is not carried out of the kernel: it is registered as it is
|
|
511
|
+
# compiled, the cell writes its code into the error slot the kernel is
|
|
512
|
+
# already watching, and the Ruby side raises when the loop is over. The
|
|
513
|
+
# message is written out rather than computed, because it has to be known
|
|
514
|
+
# at compile time to be registered at all.
|
|
515
|
+
class Raise < Node
|
|
516
|
+
attr_reader :message
|
|
517
|
+
def initialize (message, location = nil)
|
|
518
|
+
super(location)
|
|
519
|
+
@message = message
|
|
520
|
+
end
|
|
521
|
+
def children
|
|
522
|
+
[]
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
class Assignment < Node
|
|
527
|
+
attr_accessor :binding_name
|
|
528
|
+
attr_reader :name, :expression
|
|
529
|
+
def initialize (name, expression, location = nil)
|
|
530
|
+
super(location)
|
|
531
|
+
@name = name
|
|
532
|
+
@expression = expression
|
|
533
|
+
end
|
|
534
|
+
def children
|
|
535
|
+
[@expression]
|
|
536
|
+
end
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
# A cell of an array: out[i, j] = ...
|
|
540
|
+
#
|
|
541
|
+
# Writes are always at the cell the loop is on. Writing elsewhere would
|
|
542
|
+
# make the evaluation order a property of the body rather than of the
|
|
543
|
+
# dependencies, which is what lets the order be derived at all.
|
|
544
|
+
class ElementWrite < Node
|
|
545
|
+
# How the left-hand side was indexed. A kernel writes the cell its
|
|
546
|
+
# outer indices are on, so this is normally just those; a contraction
|
|
547
|
+
# reads it to learn which indices are free and which are summed over.
|
|
548
|
+
attr_accessor :subscripts
|
|
549
|
+
attr_reader :array, :expression
|
|
550
|
+
def initialize (array, expression, location = nil, subscripts = nil)
|
|
551
|
+
super(location)
|
|
552
|
+
@array = array
|
|
553
|
+
@expression = expression
|
|
554
|
+
@subscripts = subscripts
|
|
555
|
+
end
|
|
556
|
+
def children
|
|
557
|
+
[@expression]
|
|
558
|
+
end
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
class KernelBody < Node
|
|
562
|
+
attr_reader :statements
|
|
563
|
+
def initialize (statements)
|
|
564
|
+
super(nil)
|
|
565
|
+
@statements = statements
|
|
566
|
+
end
|
|
567
|
+
def children
|
|
568
|
+
@statements
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
end
|
|
573
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
require "fiddle"
|
|
2
|
+
|
|
3
|
+
class CArray
|
|
4
|
+
module JIT
|
|
5
|
+
|
|
6
|
+
# Letting CArray drive the loop instead of driving it here.
|
|
7
|
+
#
|
|
8
|
+
# `ca_call_cslab_N` is CArray's chunked sweep: it acquires the operands,
|
|
9
|
+
# broadcasts them, ORs the inputs' masks and propagates the result, and
|
|
10
|
+
# hands a callback one chunk at a time. A non-alias operand -- a gather,
|
|
11
|
+
# a lazy array, anything the tiers here would have to materialise -- is
|
|
12
|
+
# re-gathered into a ~32KB arena scratch per chunk rather than copied
|
|
13
|
+
# whole, so the input memory peak stops scaling with the operand.
|
|
14
|
+
#
|
|
15
|
+
# That is what this is for, and it is the only thing it is for. A kernel
|
|
16
|
+
# that names an index is not a sweep: it reaches neighbours, chooses an
|
|
17
|
+
# order and runs inner loops, none of which a chunked walk can offer.
|
|
18
|
+
# jit_each is the form with no neighbour in it, which is the same thing
|
|
19
|
+
# as saying it is the form a sweep can drive.
|
|
20
|
+
module Sweep
|
|
21
|
+
|
|
22
|
+
# The sweep is reached by address, the way this library reaches every
|
|
23
|
+
# other C function: CArray's extension is already loaded, so the symbol
|
|
24
|
+
# is there to be found rather than linked against.
|
|
25
|
+
def self.handle
|
|
26
|
+
return @handle if defined?(@handle)
|
|
27
|
+
path = $LOADED_FEATURES.find { |feature|
|
|
28
|
+
File.basename(feature) =~ /\Acarray_ext\.(so|bundle|dylib)\z/
|
|
29
|
+
}
|
|
30
|
+
@handle = path && begin
|
|
31
|
+
Fiddle::Handle.new(path)
|
|
32
|
+
rescue Fiddle::DLError
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# True when this CArray is new enough to have the chunked slab family.
|
|
38
|
+
def self.available?
|
|
39
|
+
return @available if defined?(@available)
|
|
40
|
+
@available = !handle.nil? && begin
|
|
41
|
+
handle["ca_call_cslab_1_r"]
|
|
42
|
+
true
|
|
43
|
+
rescue Fiddle::DLError
|
|
44
|
+
false
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# ca_call_cslab_N_r(func, fsync, rcx0..rcxN-1, userdata)
|
|
49
|
+
#
|
|
50
|
+
# `need_gvl: true` is not a detail. Fiddle releases the GVL for a call
|
|
51
|
+
# unless it is told otherwise, which is right for every other function
|
|
52
|
+
# this library reaches -- a generated kernel is pure C, touches no Ruby
|
|
53
|
+
# value and is the better for running while other threads do. This one
|
|
54
|
+
# is not that: it is CArray's own C, and acquiring the operands attaches
|
|
55
|
+
# them, allocates with `xmalloc`, creates a mask where an output needs
|
|
56
|
+
# one and raises on a shape that will not broadcast. All of that is the
|
|
57
|
+
# Ruby runtime's, and none of it may be done without the GVL. The
|
|
58
|
+
# kernel it calls back into is still pure C, but the GVL is held for the
|
|
59
|
+
# whole call rather than the callback, because Fiddle's choice is made
|
|
60
|
+
# once at the boundary and there is nowhere inside to make it again.
|
|
61
|
+
def self.function (arity)
|
|
62
|
+
@functions ||= {}
|
|
63
|
+
@functions[arity] ||=
|
|
64
|
+
begin
|
|
65
|
+
types = [Fiddle::TYPE_VOIDP] * (arity + 3)
|
|
66
|
+
begin
|
|
67
|
+
Fiddle::Function.new(handle["ca_call_cslab_#{arity}_r"], types,
|
|
68
|
+
Fiddle::TYPE_VOIDP, need_gvl: true)
|
|
69
|
+
rescue ArgumentError
|
|
70
|
+
# A Fiddle too old to be told. It is also too old to have let
|
|
71
|
+
# go of the GVL in the first place -- the keyword and the
|
|
72
|
+
# releasing arrived together -- so what is left here is what
|
|
73
|
+
# that Fiddle would have done anyway.
|
|
74
|
+
Fiddle::Function.new(handle["ca_call_cslab_#{arity}_r"], types,
|
|
75
|
+
Fiddle::TYPE_VOIDP)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# The largest arity the family was generated for. Whether a pass can
|
|
81
|
+
# go this way at all is decided in CArray::JIT.sweepable_pass?, before
|
|
82
|
+
# there is a kernel -- because the answer changes what is compiled.
|
|
83
|
+
MAX_ARITY = 7
|
|
84
|
+
|
|
85
|
+
# `arrays` are the operands in the order the kernel addresses them, and
|
|
86
|
+
# `fsync` says which of them it writes -- CArray's own spelling, one
|
|
87
|
+
# character per operand.
|
|
88
|
+
def self.call (slab, fsync, arrays, context)
|
|
89
|
+
function(arrays.size).call(slab, fsync,
|
|
90
|
+
*arrays.map { |array| Fiddle.dlwrap(array) },
|
|
91
|
+
context)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
end
|
|
97
|
+
end
|