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/analyzer.rb
CHANGED
|
@@ -12,6 +12,96 @@ class CArray
|
|
|
12
12
|
# so the caller passes them in -- it knows, because it has the values.
|
|
13
13
|
class Analyzer
|
|
14
14
|
|
|
15
|
+
# ---- the vocabulary a local array is written in ----
|
|
16
|
+
|
|
17
|
+
# The types a body may make an array of: CArray's own constructor names,
|
|
18
|
+
# each naming the storage type CArray gives it. Taken from CArray
|
|
19
|
+
# rather than invented here, which is why the aliases land where they
|
|
20
|
+
# do -- `CArray.float` is a float32 and `CArray.complex` is a cmplx64,
|
|
21
|
+
# the opposite of what Ruby's own Float and Complex would suggest.
|
|
22
|
+
LOCAL_ARRAY_TYPES = {
|
|
23
|
+
:int8 => "int8", :int16 => "int16",
|
|
24
|
+
:int32 => "int32", :int64 => "int64",
|
|
25
|
+
:uint8 => "uint8", :uint16 => "uint16",
|
|
26
|
+
:uint32 => "uint32", :uint64 => "uint64",
|
|
27
|
+
:float32 => "float32", :float64 => "float64",
|
|
28
|
+
:cmplx64 => "cmplx64", :cmplx128 => "cmplx128",
|
|
29
|
+
:boolean => "boolean",
|
|
30
|
+
# The aliases, as CArray spells them.
|
|
31
|
+
:float => "float32", :double => "float64",
|
|
32
|
+
:complex => "cmplx64", :dcomplex => "cmplx128",
|
|
33
|
+
:byte => "uint8", :short => "int16",
|
|
34
|
+
:int => "int32",
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
# What one cell of each takes, which is what the limits below are
|
|
38
|
+
# counted in. Beside the table above so that a type added to one is
|
|
39
|
+
# visibly missing from the other.
|
|
40
|
+
LOCAL_ARRAY_STORAGE_BYTES = {
|
|
41
|
+
"int8" => 1, "int16" => 2, "int32" => 4, "int64" => 8,
|
|
42
|
+
"uint8" => 1, "uint16" => 2, "uint32" => 4, "uint64" => 8,
|
|
43
|
+
"float32" => 4, "float64" => 8,
|
|
44
|
+
"cmplx64" => 8, "cmplx128" => 16,
|
|
45
|
+
"boolean" => 1,
|
|
46
|
+
}.freeze
|
|
47
|
+
|
|
48
|
+
# How much of a local array a stack frame is asked to hold. Past
|
|
49
|
+
# either number the array is allocated at the kernel's entry instead --
|
|
50
|
+
# so these say where the array goes rather than whether it is allowed,
|
|
51
|
+
# and nothing is refused for its size.
|
|
52
|
+
#
|
|
53
|
+
# Both are provisional: what a Ruby thread's stack actually is on each
|
|
54
|
+
# platform has not been measured, and until it has, these are a guess
|
|
55
|
+
# with room under it. They are in one place so that measuring moves
|
|
56
|
+
# one number rather than several. What the total does not count is in
|
|
57
|
+
# docs: a pasted function's own arrays, recursion, and whatever a
|
|
58
|
+
# future thread pool gives its threads.
|
|
59
|
+
LOCAL_ARRAY_BYTE_LIMIT = 4 * 1024
|
|
60
|
+
LOCAL_ARRAY_TOTAL_BYTE_LIMIT = 16 * 1024
|
|
61
|
+
|
|
62
|
+
# The types CArray has that a kernel computes with neither of, and what
|
|
63
|
+
# each of them holds instead -- which is what the refusal says.
|
|
64
|
+
LOCAL_ARRAY_REFUSED_TYPES_HOLD = {
|
|
65
|
+
:object => "Ruby objects",
|
|
66
|
+
:fixlen => "fixed-length byte strings",
|
|
67
|
+
}.freeze
|
|
68
|
+
LOCAL_ARRAY_REFUSED_TYPES = LOCAL_ARRAY_REFUSED_TYPES_HOLD.keys.freeze
|
|
69
|
+
|
|
70
|
+
# CArray's compatibility layer, which `data_type_extension.rb` opens by
|
|
71
|
+
# calling itself "Numo / NumPy-style" and "not the 'main' carray API".
|
|
72
|
+
# The words inside a block are carray's own, so these are sent to the
|
|
73
|
+
# spelling they stand for rather than taken.
|
|
74
|
+
LOCAL_ARRAY_COMPATIBILITY_NAMES = [:zeros, :ones, :full].freeze
|
|
75
|
+
|
|
76
|
+
# Every name a constructor may be written under, so that the receiver of
|
|
77
|
+
# one is known for what it is before anything else has been read.
|
|
78
|
+
LOCAL_ARRAY_CONSTRUCTOR_NAMES =
|
|
79
|
+
(LOCAL_ARRAY_TYPES.keys + LOCAL_ARRAY_REFUSED_TYPES +
|
|
80
|
+
LOCAL_ARRAY_COMPATIBILITY_NAMES + [:new, :empty]).freeze
|
|
81
|
+
|
|
82
|
+
# `CArray`, `::CArray`, or one of the type classes under either. A
|
|
83
|
+
# constructor is written on one of these and on nothing else.
|
|
84
|
+
def self.carray_receiver? (node)
|
|
85
|
+
case node
|
|
86
|
+
when Prism::ConstantReadNode
|
|
87
|
+
node.name == :CArray
|
|
88
|
+
when Prism::ConstantPathNode
|
|
89
|
+
text = node.slice
|
|
90
|
+
text == "::CArray" ||
|
|
91
|
+
text.start_with?("CArray::") || text.start_with?("::CArray::")
|
|
92
|
+
else
|
|
93
|
+
false
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Whether this call is one a body would make an array with -- including
|
|
98
|
+
# the spellings that are refused, so that each is refused by the reader
|
|
99
|
+
# that knows why rather than by the capture scan.
|
|
100
|
+
def self.local_array_constructor? (node)
|
|
101
|
+
node.is_a?(Prism::CallNode) && carray_receiver?(node.receiver) &&
|
|
102
|
+
LOCAL_ARRAY_CONSTRUCTOR_NAMES.include?(node.name)
|
|
103
|
+
end
|
|
104
|
+
|
|
15
105
|
# Ruby Math methods that correspond 1:1 to a math.h function.
|
|
16
106
|
#
|
|
17
107
|
# Deliberately excludes anything whose C counterpart disagrees on
|
|
@@ -35,6 +125,11 @@ class CArray
|
|
|
35
125
|
:cosh => "cosh",
|
|
36
126
|
:tanh => "tanh",
|
|
37
127
|
:hypot => "hypot",
|
|
128
|
+
:erf => "erf",
|
|
129
|
+
:erfc => "erfc",
|
|
130
|
+
# Not lowered to `tgamma` but to a helper, `Math.gamma` being what
|
|
131
|
+
# Ruby computes and tgamma only most of it -- see CGenerator.
|
|
132
|
+
:gamma => "gamma",
|
|
38
133
|
:asinh => "asinh",
|
|
39
134
|
:acosh => "acosh",
|
|
40
135
|
:atanh => "atanh",
|
|
@@ -101,12 +196,25 @@ class CArray
|
|
|
101
196
|
:rad_pi => "no math.h counterpart",
|
|
102
197
|
}.freeze
|
|
103
198
|
|
|
199
|
+
# Ruby's other ways of counting a loop. Each is a loop this could run,
|
|
200
|
+
# and each is refused by name rather than by the generic message, so
|
|
201
|
+
# that the answer is the spelling to use rather than a list to search.
|
|
202
|
+
INNER_LOOP_SPELLINGS = %i[downto upto reverse_each each_with_index
|
|
203
|
+
each_with_object].freeze
|
|
204
|
+
|
|
205
|
+
# The two questions about a number that answer true or false in Ruby,
|
|
206
|
+
# and that C answers with a macro of its own. What each accepts is
|
|
207
|
+
# Ruby's business and is checked with the types: `nan?` is Float's
|
|
208
|
+
# alone, while `finite?` answers for an Integer and a Complex too.
|
|
209
|
+
NUMERIC_PREDICATES = %i[nan? finite?].freeze
|
|
210
|
+
|
|
104
211
|
ARITHMETIC_OPERATORS = [:+, :-, :*, :/, :%].freeze
|
|
105
212
|
# Ruby's bit operators on Integers, which C has too. What they do at
|
|
106
|
-
# the edges is C's answer rather than Ruby's -- a shift wraps and
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
#
|
|
213
|
+
# the edges is C's answer rather than Ruby's -- a shift wraps, and a
|
|
214
|
+
# count of 64 or more or below zero is C's undefined one. The width is
|
|
215
|
+
# the kernel's, int64, not the cell's: CArray's own `<<` shifts an int32
|
|
216
|
+
# in 32 bits (`ext/mkkernel.rb`, :bit_lshift), so past the cell's width
|
|
217
|
+
# the two part, and the kernel is the one that agrees with a Ruby loop.
|
|
110
218
|
BIT_OPERATORS = [:&, :|, :^, :<<, :>>].freeze
|
|
111
219
|
|
|
112
220
|
# Methods whose result type is a property of the method: Float#floor and
|
|
@@ -134,13 +242,28 @@ class CArray
|
|
|
134
242
|
:phase => :arg,
|
|
135
243
|
}.freeze
|
|
136
244
|
|
|
245
|
+
# The rest of Ruby's Math, and why each is not lowered. C has a
|
|
246
|
+
# function by every one of these names -- saying it does not would be
|
|
247
|
+
# untrue, and it was what this said until the list was read against
|
|
248
|
+
# `math.h` -- so the reason is the one that actually applies: what
|
|
249
|
+
# Ruby computes and what C computes are not the same thing.
|
|
250
|
+
REFUSED_MATH = {
|
|
251
|
+
:lgamma => "Ruby's answer is a pair -- the value and the sign -- " \
|
|
252
|
+
"and a cell holds one number",
|
|
253
|
+
:frexp => "Ruby's answer is a pair -- the fraction and the " \
|
|
254
|
+
"exponent -- and a cell holds one number",
|
|
255
|
+
:ldexp => "its second argument is an exponent rather than a " \
|
|
256
|
+
"number, and a math call here computes every argument " \
|
|
257
|
+
"in the type of its result",
|
|
258
|
+
}.freeze
|
|
259
|
+
|
|
137
260
|
# Constants under Math, emitted as literals so that the C sees exactly
|
|
138
261
|
# the double Ruby would have used.
|
|
139
262
|
MATH_CONSTANTS = { :PI => Math::PI, :E => Math::E }.freeze
|
|
140
263
|
COMPARISON_OPERATORS = [:<, :<=, :>, :>=, :==, :!=].freeze
|
|
141
264
|
|
|
142
265
|
attr_reader :parameter_names, :pointer_names, :address_arrays,
|
|
143
|
-
:address_parameters
|
|
266
|
+
:address_parameters, :random_names, :assigned_parameters
|
|
144
267
|
# An offset that is an integer here rather than when the kernel runs.
|
|
145
268
|
# A literal is one; so is arithmetic over literals, which is the same
|
|
146
269
|
# number written a way that says where it came from -- `w[-RADIUS-1]`
|
|
@@ -187,7 +310,18 @@ class CArray
|
|
|
187
310
|
attr_reader :windows
|
|
188
311
|
attr_reader :index_names, :array_names, :scalar_names, :c_function_names, :body,
|
|
189
312
|
:written_arrays, :array_ranks, :subscripts, :inner_ranges,
|
|
313
|
+
:index_sources, :unbounded_indices,
|
|
190
314
|
:contracted_names
|
|
315
|
+
# The C arrays the body declares, as [scope, name, storage, shape].
|
|
316
|
+
# Nothing in the operand tables says a body has any, so this is how the
|
|
317
|
+
# generator learns it has a header to include and a mask to refuse.
|
|
318
|
+
attr_reader :local_array_declarations
|
|
319
|
+
|
|
320
|
+
# Whether any of them is one of the zeroed spellings, which is what
|
|
321
|
+
# `<string.h>` is included for.
|
|
322
|
+
def clears_a_local_array?
|
|
323
|
+
@clears_a_local_array
|
|
324
|
+
end
|
|
191
325
|
|
|
192
326
|
# A kernel that mentions UNDEF is a masked kernel whatever its arrays
|
|
193
327
|
# happen to carry: it asks about masks, or makes them.
|
|
@@ -217,6 +351,45 @@ class CArray
|
|
|
217
351
|
allocate.send(:scan_free_names, source, node)
|
|
218
352
|
end
|
|
219
353
|
|
|
354
|
+
# The names the block hands to a C function whole, and reads in no
|
|
355
|
+
# other way.
|
|
356
|
+
#
|
|
357
|
+
# Wanted before the analyzer runs, because what the caller does with
|
|
358
|
+
# the answer -- leaving those arrays out of the broadcast -- has to
|
|
359
|
+
# happen before it: the broadcast is what a shape that does not line up
|
|
360
|
+
# dies in, and an array handed over whole has no shape to line up.
|
|
361
|
+
#
|
|
362
|
+
# The parameters decide, not the spelling. A bare array name in an
|
|
363
|
+
# argument position looks like an address pass and need not be one: in
|
|
364
|
+
# the whole-array spelling a bare name is a cell, so `twice.call(a)`
|
|
365
|
+
# against `double twice(double x)` walks `a` cell by cell. Asking the
|
|
366
|
+
# declaration is not a second reading of the signature -- the CFunction
|
|
367
|
+
# parsed it once, and this asks the same object the same question the
|
|
368
|
+
# analyzer asks.
|
|
369
|
+
#
|
|
370
|
+
# A name used as a cell anywhere, subscripted or bare, is not here
|
|
371
|
+
# however else it is also used: an array that is both walked and handed
|
|
372
|
+
# over is walked, and lines up as it always did.
|
|
373
|
+
def self.address_array_names (source, node: nil, c_functions: {})
|
|
374
|
+
analyzer = allocate
|
|
375
|
+
block = node || analyzer.send(:parse_block, source)
|
|
376
|
+
analyzer.send(:scan_address_array_names, block.body, c_functions)
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# The names the block makes an array of its own under.
|
|
380
|
+
#
|
|
381
|
+
# Read off the source rather than off a built analyzer, because the
|
|
382
|
+
# caller needs them before it has anything to build one with: in the
|
|
383
|
+
# whole-array spelling a name the block assigns may be an array outside,
|
|
384
|
+
# and whether to look this one up outside is exactly what this answers.
|
|
385
|
+
# The compatibility spellings count too, so that `CArray.zeros(4)` is
|
|
386
|
+
# refused by the reader that knows why rather than by a broadcast.
|
|
387
|
+
def self.local_array_names (source, node: nil)
|
|
388
|
+
analyzer = allocate
|
|
389
|
+
block = node || analyzer.send(:parse_block, source)
|
|
390
|
+
analyzer.send(:scan_local_array_names, block.body)
|
|
391
|
+
end
|
|
392
|
+
|
|
220
393
|
# `rank` is given only for a block that takes no parameters and writes
|
|
221
394
|
# `out[] = a[] + b[]`. There the rank is a property of the arrays
|
|
222
395
|
# rather than of the block, so it arrives from the caller and the loop
|
|
@@ -245,11 +418,16 @@ class CArray
|
|
|
245
418
|
# number, and is not the same statement.
|
|
246
419
|
def initialize (source, node: nil, array_names: [], c_functions: {}, rank: nil,
|
|
247
420
|
steps: nil, contract: false, result: nil, function: false,
|
|
248
|
-
pointers: {},
|
|
421
|
+
pointers: {}, pointer_lengths: {}, map: false,
|
|
422
|
+
cell_names: [],
|
|
249
423
|
recursion: nil, windows: [], returns: true,
|
|
250
|
-
free_indices: nil
|
|
424
|
+
free_indices: nil, randoms: {}, masked: false,
|
|
425
|
+
declared_parameters: nil)
|
|
251
426
|
@source = source
|
|
252
427
|
@node = node
|
|
428
|
+
# `jit_call`'s: the parameter names its declaration gave, standing
|
|
429
|
+
# where a block's own would.
|
|
430
|
+
@declared_parameters = declared_parameters
|
|
253
431
|
@array_names = array_names
|
|
254
432
|
# Arrays with one cell and no axis to walk -- a CScalar. There is no
|
|
255
433
|
# index to write for one, which is the whole of what distinguishes it
|
|
@@ -264,6 +442,11 @@ class CArray
|
|
|
264
442
|
# none, because a window has nowhere to write one.
|
|
265
443
|
@windows = windows
|
|
266
444
|
@c_functions = c_functions
|
|
445
|
+
# The generators the block closed over, by the name it reached each
|
|
446
|
+
# one by, and the made-up name of the state array that carries it.
|
|
447
|
+
# A draw is neither a captured function nor an array read, so it is
|
|
448
|
+
# kept apart from both.
|
|
449
|
+
@randoms = randoms
|
|
267
450
|
@function = function
|
|
268
451
|
# A function declared `void` ends in a statement like any other; one
|
|
269
452
|
# that returns ends in the expression it returns. Which it is comes
|
|
@@ -279,6 +462,9 @@ class CArray
|
|
|
279
462
|
# read and write, false to read only (= const), nil for one that
|
|
280
463
|
# points at nothing in particular and so cannot be reached at all.
|
|
281
464
|
@pointers = pointers
|
|
465
|
+
# And how many cells a pointer's declaration said it points at, for
|
|
466
|
+
# the ones whose declarator carried a length: `double v[3]`.
|
|
467
|
+
@pointer_lengths = pointer_lengths
|
|
282
468
|
# What the function being compiled is called, what it takes and what
|
|
283
469
|
# it returns -- so its own body can call it. C puts a declarator's
|
|
284
470
|
# name in scope inside the body it heads, and this is that. Nil for
|
|
@@ -295,9 +481,20 @@ class CArray
|
|
|
295
481
|
reaches[name] = Array.new(rank.to_i) { [0, 0] }
|
|
296
482
|
}
|
|
297
483
|
@uses_undef = false
|
|
484
|
+
# Whether the kernel this body belongs to carries masks, which the
|
|
485
|
+
# caller settled from the operands before anything was read here. A
|
|
486
|
+
# body reads the same either way; what it decides is what a local
|
|
487
|
+
# array is -- cells alone, or cells with a shadow byte beside each of
|
|
488
|
+
# them -- and so how much stack one takes and what may be done with
|
|
489
|
+
# it. Always false for a function body, which has no cell to take a
|
|
490
|
+
# mask from and nowhere to put one.
|
|
491
|
+
@masked = masked
|
|
298
492
|
@calls_for_effect = false
|
|
299
493
|
@outer_names = []
|
|
300
494
|
@inner_names = []
|
|
495
|
+
@inner_sources_seen = []
|
|
496
|
+
@inner_aliases = {}
|
|
497
|
+
@index_sources = {}
|
|
301
498
|
# How many loops the statement being built stands inside. `break`
|
|
302
499
|
# needs a loop to leave and does not care which kind: an inner loop
|
|
303
500
|
# brings an index and a `while` brings none, but both are loops in C
|
|
@@ -306,7 +503,9 @@ class CArray
|
|
|
306
503
|
@inner_names_seen = []
|
|
307
504
|
@array_ranks = {}
|
|
308
505
|
@inner_ranges = {}
|
|
506
|
+
@unbounded_indices = []
|
|
309
507
|
@subscripts = Hash.new { |hash, key| hash[key] = [] }
|
|
508
|
+
@write_subscripts = Hash.new { |hash, key| hash[key] = [] }
|
|
310
509
|
@given_rank = rank
|
|
311
510
|
@steps = steps
|
|
312
511
|
@contract = contract
|
|
@@ -316,13 +515,32 @@ class CArray
|
|
|
316
515
|
@free_names = []
|
|
317
516
|
@index_names = []
|
|
318
517
|
@local_names = []
|
|
518
|
+
# The arrays the body made, by name: [storage type, shape] for the
|
|
519
|
+
# declaration in sight. Kept beside @local_names rather than in it,
|
|
520
|
+
# because the two are different things under one syntax and a name
|
|
521
|
+
# holds one or the other -- which is what lets each say so.
|
|
522
|
+
@local_array_names = []
|
|
523
|
+
@local_arrays = {}
|
|
524
|
+
# One entry per C array the body declares, as
|
|
525
|
+
# [scope, name, storage, shape], so that the room they take together
|
|
526
|
+
# can be counted once the body has been read. Two sibling loops that
|
|
527
|
+
# each declare `w` are one entry: their blocks do not overlap, so
|
|
528
|
+
# neither do their frames.
|
|
529
|
+
@local_array_declarations = []
|
|
530
|
+
@clears_a_local_array = false
|
|
531
|
+
# The locals each scope holds, innermost last: the kernel's block --
|
|
532
|
+
# or a function's -- and one for every inner loop's block around the
|
|
533
|
+
# statement being built. `while` and `if` make none, as in Ruby.
|
|
534
|
+
@scopes = [[]]
|
|
319
535
|
@assigned_names = []
|
|
320
536
|
@scalar_names = []
|
|
321
537
|
@c_function_names = []
|
|
322
538
|
@parameter_names = []
|
|
539
|
+
@assigned_parameters = []
|
|
323
540
|
@pointer_names = []
|
|
324
541
|
@address_arrays = []
|
|
325
542
|
@address_parameters = {}
|
|
543
|
+
@random_names = []
|
|
326
544
|
@read_offsets = Hash.new { |hash, key| hash[key] = [] }
|
|
327
545
|
@written_arrays = []
|
|
328
546
|
analyze
|
|
@@ -377,18 +595,29 @@ class CArray
|
|
|
377
595
|
end
|
|
378
596
|
|
|
379
597
|
# A write is addressed the way a read is: every axis either walks with an
|
|
380
|
-
#
|
|
381
|
-
# before the first
|
|
598
|
+
# index the kernel is running -- an outer one or an inner one, at
|
|
599
|
+
# whatever offset -- or is pinned at a position known before the first
|
|
600
|
+
# cell.
|
|
382
601
|
#
|
|
383
602
|
# Two iterations may land on the same cell -- `box[0]` puts every one of
|
|
384
603
|
# them there -- and that is not an ambiguity: the extent states the
|
|
385
604
|
# order, so what the array holds afterwards is what the same Ruby loop
|
|
386
605
|
# would leave in it. An inner loop already rests on exactly that, an
|
|
387
606
|
# accumulator being one cell written once per pass.
|
|
607
|
+
#
|
|
608
|
+
# An inner index is in that position and not a weaker one: its loop
|
|
609
|
+
# states its extent the way the kernel's own extents do, so the cell it
|
|
610
|
+
# reaches is as knowable as `i`'s and is bounds-checked with it. What
|
|
611
|
+
# the write is for is a row of workspace per outer cell -- filling one
|
|
612
|
+
# and walking back down it is the shape a small dense solve has, and it
|
|
613
|
+
# was the reason to add an axis rather than split a body into kernels
|
|
614
|
+
# that each pay a call. It was refused until it was noticed that
|
|
615
|
+
# `kk = k + 0` on the way in compiled it anyway, as a scatter: the rule
|
|
616
|
+
# was stopping the spelling rather than the thing.
|
|
388
617
|
def walking_subscripts? (subscripts)
|
|
389
618
|
subscripts.all? { |index, offset|
|
|
390
619
|
if index
|
|
391
|
-
|
|
620
|
+
index_identifier_in_scope?(index)
|
|
392
621
|
else
|
|
393
622
|
!offset.is_a?(Node) || Analyzer.fixed_subscript?(offset)
|
|
394
623
|
end
|
|
@@ -452,9 +681,89 @@ class CArray
|
|
|
452
681
|
node.is_a?(Prism::CallNode) && node.receiver.nil? && node.name == :raise
|
|
453
682
|
end
|
|
454
683
|
|
|
684
|
+
def scan_address_array_names (node, c_functions)
|
|
685
|
+
# Two passes rather than one, because the arguments of a call sit
|
|
686
|
+
# under an ArgumentsNode: pruning them out of a single walk would
|
|
687
|
+
# mean matching a grandchild, and the set is the clearer statement.
|
|
688
|
+
addressed = {}.compare_by_identity
|
|
689
|
+
handed = []
|
|
690
|
+
collect_address_arguments(node, c_functions, handed, addressed)
|
|
691
|
+
return [] if handed.empty?
|
|
692
|
+
as_cells = []
|
|
693
|
+
collect_cell_names(node, addressed, as_cells)
|
|
694
|
+
(handed.uniq - as_cells - scan_local_array_names(node))
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
# The names standing bare where a declaration wants a pointer at
|
|
698
|
+
# numbers, and the argument nodes they stand in.
|
|
699
|
+
def collect_address_arguments (node, c_functions, handed, addressed)
|
|
700
|
+
return unless node
|
|
701
|
+
return if raise_call?(node)
|
|
702
|
+
if node.is_a?(Prism::CallNode) &&
|
|
703
|
+
C_FUNCTION_CALL_NAMES.include?(node.name) &&
|
|
704
|
+
(function = captured_name(node.receiver)) &&
|
|
705
|
+
c_functions.key?(function)
|
|
706
|
+
parameters = c_functions.fetch(function).parameters
|
|
707
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
708
|
+
arguments.each_with_index do |argument, position|
|
|
709
|
+
next unless parameters[position]&.indexable?
|
|
710
|
+
given = captured_name(argument)
|
|
711
|
+
next unless given
|
|
712
|
+
handed << given
|
|
713
|
+
addressed[argument] = true
|
|
714
|
+
end
|
|
715
|
+
end
|
|
716
|
+
node.compact_child_nodes.each { |child|
|
|
717
|
+
collect_address_arguments(child, c_functions, handed, addressed)
|
|
718
|
+
}
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
# Every other way a name is reached: subscripted, or bare where a bare
|
|
722
|
+
# name is a cell. One of these takes a name back out of the answer.
|
|
723
|
+
def collect_cell_names (node, addressed, as_cells)
|
|
724
|
+
return unless node
|
|
725
|
+
return if raise_call?(node)
|
|
726
|
+
return if addressed[node]
|
|
727
|
+
if node.is_a?(Prism::CallNode) && node.name == :[] &&
|
|
728
|
+
(subscripted = captured_name(node.receiver))
|
|
729
|
+
as_cells << subscripted
|
|
730
|
+
end
|
|
731
|
+
if (bare = captured_name(node)) &&
|
|
732
|
+
!(node.is_a?(Prism::CallNode) &&
|
|
733
|
+
C_FUNCTION_CALL_NAMES.include?(node.name))
|
|
734
|
+
as_cells << bare
|
|
735
|
+
end
|
|
736
|
+
node.compact_child_nodes.each { |child|
|
|
737
|
+
collect_cell_names(child, addressed, as_cells)
|
|
738
|
+
}
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
def scan_local_array_names (node)
|
|
742
|
+
return [] unless node
|
|
743
|
+
names = []
|
|
744
|
+
if node.is_a?(Prism::LocalVariableWriteNode) &&
|
|
745
|
+
self.class.local_array_constructor?(node.value)
|
|
746
|
+
names << node.name
|
|
747
|
+
end
|
|
748
|
+
node.compact_child_nodes.each { |child|
|
|
749
|
+
names.concat(scan_local_array_names(child))
|
|
750
|
+
}
|
|
751
|
+
names.uniq
|
|
752
|
+
end
|
|
753
|
+
|
|
455
754
|
def names_constants (node)
|
|
456
755
|
return [] unless node
|
|
457
756
|
return [] if raise_call?(node)
|
|
757
|
+
# The receiver of a constructor is part of a spelling rather than a
|
|
758
|
+
# name the block reached outside for: `CArray.double(9)` says what to
|
|
759
|
+
# declare, and the class in front of it is not a value to look up.
|
|
760
|
+
# Left in, it is looked up, found to be a Class, and refused for
|
|
761
|
+
# being one -- which says nothing about the line that was written.
|
|
762
|
+
if self.class.local_array_constructor?(node)
|
|
763
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
764
|
+
return arguments.flat_map { |argument| names_constants(argument) } +
|
|
765
|
+
names_constants(node.block)
|
|
766
|
+
end
|
|
458
767
|
# `Foo::TABLE` names one thing, and `Foo` on its own names none of
|
|
459
768
|
# it, so a path is read whole and not descended into.
|
|
460
769
|
return [node.slice.to_sym] if node.is_a?(Prism::ConstantPathNode)
|
|
@@ -501,6 +810,15 @@ class CArray
|
|
|
501
810
|
raise Unsupported.new("kernel body is empty")
|
|
502
811
|
end
|
|
503
812
|
@assigned_names = collect_assigned_names(block.body)
|
|
813
|
+
read_parameter_assignments(block.body) if @function
|
|
814
|
+
# A parameter the body assigns is a local from the top of the body
|
|
815
|
+
# on, starting at the value the call passed. Bound here, before any
|
|
816
|
+
# statement is built, so every read of it -- the first included --
|
|
817
|
+
# is a read of that local.
|
|
818
|
+
entry = @assigned_parameters.map { |name|
|
|
819
|
+
@scalar_names << name unless @scalar_names.include?(name)
|
|
820
|
+
assign_local(name, CaptureRead.new(name), nil)
|
|
821
|
+
}
|
|
504
822
|
|
|
505
823
|
# A contraction may end in a bare expression rather than an
|
|
506
824
|
# assignment; that is the form that allocates its result and returns.
|
|
@@ -509,19 +827,30 @@ class CArray
|
|
|
509
827
|
# A block that ends in an assignment still has that assignment's
|
|
510
828
|
# value, which is what Ruby says it has -- so jit_map keeps the
|
|
511
829
|
# statement and reads the cell back for the value.
|
|
512
|
-
map_assignment = @map && last.is_a?(Prism::LocalVariableWriteNode)
|
|
830
|
+
map_assignment = @map && (last.is_a?(Prism::LocalVariableWriteNode) ||
|
|
831
|
+
last.is_a?(Prism::LocalVariableOperatorWriteNode))
|
|
513
832
|
returns_value = (@function && @returns) || (@map && !map_assignment) ||
|
|
514
833
|
(@contract &&
|
|
515
834
|
!(last.is_a?(Prism::CallNode) && last.name == :[]=))
|
|
516
835
|
built = statements[0..-2].map { |node| build_statement(node) }
|
|
517
836
|
if map_assignment
|
|
518
837
|
built << build_statement(last)
|
|
838
|
+
# `w = CArray.double(4)` as the last line: the block's value in Ruby
|
|
839
|
+
# is the array, and a cell of the result has nowhere to put one.
|
|
840
|
+
if built.last.is_a?(LocalArrayDeclaration)
|
|
841
|
+
raise Unsupported.new(
|
|
842
|
+
"this block ends by making `#{built.last.name}`, so its value " \
|
|
843
|
+
"is an array and there is no cell for one to go in; end with " \
|
|
844
|
+
"the number you want -- a cell of it, or `sum(#{built.last.name})` " \
|
|
845
|
+
"and its kin",
|
|
846
|
+
last.location)
|
|
847
|
+
end
|
|
519
848
|
@map_value = build_name_read(last.name, last.location)
|
|
520
849
|
else
|
|
521
850
|
built << (returns_value ? build(last) : build_statement(last))
|
|
522
851
|
@map_value = built.last if @map
|
|
523
852
|
end
|
|
524
|
-
@body = KernelBody.new(built)
|
|
853
|
+
@body = KernelBody.new(entry + built)
|
|
525
854
|
|
|
526
855
|
return if @contract == :probe || @map == :probe
|
|
527
856
|
return if @function
|
|
@@ -544,22 +873,52 @@ class CArray
|
|
|
544
873
|
end
|
|
545
874
|
end
|
|
546
875
|
|
|
547
|
-
# An array the kernel writes is written once per outer cell
|
|
548
|
-
#
|
|
549
|
-
#
|
|
876
|
+
# An array the kernel writes at `a[i]` is written once per outer cell,
|
|
877
|
+
# and reading it at `a[j]` for an inner `j` would reach cells other
|
|
878
|
+
# outer iterations own -- an order no extent states.
|
|
879
|
+
#
|
|
880
|
+
# What decides that is the axis the outer indices pick the cell by, not
|
|
881
|
+
# the array. `work[i, k] = ...` gives outer cell i a row of its own,
|
|
882
|
+
# and `work[i, k]`, `work[i, j]` or `work[i, position]` are that row
|
|
883
|
+
# being read back inside the same cell, in the order the body states.
|
|
884
|
+
# So a read carrying an inner index has to address every axis some
|
|
885
|
+
# write walks with an outer index with that same index -- at whatever
|
|
886
|
+
# offset, `work[i-1, k]` being the row before this one and a recurrence
|
|
887
|
+
# like any other displaced read. Where it does, the inner index is
|
|
888
|
+
# roaming inside the cell the outer ones already picked, and whatever
|
|
889
|
+
# the writes do along that axis they do there too.
|
|
550
890
|
def verify_written_arrays_are_not_read_through_inner_indices
|
|
551
891
|
@written_arrays.each do |array|
|
|
892
|
+
writes = @write_subscripts[array]
|
|
552
893
|
@subscripts[array].each do |per_axis|
|
|
553
|
-
per_axis.
|
|
554
|
-
|
|
894
|
+
inner = per_axis.map(&:first).find { |index|
|
|
895
|
+
@inner_names_seen.include?(index)
|
|
896
|
+
}
|
|
897
|
+
spelled = @index_sources.fetch(inner, inner) if inner
|
|
898
|
+
next unless inner
|
|
899
|
+
per_axis.each_with_index do |(index, _), axis|
|
|
900
|
+
name = outer_name_written(writes, axis)
|
|
901
|
+
next if name.nil? || name == index
|
|
555
902
|
raise Unsupported.new(
|
|
556
|
-
"`#{array}` is written
|
|
557
|
-
"read through the inner index
|
|
903
|
+
"`#{array}` is written on axis #{axis} through `#{name}`, " \
|
|
904
|
+
"so it cannot also be read through the inner index " \
|
|
905
|
+
"`#{spelled}`: a read that carries one addresses axis " \
|
|
906
|
+
"#{axis} through `#{name}` too, or it reaches cells another " \
|
|
907
|
+
"outer iteration owns")
|
|
558
908
|
end
|
|
559
909
|
end
|
|
560
910
|
end
|
|
561
911
|
end
|
|
562
912
|
|
|
913
|
+
# The outer index the writes walk one axis with, where they agree on
|
|
914
|
+
# one. Writes that pin the axis or work its position out say nothing
|
|
915
|
+
# about which cell is whose, and so name nothing here.
|
|
916
|
+
def outer_name_written (writes, axis)
|
|
917
|
+
names = writes.map { |written| written[axis]&.first }
|
|
918
|
+
.select { |name| @outer_names.include?(name) }.uniq
|
|
919
|
+
names.size == 1 ? names.first : nil
|
|
920
|
+
end
|
|
921
|
+
|
|
563
922
|
# Turns `c[i,j] = a[i,k] * b[k,j]` into the loops it stands for.
|
|
564
923
|
#
|
|
565
924
|
# Which indices are summed is not the assignment's business: a repeated
|
|
@@ -593,6 +952,23 @@ class CArray
|
|
|
593
952
|
"array of your own")
|
|
594
953
|
end
|
|
595
954
|
|
|
955
|
+
# And one expression is all of it: a local assigned before the summand
|
|
956
|
+
# is a run of statements, which a contraction has no room for -- the
|
|
957
|
+
# same reason it takes no local array. Said here, because what said
|
|
958
|
+
# it before was the type assignment, which walks the accumulator's
|
|
959
|
+
# zero -- taken from the summand, above the loop the local is
|
|
960
|
+
# assigned in -- and reported the local as read before it is assigned.
|
|
961
|
+
leading = statements[0..-2].grep(Assignment).map(&:name)
|
|
962
|
+
unless leading.empty?
|
|
963
|
+
names = leading.uniq.map { |name| "`#{name}`" }.join(" and ")
|
|
964
|
+
raise Unsupported.new(
|
|
965
|
+
"#{names} #{leading.uniq.size == 1 ? 'is' : 'are'} assigned " \
|
|
966
|
+
"before the summand, and a contraction is one expression: there " \
|
|
967
|
+
"is no run of statements to assign in. Write the value into the " \
|
|
968
|
+
"expression, or write the loop with jit_for",
|
|
969
|
+
statements.first.location)
|
|
970
|
+
end
|
|
971
|
+
|
|
596
972
|
write = writes.first
|
|
597
973
|
unless write
|
|
598
974
|
# The returned form: the free indices, in the order the block named
|
|
@@ -664,6 +1040,7 @@ class CArray
|
|
|
664
1040
|
def classify_indices (statements, write)
|
|
665
1041
|
counts = Hash.new(0)
|
|
666
1042
|
subscripts_of(statements, write).each { |index, _| counts[index] += 1 }
|
|
1043
|
+
refuse_a_gathered_index(statements, write)
|
|
667
1044
|
|
|
668
1045
|
missing = @index_names - counts.keys
|
|
669
1046
|
unless missing.empty?
|
|
@@ -671,25 +1048,84 @@ class CArray
|
|
|
671
1048
|
"#{missing.map { |name| "`#{name}`" }.join(', ')} " \
|
|
672
1049
|
"#{missing.size == 1 ? 'names no axis' : 'name no axis'} here")
|
|
673
1050
|
end
|
|
674
|
-
# Naming the result's axes says which indices are free
|
|
675
|
-
#
|
|
676
|
-
# a named index is free however often it
|
|
677
|
-
# point number does, and `square[a,a]` is
|
|
678
|
-
# trace --
|
|
679
|
-
#
|
|
680
|
-
#
|
|
1051
|
+
# Naming the result's axes says which indices are free, and says it
|
|
1052
|
+
# of all of them: what is not named is summed, at however few
|
|
1053
|
+
# positions it sits. So a named index is free however often it
|
|
1054
|
+
# appears -- twice is what a point number does, and `square[a,a]` is
|
|
1055
|
+
# the diagonal rather than a trace -- and a parameter is summed
|
|
1056
|
+
# whether it repeats or not, which is what lets `"ik->i"` be written.
|
|
1057
|
+
#
|
|
1058
|
+
# Where nothing is named the convention decides instead, and there a
|
|
1059
|
+
# repetition is what makes a sum: an index at one position is free,
|
|
1060
|
+
# because a list that would have said otherwise was not given.
|
|
681
1061
|
if @free_indices
|
|
682
|
-
|
|
683
|
-
alone = parameters.select { |name| counts[name] == 1 }
|
|
684
|
-
unless alone.empty?
|
|
685
|
-
raise Unsupported.new(describe_a_lone_parameter(alone))
|
|
686
|
-
end
|
|
687
|
-
return [@free_indices, parameters]
|
|
1062
|
+
return [@free_indices, @index_names - @free_indices]
|
|
688
1063
|
end
|
|
689
1064
|
[@index_names.select { |name| counts[name] == 1 },
|
|
690
1065
|
@index_names.select { |name| counts[name] > 1 }]
|
|
691
1066
|
end
|
|
692
1067
|
|
|
1068
|
+
# An index inside a computed subscript -- `m[i, idx[k]]` -- is read at a
|
|
1069
|
+
# position, but not at a position of the array it stands in: it
|
|
1070
|
+
# addresses an axis of `idx`. A contraction is a count of positions,
|
|
1071
|
+
# and this one cannot be counted either way. Counting it makes
|
|
1072
|
+
# `m[i, idx[k]] * v[k]` a sum over k, which is what the notation says,
|
|
1073
|
+
# and makes `a[i, k] * w[idx[i]]` a sum over i, which is not what
|
|
1074
|
+
# anybody writing a gather means. Not counting it leaves the first one
|
|
1075
|
+
# a free index and the answer an outer product -- a whole matrix where
|
|
1076
|
+
# the reader asked for a vector, quietly.
|
|
1077
|
+
#
|
|
1078
|
+
# So it is refused, and the loop it means is written with jit_for,
|
|
1079
|
+
# where the gather is the ordinary computed subscript it already is.
|
|
1080
|
+
def refuse_a_gathered_index (statements, write)
|
|
1081
|
+
summand = write.is_a?(ElementWrite) ? write.expression : write
|
|
1082
|
+
found = []
|
|
1083
|
+
# Inside a gathering subscript every index is one of these, however
|
|
1084
|
+
# deep: `a[idx[jdx[k]]]` gathers twice and k is no more a position of
|
|
1085
|
+
# `a` for it.
|
|
1086
|
+
inside = lambda do |node|
|
|
1087
|
+
return unless node.is_a?(Node)
|
|
1088
|
+
subscript_indices(node).each do |index|
|
|
1089
|
+
found << index if @index_names.include?(index)
|
|
1090
|
+
end
|
|
1091
|
+
gathered_subscripts(node).each { |offset| inside.call(offset) }
|
|
1092
|
+
node.children.each { |child| inside.call(child) }
|
|
1093
|
+
end
|
|
1094
|
+
walk = lambda do |node|
|
|
1095
|
+
return unless node.is_a?(Node)
|
|
1096
|
+
gathered_subscripts(node).each { |offset| inside.call(offset) }
|
|
1097
|
+
node.children.each { |child| walk.call(child) }
|
|
1098
|
+
end
|
|
1099
|
+
(statements[0..-2] + [summand]).each { |statement| walk.call(statement) }
|
|
1100
|
+
return if found.empty?
|
|
1101
|
+
names = found.uniq.map { |name| "`#{name}`" }.join(" and ")
|
|
1102
|
+
raise Unsupported.new(
|
|
1103
|
+
"#{names} #{found.uniq.size == 1 ? 'stands' : 'stand'} inside a " \
|
|
1104
|
+
"subscript the kernel works out, where a contraction cannot count " \
|
|
1105
|
+
"it: an index there addresses an axis of the array doing the " \
|
|
1106
|
+
"gathering, not of the array being read, so whether it is summed " \
|
|
1107
|
+
"or free is not something the notation says. Write the loop with " \
|
|
1108
|
+
"jit_for",
|
|
1109
|
+
summand.location)
|
|
1110
|
+
end
|
|
1111
|
+
|
|
1112
|
+
# The indices a read names directly, as the symbols a subscript pair
|
|
1113
|
+
# carries rather than as nodes.
|
|
1114
|
+
def subscript_indices (node)
|
|
1115
|
+
return [] unless node.is_a?(ElementRead) || node.is_a?(MaskTest)
|
|
1116
|
+
node.subscripts.filter_map { |index, _| index }
|
|
1117
|
+
end
|
|
1118
|
+
|
|
1119
|
+
# The subscript expressions a read works out, which are the ones an
|
|
1120
|
+
# index can hide in. A plain `a[i]` or `a[i-1]` carries its index in
|
|
1121
|
+
# the pair itself and has none of these.
|
|
1122
|
+
def gathered_subscripts (node)
|
|
1123
|
+
return [] unless node.is_a?(ElementRead) || node.is_a?(MaskTest)
|
|
1124
|
+
node.subscripts.filter_map { |index, offset|
|
|
1125
|
+
offset if index.nil? && offset.is_a?(Node)
|
|
1126
|
+
}
|
|
1127
|
+
end
|
|
1128
|
+
|
|
693
1129
|
# Every subscript on the right-hand side: the summand, and whatever the
|
|
694
1130
|
# locals before it read.
|
|
695
1131
|
def subscripts_of (statements, write)
|
|
@@ -706,22 +1142,6 @@ class CArray
|
|
|
706
1142
|
collected.reject { |index, _| index.nil? }
|
|
707
1143
|
end
|
|
708
1144
|
|
|
709
|
-
# A parameter at one position only. Nothing there stands in for a
|
|
710
|
-
# sigma, so summing it would be the argument list quietly meaning more
|
|
711
|
-
# than it says -- the same reason the convention refuses it.
|
|
712
|
-
def describe_a_lone_parameter (alone)
|
|
713
|
-
listed = alone.map { |name| "`#{name}`" }.join(", ")
|
|
714
|
-
named = (@free_indices + alone).map { |name| ":#{name}" }.join(", ")
|
|
715
|
-
"#{listed} #{alone.size == 1 ? 'appears' : 'appear'} once, so " \
|
|
716
|
-
"#{alone.size == 1 ? 'it is' : 'they are'} free rather than summed. " \
|
|
717
|
-
"A contraction sums the indices that repeat; name " \
|
|
718
|
-
"#{alone.size == 1 ? 'it' : 'them'} as " \
|
|
719
|
-
"#{alone.size == 1 ? 'an axis' : 'axes'} of the result " \
|
|
720
|
-
"(`CArray.jit_contract(#{named})`) to keep " \
|
|
721
|
-
"#{alone.size == 1 ? 'it' : 'them'}, or use sum(axis:) to sum along " \
|
|
722
|
-
"the axis"
|
|
723
|
-
end
|
|
724
|
-
|
|
725
1145
|
def describe_index_mismatch (written, free, summed)
|
|
726
1146
|
summed_on_left = written & summed
|
|
727
1147
|
unless summed_on_left.empty?
|
|
@@ -729,6 +1149,16 @@ class CArray
|
|
|
729
1149
|
# the result's axes, and the one that repeats is only the reason
|
|
730
1150
|
# the convention could not see it.
|
|
731
1151
|
named = written.map { |name| ":#{name}" }.join(", ")
|
|
1152
|
+
if @free_indices
|
|
1153
|
+
# A list was given, so what is summed is what the list left out.
|
|
1154
|
+
# An axis of the left-hand side that is not in it is the list
|
|
1155
|
+
# falling short of the result, not the index being a sum.
|
|
1156
|
+
listed = summed_on_left.map { |name| "`#{name}`" }.join(", ")
|
|
1157
|
+
return "#{listed} #{summed_on_left.size == 1 ? 'is an axis' : 'are axes'} " \
|
|
1158
|
+
"of the left-hand side and #{summed_on_left.size == 1 ? 'was' : 'were'} " \
|
|
1159
|
+
"not named. Naming the result's axes names all of them, and " \
|
|
1160
|
+
"what is left out is summed: `CArray.jit_contract(#{named})`"
|
|
1161
|
+
end
|
|
732
1162
|
return "#{summed_on_left.map { |name| "`#{name}`" }.join(', ')} " \
|
|
733
1163
|
"#{summed_on_left.size == 1 ? 'is repeated' : 'are repeated'} " \
|
|
734
1164
|
"on the right, so #{summed_on_left.size == 1 ? 'it is' : 'they are'} " \
|
|
@@ -768,6 +1198,19 @@ class CArray
|
|
|
768
1198
|
# block's locals, and the indices -- an index called `contraction` would
|
|
769
1199
|
# have shared the identifier with the accumulator and the sum would have
|
|
770
1200
|
# come out zero, with nothing said.
|
|
1201
|
+
# The identifier an inner index gets in the generated C: its own name
|
|
1202
|
+
# the first time it is written, and a numbered one after that. Every
|
|
1203
|
+
# name the block has said is avoided, so nothing collides with a local
|
|
1204
|
+
# or another index.
|
|
1205
|
+
def free_index_name (index)
|
|
1206
|
+
return index unless @inner_sources_seen.include?(index)
|
|
1207
|
+
taken = @local_names + @index_names + @outer_names + @inner_names +
|
|
1208
|
+
@inner_names_seen + @contracted_names
|
|
1209
|
+
suffix = 2
|
|
1210
|
+
suffix += 1 while taken.include?(:"#{index}__#{suffix}")
|
|
1211
|
+
:"#{index}__#{suffix}"
|
|
1212
|
+
end
|
|
1213
|
+
|
|
771
1214
|
def free_local_name
|
|
772
1215
|
taken = @local_names + @index_names + @outer_names + @inner_names +
|
|
773
1216
|
@contracted_names
|
|
@@ -785,16 +1228,72 @@ class CArray
|
|
|
785
1228
|
extract_block(result.value)
|
|
786
1229
|
end
|
|
787
1230
|
|
|
1231
|
+
# Every spelling that binds a local: `x = e`, the operator forms, which
|
|
1232
|
+
# read the name and write it back, and a name on the left of a parallel
|
|
1233
|
+
# assignment. A name the block assigns is not a name it captures, and
|
|
1234
|
+
# one that is an array outside is the array the whole-array spelling
|
|
1235
|
+
# writes -- so an operator form left out here would have `out += a`
|
|
1236
|
+
# reaching for a capture that is not there.
|
|
1237
|
+
ASSIGNMENT_NODES = [Prism::LocalVariableWriteNode,
|
|
1238
|
+
Prism::LocalVariableOperatorWriteNode,
|
|
1239
|
+
Prism::LocalVariableOrWriteNode,
|
|
1240
|
+
Prism::LocalVariableAndWriteNode,
|
|
1241
|
+
Prism::LocalVariableTargetNode].freeze
|
|
1242
|
+
|
|
788
1243
|
def collect_assigned_names (node)
|
|
789
1244
|
return [] unless node
|
|
790
1245
|
names = []
|
|
791
|
-
names << node.name if node.is_a?(
|
|
1246
|
+
names << node.name if ASSIGNMENT_NODES.any? { |kind| node.is_a?(kind) }
|
|
792
1247
|
node.compact_child_nodes.each do |child|
|
|
793
1248
|
names.concat(collect_assigned_names(child))
|
|
794
1249
|
end
|
|
795
1250
|
names.uniq
|
|
796
1251
|
end
|
|
797
1252
|
|
|
1253
|
+
# A parameter the body assigns. Left alone, the assignment was taken
|
|
1254
|
+
# for a new local of the same name -- declared a second time in the C,
|
|
1255
|
+
# which the C compiler refused, or read before it was assigned, which
|
|
1256
|
+
# is not what it is.
|
|
1257
|
+
#
|
|
1258
|
+
# In a `jit_function` body it is what C makes of it: the parameter is
|
|
1259
|
+
# the function's own copy of what it was called with, and a body may
|
|
1260
|
+
# count it down or replace it. So a number is kept, and bound as a
|
|
1261
|
+
# local at the top of the body (see #analyze). A pointer is not: the
|
|
1262
|
+
# name is the array the body indexes, and a second array under it is
|
|
1263
|
+
# nothing the body's arrays can be.
|
|
1264
|
+
#
|
|
1265
|
+
# For `jit_call` it is the leak in the block reading as a closure: the
|
|
1266
|
+
# name is a local of the method around the call, and assigning it
|
|
1267
|
+
# looks like writing that local, which a value passed by value cannot
|
|
1268
|
+
# do. An array parameter does come back, through its cells, so the
|
|
1269
|
+
# two look alike and only one of them works.
|
|
1270
|
+
def read_parameter_assignments (node)
|
|
1271
|
+
return unless node
|
|
1272
|
+
if ASSIGNMENT_NODES.any? { |kind| node.is_a?(kind) } &&
|
|
1273
|
+
@parameter_names.include?(node.name)
|
|
1274
|
+
name = node.name
|
|
1275
|
+
if @declared_parameters
|
|
1276
|
+
raise Unsupported.new(
|
|
1277
|
+
"`#{name}` is a parameter of this `jit_call`, so assigning it " \
|
|
1278
|
+
"looks like writing the local of that name -- and a parameter " \
|
|
1279
|
+
"is passed by value, so nothing written here would reach it. " \
|
|
1280
|
+
"An array's cells do come back, as in `#{name}[0] = ...`; for " \
|
|
1281
|
+
"a number, give the body a local of its own name, or pass a " \
|
|
1282
|
+
"one-cell array and write its cell",
|
|
1283
|
+
node.location)
|
|
1284
|
+
end
|
|
1285
|
+
if @pointers.key?(name)
|
|
1286
|
+
raise Unsupported.new(
|
|
1287
|
+
"`#{name}` is a pointer parameter, and the body's arrays are " \
|
|
1288
|
+
"the ones it was called with; write its cells, as in " \
|
|
1289
|
+
"`#{name}[0] = ...`, rather than the name",
|
|
1290
|
+
node.location)
|
|
1291
|
+
end
|
|
1292
|
+
@assigned_parameters << name unless @assigned_parameters.include?(name)
|
|
1293
|
+
end
|
|
1294
|
+
node.compact_child_nodes.each { |child| read_parameter_assignments(child) }
|
|
1295
|
+
end
|
|
1296
|
+
|
|
798
1297
|
# Accepts `->(i) { ... }`, `proc { |i| ... }` and `lambda { |i| ... }`.
|
|
799
1298
|
def extract_block (program)
|
|
800
1299
|
statements = program.statements.body
|
|
@@ -827,7 +1326,23 @@ class CArray
|
|
|
827
1326
|
end
|
|
828
1327
|
|
|
829
1328
|
if @function
|
|
830
|
-
|
|
1329
|
+
# A body written for `jit_call` takes no parameters: the names are
|
|
1330
|
+
# in the declaration, where they also name the locals the call
|
|
1331
|
+
# reads. Written in both places they could disagree, so the block
|
|
1332
|
+
# is refused for naming any.
|
|
1333
|
+
if @declared_parameters
|
|
1334
|
+
unless requireds.empty?
|
|
1335
|
+
raise Unsupported.new(
|
|
1336
|
+
"this body's parameters are the ones its declaration named " \
|
|
1337
|
+
"-- #{@declared_parameters.map { |n| "`#{n}`" }.join(', ')} " \
|
|
1338
|
+
"-- and the block names #{requireds.map { |p| "`#{p.name}`" }.join(', ')} " \
|
|
1339
|
+
"again. Written in two places they could disagree; leave " \
|
|
1340
|
+
"the block's off")
|
|
1341
|
+
end
|
|
1342
|
+
@parameter_names = @declared_parameters
|
|
1343
|
+
else
|
|
1344
|
+
@parameter_names = requireds.map(&:name)
|
|
1345
|
+
end
|
|
831
1346
|
@index_names = []
|
|
832
1347
|
@outer_names = []
|
|
833
1348
|
return
|
|
@@ -955,31 +1470,34 @@ class CArray
|
|
|
955
1470
|
@uses_undef = true
|
|
956
1471
|
return MaskWrite.new(node.name, node.location)
|
|
957
1472
|
end
|
|
958
|
-
|
|
959
|
-
#
|
|
960
|
-
#
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
return whole_array_write(node.name, expression, node.location)
|
|
1473
|
+
# A constructor is read before the right-hand side is built, the way
|
|
1474
|
+
# `out = UNDEF` above is: what it says is a declaration, not a value
|
|
1475
|
+
# to compute and store.
|
|
1476
|
+
if (made = read_local_array_constructor(node.value))
|
|
1477
|
+
storage, shape, zeroed = made
|
|
1478
|
+
return declare_local_array(node.name, storage, shape, zeroed,
|
|
1479
|
+
node.location, node.depth)
|
|
966
1480
|
end
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1481
|
+
expression = build(node.value)
|
|
1482
|
+
assign_local(node.name, expression, node.location, node.depth)
|
|
1483
|
+
when Prism::LocalVariableOperatorWriteNode
|
|
1484
|
+
build_operator_assignment(node)
|
|
1485
|
+
when Prism::MultiWriteNode
|
|
1486
|
+
build_parallel_assignment(node)
|
|
1487
|
+
when Prism::IndexOperatorWriteNode
|
|
1488
|
+
build_operator_element_write(node)
|
|
1489
|
+
when Prism::LocalVariableOrWriteNode, Prism::LocalVariableAndWriteNode,
|
|
1490
|
+
Prism::IndexOrWriteNode, Prism::IndexAndWriteNode
|
|
1491
|
+
raise Unsupported.new(
|
|
1492
|
+
"`#{node.is_a?(Prism::LocalVariableOrWriteNode) ||
|
|
1493
|
+
node.is_a?(Prism::IndexOrWriteNode) ? '||=' : '&&='}` asks " \
|
|
1494
|
+
"whether the value is already nil or false, which a number in a " \
|
|
1495
|
+
"kernel never is; write the assignment out",
|
|
1496
|
+
node.location)
|
|
982
1497
|
when Prism::CallNode
|
|
1498
|
+
if (intrinsic = build_intrinsic_statement(node))
|
|
1499
|
+
return intrinsic
|
|
1500
|
+
end
|
|
983
1501
|
if node.receiver.nil? && node.name == :printf
|
|
984
1502
|
return build_print(node)
|
|
985
1503
|
end
|
|
@@ -989,7 +1507,8 @@ class CArray
|
|
|
989
1507
|
# pointed -- which is the whole reason C has statements that are
|
|
990
1508
|
# calls. Everything else keeps the refusal below: a computation
|
|
991
1509
|
# standing where a statement stands is a line that does nothing.
|
|
992
|
-
if (call = recursive_call(node) || c_function_call(node)
|
|
1510
|
+
if (call = recursive_call(node) || c_function_call(node) ||
|
|
1511
|
+
random_call(node))
|
|
993
1512
|
@calls_for_effect = true
|
|
994
1513
|
return CallStatement.new(call, node.location)
|
|
995
1514
|
end
|
|
@@ -1003,13 +1522,58 @@ class CArray
|
|
|
1003
1522
|
when Prism::BreakNode
|
|
1004
1523
|
build_loop_jump(node, LoopStop, "break")
|
|
1005
1524
|
else
|
|
1006
|
-
|
|
1007
|
-
"a kernel body holds assignments, `if`, `while`, `(a...b).each` " \
|
|
1008
|
-
"and `n.times` only, got #{node_name(node)}",
|
|
1009
|
-
node.location)
|
|
1525
|
+
refuse_as_a_statement(node, statement_description(node))
|
|
1010
1526
|
end
|
|
1011
1527
|
end
|
|
1012
1528
|
|
|
1529
|
+
# What a body may hold, said in one place: it was said in two, and they
|
|
1530
|
+
# had already come apart -- one of them listing `step` and the other
|
|
1531
|
+
# not.
|
|
1532
|
+
def refuse_as_a_statement (node, description)
|
|
1533
|
+
hint = STATEMENT_HINTS[node.class]
|
|
1534
|
+
raise Unsupported.new(
|
|
1535
|
+
"a kernel body holds assignments, `if`, `while`, `(a...b).each`, " \
|
|
1536
|
+
"`n.times` and `a.step(b, s)` only, got #{description}" +
|
|
1537
|
+
(hint ? " -- #{hint}" : ""),
|
|
1538
|
+
node.location)
|
|
1539
|
+
end
|
|
1540
|
+
|
|
1541
|
+
# A refusal names what was written rather than the node this compiler
|
|
1542
|
+
# read it as: `Prism::UnlessNode` is not a thing anyone typed.
|
|
1543
|
+
STATEMENT_DESCRIPTIONS = {
|
|
1544
|
+
Prism::UnlessNode => "`unless`",
|
|
1545
|
+
Prism::UntilNode => "`until`",
|
|
1546
|
+
Prism::CaseNode => "`case`",
|
|
1547
|
+
Prism::CaseMatchNode => "`case ... in`",
|
|
1548
|
+
Prism::ForNode => "`for`",
|
|
1549
|
+
Prism::ReturnNode => "`return`",
|
|
1550
|
+
Prism::BeginNode => "`begin`",
|
|
1551
|
+
Prism::RescueModifierNode => "`rescue`",
|
|
1552
|
+
Prism::MultiWriteNode => "a parallel assignment",
|
|
1553
|
+
Prism::InstanceVariableWriteNode => "an instance variable",
|
|
1554
|
+
Prism::GlobalVariableWriteNode => "a global variable",
|
|
1555
|
+
Prism::ConstantWriteNode => "an assignment to a constant",
|
|
1556
|
+
Prism::DefNode => "a method definition",
|
|
1557
|
+
}.freeze
|
|
1558
|
+
|
|
1559
|
+
STATEMENT_HINTS = {
|
|
1560
|
+
Prism::UnlessNode => "write it as `if` with the condition negated",
|
|
1561
|
+
Prism::UntilNode => "write it as `while` with the condition negated",
|
|
1562
|
+
Prism::CaseNode => "write the branches out with `if` and `elsif`",
|
|
1563
|
+
Prism::ReturnNode => "a body is one expression, and its value is " \
|
|
1564
|
+
"the last thing in it",
|
|
1565
|
+
# A statement, and a supported one; what it has not got is a value.
|
|
1566
|
+
# Ruby's is the array of what it wrote, and a cell holds a number.
|
|
1567
|
+
Prism::MultiWriteNode =>
|
|
1568
|
+
"its value in Ruby is the array of what it " \
|
|
1569
|
+
"wrote, and a cell holds a number; end with " \
|
|
1570
|
+
"the one you want",
|
|
1571
|
+
}.freeze
|
|
1572
|
+
|
|
1573
|
+
def statement_description (node)
|
|
1574
|
+
STATEMENT_DESCRIPTIONS.fetch(node.class) { node_name(node) }
|
|
1575
|
+
end
|
|
1576
|
+
|
|
1013
1577
|
# `next` skips the rest of this iteration, `break` leaves the loop. Both
|
|
1014
1578
|
# mean in the generated loop what they mean in the Ruby loop it replaces
|
|
1015
1579
|
# -- with one exception: Ruby's `break` in the kernel block is not a
|
|
@@ -1061,7 +1625,7 @@ class CArray
|
|
|
1061
1625
|
"Ruby; write the test at the top",
|
|
1062
1626
|
node.location)
|
|
1063
1627
|
end
|
|
1064
|
-
condition =
|
|
1628
|
+
condition = build_condition(node.predicate)
|
|
1065
1629
|
@loop_depth += 1
|
|
1066
1630
|
statements = statements_of(node.statements).map { |inner|
|
|
1067
1631
|
build_statement(inner)
|
|
@@ -1116,7 +1680,12 @@ class CArray
|
|
|
1116
1680
|
def build_inner_loop (node)
|
|
1117
1681
|
counted = node.name == :times
|
|
1118
1682
|
range = unwrap(node.receiver)
|
|
1119
|
-
|
|
1683
|
+
# Two spellings carry a stride, and they are Ruby's two: a range
|
|
1684
|
+
# steps within the ends it already states, and an integer steps to
|
|
1685
|
+
# one it is given. Which is which is the receiver.
|
|
1686
|
+
stepped = node.name == :step && !range.is_a?(Prism::RangeNode)
|
|
1687
|
+
walked = node.name == :step && range.is_a?(Prism::RangeNode)
|
|
1688
|
+
if !counted && !stepped && !range.is_a?(Prism::RangeNode)
|
|
1120
1689
|
raise Unsupported.new(
|
|
1121
1690
|
"an inner loop runs over a range, as in `(0...n).each { |j| ... }`",
|
|
1122
1691
|
node.location)
|
|
@@ -1132,7 +1701,7 @@ class CArray
|
|
|
1132
1701
|
if index_in_scope?(index)
|
|
1133
1702
|
raise Unsupported.new("`#{index}` is already an index here", node.location)
|
|
1134
1703
|
end
|
|
1135
|
-
unless counted || (range.left && range.right)
|
|
1704
|
+
unless counted || stepped || (range.left && range.right)
|
|
1136
1705
|
raise Unsupported.new("an inner loop needs both ends of its range",
|
|
1137
1706
|
node.location)
|
|
1138
1707
|
end
|
|
@@ -1140,26 +1709,106 @@ class CArray
|
|
|
1140
1709
|
# `n.times` is `(0...n).each`: the count is the exclusive end, and it
|
|
1141
1710
|
# is built from the same vocabulary a range end is, so a literal and a
|
|
1142
1711
|
# captured integer both serve.
|
|
1712
|
+
#
|
|
1713
|
+
# `from.step(to, s)` is the spelling an extent already says a stride
|
|
1714
|
+
# or a downward sweep with, and it means here what it means there --
|
|
1715
|
+
# `to` included, which is Ruby's own reading of it and the opposite of
|
|
1716
|
+
# a `...` range's. It is turned into an exclusive end at once, so
|
|
1717
|
+
# everything below this line counts the way `each` does.
|
|
1718
|
+
step = 1
|
|
1143
1719
|
if counted
|
|
1144
1720
|
from = IntegerLiteral.new(0, node.location)
|
|
1145
1721
|
to = build(node.receiver)
|
|
1722
|
+
elsif stepped
|
|
1723
|
+
from = build(node.receiver)
|
|
1724
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
1725
|
+
unless arguments.size == 2
|
|
1726
|
+
raise Unsupported.new(
|
|
1727
|
+
"an inner loop written with `step` gives it both the last " \
|
|
1728
|
+
"index and the stride, as in `(n-1).step(0, -1) { |k| ... }`",
|
|
1729
|
+
node.location)
|
|
1730
|
+
end
|
|
1731
|
+
step = inner_loop_step(arguments.last)
|
|
1732
|
+
to = build(arguments.first)
|
|
1733
|
+
to = BinaryOperation.new(step.positive? ? :+ : :-, to,
|
|
1734
|
+
IntegerLiteral.new(1, node.location),
|
|
1735
|
+
node.location)
|
|
1146
1736
|
else
|
|
1147
1737
|
from = build(range.left)
|
|
1148
1738
|
to = build(range.right)
|
|
1149
1739
|
to = BinaryOperation.new(:+, to, IntegerLiteral.new(1, node.location),
|
|
1150
1740
|
node.location) unless range.exclude_end?
|
|
1741
|
+
if walked
|
|
1742
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
1743
|
+
unless arguments.size == 1
|
|
1744
|
+
raise Unsupported.new(
|
|
1745
|
+
"a range steps by one stride, as in `(0...n).step(2) " \
|
|
1746
|
+
"{ |j| ... }`", node.location)
|
|
1747
|
+
end
|
|
1748
|
+
step = inner_loop_step(arguments.first)
|
|
1749
|
+
if step.negative?
|
|
1750
|
+
raise Unsupported.new(
|
|
1751
|
+
"a range cannot step backwards, in Ruby either -- count " \
|
|
1752
|
+
"down with `(n-1).step(0, -1) { |j| ... }`, which is the " \
|
|
1753
|
+
"spelling an extent takes too",
|
|
1754
|
+
node.location)
|
|
1755
|
+
end
|
|
1756
|
+
end
|
|
1151
1757
|
end
|
|
1152
1758
|
|
|
1759
|
+
# Two sibling loops may both be written `{ |k| ... }`, and each has
|
|
1760
|
+
# a range of its own -- a forward sweep from 1 and the sweep back
|
|
1761
|
+
# down from the far end are exactly that pair. They are one name in
|
|
1762
|
+
# the block and cannot be one variable here, because the reach of
|
|
1763
|
+
# `a[k-1]` is checked against the loop it sits in. So the second
|
|
1764
|
+
# gets an identifier of its own, and the name in the block goes on
|
|
1765
|
+
# meaning whichever loop it is inside.
|
|
1766
|
+
internal = free_index_name(index)
|
|
1153
1767
|
@inner_names << index
|
|
1154
|
-
@
|
|
1155
|
-
@
|
|
1768
|
+
@inner_sources_seen << index
|
|
1769
|
+
@inner_names_seen << internal
|
|
1770
|
+
@inner_aliases[index] = internal
|
|
1771
|
+
@index_sources[internal] = index
|
|
1772
|
+
@inner_ranges[internal] = [from, to, step]
|
|
1773
|
+
unless Analyzer.boundable_range?(from, @unbounded_indices) &&
|
|
1774
|
+
Analyzer.boundable_range?(to, @unbounded_indices)
|
|
1775
|
+
@unbounded_indices << internal
|
|
1776
|
+
end
|
|
1156
1777
|
@loop_depth += 1
|
|
1778
|
+
@scopes.push([])
|
|
1157
1779
|
statements = statements_of(node.block.body).map { |inner|
|
|
1158
1780
|
build_statement(inner)
|
|
1159
1781
|
}
|
|
1782
|
+
@scopes.pop
|
|
1160
1783
|
@loop_depth -= 1
|
|
1161
1784
|
@inner_names.pop
|
|
1162
|
-
|
|
1785
|
+
@inner_aliases.delete(index)
|
|
1786
|
+
InnerLoop.new(internal, from, to, statements, node.location, step)
|
|
1787
|
+
end
|
|
1788
|
+
|
|
1789
|
+
# The stride is read off the page rather than computed, because it is
|
|
1790
|
+
# what says which way the loop runs and the C has to be written one way
|
|
1791
|
+
# or the other before anything is known. A captured integer would
|
|
1792
|
+
# leave that open.
|
|
1793
|
+
def inner_loop_step (node)
|
|
1794
|
+
value = case node
|
|
1795
|
+
when Prism::IntegerNode then node.value
|
|
1796
|
+
when Prism::CallNode
|
|
1797
|
+
if node.name == :-@ && node.receiver.is_a?(Prism::IntegerNode)
|
|
1798
|
+
-node.receiver.value
|
|
1799
|
+
end
|
|
1800
|
+
end
|
|
1801
|
+
if value.nil?
|
|
1802
|
+
raise Unsupported.new(
|
|
1803
|
+
"an inner loop's stride says which way it runs, so it is an " \
|
|
1804
|
+
"integer literal -- `step(0, -1)` for a downward sweep",
|
|
1805
|
+
node.location)
|
|
1806
|
+
end
|
|
1807
|
+
if value.zero?
|
|
1808
|
+
raise Unsupported.new("an inner loop's stride cannot be zero",
|
|
1809
|
+
node.location)
|
|
1810
|
+
end
|
|
1811
|
+
value
|
|
1163
1812
|
end
|
|
1164
1813
|
|
|
1165
1814
|
def unwrap (node)
|
|
@@ -1180,7 +1829,7 @@ class CArray
|
|
|
1180
1829
|
else
|
|
1181
1830
|
raise Unsupported.new("unsupported `if` continuation", node.location)
|
|
1182
1831
|
end
|
|
1183
|
-
Branch.new(
|
|
1832
|
+
Branch.new(build_condition(node.predicate),
|
|
1184
1833
|
statements_of(node.statements).map { |inner|
|
|
1185
1834
|
build_statement(inner)
|
|
1186
1835
|
},
|
|
@@ -1192,8 +1841,1014 @@ class CArray
|
|
|
1192
1841
|
node ? node.body : []
|
|
1193
1842
|
end
|
|
1194
1843
|
|
|
1844
|
+
# Handing a local array to a C function, with the declaration matched
|
|
1845
|
+
# against what the block wrote.
|
|
1846
|
+
#
|
|
1847
|
+
# The length check is the one that matters, and it is not decoration: a
|
|
1848
|
+
# compiled function does not check a subscript on a pointer parameter
|
|
1849
|
+
# (unchecked by design -- the caller's business, as in C), and this
|
|
1850
|
+
# array is on the stack of the block that made it. A declaration that
|
|
1851
|
+
# reads more cells than the array holds would walk over the frame, and
|
|
1852
|
+
# the return address is in it.
|
|
1853
|
+
def build_local_array_address (name, argument, parameter, c_function)
|
|
1854
|
+
storage, shape = local_array_in_sight(name)
|
|
1855
|
+
# The callee is handed a pointer to the cells, and the shadow beside
|
|
1856
|
+
# them travels in no C declaration there is a spelling for here: a
|
|
1857
|
+
# signature says what it points at, and "these cells, and a byte each
|
|
1858
|
+
# saying whether they are there" is not one of the things it can say.
|
|
1859
|
+
if @masked
|
|
1860
|
+
raise Unsupported.new(
|
|
1861
|
+
"`#{name}` is a local array of this kernel, and the kernel " \
|
|
1862
|
+
"carries masks -- so `#{name}` carries one beside each of its " \
|
|
1863
|
+
"cells, and there is no way to hand that to `#{c_function}`: a " \
|
|
1864
|
+
"signature says what a pointer points at, and a mask travels in " \
|
|
1865
|
+
"no C declaration. Copy the cells the function is to see into a " \
|
|
1866
|
+
"captured array, or answer the mask in the kernel",
|
|
1867
|
+
argument.location)
|
|
1868
|
+
end
|
|
1869
|
+
unless parameter&.pointer
|
|
1870
|
+
raise Unsupported.new(
|
|
1871
|
+
"`#{c_function}` takes #{parameter ? "`#{parameter.text}`" : 'a number'} " \
|
|
1872
|
+
"there, and `#{name}` is an array; pass a cell of it, as in " \
|
|
1873
|
+
"`#{name}[0]`",
|
|
1874
|
+
argument.location)
|
|
1875
|
+
end
|
|
1876
|
+
unless parameter.indexable?
|
|
1877
|
+
raise Unsupported.new(
|
|
1878
|
+
"`#{c_function}` takes `#{parameter.text}` there, which points at " \
|
|
1879
|
+
"nothing in particular, so there is no element type to hold " \
|
|
1880
|
+
"`#{name}` to; declare what it points at",
|
|
1881
|
+
argument.location)
|
|
1882
|
+
end
|
|
1883
|
+
wanted = CDeclaration::DATA_TYPES.fetch(parameter.element.fiddle).to_s
|
|
1884
|
+
unless storage == wanted
|
|
1885
|
+
raise Unsupported.new(
|
|
1886
|
+
"`#{c_function}` takes `#{parameter.text}` there, which is a " \
|
|
1887
|
+
"#{wanted} array, and `#{name}` is #{storage}; make it with " \
|
|
1888
|
+
"`CArray.new(:#{wanted}, [#{shape.first}])` -- an element type is " \
|
|
1889
|
+
"matched exactly here, as it is for a captured array",
|
|
1890
|
+
argument.location)
|
|
1891
|
+
end
|
|
1892
|
+
# The cells over every axis, because that is what the callee is handed:
|
|
1893
|
+
# a local array is packed row-major, so `double[3][4]` is the twelve
|
|
1894
|
+
# cells `const double a[12]` asks for.
|
|
1895
|
+
#
|
|
1896
|
+
# A declaration that names a length can only be held to a shape that
|
|
1897
|
+
# is numbers: the two are matched as the block is read, and a length
|
|
1898
|
+
# the kernel works out is not there yet. A pointer with no length is
|
|
1899
|
+
# the C that says "however many you have", and takes one.
|
|
1900
|
+
unless literal_shape?(shape)
|
|
1901
|
+
if parameter.sized?
|
|
1902
|
+
raise Unsupported.new(
|
|
1903
|
+
"`#{c_function}` takes `#{parameter.text}` there, which reads " \
|
|
1904
|
+
"#{parameter.array} cells, and the shape of `#{name}` is " \
|
|
1905
|
+
"worked out when the kernel runs -- so the two cannot be " \
|
|
1906
|
+
"matched here, and the function checks no subscript of its " \
|
|
1907
|
+
"own. Declare the parameter without a length " \
|
|
1908
|
+
"(`#{parameter.element.text} *`), which promises nothing about " \
|
|
1909
|
+
"how many cells there are, or give `#{name}` a shape written " \
|
|
1910
|
+
"out",
|
|
1911
|
+
argument.location)
|
|
1912
|
+
end
|
|
1913
|
+
return LocalArrayAddress.new(name, storage, shape, argument.location)
|
|
1914
|
+
end
|
|
1915
|
+
cells = shape.inject(1, :*)
|
|
1916
|
+
if parameter.sized? && cells < parameter.array
|
|
1917
|
+
shaped = shape.size == 1 ? "" :
|
|
1918
|
+
" (#{shape.join(' x ')}, packed row-major)"
|
|
1919
|
+
raise Unsupported.new(
|
|
1920
|
+
"`#{c_function}` takes `#{parameter.text}` there, which reads " \
|
|
1921
|
+
"#{parameter.array} cells, and `#{name}` has #{cells}#{shaped}. A " \
|
|
1922
|
+
"local array is on this block's stack and the function does not " \
|
|
1923
|
+
"check a subscript of its own, so the missing cells would be the " \
|
|
1924
|
+
"frame; give `#{name}` at least #{parameter.array}",
|
|
1925
|
+
argument.location)
|
|
1926
|
+
end
|
|
1927
|
+
LocalArrayAddress.new(name, storage, shape, argument.location)
|
|
1928
|
+
end
|
|
1929
|
+
|
|
1930
|
+
# ---- the functions the compiler brings with it ----
|
|
1931
|
+
|
|
1932
|
+
# Written as bare calls, the way `random(rng: r)` is: names of this
|
|
1933
|
+
# compiler's own rather than methods on an array, so that what each one
|
|
1934
|
+
# means is settled here instead of promising CArray's method semantics
|
|
1935
|
+
# -- `axis:`, masks skipped, an identity for an empty array, a CScalar
|
|
1936
|
+
# or a number back, one type promoted to another.
|
|
1937
|
+
#
|
|
1938
|
+
# A bare name with no arguments is a capture (`sum = 0.0` beside
|
|
1939
|
+
# `sum(w)` is a local and reads as one), and a name with a receiver is a
|
|
1940
|
+
# call to whatever holds it (`sum.call(x)`), so neither collides.
|
|
1941
|
+
INTRINSIC_VALUES = [:sum, :min, :max].freeze
|
|
1942
|
+
INTRINSIC_STATEMENTS = [:sort].freeze
|
|
1943
|
+
INTRINSIC_NAMES = (INTRINSIC_VALUES + INTRINSIC_STATEMENTS).freeze
|
|
1944
|
+
|
|
1945
|
+
# `sum(w)` / `min(w)` / `max(w)` in an expression, or nil where this
|
|
1946
|
+
# call is not one of them.
|
|
1947
|
+
def build_intrinsic_call (node)
|
|
1948
|
+
return nil unless intrinsic_spelling?(node)
|
|
1949
|
+
if INTRINSIC_STATEMENTS.include?(node.name)
|
|
1950
|
+
raise Unsupported.new(
|
|
1951
|
+
"`#{node.name}` rearranges the array and has no value to put " \
|
|
1952
|
+
"anywhere; write it on a line of its own, then read the cells " \
|
|
1953
|
+
"you want",
|
|
1954
|
+
node.location)
|
|
1955
|
+
end
|
|
1956
|
+
return nil unless INTRINSIC_VALUES.include?(node.name)
|
|
1957
|
+
name, storage, shape = intrinsic_array(node)
|
|
1958
|
+
IntrinsicCall.new(node.name, name, storage, shape, node.location)
|
|
1959
|
+
end
|
|
1960
|
+
|
|
1961
|
+
# `sort(w)` standing as a statement.
|
|
1962
|
+
def build_intrinsic_statement (node)
|
|
1963
|
+
return nil unless intrinsic_spelling?(node)
|
|
1964
|
+
if INTRINSIC_VALUES.include?(node.name)
|
|
1965
|
+
raise Unsupported.new(
|
|
1966
|
+
"`#{node.name}` works out a value, and standing on a line of its " \
|
|
1967
|
+
"own it puts that value nowhere; assign it, as in " \
|
|
1968
|
+
"`total = #{node.name}(...)`",
|
|
1969
|
+
node.location)
|
|
1970
|
+
end
|
|
1971
|
+
return nil unless INTRINSIC_STATEMENTS.include?(node.name)
|
|
1972
|
+
name, storage, shape = intrinsic_array(node)
|
|
1973
|
+
# Deliberately not `@calls_for_effect`: rearranging an array the body
|
|
1974
|
+
# made leaves nothing outside the kernel, so a kernel whose only work
|
|
1975
|
+
# is a `sort` has still put its work nowhere and is told so.
|
|
1976
|
+
IntrinsicStatement.new(node.name, name, storage, shape, node.location)
|
|
1977
|
+
end
|
|
1978
|
+
|
|
1979
|
+
def intrinsic_spelling? (node)
|
|
1980
|
+
node.is_a?(Prism::CallNode) && node.receiver.nil? && node.block.nil? &&
|
|
1981
|
+
INTRINSIC_NAMES.include?(node.name) && !node.arguments.nil?
|
|
1982
|
+
end
|
|
1983
|
+
|
|
1984
|
+
# The one argument, which is a local array of one axis.
|
|
1985
|
+
def intrinsic_array (node)
|
|
1986
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
1987
|
+
unless arguments.size == 1
|
|
1988
|
+
raise Unsupported.new(
|
|
1989
|
+
"`#{node.name}` takes one local array, and #{arguments.size} " \
|
|
1990
|
+
"#{arguments.size == 1 ? 'argument was' : 'arguments were'} " \
|
|
1991
|
+
"given" +
|
|
1992
|
+
(node.name == :min || node.name == :max ?
|
|
1993
|
+
". For the #{node.name} of two numbers write " \
|
|
1994
|
+
"`x #{node.name == :min ? '<' : '>'} y ? x : y`, and to hold " \
|
|
1995
|
+
"a value between two bounds write `x.clamp(lo, hi)`" : ""),
|
|
1996
|
+
node.location)
|
|
1997
|
+
end
|
|
1998
|
+
argument = arguments.first
|
|
1999
|
+
name = local_array_name(argument)
|
|
2000
|
+
refuse_an_intrinsic_argument(node, argument) unless name
|
|
2001
|
+
# What the four should do with a missing cell is not decided, and
|
|
2002
|
+
# there is more than one defensible answer: skip it, gather it at the
|
|
2003
|
+
# end the way `sort` gathers NaN, count it where a median is counted.
|
|
2004
|
+
# So the array is refused rather than given a meaning here.
|
|
2005
|
+
if @masked
|
|
2006
|
+
raise Unsupported.new(
|
|
2007
|
+
"`#{node.name}(#{name})` is over a local array of this kernel, " \
|
|
2008
|
+
"and the kernel carries masks -- so `#{name}` carries one beside " \
|
|
2009
|
+
"each of its cells, and what `#{node.name}` should do with a " \
|
|
2010
|
+
"missing one is not decided: whether to pass over it, to gather " \
|
|
2011
|
+
"it at the end as `sort` gathers NaN, or to count it where a " \
|
|
2012
|
+
"median counts. Write the loop yourself, deciding it there",
|
|
2013
|
+
node.location)
|
|
2014
|
+
end
|
|
2015
|
+
storage, shape = local_array_in_sight(name)
|
|
2016
|
+
unless shape.size == 1
|
|
2017
|
+
raise Unsupported.new(
|
|
2018
|
+
"`#{name}` has #{shape.size} axes, and `#{node.name}` takes an " \
|
|
2019
|
+
"array of one axis. A sweep over every cell would read well " \
|
|
2020
|
+
"enough for `sum`, and `sort` over one has no obvious meaning at " \
|
|
2021
|
+
"all, so the four keep one rule; walk the axes yourself, or copy " \
|
|
2022
|
+
"the cells you want into an array of one axis",
|
|
2023
|
+
node.location)
|
|
2024
|
+
end
|
|
2025
|
+
[name, storage, shape]
|
|
2026
|
+
end
|
|
2027
|
+
|
|
2028
|
+
# What was passed instead, said in the terms it was written in.
|
|
2029
|
+
def refuse_an_intrinsic_argument (node, argument)
|
|
2030
|
+
passed = captured_name(argument)
|
|
2031
|
+
if passed && @array_names.include?(passed)
|
|
2032
|
+
raise Unsupported.new(
|
|
2033
|
+
"`#{node.name}` takes a local array -- one this block made with " \
|
|
2034
|
+
"`CArray.double(n)` or its kin -- and `#{passed}` is an array " \
|
|
2035
|
+
"the block closed over. Walking all of it at every cell is a " \
|
|
2036
|
+
"pass over the whole array per cell, which is not what the line " \
|
|
2037
|
+
"looks like it costs; `#{passed}.#{node.name}` outside the " \
|
|
2038
|
+
"kernel says what it costs, and a loop written out says it too",
|
|
2039
|
+
node.location)
|
|
2040
|
+
end
|
|
2041
|
+
raise Unsupported.new(
|
|
2042
|
+
"`#{node.name}` takes a local array -- one this block made with " \
|
|
2043
|
+
"`CArray.double(n)` or its kin -- named on its own, and " \
|
|
2044
|
+
"`#{argument.slice}` is not one",
|
|
2045
|
+
node.location)
|
|
2046
|
+
end
|
|
2047
|
+
|
|
2048
|
+
# ---- the arrays a body makes for itself ----
|
|
2049
|
+
|
|
2050
|
+
LOCAL_ARRAY_SUBSCRIPTS = [:[], :[]=].freeze
|
|
2051
|
+
|
|
2052
|
+
# A constructor stands on the right of an assignment and nowhere else.
|
|
2053
|
+
# Everywhere else it is an array standing where a number goes -- in an
|
|
2054
|
+
# argument, under a subscript, as an operand -- and there is no name for
|
|
2055
|
+
# the C array to be declared under.
|
|
2056
|
+
def refuse_a_constructor_in_an_expression (node)
|
|
2057
|
+
return unless node
|
|
2058
|
+
return unless self.class.local_array_constructor?(node)
|
|
2059
|
+
raise Unsupported.new(
|
|
2060
|
+
"`#{node.slice}` makes an array, and a constructor stands on the " \
|
|
2061
|
+
"right of an assignment and nowhere else: write " \
|
|
2062
|
+
"`w = #{node.slice}` on a line of its own, then reach `w` at a " \
|
|
2063
|
+
"subscript",
|
|
2064
|
+
node.location)
|
|
2065
|
+
end
|
|
2066
|
+
|
|
2067
|
+
# `[storage type, shape, zeroed]` for a constructor, or nil where this
|
|
2068
|
+
# call is not one. Nil is the answer for everything else a body may
|
|
2069
|
+
# write, so nothing outside this vocabulary is touched; once the answer
|
|
2070
|
+
# is not nil the call is a constructor, and a spelling that cannot be
|
|
2071
|
+
# taken is refused here rather than further along.
|
|
2072
|
+
def read_local_array_constructor (node)
|
|
2073
|
+
return nil unless self.class.local_array_constructor?(node)
|
|
2074
|
+
refuse_a_local_array_in_this_spelling(node)
|
|
2075
|
+
if node.block
|
|
2076
|
+
raise Unsupported.new(
|
|
2077
|
+
"`#{node.slice}` fills the array from a block, and a local " \
|
|
2078
|
+
"array is either zero-filled or left as it stands; write the " \
|
|
2079
|
+
"fill out as a loop over its cells",
|
|
2080
|
+
node.location)
|
|
2081
|
+
end
|
|
2082
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
2083
|
+
if arguments.any? { |argument| argument.is_a?(Prism::KeywordHashNode) }
|
|
2084
|
+
raise Unsupported.new(
|
|
2085
|
+
"`#{node.slice}` passes a keyword, and a local array is the " \
|
|
2086
|
+
"type and the shape only", node.location)
|
|
2087
|
+
end
|
|
2088
|
+
if node.receiver.is_a?(Prism::ConstantPathNode) &&
|
|
2089
|
+
node.receiver.slice != "::CArray"
|
|
2090
|
+
return refuse_a_type_class_constructor(node, arguments)
|
|
2091
|
+
end
|
|
2092
|
+
case node.name
|
|
2093
|
+
when :new then read_carray_new(node, arguments, true)
|
|
2094
|
+
when :empty then read_carray_empty(node, arguments)
|
|
2095
|
+
else read_type_constructor(node, arguments)
|
|
2096
|
+
end
|
|
2097
|
+
end
|
|
2098
|
+
|
|
2099
|
+
# `CArray.double(9)` and the rest of the singleton methods CArray's own
|
|
2100
|
+
# `Init` defines: the type is the name and the shape is the arguments.
|
|
2101
|
+
def read_type_constructor (node, arguments)
|
|
2102
|
+
if LOCAL_ARRAY_REFUSED_TYPES.include?(node.name)
|
|
2103
|
+
raise Unsupported.new(
|
|
2104
|
+
"`#{node.name}` holds #{LOCAL_ARRAY_REFUSED_TYPES_HOLD.fetch(node.name)} " \
|
|
2105
|
+
"rather than numbers, and a kernel computes with numbers; a " \
|
|
2106
|
+
"local array is one of " \
|
|
2107
|
+
"#{LOCAL_ARRAY_TYPES.keys.take(13).map { |name| "`#{name}`" }.join(', ')}",
|
|
2108
|
+
node.location)
|
|
2109
|
+
end
|
|
2110
|
+
if LOCAL_ARRAY_COMPATIBILITY_NAMES.include?(node.name)
|
|
2111
|
+
return refuse_a_compatibility_constructor(node, arguments, nil)
|
|
2112
|
+
end
|
|
2113
|
+
storage = LOCAL_ARRAY_TYPES.fetch(node.name)
|
|
2114
|
+
if arguments.empty?
|
|
2115
|
+
raise Unsupported.new(
|
|
2116
|
+
"`#{node.slice}` says no shape; a local array is written " \
|
|
2117
|
+
"`CArray.#{node.name}(n)` with the length written out",
|
|
2118
|
+
node.location)
|
|
2119
|
+
end
|
|
2120
|
+
[storage, read_local_array_shape(node, arguments), true]
|
|
2121
|
+
end
|
|
2122
|
+
|
|
2123
|
+
# `CArray.new(:int64, [256])` -- `rb_ca_initialize`, whose
|
|
2124
|
+
# `carray_safe_setup` is what fills the cells with zero.
|
|
2125
|
+
def read_carray_new (node, arguments, zeroed)
|
|
2126
|
+
unless arguments.size == 2
|
|
2127
|
+
raise Unsupported.new(
|
|
2128
|
+
"`CArray.#{node.name}` takes the type and the shape, as in " \
|
|
2129
|
+
"`CArray.#{node.name}(:float64, [9])`", node.location)
|
|
2130
|
+
end
|
|
2131
|
+
[read_local_array_type(node, arguments.first),
|
|
2132
|
+
read_local_array_shape(node, read_shape_arguments(node, arguments.last)),
|
|
2133
|
+
zeroed]
|
|
2134
|
+
end
|
|
2135
|
+
|
|
2136
|
+
# Two spellings share the name. `CArray.empty(:float64, [9])` is
|
|
2137
|
+
# carray's, and stands beside `CArray.new` with the fill left out;
|
|
2138
|
+
# `CArray.empty(9)` is the compatibility layer's NumPy form, where the
|
|
2139
|
+
# type is float64 by convention rather than by anything written down.
|
|
2140
|
+
def read_carray_empty (node, arguments)
|
|
2141
|
+
carray_form = arguments.size == 2 &&
|
|
2142
|
+
arguments.first.is_a?(Prism::SymbolNode) &&
|
|
2143
|
+
arguments.last.is_a?(Prism::ArrayNode)
|
|
2144
|
+
return read_carray_new(node, arguments, false) if carray_form
|
|
2145
|
+
refuse_a_compatibility_constructor(node, arguments, nil)
|
|
2146
|
+
end
|
|
2147
|
+
|
|
2148
|
+
def read_shape_arguments (node, argument)
|
|
2149
|
+
unless argument.is_a?(Prism::ArrayNode)
|
|
2150
|
+
raise Unsupported.new(
|
|
2151
|
+
"the shape `CArray.#{node.name}` takes is an Array, as in " \
|
|
2152
|
+
"`CArray.#{node.name}(:float64, [9])`", node.location)
|
|
2153
|
+
end
|
|
2154
|
+
argument.elements
|
|
2155
|
+
end
|
|
2156
|
+
|
|
2157
|
+
# The type is a Symbol written out. One of CArray's constants would be
|
|
2158
|
+
# a name looked up outside the block, and one kernel serves every value
|
|
2159
|
+
# of a capture -- so a type that arrived that way would not be in the C
|
|
2160
|
+
# at all.
|
|
2161
|
+
def read_local_array_type (node, argument)
|
|
2162
|
+
unless argument.is_a?(Prism::SymbolNode)
|
|
2163
|
+
raise Unsupported.new(
|
|
2164
|
+
"the type `CArray.#{node.name}` takes is a Symbol written out, " \
|
|
2165
|
+
"as in `CArray.#{node.name}(:float64, [9])`; " \
|
|
2166
|
+
"`#{argument.slice}` is looked up where the block was written, " \
|
|
2167
|
+
"and one compiled kernel serves every value of that",
|
|
2168
|
+
node.location)
|
|
2169
|
+
end
|
|
2170
|
+
name = argument.unescaped.to_sym
|
|
2171
|
+
if LOCAL_ARRAY_REFUSED_TYPES.include?(name)
|
|
2172
|
+
raise Unsupported.new(
|
|
2173
|
+
"`:#{name}` holds #{LOCAL_ARRAY_REFUSED_TYPES_HOLD.fetch(name)} " \
|
|
2174
|
+
"rather than numbers, and a kernel computes with numbers",
|
|
2175
|
+
node.location)
|
|
2176
|
+
end
|
|
2177
|
+
LOCAL_ARRAY_TYPES.fetch(name) do
|
|
2178
|
+
raise Unsupported.new(
|
|
2179
|
+
"`#{argument.slice}` is not a type a local array holds; they are " \
|
|
2180
|
+
"#{LOCAL_ARRAY_TYPES.keys.take(13).map { |n| "`:#{n}`" }.join(', ')}",
|
|
2181
|
+
node.location)
|
|
2182
|
+
end
|
|
2183
|
+
end
|
|
2184
|
+
|
|
2185
|
+
# The shape, one entry an axis: an Integer where the block wrote a
|
|
2186
|
+
# number, and a LocalArrayExtent where it wrote an expression over the
|
|
2187
|
+
# integers it captured. A number is what the C declares and what a
|
|
2188
|
+
# subscript is checked against as the block is read; an expression is
|
|
2189
|
+
# worked out once at the kernel's entry, and every check against it
|
|
2190
|
+
# happens where the cell is reached.
|
|
2191
|
+
def read_local_array_shape (node, arguments)
|
|
2192
|
+
if arguments.empty?
|
|
2193
|
+
raise Unsupported.new(
|
|
2194
|
+
"`#{node.slice}` says no shape; a local array is written " \
|
|
2195
|
+
"`CArray.#{node.name}(n)` with the length written out",
|
|
2196
|
+
node.location)
|
|
2197
|
+
end
|
|
2198
|
+
arguments.map { |argument|
|
|
2199
|
+
extent = literal_integer(argument)
|
|
2200
|
+
next captured_extent(node, argument) unless extent
|
|
2201
|
+
unless extent.positive?
|
|
2202
|
+
raise Unsupported.new(
|
|
2203
|
+
"a local array holds at least one cell, and `#{argument.slice}` " \
|
|
2204
|
+
"asks for #{extent}", node.location)
|
|
2205
|
+
end
|
|
2206
|
+
extent
|
|
2207
|
+
}
|
|
2208
|
+
end
|
|
2209
|
+
|
|
2210
|
+
# A length the kernel works out at its entry, from the integers it
|
|
2211
|
+
# captured.
|
|
2212
|
+
#
|
|
2213
|
+
# It may read nothing that varies from cell to cell: the array is one
|
|
2214
|
+
# allocation for the whole pass, made before the first cell, so an
|
|
2215
|
+
# extent over a loop index or an array's cell is a length that would
|
|
2216
|
+
# have to change underneath it. That is what is refused here, and the
|
|
2217
|
+
# message says which of the two was written.
|
|
2218
|
+
def captured_extent (node, argument)
|
|
2219
|
+
built = build(argument)
|
|
2220
|
+
varies = extent_varies_by_cell(built)
|
|
2221
|
+
if varies
|
|
2222
|
+
raise Unsupported.new(
|
|
2223
|
+
"`#{argument.slice}` is the shape of a local array, and " \
|
|
2224
|
+
"#{varies} -- the array is one allocation made before the first " \
|
|
2225
|
+
"cell, so its length cannot change from cell to cell. Take the " \
|
|
2226
|
+
"widest length the pass needs, or pass a captured array of " \
|
|
2227
|
+
"workspace",
|
|
2228
|
+
node.location)
|
|
2229
|
+
end
|
|
2230
|
+
LocalArrayExtent.new(argument.slice, built)
|
|
2231
|
+
end
|
|
2232
|
+
|
|
2233
|
+
# What in an extent expression is not settled before the loop runs, in
|
|
2234
|
+
# the words it was written in, or nil where everything in it is.
|
|
2235
|
+
def extent_varies_by_cell (node)
|
|
2236
|
+
case node
|
|
2237
|
+
when IntegerLiteral, CaptureRead then nil
|
|
2238
|
+
when UnaryMinus then extent_varies_by_cell(node.operand)
|
|
2239
|
+
when BinaryOperation
|
|
2240
|
+
unless [:+, :-, :*].include?(node.operator)
|
|
2241
|
+
return "`#{node.operator}` is not one of `+`, `-` and `*`"
|
|
2242
|
+
end
|
|
2243
|
+
extent_varies_by_cell(node.left) || extent_varies_by_cell(node.right)
|
|
2244
|
+
when IndexVariable then "`#{node.name}` is a loop index"
|
|
2245
|
+
when LocalRead then "`#{node.name}` is worked out inside the block"
|
|
2246
|
+
when ElementRead then "`#{node.array}` is a cell of an array"
|
|
2247
|
+
when LocalArrayRead then "`#{node.name}` is a cell of an array"
|
|
2248
|
+
else "it is not an integer expression over the captured integers"
|
|
2249
|
+
end
|
|
2250
|
+
end
|
|
2251
|
+
|
|
2252
|
+
# `CArray.zeros(4)`, `CArray.empty(4)`, `CArray.ones(4)`: the
|
|
2253
|
+
# compatibility layer, which `data_type_extension.rb` opens by calling
|
|
2254
|
+
# itself "Numo / NumPy-style" and "not the 'main' carray API". The
|
|
2255
|
+
# words inside a block are carray's own.
|
|
2256
|
+
def refuse_a_compatibility_constructor (node, arguments, type)
|
|
2257
|
+
storage = type || "float64"
|
|
2258
|
+
length = arguments.empty? ? nil : literal_integer(arguments.first)
|
|
2259
|
+
shape = length ? "[#{length}]" : "[n]"
|
|
2260
|
+
raise Unsupported.new(
|
|
2261
|
+
"`#{node.slice}` is CArray's Numo/NumPy compatibility layer, and " \
|
|
2262
|
+
"a kernel body is written in carray's own words: " \
|
|
2263
|
+
"`CArray.new(:#{storage}, #{shape})` for cells that start at zero, " \
|
|
2264
|
+
"or `CArray.empty(:#{storage}, #{shape})` where every cell is " \
|
|
2265
|
+
"written before it is read",
|
|
2266
|
+
node.location)
|
|
2267
|
+
end
|
|
2268
|
+
|
|
2269
|
+
# `CArray::Int64.empty(4)` and `CArray::Int64.zeros(4)`: the same layer
|
|
2270
|
+
# reached through a type class, which is the Numo spelling of it.
|
|
2271
|
+
def refuse_a_type_class_constructor (node, arguments)
|
|
2272
|
+
tail = node.receiver.slice.split("::").last.to_s
|
|
2273
|
+
type = LOCAL_ARRAY_TYPES[tail.downcase.to_sym]
|
|
2274
|
+
refuse_a_compatibility_constructor(node, arguments, type)
|
|
2275
|
+
end
|
|
2276
|
+
|
|
2277
|
+
# Which entry points take one, and which release takes the rest. The
|
|
2278
|
+
# shape of the check is the mode this analyzer was made in, because that
|
|
2279
|
+
# is what says how the block is being read.
|
|
2280
|
+
LOCAL_ARRAY_ELSEWHERE = {
|
|
2281
|
+
:contract => ["a contraction's body", nil],
|
|
2282
|
+
}.freeze
|
|
2283
|
+
|
|
2284
|
+
def refuse_a_local_array_in_this_spelling (node)
|
|
2285
|
+
mode = if @contract then :contract
|
|
2286
|
+
end
|
|
2287
|
+
return unless mode
|
|
2288
|
+
where, when_ = LOCAL_ARRAY_ELSEWHERE.fetch(mode)
|
|
2289
|
+
if mode == :contract
|
|
2290
|
+
raise Unsupported.new(
|
|
2291
|
+
"`#{node.slice}` makes a local array, and #{where} is one " \
|
|
2292
|
+
"expression -- with, at most, an assignment into an array of its " \
|
|
2293
|
+
"own -- so there is no run of statements for a workspace to be " \
|
|
2294
|
+
"used by. Write the body as `CArray.jit_for`",
|
|
2295
|
+
node.location)
|
|
2296
|
+
end
|
|
2297
|
+
raise Unsupported.new(
|
|
2298
|
+
"`#{node.slice}` makes a local array, which #{where} takes in " \
|
|
2299
|
+
"#{when_}; `CArray.jit_for` takes one now, and a captured array of " \
|
|
2300
|
+
"workspace works here today",
|
|
2301
|
+
node.location)
|
|
2302
|
+
end
|
|
2303
|
+
|
|
2304
|
+
# The declaration itself.
|
|
2305
|
+
def declare_local_array (name, storage, shape, zeroed, location, depth)
|
|
2306
|
+
if index_in_scope?(name)
|
|
2307
|
+
raise Unsupported.new(
|
|
2308
|
+
"`#{name}` is a loop index, and the loop is what owns it; give " \
|
|
2309
|
+
"the array a name of its own",
|
|
2310
|
+
location)
|
|
2311
|
+
end
|
|
2312
|
+
if @local_names.include?(name) && local_in_sight?(name)
|
|
2313
|
+
raise Unsupported.new(
|
|
2314
|
+
"`#{name}` holds a number here, and a local array is not a " \
|
|
2315
|
+
"number; the two cannot be one C variable, so give the array a " \
|
|
2316
|
+
"name of its own",
|
|
2317
|
+
location)
|
|
2318
|
+
end
|
|
2319
|
+
heap = heap_array?(storage, shape)
|
|
2320
|
+
refuse_a_heap_array_in_a_body(name, storage, shape, location) if heap
|
|
2321
|
+
@local_array_names << name unless @local_array_names.include?(name)
|
|
2322
|
+
@local_arrays[name] = [storage, shape]
|
|
2323
|
+
scope = [@scopes.size - 1 - depth, 0].max
|
|
2324
|
+
@scopes[scope] << name unless @scopes[scope].include?(name)
|
|
2325
|
+
entry = [scope, name, storage, shape, heap]
|
|
2326
|
+
@local_array_declarations << entry unless @local_array_declarations.include?(entry)
|
|
2327
|
+
@clears_a_local_array ||= zeroed
|
|
2328
|
+
LocalArrayDeclaration.new(name, storage, shape, zeroed, location, scope,
|
|
2329
|
+
heap)
|
|
2330
|
+
end
|
|
2331
|
+
|
|
2332
|
+
# Where the array goes. The stack while it is small enough to be a
|
|
2333
|
+
# frame's business, and the heap otherwise -- which is a placement and
|
|
2334
|
+
# not a refusal: nothing is turned away for its size any more.
|
|
2335
|
+
#
|
|
2336
|
+
# Three ways onto the heap: a length the kernel works out, an array
|
|
2337
|
+
# larger than one frame should hold, and an array that fits on its own
|
|
2338
|
+
# but not beside the ones this kernel already has.
|
|
2339
|
+
def heap_array? (storage, shape)
|
|
2340
|
+
return true unless literal_shape?(shape)
|
|
2341
|
+
bytes = local_array_bytes(storage, shape)
|
|
2342
|
+
return true if bytes > LOCAL_ARRAY_BYTE_LIMIT
|
|
2343
|
+
bytes + stack_array_bytes > LOCAL_ARRAY_TOTAL_BYTE_LIMIT
|
|
2344
|
+
end
|
|
2345
|
+
|
|
2346
|
+
def literal_shape? (shape)
|
|
2347
|
+
shape.all? { |extent| extent.is_a?(Integer) }
|
|
2348
|
+
end
|
|
2349
|
+
|
|
2350
|
+
# What the arrays declared so far take on the stack. Two sibling
|
|
2351
|
+
# loops that each declare `w` are one entry: their blocks do not
|
|
2352
|
+
# overlap, so neither do their frames.
|
|
2353
|
+
def stack_array_bytes
|
|
2354
|
+
@local_array_declarations.sum { |_scope, _name, storage, shape, heap|
|
|
2355
|
+
heap ? 0 : local_array_bytes(storage, shape)
|
|
2356
|
+
}
|
|
2357
|
+
end
|
|
2358
|
+
|
|
2359
|
+
# A compiled function's body is called once per cell, so an allocation
|
|
2360
|
+
# in it would be one per cell -- and the body has no entry of its own
|
|
2361
|
+
# outside the kernel's loop to make it at instead.
|
|
2362
|
+
def refuse_a_heap_array_in_a_body (name, storage, shape, location)
|
|
2363
|
+
return unless @function
|
|
2364
|
+
reason =
|
|
2365
|
+
if literal_shape?(shape)
|
|
2366
|
+
"`#{name}` asks for " \
|
|
2367
|
+
"#{local_array_bytes_phrase(storage, shape)}, which is past the " \
|
|
2368
|
+
"#{LOCAL_ARRAY_BYTE_LIMIT} a frame here holds"
|
|
2369
|
+
else
|
|
2370
|
+
"the shape of `#{name}` is worked out when the kernel runs"
|
|
2371
|
+
end
|
|
2372
|
+
raise Unsupported.new(
|
|
2373
|
+
"#{reason}, so `#{name}` would have to be allocated -- and a " \
|
|
2374
|
+
"function's body is called once per cell, which would be one " \
|
|
2375
|
+
"allocation per cell. Make the array in the kernel that calls " \
|
|
2376
|
+
"this and pass it in, as a pointer parameter",
|
|
2377
|
+
location)
|
|
2378
|
+
end
|
|
2379
|
+
|
|
2380
|
+
# A shadow byte a cell where the kernel carries masks: it is on the
|
|
2381
|
+
# same stack as the cells, so it is counted with them.
|
|
2382
|
+
def local_array_bytes (storage, shape)
|
|
2383
|
+
cells = shape.inject(1) { |product, extent| product * extent }
|
|
2384
|
+
(LOCAL_ARRAY_STORAGE_BYTES.fetch(storage) + (@masked ? 1 : 0)) * cells
|
|
2385
|
+
end
|
|
2386
|
+
|
|
2387
|
+
# How the refusal accounts for a size the block did not write out.
|
|
2388
|
+
def local_array_bytes_phrase (storage, shape)
|
|
2389
|
+
cells = shape.inject(1) { |product, extent| product * extent }
|
|
2390
|
+
values = LOCAL_ARRAY_STORAGE_BYTES.fetch(storage) * cells
|
|
2391
|
+
return "#{values} bytes of #{storage}" unless @masked
|
|
2392
|
+
"#{values + cells} bytes -- #{values} of #{storage} and #{cells} of " \
|
|
2393
|
+
"the mask beside it, a byte a cell, since this kernel carries masks"
|
|
2394
|
+
end
|
|
2395
|
+
|
|
2396
|
+
# A name is in sight where some scope still standing holds it. The
|
|
2397
|
+
# scopes are pushed and popped with the inner loops' blocks, so a name
|
|
2398
|
+
# an inner loop introduced is out of sight after it -- which is what
|
|
2399
|
+
# Ruby says about it too.
|
|
2400
|
+
def local_in_sight? (name)
|
|
2401
|
+
@scopes.any? { |scope| scope.include?(name) }
|
|
2402
|
+
end
|
|
2403
|
+
|
|
2404
|
+
# The name of the local array this receiver is, or nil.
|
|
2405
|
+
#
|
|
2406
|
+
# A name the body did make an array under, but in a block that has since
|
|
2407
|
+
# closed, is neither: it says so rather than falling through to be
|
|
2408
|
+
# refused for not being a captured array, which is not what happened.
|
|
2409
|
+
def local_array_name (node)
|
|
2410
|
+
name = case node
|
|
2411
|
+
when Prism::LocalVariableReadNode
|
|
2412
|
+
node.name
|
|
2413
|
+
when Prism::CallNode
|
|
2414
|
+
node.name if node.receiver.nil? && node.arguments.nil? &&
|
|
2415
|
+
node.block.nil?
|
|
2416
|
+
end
|
|
2417
|
+
return nil unless name && @local_array_names.include?(name)
|
|
2418
|
+
unless local_in_sight?(name)
|
|
2419
|
+
raise Unsupported.new(
|
|
2420
|
+
"`#{name}` belongs to the loop block it was assigned in, and is " \
|
|
2421
|
+
"not there after it; make the array before the loop",
|
|
2422
|
+
node.location)
|
|
2423
|
+
end
|
|
2424
|
+
name
|
|
2425
|
+
end
|
|
2426
|
+
|
|
2427
|
+
# `[storage, shape]` for the declaration in sight under this name.
|
|
2428
|
+
def local_array_in_sight (name)
|
|
2429
|
+
@local_arrays.fetch(name)
|
|
2430
|
+
end
|
|
2431
|
+
|
|
2432
|
+
# A local array's subscripts.
|
|
2433
|
+
#
|
|
2434
|
+
# Not `read_subscripts`. That one answers for the operand tables: it
|
|
2435
|
+
# refuses an index where the block takes none, records the array's rank
|
|
2436
|
+
# in a table shared with every other array, and hands a window its
|
|
2437
|
+
# offsets instead. A local array is in none of that -- its rank is its
|
|
2438
|
+
# shape's, and its subscripts are written wherever the block stands.
|
|
2439
|
+
# What the two share is only the two ways a position is checked.
|
|
2440
|
+
def local_array_subscripts (name, shape, arguments, location)
|
|
2441
|
+
unless arguments.size == shape.size
|
|
2442
|
+
# A block may give one of its own arrays the name of the window it
|
|
2443
|
+
# was handed. Ruby reads that as rebinding the parameter, and so
|
|
2444
|
+
# does this -- but then the window's own spelling stops working, and
|
|
2445
|
+
# the axis count alone is a poor account of why.
|
|
2446
|
+
if @windows.include?(name)
|
|
2447
|
+
raise Unsupported.new(
|
|
2448
|
+
"`#{name}` is the window this block was given, and the block " \
|
|
2449
|
+
"also makes an array of its own under that name -- which " \
|
|
2450
|
+
"rebinds it, here as in Ruby, so from that line on `#{name}` " \
|
|
2451
|
+
"is the array and the window has no name left. " \
|
|
2452
|
+
"`#{name}[#{Array.new(rank, 0).join(', ')}]` is the window's " \
|
|
2453
|
+
"spelling and wants #{rank} #{rank == 1 ? 'offset' : 'offsets'}; " \
|
|
2454
|
+
"give the array a name of its own",
|
|
2455
|
+
location)
|
|
2456
|
+
end
|
|
2457
|
+
raise Unsupported.new(
|
|
2458
|
+
"`#{name}` has #{shape.size == 1 ? 'one axis' : "#{shape.size} axes"}, " \
|
|
2459
|
+
"so it takes #{shape.size == 1 ? 'one subscript' : "#{shape.size} subscripts"}, " \
|
|
2460
|
+
"and #{arguments.size} #{arguments.size == 1 ? 'was' : 'were'} written",
|
|
2461
|
+
location)
|
|
2462
|
+
end
|
|
2463
|
+
arguments.each_with_index.map { |argument, axis|
|
|
2464
|
+
local_array_subscript(name, shape, axis, argument, location)
|
|
2465
|
+
}
|
|
2466
|
+
end
|
|
2467
|
+
|
|
2468
|
+
# How a message names the thing that is out of range.
|
|
2469
|
+
#
|
|
2470
|
+
# With one axis the whole subscript is the thing, and quoting it back is
|
|
2471
|
+
# exactly right. With more than one it is not: the block wrote
|
|
2472
|
+
# `m[r, c]`, so quoting `m[c]` would be a spelling nobody typed, and an
|
|
2473
|
+
# extent on its own ("a local array of 4 cells") is a poor account of a
|
|
2474
|
+
# 3 x 4 one. So the axis is named instead, and the shape with it.
|
|
2475
|
+
def local_array_subject (name, shape, axis, argument)
|
|
2476
|
+
if shape.size == 1
|
|
2477
|
+
"`#{name}[#{argument.slice}]`"
|
|
2478
|
+
else
|
|
2479
|
+
"the subscript `#{argument.slice}` on axis #{axis} of `#{name}`, " \
|
|
2480
|
+
"which is #{shape.join(' x ')},"
|
|
2481
|
+
end
|
|
2482
|
+
end
|
|
2483
|
+
|
|
2484
|
+
# What the cells of the axis in question are, said whole rather than as
|
|
2485
|
+
# a fragment to glue on: the two ranks want different sentences and
|
|
2486
|
+
# composing them produced "is outside and that axis has 4 cells".
|
|
2487
|
+
def local_array_cells_phrase (shape, axis)
|
|
2488
|
+
extent = shape[axis]
|
|
2489
|
+
unless extent.is_a?(Integer)
|
|
2490
|
+
length = "a length of `#{extent}`, which the kernel works out"
|
|
2491
|
+
return shape.size == 1 ? "a local array of #{length}"
|
|
2492
|
+
: "that axis's #{length}"
|
|
2493
|
+
end
|
|
2494
|
+
cells = "#{extent} #{extent == 1 ? 'cell' : 'cells'}"
|
|
2495
|
+
run = extent == 1 ? "only cell is 0" : "cells are 0 to #{extent - 1}"
|
|
2496
|
+
shape.size == 1 ? "a local array of #{cells}, whose #{run}"
|
|
2497
|
+
: "that axis's #{cells}, whose #{run}"
|
|
2498
|
+
end
|
|
2499
|
+
|
|
2500
|
+
# (a) An index whose loop states its range in literals reaches a set of
|
|
2501
|
+
# positions that is known now, so it is checked now and the C carries no
|
|
2502
|
+
# test. (b) Anything else is checked where the cell is reached.
|
|
2503
|
+
#
|
|
2504
|
+
# A literal position is (a) as well, its range being itself.
|
|
2505
|
+
def local_array_subscript (name, shape, axis, argument, location)
|
|
2506
|
+
extent = shape[axis]
|
|
2507
|
+
# An axis whose length the kernel works out has nothing for (a) to
|
|
2508
|
+
# compare against: a literal position included, since 3 is inside an
|
|
2509
|
+
# array of 4 cells and outside one of 2. So every position on such
|
|
2510
|
+
# an axis is checked where the cell is reached.
|
|
2511
|
+
return [nil, build(argument)] unless extent.is_a?(Integer)
|
|
2512
|
+
if (index = index_name(argument)) && index_in_scope?(index)
|
|
2513
|
+
settled = checked_index_subscript(name, shape, axis, index, 0,
|
|
2514
|
+
argument, location)
|
|
2515
|
+
return settled if settled
|
|
2516
|
+
elsif argument.is_a?(Prism::CallNode) &&
|
|
2517
|
+
[:+, :-].include?(argument.name) &&
|
|
2518
|
+
(receiver = index_name(argument.receiver)) &&
|
|
2519
|
+
index_in_scope?(receiver)
|
|
2520
|
+
arguments = argument.arguments ? argument.arguments.arguments : []
|
|
2521
|
+
if arguments.size == 1 && (offset = literal_integer(arguments.first))
|
|
2522
|
+
offset = -offset if argument.name == :-
|
|
2523
|
+
settled = checked_index_subscript(name, shape, axis, receiver,
|
|
2524
|
+
offset, argument, location)
|
|
2525
|
+
return settled if settled
|
|
2526
|
+
end
|
|
2527
|
+
end
|
|
2528
|
+
if (position = literal_integer(argument))
|
|
2529
|
+
unless (0...extent).cover?(position)
|
|
2530
|
+
raise Unsupported.new(
|
|
2531
|
+
"#{local_array_subject(name, shape, axis, argument)} is " \
|
|
2532
|
+
"outside #{local_array_cells_phrase(shape, axis)}" +
|
|
2533
|
+
(position.negative? ?
|
|
2534
|
+
". A subscript here counts from the start, so CArray's " \
|
|
2535
|
+
"`#{name}[-1]` for the last cell is written " \
|
|
2536
|
+
"`#{name}[#{extent - 1}]`" : ""),
|
|
2537
|
+
location)
|
|
2538
|
+
end
|
|
2539
|
+
return [nil, IntegerLiteral.new(position, location)]
|
|
2540
|
+
end
|
|
2541
|
+
[nil, build(argument)]
|
|
2542
|
+
end
|
|
2543
|
+
|
|
2544
|
+
# The subscript settled as the block is read, or nil where it cannot be.
|
|
2545
|
+
#
|
|
2546
|
+
# Nil is not "allow it": it sends the axis to the check at the access,
|
|
2547
|
+
# which is where every position this compiler cannot settle goes. The
|
|
2548
|
+
# two kinds it cannot settle are a loop whose bound is a captured
|
|
2549
|
+
# integer and one whose range is written over another index -- in both
|
|
2550
|
+
# the reach is not knowable until the kernel runs. Returning the index
|
|
2551
|
+
# unchecked would leave a write past the end of a stack array with
|
|
2552
|
+
# nothing at all looking at it.
|
|
2553
|
+
def checked_index_subscript (name, shape, axis, index, offset, argument,
|
|
2554
|
+
location)
|
|
2555
|
+
identifier = index_identifier(index)
|
|
2556
|
+
return nil unless index_reach(identifier)
|
|
2557
|
+
verify_index_reach(name, shape, axis, identifier, offset, argument,
|
|
2558
|
+
location)
|
|
2559
|
+
[identifier, offset]
|
|
2560
|
+
end
|
|
2561
|
+
|
|
2562
|
+
# The lowest and highest position an index takes, where its loop says so
|
|
2563
|
+
# in literals; nil where it does not, or where the loop runs no passes
|
|
2564
|
+
# at all and so reaches nothing.
|
|
2565
|
+
#
|
|
2566
|
+
# Computed rather than enumerated: `(0...10_000_000).each` states its
|
|
2567
|
+
# range in literals too.
|
|
2568
|
+
def index_reach (identifier)
|
|
2569
|
+
range = @inner_ranges[identifier]
|
|
2570
|
+
return nil unless range
|
|
2571
|
+
from, to, step = range
|
|
2572
|
+
return nil unless from.is_a?(IntegerLiteral) && to.is_a?(IntegerLiteral)
|
|
2573
|
+
first = from.value
|
|
2574
|
+
last_excluded = to.value
|
|
2575
|
+
if step.positive?
|
|
2576
|
+
return nil unless first < last_excluded
|
|
2577
|
+
[first, first + ((last_excluded - 1 - first) / step) * step]
|
|
2578
|
+
else
|
|
2579
|
+
return nil unless first > last_excluded
|
|
2580
|
+
stride = -step
|
|
2581
|
+
[first - ((first - (last_excluded + 1)) / stride) * stride, first]
|
|
2582
|
+
end
|
|
2583
|
+
end
|
|
2584
|
+
|
|
2585
|
+
# This looks at the loop's range and not at what stands around the
|
|
2586
|
+
# access, which is the character the captured arrays' own bounds check
|
|
2587
|
+
# already has: an `if` around the line does not make the reach smaller,
|
|
2588
|
+
# because the reach is the loop's. Narrowing the loop's range is what
|
|
2589
|
+
# makes it smaller.
|
|
2590
|
+
def verify_index_reach (name, shape, axis, identifier, offset, argument,
|
|
2591
|
+
location)
|
|
2592
|
+
extent = shape[axis]
|
|
2593
|
+
reach = index_reach(identifier)
|
|
2594
|
+
return unless reach
|
|
2595
|
+
low = reach.first + offset
|
|
2596
|
+
high = reach.last + offset
|
|
2597
|
+
return if low >= 0 && high < extent
|
|
2598
|
+
outside = low.negative? ? low : high
|
|
2599
|
+
spelled = @index_sources.fetch(identifier, identifier)
|
|
2600
|
+
raise Unsupported.new(
|
|
2601
|
+
"#{local_array_subject(name, shape, axis, argument)} reaches cell " \
|
|
2602
|
+
"#{outside} of #{local_array_cells_phrase(shape, axis)}: " \
|
|
2603
|
+
"`#{spelled}` runs #{reach.first} to #{reach.last} here. Narrow " \
|
|
2604
|
+
"the loop's range, or give `#{name}` more cells -- an `if` around " \
|
|
2605
|
+
"the line does not narrow the reach, which is the loop's",
|
|
2606
|
+
location)
|
|
2607
|
+
end
|
|
2608
|
+
|
|
2609
|
+
# In the whole-array spelling every name in the block is a cell, so an
|
|
2610
|
+
# assignment to a name that is an array outside writes that array's
|
|
2611
|
+
# cell -- the one the loop is on, the same cell every read in the block
|
|
2612
|
+
# is at. A name that is not an array is a local, as it is anywhere
|
|
2613
|
+
# else.
|
|
2614
|
+
#
|
|
2615
|
+
# `depth` is Prism's: how many scopes out the variable is. Counted from
|
|
2616
|
+
# the statement, it says which of the scopes here holds the local -- and
|
|
2617
|
+
# one past the kernel's block is a name of the method around it, which
|
|
2618
|
+
# the kernel holds as its own, as it always has.
|
|
2619
|
+
def assign_local (name, expression, location, depth = 0)
|
|
2620
|
+
if @whole_array && @array_names.include?(name)
|
|
2621
|
+
return whole_array_write(name, expression, location)
|
|
2622
|
+
end
|
|
2623
|
+
# An index is the loop's, not the block's. Ruby reads `i = 2` as
|
|
2624
|
+
# rebinding the parameter and leaves the loop alone; the C would
|
|
2625
|
+
# assign to the counter, so the loop would walk somewhere else -- to
|
|
2626
|
+
# cells outside the array, given a value outside its extent. The two
|
|
2627
|
+
# do not mean the same thing, so this is not compiled.
|
|
2628
|
+
if index_in_scope?(name)
|
|
2629
|
+
raise Unsupported.new(
|
|
2630
|
+
"`#{name}` is a loop index, and assigning to it here would " \
|
|
2631
|
+
"move the loop rather than the value: in Ruby the same line " \
|
|
2632
|
+
"rebinds the parameter and the loop runs on. Use a local of " \
|
|
2633
|
+
"another name",
|
|
2634
|
+
location)
|
|
2635
|
+
end
|
|
2636
|
+
if @local_array_names.include?(name)
|
|
2637
|
+
if local_in_sight?(name)
|
|
2638
|
+
raise Unsupported.new(
|
|
2639
|
+
"`#{name}` is a local array here, and cannot hold a number; the " \
|
|
2640
|
+
"two cannot be one C variable, so give the number a name of its " \
|
|
2641
|
+
"own",
|
|
2642
|
+
location)
|
|
2643
|
+
end
|
|
2644
|
+
# Out of sight, the array belonged to a block that has closed, and
|
|
2645
|
+
# this line introduces a variable that merely shares its name. The
|
|
2646
|
+
# record goes, so a read below is read as the number it now is.
|
|
2647
|
+
# The array itself stays counted: its C declaration is still in that
|
|
2648
|
+
# block, and still on that block's stack.
|
|
2649
|
+
@local_array_names.delete(name)
|
|
2650
|
+
@local_arrays.delete(name)
|
|
2651
|
+
end
|
|
2652
|
+
@local_names << name unless @local_names.include?(name)
|
|
2653
|
+
scope = [@scopes.size - 1 - depth, 0].max
|
|
2654
|
+
@scopes[scope] << name unless @scopes[scope].include?(name)
|
|
2655
|
+
Assignment.new(name, expression, location, scope)
|
|
2656
|
+
end
|
|
2657
|
+
|
|
2658
|
+
# What a name on the left of a parallel assignment may not be, said the
|
|
2659
|
+
# way it was written. A cell may: `a[i], a[j] = a[j], a[i]` is the swap
|
|
2660
|
+
# a sort is written with, and each half of it is the write `a[i] = ...`
|
|
2661
|
+
# already was.
|
|
2662
|
+
TARGET_DESCRIPTIONS = {
|
|
2663
|
+
Prism::MultiTargetNode => "a nested target",
|
|
2664
|
+
Prism::SplatNode => "a splat",
|
|
2665
|
+
Prism::ImplicitRestNode => "a bare comma",
|
|
2666
|
+
Prism::InstanceVariableTargetNode => "an instance variable",
|
|
2667
|
+
Prism::GlobalVariableTargetNode => "a global variable",
|
|
2668
|
+
Prism::ClassVariableTargetNode => "a class variable",
|
|
2669
|
+
Prism::ConstantTargetNode => "a constant",
|
|
2670
|
+
Prism::ConstantPathTargetNode => "a constant",
|
|
2671
|
+
Prism::CallTargetNode => "an attribute",
|
|
2672
|
+
}.freeze
|
|
2673
|
+
|
|
2674
|
+
TARGET_HINTS = {
|
|
2675
|
+
Prism::MultiTargetNode => "it unpacks one value into several, and " \
|
|
2676
|
+
"nothing in a kernel holds several; write " \
|
|
2677
|
+
"the names out flat",
|
|
2678
|
+
Prism::SplatNode => "it takes however many values are left " \
|
|
2679
|
+
"over, and how many there are is the one " \
|
|
2680
|
+
"thing a C variable cannot be",
|
|
2681
|
+
Prism::ImplicitRestNode => "it drops the rest of the values, and how " \
|
|
2682
|
+
"many that is has to be written out here",
|
|
2683
|
+
}.freeze
|
|
2684
|
+
|
|
2685
|
+
# `prev, cur = cur, prev + cur`, and the swap it is the general case of.
|
|
2686
|
+
#
|
|
2687
|
+
# Ruby settles every value on the right before it writes any of them,
|
|
2688
|
+
# and so does this: each value is assigned to a name of its own first,
|
|
2689
|
+
# and only then are the writes made. The other way round, the line
|
|
2690
|
+
# above would write `prev` and then read the new one back while working
|
|
2691
|
+
# out `cur` -- which is the one thing the spelling exists to prevent,
|
|
2692
|
+
# and why `jit_for` wanted it. A recurrence advances by a parallel
|
|
2693
|
+
# assignment in Ruby; until now it had to be unpicked by hand here, and
|
|
2694
|
+
# the temporary a reader had to introduce is where the bug lives.
|
|
2695
|
+
#
|
|
2696
|
+
# The names are written even where nothing on the right reads what the
|
|
2697
|
+
# left writes. A rule that dropped them sometimes would be one a reader
|
|
2698
|
+
# of the generated C had to know before they could tell what a line
|
|
2699
|
+
# meant, and a C compiler drops a name assigned once and read once
|
|
2700
|
+
# without being asked.
|
|
2701
|
+
def build_parallel_assignment (node)
|
|
2702
|
+
targets = parallel_targets(node)
|
|
2703
|
+
values = parallel_values(node, targets)
|
|
2704
|
+
temporaries = values.map { |value|
|
|
2705
|
+
assign_local(parallel_value_name, build(value), value.location)
|
|
2706
|
+
}
|
|
2707
|
+
writes = targets.each_with_index.map { |target, k|
|
|
2708
|
+
value = build_name_read(temporaries[k].name, target.location)
|
|
2709
|
+
if target.is_a?(Prism::LocalVariableTargetNode)
|
|
2710
|
+
assign_local(target.name, value, target.location, target.depth)
|
|
2711
|
+
else
|
|
2712
|
+
indices = target.arguments ? target.arguments.arguments : []
|
|
2713
|
+
element_write(target, indices, value, target.location)
|
|
2714
|
+
end
|
|
2715
|
+
}
|
|
2716
|
+
ParallelAssignment.new(temporaries + writes, node.location)
|
|
2717
|
+
end
|
|
2718
|
+
|
|
2719
|
+
def parallel_targets (node)
|
|
2720
|
+
targets = node.lefts + (node.rest ? [node.rest] : []) + node.rights
|
|
2721
|
+
targets.each do |target|
|
|
2722
|
+
next if target.is_a?(Prism::LocalVariableTargetNode) ||
|
|
2723
|
+
target.is_a?(Prism::IndexTargetNode)
|
|
2724
|
+
described = TARGET_DESCRIPTIONS[target.class]
|
|
2725
|
+
hint = TARGET_HINTS[target.class]
|
|
2726
|
+
raise Unsupported.new(
|
|
2727
|
+
"a parallel assignment writes names and cells here -- " \
|
|
2728
|
+
"`a, b = b, a`, `a[i], a[j] = a[j], a[i]` -- and this one writes " \
|
|
2729
|
+
"#{described || node_name(target)}#{hint ? " -- #{hint}" : ""}",
|
|
2730
|
+
target.location)
|
|
2731
|
+
end
|
|
2732
|
+
targets
|
|
2733
|
+
end
|
|
2734
|
+
|
|
2735
|
+
# The values, told apart from the one value Ruby would spread.
|
|
2736
|
+
#
|
|
2737
|
+
# `a, b = b, a` writes its values out, and Prism hands them over as an
|
|
2738
|
+
# array with no brackets around it. Brackets are Ruby's other spelling
|
|
2739
|
+
# -- `a, b = [b, a]` -- and it means something else: one value, taken
|
|
2740
|
+
# apart across the names. Nothing in a kernel is a value that can be
|
|
2741
|
+
# taken apart, so the two are told apart here rather than quietly read
|
|
2742
|
+
# as one.
|
|
2743
|
+
def parallel_values (node, targets)
|
|
2744
|
+
value = node.value
|
|
2745
|
+
unless value.is_a?(Prism::ArrayNode) && value.opening_loc.nil?
|
|
2746
|
+
raise Unsupported.new(
|
|
2747
|
+
"a parallel assignment takes one value per name, written out on " \
|
|
2748
|
+
"the right, as in `a, b = b, a`. Ruby takes a single value apart " \
|
|
2749
|
+
"across the names, and a kernel computes numbers -- there is " \
|
|
2750
|
+
"nothing here to take apart",
|
|
2751
|
+
value.location)
|
|
2752
|
+
end
|
|
2753
|
+
if (splat = value.elements.find { |element|
|
|
2754
|
+
element.is_a?(Prism::SplatNode) })
|
|
2755
|
+
raise Unsupported.new(
|
|
2756
|
+
"`*` spreads a value across the names, and how many it comes to " \
|
|
2757
|
+
"is the one thing a C variable cannot be; write the values out",
|
|
2758
|
+
splat.location)
|
|
2759
|
+
end
|
|
2760
|
+
unless value.elements.size == targets.size
|
|
2761
|
+
# Ruby has an answer for either way round, and neither is one a
|
|
2762
|
+
# kernel can keep: the names it runs out of values for are nil, and
|
|
2763
|
+
# a value it runs out of names for is dropped.
|
|
2764
|
+
raise Unsupported.new(
|
|
2765
|
+
"this writes #{count_of(targets.size, "name")} and has " \
|
|
2766
|
+
"#{count_of(value.elements.size, "value")} for them. Ruby " \
|
|
2767
|
+
"#{value.elements.size < targets.size ?
|
|
2768
|
+
"leaves the rest of the names nil, which is not a number a " \
|
|
2769
|
+
"local or a cell can hold" :
|
|
2770
|
+
"drops the values it has no name for, and a value computed " \
|
|
2771
|
+
"and dropped is a line that does nothing"}",
|
|
2772
|
+
node.location)
|
|
2773
|
+
end
|
|
2774
|
+
value.elements
|
|
2775
|
+
end
|
|
2776
|
+
|
|
2777
|
+
def count_of (n, noun)
|
|
2778
|
+
"#{n} #{n == 1 ? noun : noun + "s"}"
|
|
2779
|
+
end
|
|
2780
|
+
|
|
2781
|
+
# A name for a value on the right that no name in the block can be, and
|
|
2782
|
+
# that no other statement uses.
|
|
2783
|
+
#
|
|
2784
|
+
# The block's own text settles the first half: a local, a capture, an
|
|
2785
|
+
# index and an array all appear in it, so a name that appears nowhere
|
|
2786
|
+
# in it is none of them. An underscore is added until that holds,
|
|
2787
|
+
# which keeps the same block reading the same way every time -- it has
|
|
2788
|
+
# to, the generated C being part of the cache key.
|
|
2789
|
+
#
|
|
2790
|
+
# The second half is the counter. Two parallel assignments sharing a
|
|
2791
|
+
# name would be two statements sharing a C variable, and a variable
|
|
2792
|
+
# live at the head of a loop is one the loop carries: a body that
|
|
2793
|
+
# swapped Integers before the loop and Floats inside it was refused for
|
|
2794
|
+
# changing the type of `value1` on the way round -- a name its author
|
|
2795
|
+
# never wrote, about a value that is dead by the end of the line it is
|
|
2796
|
+
# on. A name of its own per statement cannot be carried anywhere.
|
|
2797
|
+
def parallel_value_name
|
|
2798
|
+
@parallel_value_prefix ||= begin
|
|
2799
|
+
text = (@source || @node&.slice).to_s
|
|
2800
|
+
prefix = "value"
|
|
2801
|
+
prefix = "_" + prefix while text.include?(prefix)
|
|
2802
|
+
prefix
|
|
2803
|
+
end
|
|
2804
|
+
@parallel_value_count = @parallel_value_count.to_i + 1
|
|
2805
|
+
:"#{@parallel_value_prefix}#{@parallel_value_count}"
|
|
2806
|
+
end
|
|
2807
|
+
|
|
2808
|
+
# `x += e` is `x = x + e`, and is read as exactly that: the name is
|
|
2809
|
+
# read where Ruby reads it -- before the right-hand side, and by the
|
|
2810
|
+
# same rule, so a name the block has not assigned yet says so rather
|
|
2811
|
+
# than starting from whatever C left in the variable.
|
|
2812
|
+
def build_operator_assignment (node)
|
|
2813
|
+
operator = assignment_operator(node)
|
|
2814
|
+
read = build_name_read(node.name, node.location)
|
|
2815
|
+
expression = combined(operator, read, build(node.value), node.location)
|
|
2816
|
+
assign_local(node.name, expression, node.location, node.depth)
|
|
2817
|
+
end
|
|
2818
|
+
|
|
2819
|
+
# `a[i] += e` is `a[i] = a[i] + e`, cell for cell -- including under a
|
|
2820
|
+
# mask, where the read is a read like any other and carries what it
|
|
2821
|
+
# finds into what is written.
|
|
2822
|
+
def build_operator_element_write (node)
|
|
2823
|
+
operator = assignment_operator(node)
|
|
2824
|
+
indices = node.arguments ? node.arguments.arguments : []
|
|
2825
|
+
read = build_element_read(node, indices)
|
|
2826
|
+
expression = combined(operator, read, build(node.value), node.location)
|
|
2827
|
+
element_write(node, indices, expression, node.location)
|
|
2828
|
+
end
|
|
2829
|
+
|
|
2830
|
+
# The operators an assignment may carry are the ones an expression may:
|
|
2831
|
+
# `||=` and `&&=` are refused where they are read, being about nil and
|
|
2832
|
+
# false rather than about arithmetic.
|
|
2833
|
+
def assignment_operator (node)
|
|
2834
|
+
operator = node.binary_operator
|
|
2835
|
+
unless (ARITHMETIC_OPERATORS + BIT_OPERATORS + [:**]).include?(operator)
|
|
2836
|
+
raise Unsupported.new(
|
|
2837
|
+
"`#{operator}=` is not one of the operators a kernel computes " \
|
|
2838
|
+
"with", node.location)
|
|
2839
|
+
end
|
|
2840
|
+
operator
|
|
2841
|
+
end
|
|
2842
|
+
|
|
2843
|
+
def combined (operator, left, right, location)
|
|
2844
|
+
return power_node(left, right, location) if operator == :**
|
|
2845
|
+
BinaryOperation.new(operator, left, right, location)
|
|
2846
|
+
end
|
|
2847
|
+
|
|
1195
2848
|
def build_element_write (node)
|
|
1196
|
-
|
|
2849
|
+
if node.name == :each || node.name == :times || node.name == :step
|
|
2850
|
+
return build_inner_loop(node)
|
|
2851
|
+
end
|
|
1197
2852
|
unless node.name == :[]=
|
|
1198
2853
|
# An expression standing alone in this spelling is a computation
|
|
1199
2854
|
# nobody can see: there is no index to have written it against, and
|
|
@@ -1205,54 +2860,98 @@ class CArray
|
|
|
1205
2860
|
"`CArray.jit_map`",
|
|
1206
2861
|
node.location)
|
|
1207
2862
|
end
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
2863
|
+
if INNER_LOOP_SPELLINGS.include?(node.name)
|
|
2864
|
+
raise Unsupported.new(
|
|
2865
|
+
"an inner loop is written `(a...b).each`, `n.times` or " \
|
|
2866
|
+
"`a.step(b, s)`, which is the spelling an extent takes a " \
|
|
2867
|
+
"stride and a direction in -- `#{node.name}` is not one of " \
|
|
2868
|
+
"them",
|
|
2869
|
+
node.location)
|
|
2870
|
+
end
|
|
2871
|
+
refuse_as_a_statement(node, "a call to `#{node.name}`")
|
|
1212
2872
|
end
|
|
1213
|
-
|
|
2873
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
2874
|
+
element_write(node, arguments[0..-2], nil, node.location,
|
|
2875
|
+
value_node: arguments.last)
|
|
2876
|
+
end
|
|
2877
|
+
|
|
2878
|
+
# The write itself, with the indices and the value already told apart:
|
|
2879
|
+
# `a[i] = e` puts the value last among the arguments, while `a[i] += e`
|
|
2880
|
+
# keeps it somewhere else, and everything from here down is the same
|
|
2881
|
+
# for both. `value_node` is passed where the value is still a Prism
|
|
2882
|
+
# node, since `a[i] = UNDEF` is a mark rather than a value and is read
|
|
2883
|
+
# off the node.
|
|
2884
|
+
def element_write (node, indices, expression, location, value_node: nil)
|
|
2885
|
+
if (pointer = pointer_subscript(node, write: true, indices: indices))
|
|
1214
2886
|
name, index = pointer
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
2887
|
+
return PointerWrite.new(name, index,
|
|
2888
|
+
expression || build(value_node), location)
|
|
2889
|
+
end
|
|
2890
|
+
refuse_a_constructor_in_an_expression(node.receiver)
|
|
2891
|
+
if (name = local_array_name(node.receiver))
|
|
2892
|
+
storage, shape = local_array_in_sight(name)
|
|
2893
|
+
if value_node && undef_constant?(value_node)
|
|
2894
|
+
# A kernel that writes UNDEF anywhere carries masks by that
|
|
2895
|
+
# alone, so this is a mark on the shadow beside the cell. In a
|
|
2896
|
+
# function body there is no shadow: a function is handed numbers
|
|
2897
|
+
# and pointers, and a mask reaches neither.
|
|
2898
|
+
unless @masked
|
|
2899
|
+
raise Unsupported.new(
|
|
2900
|
+
"`#{name}` is a local array in a compiled function's body, " \
|
|
2901
|
+
"and nothing there carries masks -- a function is handed " \
|
|
2902
|
+
"numbers and pointers, and a mask travels in neither. Keep " \
|
|
2903
|
+
"a value that stands for missing, or mark the cell in the " \
|
|
2904
|
+
"kernel that called this",
|
|
2905
|
+
location)
|
|
2906
|
+
end
|
|
2907
|
+
@uses_undef = true
|
|
2908
|
+
return LocalArrayMaskWrite.new(
|
|
2909
|
+
name, storage, shape,
|
|
2910
|
+
local_array_subscripts(name, shape, indices, location), location)
|
|
2911
|
+
end
|
|
2912
|
+
return LocalArrayWrite.new(
|
|
2913
|
+
name, storage, shape,
|
|
2914
|
+
local_array_subscripts(name, shape, indices, location),
|
|
2915
|
+
expression || build(value_node), location)
|
|
1218
2916
|
end
|
|
1219
|
-
array = array_name(node.receiver,
|
|
1220
|
-
arguments = node.arguments ? node.arguments.arguments : []
|
|
2917
|
+
array = array_name(node.receiver, location)
|
|
1221
2918
|
# `out[] = ...` was how a block that had to run as Ruby said "the
|
|
1222
2919
|
# whole array", `[]=` being the only spelling Ruby has for it. The
|
|
1223
2920
|
# block is read rather than run, and every name in it is a cell, so
|
|
1224
2921
|
# the assignment is Ruby's own: `out = ...`.
|
|
1225
|
-
if @whole_array &&
|
|
2922
|
+
if @whole_array && indices.empty?
|
|
1226
2923
|
raise Unsupported.new(
|
|
1227
2924
|
"`#{array}[] = ...` writes the cell the loop is on, which is what " \
|
|
1228
2925
|
"`#{array} = ...` says; the block is read rather than run, so the " \
|
|
1229
2926
|
"assignment is an ordinary one",
|
|
1230
|
-
|
|
2927
|
+
location)
|
|
1231
2928
|
end
|
|
1232
|
-
subscripts = read_subscripts(array,
|
|
1233
|
-
# A kernel writes a cell its own indices
|
|
1234
|
-
#
|
|
1235
|
-
#
|
|
1236
|
-
#
|
|
2929
|
+
subscripts = read_subscripts(array, indices, location)
|
|
2930
|
+
# A kernel writes a cell one of its own indices reaches -- an outer
|
|
2931
|
+
# one or an inner one -- or one it works out, which is a scatter and
|
|
2932
|
+
# is checked as it is reached rather than in advance. What it may not
|
|
2933
|
+
# do is write through a name that addresses no cell at all.
|
|
1237
2934
|
#
|
|
1238
2935
|
# In a contraction the left-hand side names which indices are free and
|
|
1239
2936
|
# which are summed over, so it is checked there instead.
|
|
1240
2937
|
unless @contract || walking_subscripts?(subscripts) ||
|
|
1241
2938
|
computed_subscripts?(subscripts)
|
|
1242
2939
|
raise Unsupported.new(
|
|
1243
|
-
"a kernel writes through
|
|
1244
|
-
"the left of `=` is addressed by
|
|
1245
|
-
"position fixed before the loop runs
|
|
1246
|
-
"works out, which is a scatter",
|
|
1247
|
-
|
|
2940
|
+
"a kernel writes through the indices it is running, so " \
|
|
2941
|
+
"`#{array}[...]` on the left of `=` is addressed by " \
|
|
2942
|
+
"#{available_indices}, by a position fixed before the loop runs " \
|
|
2943
|
+
"-- or by a value the kernel works out, which is a scatter",
|
|
2944
|
+
location)
|
|
1248
2945
|
end
|
|
1249
2946
|
record_subscripts(array, subscripts)
|
|
2947
|
+
record_write_subscripts(array, subscripts)
|
|
1250
2948
|
@written_arrays << array unless @written_arrays.include?(array)
|
|
1251
|
-
if undef_constant?(
|
|
2949
|
+
if value_node && undef_constant?(value_node)
|
|
1252
2950
|
@uses_undef = true
|
|
1253
|
-
return MaskWrite.new(array,
|
|
2951
|
+
return MaskWrite.new(array, location)
|
|
1254
2952
|
end
|
|
1255
|
-
ElementWrite.new(array, build(
|
|
2953
|
+
ElementWrite.new(array, expression || build(value_node), location,
|
|
2954
|
+
subscripts)
|
|
1256
2955
|
end
|
|
1257
2956
|
|
|
1258
2957
|
def build (node)
|
|
@@ -1301,8 +3000,19 @@ class CArray
|
|
|
1301
3000
|
# closes over nothing.
|
|
1302
3001
|
build_name_read(node.name, node.location)
|
|
1303
3002
|
else
|
|
1304
|
-
|
|
1305
|
-
|
|
3003
|
+
# Named as it was written where there is a name for it, and with
|
|
3004
|
+
# the same advice a statement in that position gets: `unless` in
|
|
3005
|
+
# value position was "unsupported expression Unless", which is
|
|
3006
|
+
# this compiler's reading of it rather than the reader's.
|
|
3007
|
+
described = STATEMENT_DESCRIPTIONS[node.class]
|
|
3008
|
+
hint = STATEMENT_HINTS[node.class]
|
|
3009
|
+
raise Unsupported.new(
|
|
3010
|
+
described ?
|
|
3011
|
+
"a kernel computes its value from arithmetic, comparisons, " \
|
|
3012
|
+
"cells and calls, and this one is #{described}" \
|
|
3013
|
+
"#{hint ? " -- #{hint}" : ""}" :
|
|
3014
|
+
"unsupported expression #{node_name(node)}",
|
|
3015
|
+
node.location)
|
|
1306
3016
|
end
|
|
1307
3017
|
end
|
|
1308
3018
|
|
|
@@ -1314,8 +3024,31 @@ class CArray
|
|
|
1314
3024
|
def build_name_read (name, location)
|
|
1315
3025
|
axis = @outer_names.index(name)
|
|
1316
3026
|
return IndexVariable.new(name, axis, location) if axis
|
|
1317
|
-
|
|
3027
|
+
if @inner_names.include?(name)
|
|
3028
|
+
return IndexVariable.new(@inner_aliases.fetch(name, name), nil, location)
|
|
3029
|
+
end
|
|
3030
|
+
if @local_array_names.include?(name)
|
|
3031
|
+
unless local_in_sight?(name)
|
|
3032
|
+
raise Unsupported.new(
|
|
3033
|
+
"`#{name}` belongs to the loop block it was assigned in, and is " \
|
|
3034
|
+
"not there after it; make the array before the loop",
|
|
3035
|
+
location)
|
|
3036
|
+
end
|
|
3037
|
+
raise Unsupported.new(
|
|
3038
|
+
"`#{name}` is a local array; index it, as in `#{name}[0]`. An " \
|
|
3039
|
+
"array has no value of its own in a kernel -- what the name " \
|
|
3040
|
+
"stands for is its cells",
|
|
3041
|
+
location)
|
|
3042
|
+
end
|
|
1318
3043
|
if @local_names.include?(name)
|
|
3044
|
+
# A local of an inner loop's block that has closed is not in sight:
|
|
3045
|
+
# Ruby reads the same name after the block as a method call.
|
|
3046
|
+
unless @scopes.any? { |scope| scope.include?(name) }
|
|
3047
|
+
raise Unsupported.new(
|
|
3048
|
+
"`#{name}` belongs to the loop block it was assigned in, and is " \
|
|
3049
|
+
"not there after it; give it a value before the loop",
|
|
3050
|
+
location)
|
|
3051
|
+
end
|
|
1319
3052
|
return LocalRead.new(name, location)
|
|
1320
3053
|
end
|
|
1321
3054
|
if @cell_names.include?(name)
|
|
@@ -1347,6 +3080,42 @@ class CArray
|
|
|
1347
3080
|
CaptureRead.new(name, location)
|
|
1348
3081
|
end
|
|
1349
3082
|
|
|
3083
|
+
# What `if`, `while` and `?:` read. A pointer parameter named bare
|
|
3084
|
+
# there is Ruby's truthiness -- nil is false and an array is true -- so
|
|
3085
|
+
# `if grad` asks whether an address came, which is what NLopt's
|
|
3086
|
+
# derivative-free methods leave out. Only where a condition is read:
|
|
3087
|
+
# anywhere else the bare name would be a value, and a pointer is not
|
|
3088
|
+
# one. `!`, `&&`, `||` and parentheses are looked through, since each
|
|
3089
|
+
# of those reads its operands as conditions too.
|
|
3090
|
+
def build_condition (node)
|
|
3091
|
+
case node
|
|
3092
|
+
when Prism::ParenthesesNode
|
|
3093
|
+
inner = node.body ? node.body.body : []
|
|
3094
|
+
return build_condition(inner.first) if inner.size == 1
|
|
3095
|
+
when Prism::AndNode
|
|
3096
|
+
return LogicalOperation.new(:"&&", build_condition(node.left),
|
|
3097
|
+
build_condition(node.right), node.location)
|
|
3098
|
+
when Prism::OrNode
|
|
3099
|
+
return LogicalOperation.new(:"||", build_condition(node.left),
|
|
3100
|
+
build_condition(node.right), node.location)
|
|
3101
|
+
when Prism::CallNode
|
|
3102
|
+
if node.name == :! && node.receiver && node.arguments.nil?
|
|
3103
|
+
return LogicalNot.new(build_condition(node.receiver), node.location)
|
|
3104
|
+
end
|
|
3105
|
+
end
|
|
3106
|
+
pointer_test(node, false, node.location) || build(node)
|
|
3107
|
+
end
|
|
3108
|
+
|
|
3109
|
+
# A PointerTest when `receiver` names a pointer parameter, nil when it
|
|
3110
|
+
# does not. Asking is not reaching through, so a `void *` may be asked
|
|
3111
|
+
# too: whether a slot was filled is a question about the slot.
|
|
3112
|
+
def pointer_test (receiver, null, location)
|
|
3113
|
+
name = captured_name(receiver)
|
|
3114
|
+
return nil unless name && @pointers.key?(name)
|
|
3115
|
+
return nil if @local_names.include?(name)
|
|
3116
|
+
PointerTest.new(name, null, location)
|
|
3117
|
+
end
|
|
3118
|
+
|
|
1350
3119
|
def build_conditional (node)
|
|
1351
3120
|
unless node.subsequent
|
|
1352
3121
|
raise Unsupported.new("`if` without `else` has no value for every cell",
|
|
@@ -1355,7 +3124,7 @@ class CArray
|
|
|
1355
3124
|
unless node.subsequent.is_a?(Prism::ElseNode)
|
|
1356
3125
|
raise Unsupported.new("`elsif` is not supported", node.location)
|
|
1357
3126
|
end
|
|
1358
|
-
Conditional.new(
|
|
3127
|
+
Conditional.new(build_condition(node.predicate),
|
|
1359
3128
|
build_single(node.statements, node.location, "if"),
|
|
1360
3129
|
build_single(node.subsequent.statements, node.location, "else"),
|
|
1361
3130
|
node.location)
|
|
@@ -1380,9 +3149,29 @@ class CArray
|
|
|
1380
3149
|
if node.receiver.nil? && node.arguments.nil? && node.block.nil?
|
|
1381
3150
|
return build_name_read(node.name, node.location)
|
|
1382
3151
|
end
|
|
3152
|
+
if (intrinsic = build_intrinsic_call(node))
|
|
3153
|
+
return intrinsic
|
|
3154
|
+
end
|
|
3155
|
+
refuse_a_constructor_in_an_expression(node)
|
|
3156
|
+
# A method on a local array. The operators are left alone so that
|
|
3157
|
+
# `w + 1` is refused for reading the array bare, which is what it
|
|
3158
|
+
# does; a named method is refused here, where the name can be said.
|
|
3159
|
+
if (holder = local_array_name(node.receiver)) &&
|
|
3160
|
+
!LOCAL_ARRAY_SUBSCRIPTS.include?(node.name) &&
|
|
3161
|
+
!(ARITHMETIC_OPERATORS + COMPARISON_OPERATORS +
|
|
3162
|
+
BIT_OPERATORS + [:**]).include?(node.name)
|
|
3163
|
+
raise Unsupported.new(
|
|
3164
|
+
"`#{holder}` is a local array, and `#{node.name}` is a method " \
|
|
3165
|
+
"CArray answers outside a kernel; inside one an array is reached " \
|
|
3166
|
+
"at a subscript. Write the loop out over `#{holder}[k]`",
|
|
3167
|
+
node.location)
|
|
3168
|
+
end
|
|
1383
3169
|
if (recursive = recursive_call(node))
|
|
1384
3170
|
return recursive
|
|
1385
3171
|
end
|
|
3172
|
+
if (draw = random_call(node))
|
|
3173
|
+
return draw
|
|
3174
|
+
end
|
|
1386
3175
|
if (c_function = c_function_call(node))
|
|
1387
3176
|
return c_function
|
|
1388
3177
|
end
|
|
@@ -1421,6 +3210,42 @@ class CArray
|
|
|
1421
3210
|
if node.name == :** && arguments.size == 1
|
|
1422
3211
|
return build_power(node.receiver, arguments.first, node.location)
|
|
1423
3212
|
end
|
|
3213
|
+
if node.name == :clamp
|
|
3214
|
+
unless arguments.size == 2
|
|
3215
|
+
raise Unsupported.new(
|
|
3216
|
+
"`clamp` takes the two bounds, as in `x.clamp(0.0, 1.0)`" +
|
|
3217
|
+
(arguments.size == 1 ? " -- the range form is not in the subset" : ""),
|
|
3218
|
+
node.location)
|
|
3219
|
+
end
|
|
3220
|
+
return Clamp.new(build(node.receiver), build(arguments.first),
|
|
3221
|
+
build(arguments.last), node.location)
|
|
3222
|
+
end
|
|
3223
|
+
if node.name == :nil? && arguments.empty? &&
|
|
3224
|
+
(test = pointer_test(node.receiver, true, node.location))
|
|
3225
|
+
return test
|
|
3226
|
+
end
|
|
3227
|
+
if [:==, :!=].include?(node.name) && arguments.size == 1
|
|
3228
|
+
# `grad == nil` and `nil == grad`, which Ruby answers the way
|
|
3229
|
+
# `grad.nil?` does and so may be written wherever that is.
|
|
3230
|
+
receiver, other = node.receiver, arguments.first
|
|
3231
|
+
receiver, other = other, receiver if receiver.is_a?(Prism::NilNode)
|
|
3232
|
+
if other.is_a?(Prism::NilNode) &&
|
|
3233
|
+
(test = pointer_test(receiver, node.name == :==, node.location))
|
|
3234
|
+
return test
|
|
3235
|
+
end
|
|
3236
|
+
end
|
|
3237
|
+
if arguments.empty? && NUMERIC_PREDICATES.include?(node.name)
|
|
3238
|
+
return NumericPredicate.new(node.name, build(node.receiver),
|
|
3239
|
+
node.location)
|
|
3240
|
+
end
|
|
3241
|
+
if arguments.empty? && node.name == :infinite?
|
|
3242
|
+
raise Unsupported.new(
|
|
3243
|
+
"`infinite?` answers nil, 1 or -1 in Ruby rather than true or " \
|
|
3244
|
+
"false, and a kernel has no nil to answer with; ask " \
|
|
3245
|
+
"`x.abs == Float::INFINITY`, or `x == Float::INFINITY` where " \
|
|
3246
|
+
"the sign is the question",
|
|
3247
|
+
node.location)
|
|
3248
|
+
end
|
|
1424
3249
|
if arguments.empty?
|
|
1425
3250
|
if (function = postfix_math(node.name))
|
|
1426
3251
|
return MathCall.new(function, [build(node.receiver)], node.location)
|
|
@@ -1507,11 +3332,26 @@ class CArray
|
|
|
1507
3332
|
"only a cell can be compared with UNDEF, as in `a[i] == UNDEF`",
|
|
1508
3333
|
location)
|
|
1509
3334
|
end
|
|
3335
|
+
arguments = receiver.arguments ? receiver.arguments.arguments : []
|
|
3336
|
+
if (made = local_array_name(receiver.receiver))
|
|
3337
|
+
storage, shape = local_array_in_sight(made)
|
|
3338
|
+
unless @masked
|
|
3339
|
+
raise Unsupported.new(
|
|
3340
|
+
"`#{made}` is a local array in a compiled function's body, " \
|
|
3341
|
+
"and nothing there carries masks -- a function is handed " \
|
|
3342
|
+
"numbers and pointers, and a mask travels in neither, so " \
|
|
3343
|
+
"there is no shadow beside these cells to ask about",
|
|
3344
|
+
location)
|
|
3345
|
+
end
|
|
3346
|
+
@uses_undef = true
|
|
3347
|
+
return LocalArrayMaskTest.new(
|
|
3348
|
+
made, storage, shape,
|
|
3349
|
+
local_array_subscripts(made, shape, arguments, location),
|
|
3350
|
+
negated, location)
|
|
3351
|
+
end
|
|
1510
3352
|
array = array_name(receiver.receiver, location)
|
|
1511
3353
|
array_for_test = array
|
|
1512
|
-
subscripts = read_subscripts(array_for_test,
|
|
1513
|
-
receiver.arguments ? receiver.arguments.arguments : [],
|
|
1514
|
-
location)
|
|
3354
|
+
subscripts = read_subscripts(array_for_test, arguments, location)
|
|
1515
3355
|
# Recorded like any other read, because the cell still has to exist
|
|
1516
3356
|
# and still has to have been settled before it is asked about. What
|
|
1517
3357
|
# it does not do is feed the value-mask propagation, which is decided
|
|
@@ -1525,7 +3365,7 @@ class CArray
|
|
|
1525
3365
|
# does not. A pointer declared const is refused on the left rather than
|
|
1526
3366
|
# silently written through: the declaration is a promise to the caller,
|
|
1527
3367
|
# not decoration.
|
|
1528
|
-
def pointer_subscript (node, write: false)
|
|
3368
|
+
def pointer_subscript (node, write: false, indices: nil)
|
|
1529
3369
|
name = captured_name(node.receiver)
|
|
1530
3370
|
return nil unless name && @pointers.key?(name)
|
|
1531
3371
|
case @pointers.fetch(name)
|
|
@@ -1541,14 +3381,36 @@ class CArray
|
|
|
1541
3381
|
"not write through it", node.location)
|
|
1542
3382
|
end
|
|
1543
3383
|
end
|
|
1544
|
-
arguments =
|
|
1545
|
-
|
|
3384
|
+
arguments = indices
|
|
3385
|
+
unless arguments
|
|
3386
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
3387
|
+
arguments = arguments[0..-2] if write
|
|
3388
|
+
end
|
|
1546
3389
|
unless arguments.size == 1
|
|
1547
3390
|
raise Unsupported.new(
|
|
1548
3391
|
"`#{name}` is a pointer, so it takes one index", node.location)
|
|
1549
3392
|
end
|
|
1550
3393
|
@pointer_names << name unless @pointer_names.include?(name)
|
|
1551
|
-
|
|
3394
|
+
index = build(arguments.first)
|
|
3395
|
+
refuse_undeclared_cell(name, index, node.location)
|
|
3396
|
+
[name, index]
|
|
3397
|
+
end
|
|
3398
|
+
|
|
3399
|
+
# A subscript on a pointer is not checked when the function runs: that
|
|
3400
|
+
# is C's bargain, and a computed one could only be checked there. A
|
|
3401
|
+
# literal needs no running to be read, though, and where the
|
|
3402
|
+
# declaration said how many cells there are, one outside them is a
|
|
3403
|
+
# write or a read past what the caller was held to -- the caller is
|
|
3404
|
+
# checked for at least that many and no more. C says nothing about
|
|
3405
|
+
# `v[7]` against `double v[2]`; this says it as the body is read.
|
|
3406
|
+
def refuse_undeclared_cell (name, index, location)
|
|
3407
|
+
length = @pointer_lengths[name]
|
|
3408
|
+
return unless length && index.is_a?(IntegerLiteral)
|
|
3409
|
+
return if index.value >= 0 && index.value < length
|
|
3410
|
+
raise Unsupported.new(
|
|
3411
|
+
"`#{name}[#{index.value}]` is outside the #{length} " \
|
|
3412
|
+
"#{length == 1 ? 'cell' : 'cells'} `#{name}` is declared with; " \
|
|
3413
|
+
"a caller is held to that many and no more", location)
|
|
1552
3414
|
end
|
|
1553
3415
|
|
|
1554
3416
|
# `fact(n - 1)` inside the body of `double fact(double)`, and
|
|
@@ -1561,16 +3423,19 @@ class CArray
|
|
|
1561
3423
|
def recursive_call (node)
|
|
1562
3424
|
return nil unless @recursion
|
|
1563
3425
|
name, parameters, result_type = @recursion
|
|
1564
|
-
# `fact.call(n - 1)`, the spelling every other C function takes
|
|
1565
|
-
# bare `fact(n - 1)`
|
|
1566
|
-
#
|
|
1567
|
-
#
|
|
1568
|
-
#
|
|
3426
|
+
# `fact.call(n - 1)`, the spelling every other C function takes,
|
|
3427
|
+
# borrowed or written. A bare `fact(n - 1)` reads better as C and is
|
|
3428
|
+
# refused all the same, because a bare call is a spelling this
|
|
3429
|
+
# compiler has taken for itself: `sum(w)` and `sort(w)` are its own
|
|
3430
|
+
# functions, reached by no name the block wrote. A function of yours
|
|
3431
|
+
# is reached through the name that holds it -- which is also what
|
|
3432
|
+
# makes a recursion read like a call to anything else.
|
|
1569
3433
|
if node.receiver.nil? && node.name == name && node.arguments
|
|
1570
3434
|
raise Unsupported.new(
|
|
1571
3435
|
"`#{name}` calls itself the way any C function is called here, " \
|
|
1572
|
-
"as `#{name}.call(...)` -- a bare `#{name}(...)` is
|
|
1573
|
-
"
|
|
3436
|
+
"as `#{name}.call(...)` -- a bare `#{name}(...)` is how this " \
|
|
3437
|
+
"compiler spells its own functions, such as `sum(w)`, and a " \
|
|
3438
|
+
"function of yours is reached through the name that holds it",
|
|
1574
3439
|
node.location)
|
|
1575
3440
|
end
|
|
1576
3441
|
return nil unless C_FUNCTION_CALL_NAMES.include?(node.name) &&
|
|
@@ -1590,6 +3455,111 @@ class CArray
|
|
|
1590
3455
|
RecursiveCall.new(name, built, parameters, result_type, node.location)
|
|
1591
3456
|
end
|
|
1592
3457
|
|
|
3458
|
+
# `int64_t s[4]`, the parameter the generator's own C takes. Held as a
|
|
3459
|
+
# parsed declaration rather than described here, so that what the state
|
|
3460
|
+
# array is checked against is the C the draw will actually be compiled
|
|
3461
|
+
# into -- the same check `poly.call(x, coef)` gets, from the same code.
|
|
3462
|
+
# Parsed on first use: the declaration parser is in c_function.rb,
|
|
3463
|
+
# which is required after this file.
|
|
3464
|
+
def self.random_state_parameter
|
|
3465
|
+
@random_state_parameter ||=
|
|
3466
|
+
CDeclaration.parse("double draw(int64_t state[4])").last.first
|
|
3467
|
+
end
|
|
3468
|
+
|
|
3469
|
+
# `r.rand` and `r.bits` -- a draw written as a method of the generator,
|
|
3470
|
+
# which is how Ruby writes one. `#rand` and `#bits` do these outside a
|
|
3471
|
+
# kernel, and the same names inside it mean the same two things.
|
|
3472
|
+
RANDOM_DRAW_METHODS =
|
|
3473
|
+
{ :random => :random, :randomn => :randomn, :bits => :bits }.freeze
|
|
3474
|
+
|
|
3475
|
+
# `random(rng: r)` -- the same draw as `r.rand`, written as the array
|
|
3476
|
+
# language writes it. `a.random!(rng: r)` fills an array from a
|
|
3477
|
+
# generator, and this is that sentence about one cell, so a reader
|
|
3478
|
+
# moving between the two reads one word rather than matching two up.
|
|
3479
|
+
#
|
|
3480
|
+
# It is the only keyword argument in the subset and the only bare name
|
|
3481
|
+
# in it that Ruby does not have; both are paid for that.
|
|
3482
|
+
RANDOM_KEYWORD_NAMES =
|
|
3483
|
+
{ :random => :random, :randomn => :randomn }.freeze
|
|
3484
|
+
RANDOM_DRAW_KEYWORD = "rng"
|
|
3485
|
+
|
|
3486
|
+
# Returns the node for a draw from a captured CArray::Rng, or nil when
|
|
3487
|
+
# this is not one.
|
|
3488
|
+
def random_call (node)
|
|
3489
|
+
if node.receiver.nil? && (kind = RANDOM_KEYWORD_NAMES[node.name])
|
|
3490
|
+
return build_random_draw(random_generator_name(node), kind, node)
|
|
3491
|
+
end
|
|
3492
|
+
name = captured_name(node.receiver)
|
|
3493
|
+
return nil unless name && @randoms.key?(name)
|
|
3494
|
+
kind = RANDOM_DRAW_METHODS[node.name]
|
|
3495
|
+
refuse_a_generator_call(name, node) unless kind
|
|
3496
|
+
unless (node.arguments ? node.arguments.arguments : []).empty?
|
|
3497
|
+
bounded = node.name == :random ?
|
|
3498
|
+
". `CArray#random!` takes a range for a whole array; a kernel " \
|
|
3499
|
+
"has no bounded draw, so scale the one you get" : ""
|
|
3500
|
+
raise Unsupported.new(
|
|
3501
|
+
"`#{name}.#{node.name}` in a kernel is one draw and takes no " \
|
|
3502
|
+
"arguments#{bounded}",
|
|
3503
|
+
node.location)
|
|
3504
|
+
end
|
|
3505
|
+
build_random_draw(name, kind, node)
|
|
3506
|
+
end
|
|
3507
|
+
|
|
3508
|
+
def build_random_draw (name, kind, node)
|
|
3509
|
+
state = @randoms.fetch(name)
|
|
3510
|
+
@random_names << name unless @random_names.include?(name)
|
|
3511
|
+
# The state travels the way any array handed to a C function whole
|
|
3512
|
+
# does: by address, in the `data` buffer, copied back after the call.
|
|
3513
|
+
# That copy-back is what leaves the generator advanced.
|
|
3514
|
+
@address_arrays << state unless @address_arrays.include?(state)
|
|
3515
|
+
(@address_parameters[state] ||= []) << self.class.random_state_parameter
|
|
3516
|
+
RandomDraw.new(name, state, kind, node.location)
|
|
3517
|
+
end
|
|
3518
|
+
|
|
3519
|
+
# Everything else reached on a generator. There are three spellings
|
|
3520
|
+
# and no fourth, so the message lists them rather than saying only that
|
|
3521
|
+
# this one is not among them.
|
|
3522
|
+
def refuse_a_generator_call (name, node)
|
|
3523
|
+
raise Unsupported.new(
|
|
3524
|
+
"`#{name}` is a generator, and `#{name}.#{node.name}` is not a way " \
|
|
3525
|
+
"to draw from one. A kernel has `#{name}.random` for a double in " \
|
|
3526
|
+
"[0.0, 1.0), `#{name}.randomn` for a standard normal, " \
|
|
3527
|
+
"`#{name}.bits` for the raw word, and `random(rng: #{name})` or " \
|
|
3528
|
+
"`randomn(rng: #{name})` for the first two written as " \
|
|
3529
|
+
"`CArray#random!` writes them",
|
|
3530
|
+
node.location)
|
|
3531
|
+
end
|
|
3532
|
+
|
|
3533
|
+
# Which generator `random` was pointed at, or a refusal saying how to
|
|
3534
|
+
# point it at one. Nothing else is accepted in the parentheses: a
|
|
3535
|
+
# range, a bound, a data type are all things `CArray#random!` takes and
|
|
3536
|
+
# none of them has an answer for one cell.
|
|
3537
|
+
def random_generator_name (node)
|
|
3538
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
3539
|
+
keywords = arguments.last if arguments.last.is_a?(Prism::KeywordHashNode)
|
|
3540
|
+
pairs = keywords ? keywords.elements : []
|
|
3541
|
+
unless arguments.size == 1 && keywords && pairs.size == 1 &&
|
|
3542
|
+
pairs.first.is_a?(Prism::AssocNode) &&
|
|
3543
|
+
pairs.first.key.is_a?(Prism::SymbolNode) &&
|
|
3544
|
+
pairs.first.key.unescaped == RANDOM_DRAW_KEYWORD
|
|
3545
|
+
raise Unsupported.new(
|
|
3546
|
+
"`#{node.name}` in a kernel draws one number from a generator " \
|
|
3547
|
+
"and takes `rng:` and nothing else -- `#{node.name}(rng: r)`, " \
|
|
3548
|
+
"where `r` is a `CArray::Rng` the block closed over",
|
|
3549
|
+
node.location)
|
|
3550
|
+
end
|
|
3551
|
+
name = captured_name(pairs.first.value)
|
|
3552
|
+
unless name && @randoms.key?(name)
|
|
3553
|
+
reached = name ? "`#{name}`" : "what was written there"
|
|
3554
|
+
raise Unsupported.new(
|
|
3555
|
+
"`rng:` takes a `CArray::Rng` the block closed over, and " \
|
|
3556
|
+
"#{reached} is not one. A Ruby `Random` cannot be reached from a " \
|
|
3557
|
+
"kernel at all; `CArray::Rng.new` makes one that can",
|
|
3558
|
+
pairs.first.value.location)
|
|
3559
|
+
end
|
|
3560
|
+
name
|
|
3561
|
+
end
|
|
3562
|
+
|
|
1593
3563
|
# Returns the node for a call on a captured C function, or nil when this
|
|
1594
3564
|
# is not one.
|
|
1595
3565
|
def c_function_call (node)
|
|
@@ -1600,7 +3570,8 @@ class CArray
|
|
|
1600
3570
|
arguments = node.arguments ? node.arguments.arguments : []
|
|
1601
3571
|
unless arguments.size == c_function.arity
|
|
1602
3572
|
raise Unsupported.new(
|
|
1603
|
-
"`#{name}` is `#{c_function}`, so it takes
|
|
3573
|
+
"`#{name}` is `#{c_function.declaration_as(name)}`, so it takes " \
|
|
3574
|
+
"#{c_function.arity} " \
|
|
1604
3575
|
"#{c_function.arity == 1 ? 'argument' : 'arguments'}; " \
|
|
1605
3576
|
"#{arguments.size} #{arguments.size == 1 ? 'was' : 'were'} given",
|
|
1606
3577
|
node.location)
|
|
@@ -1618,6 +3589,16 @@ class CArray
|
|
|
1618
3589
|
# name means a cell in one argument position and the whole array in
|
|
1619
3590
|
# another -- `poly.call(x[i], coef)` says both.
|
|
1620
3591
|
def build_c_function_argument (argument, parameter, c_function)
|
|
3592
|
+
# A local array decays to the pointer the callee wants, as it does in
|
|
3593
|
+
# C -- the name its declaration was written under is already the
|
|
3594
|
+
# address. What the declaration says about it is matched here, as the
|
|
3595
|
+
# block is read, because both halves are written in the block: the
|
|
3596
|
+
# element type is the constructor and the length is a literal. A
|
|
3597
|
+
# captured array is matched at the call instead (`address_buffer`),
|
|
3598
|
+
# neither being knowable until there is an array.
|
|
3599
|
+
if (name = local_array_name(argument))
|
|
3600
|
+
return build_local_array_address(name, argument, parameter, c_function)
|
|
3601
|
+
end
|
|
1621
3602
|
# Inside a function, one of its own pointer parameters is already the
|
|
1622
3603
|
# address the callee wants, and handing it on is what C does -- for a
|
|
1623
3604
|
# `void *` slot as much as for a run of numbers, since neither is
|
|
@@ -1670,12 +3651,24 @@ class CArray
|
|
|
1670
3651
|
ElementWrite.new(name, expression, location, subscripts)
|
|
1671
3652
|
end
|
|
1672
3653
|
|
|
1673
|
-
|
|
1674
|
-
|
|
3654
|
+
# `indices` is given where the node is not a plain `a[i]` -- `a[i] += e`
|
|
3655
|
+
# carries its indices and its value in different places, and the read
|
|
3656
|
+
# it stands for is the same read either way.
|
|
3657
|
+
def build_element_read (node, indices = nil)
|
|
3658
|
+
if (pointer = pointer_subscript(node, indices: indices))
|
|
1675
3659
|
return PointerRead.new(pointer.first, pointer.last, node.location)
|
|
1676
3660
|
end
|
|
3661
|
+
refuse_a_constructor_in_an_expression(node.receiver)
|
|
3662
|
+
if (name = local_array_name(node.receiver))
|
|
3663
|
+
storage, shape = local_array_in_sight(name)
|
|
3664
|
+
arguments = indices || (node.arguments ? node.arguments.arguments : [])
|
|
3665
|
+
return LocalArrayRead.new(
|
|
3666
|
+
name, storage, shape,
|
|
3667
|
+
local_array_subscripts(name, shape, arguments, node.location),
|
|
3668
|
+
node.location)
|
|
3669
|
+
end
|
|
1677
3670
|
array = array_name(node.receiver, node.location)
|
|
1678
|
-
arguments = node.arguments ? node.arguments.arguments : []
|
|
3671
|
+
arguments = indices || (node.arguments ? node.arguments.arguments : [])
|
|
1679
3672
|
subscripts = read_subscripts(array, arguments, node.location)
|
|
1680
3673
|
record_subscripts(array, subscripts)
|
|
1681
3674
|
ElementRead.new(array, subscripts, node.location)
|
|
@@ -1685,6 +3678,15 @@ class CArray
|
|
|
1685
3678
|
@subscripts[array] << subscripts unless @subscripts[array].include?(subscripts)
|
|
1686
3679
|
end
|
|
1687
3680
|
|
|
3681
|
+
# Kept apart from the reads because the two are asked different
|
|
3682
|
+
# questions: every use of an array decides the box a view is
|
|
3683
|
+
# transferred in, while only the writes say which axes the kernel owns
|
|
3684
|
+
# a cell of.
|
|
3685
|
+
def record_write_subscripts (array, subscripts)
|
|
3686
|
+
list = @write_subscripts[array]
|
|
3687
|
+
list << subscripts unless list.include?(subscripts)
|
|
3688
|
+
end
|
|
3689
|
+
|
|
1688
3690
|
def array_name (receiver, location)
|
|
1689
3691
|
name = captured_name(receiver)
|
|
1690
3692
|
unless name && @array_names.include?(name)
|
|
@@ -1769,9 +3771,13 @@ class CArray
|
|
|
1769
3771
|
# in scope, so that the offset is a compile-time constant.
|
|
1770
3772
|
def read_subscript (node, location)
|
|
1771
3773
|
name = index_name(node)
|
|
1772
|
-
|
|
3774
|
+
if name && index_in_scope?(name)
|
|
3775
|
+
return [nil, pinned_subscript(node)] if unbounded_index?(name)
|
|
3776
|
+
return [index_identifier(name), 0]
|
|
3777
|
+
end
|
|
1773
3778
|
if node.is_a?(Prism::CallNode) && [:+, :-].include?(node.name) &&
|
|
1774
3779
|
(receiver = index_name(node.receiver)) && index_in_scope?(receiver)
|
|
3780
|
+
return [nil, pinned_subscript(node)] if unbounded_index?(receiver)
|
|
1775
3781
|
return walked_subscript(node)
|
|
1776
3782
|
end
|
|
1777
3783
|
# Anything else pins the axis at a position the loop does not walk:
|
|
@@ -1785,6 +3791,30 @@ class CArray
|
|
|
1785
3791
|
build(node)
|
|
1786
3792
|
end
|
|
1787
3793
|
|
|
3794
|
+
# An inner index whose range reads a local or a cell. Its range is not
|
|
3795
|
+
# known until the loop is reached, so a subscript it walks cannot be
|
|
3796
|
+
# checked before the kernel runs; it is checked at each access instead,
|
|
3797
|
+
# as `a[b[i]]` is.
|
|
3798
|
+
def unbounded_index? (name)
|
|
3799
|
+
@unbounded_indices.include?(index_identifier(name))
|
|
3800
|
+
end
|
|
3801
|
+
|
|
3802
|
+
# Whether an inner loop's range can be worked out before the kernel
|
|
3803
|
+
# runs: literals, captured integers, the extents, and indices whose own
|
|
3804
|
+
# range can, under `+`, `-` and `*`.
|
|
3805
|
+
def self.boundable_range? (node, unbounded)
|
|
3806
|
+
case node
|
|
3807
|
+
when IntegerLiteral, CaptureRead, BoundsValue then true
|
|
3808
|
+
when IndexVariable then !unbounded.include?(node.name)
|
|
3809
|
+
when UnaryMinus then boundable_range?(node.operand, unbounded)
|
|
3810
|
+
when BinaryOperation
|
|
3811
|
+
[:+, :-, :*].include?(node.operator) &&
|
|
3812
|
+
boundable_range?(node.left, unbounded) &&
|
|
3813
|
+
boundable_range?(node.right, unbounded)
|
|
3814
|
+
else false
|
|
3815
|
+
end
|
|
3816
|
+
end
|
|
3817
|
+
|
|
1788
3818
|
# The one cell a CScalar has: position zero on its one axis, which is a
|
|
1789
3819
|
# pinned subscript like any other and is checked like one.
|
|
1790
3820
|
def cell_subscripts (array, location)
|
|
@@ -1840,10 +3870,39 @@ class CArray
|
|
|
1840
3870
|
raise Unsupported.new("write the offset as `j - c` with c >= 0",
|
|
1841
3871
|
node.location)
|
|
1842
3872
|
end
|
|
1843
|
-
return [receiver.name,
|
|
3873
|
+
return [index_identifier(receiver.name),
|
|
3874
|
+
node.name == :+ ? constant : -constant]
|
|
1844
3875
|
end
|
|
1845
3876
|
offset = pinned_subscript(argument)
|
|
1846
|
-
|
|
3877
|
+
refuse_an_index_in_the_offset(node, receiver.name, offset)
|
|
3878
|
+
[index_identifier(receiver.name),
|
|
3879
|
+
node.name == :+ ? offset : UnaryMinus.new(offset)]
|
|
3880
|
+
end
|
|
3881
|
+
|
|
3882
|
+
# `a[i + r]`, where `r` is an inner loop's index: the subscript walks
|
|
3883
|
+
# with `i`, and an offset is worked out once per call, before any cell,
|
|
3884
|
+
# which an index cannot be. Left to the call, it was refused there
|
|
3885
|
+
# for a reason about an inner loop's range -- true of the code that
|
|
3886
|
+
# happened to read it, and nothing to do with what was written. The
|
|
3887
|
+
# same sum put in a local first is a subscript the kernel works out
|
|
3888
|
+
# and checks at the cell, which is what the message points to.
|
|
3889
|
+
def refuse_an_index_in_the_offset (node, walker, offset)
|
|
3890
|
+
others = []
|
|
3891
|
+
visit = lambda { |item|
|
|
3892
|
+
next unless item.is_a?(Node)
|
|
3893
|
+
others << item.name if item.is_a?(IndexVariable)
|
|
3894
|
+
item.children.each(&visit)
|
|
3895
|
+
}
|
|
3896
|
+
visit.call(offset)
|
|
3897
|
+
return if others.empty?
|
|
3898
|
+
names = others.uniq.map { |name| "`#{name}`" }.join(" and ")
|
|
3899
|
+
raise Unsupported.new(
|
|
3900
|
+
"`#{node.slice}` walks with `#{walker}` and is offset by #{names}, " \
|
|
3901
|
+
"an index as well; an offset is worked out once per call, from " \
|
|
3902
|
+
"literals and captured integers -- put the sum in a local first " \
|
|
3903
|
+
"(`k = #{node.slice}`, then `[k]`) and it is worked out and " \
|
|
3904
|
+
"checked at each cell",
|
|
3905
|
+
node.location)
|
|
1847
3906
|
end
|
|
1848
3907
|
|
|
1849
3908
|
# An index the block names as a parameter reads as a local variable.
|
|
@@ -1860,6 +3919,19 @@ class CArray
|
|
|
1860
3919
|
end
|
|
1861
3920
|
end
|
|
1862
3921
|
|
|
3922
|
+
# An index as this compiler knows it: the name the block wrote, unless
|
|
3923
|
+
# that name belongs to a second loop of the same name, which has an
|
|
3924
|
+
# identifier of its own.
|
|
3925
|
+
def index_identifier (name)
|
|
3926
|
+
@inner_aliases.fetch(name, name)
|
|
3927
|
+
end
|
|
3928
|
+
|
|
3929
|
+
# The same question asked of an identifier a subscript carries, which
|
|
3930
|
+
# for a second loop of the same name is not the name the block wrote.
|
|
3931
|
+
def index_identifier_in_scope? (name)
|
|
3932
|
+
@outer_names.include?(name) || @inner_aliases.value?(name)
|
|
3933
|
+
end
|
|
3934
|
+
|
|
1863
3935
|
def index_in_scope? (name)
|
|
1864
3936
|
@outer_names.include?(name) || @inner_names.include?(name)
|
|
1865
3937
|
end
|
|
@@ -1930,8 +4002,10 @@ class CArray
|
|
|
1930
4002
|
# A non-negative literal exponent can still be squared out; anything
|
|
1931
4003
|
# else has to be asked for in floating point.
|
|
1932
4004
|
def build_power (receiver, exponent, location)
|
|
1933
|
-
|
|
1934
|
-
|
|
4005
|
+
power_node(build(receiver), build(exponent), location)
|
|
4006
|
+
end
|
|
4007
|
+
|
|
4008
|
+
def power_node (base, power, location)
|
|
1935
4009
|
if power.is_a?(IntegerLiteral) && power.value < 0
|
|
1936
4010
|
raise Unsupported.new(
|
|
1937
4011
|
"a negative exponent gives a Rational in Ruby; write `1.0 / x ** n`",
|
|
@@ -1943,8 +4017,12 @@ class CArray
|
|
|
1943
4017
|
def build_math_call (node)
|
|
1944
4018
|
function = MATH_FUNCTIONS[node.name]
|
|
1945
4019
|
unless function
|
|
1946
|
-
|
|
1947
|
-
|
|
4020
|
+
if (reason = REFUSED_MATH[node.name])
|
|
4021
|
+
raise Unsupported.new("Math.#{node.name} is not compiled: " \
|
|
4022
|
+
"#{reason}", node.location)
|
|
4023
|
+
end
|
|
4024
|
+
raise Unsupported.new(
|
|
4025
|
+
"Math.#{node.name} is not a name this compiles", node.location)
|
|
1948
4026
|
end
|
|
1949
4027
|
arguments = node.arguments ? node.arguments.arguments : []
|
|
1950
4028
|
expected = [:atan2, :hypot].include?(node.name) ? 2 : 1
|