carray-jit 0.1.2 → 0.1.3
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 +771 -3
- data/README.md +7 -6
- data/carray-jit.gemspec +1 -3
- data/docs/00_Introduction.md +4 -3
- data/docs/01_GettingStarted.md +1 -1
- data/docs/02_KernelShapes.md +93 -14
- data/docs/03_SupportedFeatures.md +582 -26
- data/docs/04_Compiling.md +33 -6
- data/docs/05_DesignNotes.md +3 -3
- data/docs/06_Cheatsheet.md +198 -5
- data/docs/07_StepByStep.ja.md +534 -0
- data/docs/07_StepByStep.md +535 -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 +2077 -136
- data/lib/carray/jit/block_reader.rb +37 -6
- data/lib/carray/jit/c_function.rb +613 -76
- data/lib/carray/jit/c_generator.rb +1595 -156
- data/lib/carray/jit/call.rb +68 -0
- data/lib/carray/jit/compiler.rb +75 -11
- data/lib/carray/jit/kernel.rb +369 -32
- data/lib/carray/jit/node.rb +359 -9
- data/lib/carray/jit/sorting_networks.rb +182 -0
- data/lib/carray/jit/type_assignment.rb +371 -34
- data/lib/carray/jit/version.rb +1 -1
- data/lib/carray/jit.rb +560 -64
- metadata +22 -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
|
|
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,
|
|
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
|
|
@@ -307,6 +504,7 @@ class CArray
|
|
|
307
504
|
@array_ranks = {}
|
|
308
505
|
@inner_ranges = {}
|
|
309
506
|
@subscripts = Hash.new { |hash, key| hash[key] = [] }
|
|
507
|
+
@write_subscripts = Hash.new { |hash, key| hash[key] = [] }
|
|
310
508
|
@given_rank = rank
|
|
311
509
|
@steps = steps
|
|
312
510
|
@contract = contract
|
|
@@ -316,6 +514,23 @@ class CArray
|
|
|
316
514
|
@free_names = []
|
|
317
515
|
@index_names = []
|
|
318
516
|
@local_names = []
|
|
517
|
+
# The arrays the body made, by name: [storage type, shape] for the
|
|
518
|
+
# declaration in sight. Kept beside @local_names rather than in it,
|
|
519
|
+
# because the two are different things under one syntax and a name
|
|
520
|
+
# holds one or the other -- which is what lets each say so.
|
|
521
|
+
@local_array_names = []
|
|
522
|
+
@local_arrays = {}
|
|
523
|
+
# One entry per C array the body declares, as
|
|
524
|
+
# [scope, name, storage, shape], so that the room they take together
|
|
525
|
+
# can be counted once the body has been read. Two sibling loops that
|
|
526
|
+
# each declare `w` are one entry: their blocks do not overlap, so
|
|
527
|
+
# neither do their frames.
|
|
528
|
+
@local_array_declarations = []
|
|
529
|
+
@clears_a_local_array = false
|
|
530
|
+
# The locals each scope holds, innermost last: the kernel's block --
|
|
531
|
+
# or a function's -- and one for every inner loop's block around the
|
|
532
|
+
# statement being built. `while` and `if` make none, as in Ruby.
|
|
533
|
+
@scopes = [[]]
|
|
319
534
|
@assigned_names = []
|
|
320
535
|
@scalar_names = []
|
|
321
536
|
@c_function_names = []
|
|
@@ -323,6 +538,7 @@ class CArray
|
|
|
323
538
|
@pointer_names = []
|
|
324
539
|
@address_arrays = []
|
|
325
540
|
@address_parameters = {}
|
|
541
|
+
@random_names = []
|
|
326
542
|
@read_offsets = Hash.new { |hash, key| hash[key] = [] }
|
|
327
543
|
@written_arrays = []
|
|
328
544
|
analyze
|
|
@@ -377,18 +593,29 @@ class CArray
|
|
|
377
593
|
end
|
|
378
594
|
|
|
379
595
|
# A write is addressed the way a read is: every axis either walks with an
|
|
380
|
-
#
|
|
381
|
-
# before the first
|
|
596
|
+
# index the kernel is running -- an outer one or an inner one, at
|
|
597
|
+
# whatever offset -- or is pinned at a position known before the first
|
|
598
|
+
# cell.
|
|
382
599
|
#
|
|
383
600
|
# Two iterations may land on the same cell -- `box[0]` puts every one of
|
|
384
601
|
# them there -- and that is not an ambiguity: the extent states the
|
|
385
602
|
# order, so what the array holds afterwards is what the same Ruby loop
|
|
386
603
|
# would leave in it. An inner loop already rests on exactly that, an
|
|
387
604
|
# accumulator being one cell written once per pass.
|
|
605
|
+
#
|
|
606
|
+
# An inner index is in that position and not a weaker one: its loop
|
|
607
|
+
# states its extent the way the kernel's own extents do, so the cell it
|
|
608
|
+
# reaches is as knowable as `i`'s and is bounds-checked with it. What
|
|
609
|
+
# the write is for is a row of workspace per outer cell -- filling one
|
|
610
|
+
# and walking back down it is the shape a small dense solve has, and it
|
|
611
|
+
# was the reason to add an axis rather than split a body into kernels
|
|
612
|
+
# that each pay a call. It was refused until it was noticed that
|
|
613
|
+
# `kk = k + 0` on the way in compiled it anyway, as a scatter: the rule
|
|
614
|
+
# was stopping the spelling rather than the thing.
|
|
388
615
|
def walking_subscripts? (subscripts)
|
|
389
616
|
subscripts.all? { |index, offset|
|
|
390
617
|
if index
|
|
391
|
-
|
|
618
|
+
index_identifier_in_scope?(index)
|
|
392
619
|
else
|
|
393
620
|
!offset.is_a?(Node) || Analyzer.fixed_subscript?(offset)
|
|
394
621
|
end
|
|
@@ -452,9 +679,89 @@ class CArray
|
|
|
452
679
|
node.is_a?(Prism::CallNode) && node.receiver.nil? && node.name == :raise
|
|
453
680
|
end
|
|
454
681
|
|
|
682
|
+
def scan_address_array_names (node, c_functions)
|
|
683
|
+
# Two passes rather than one, because the arguments of a call sit
|
|
684
|
+
# under an ArgumentsNode: pruning them out of a single walk would
|
|
685
|
+
# mean matching a grandchild, and the set is the clearer statement.
|
|
686
|
+
addressed = {}.compare_by_identity
|
|
687
|
+
handed = []
|
|
688
|
+
collect_address_arguments(node, c_functions, handed, addressed)
|
|
689
|
+
return [] if handed.empty?
|
|
690
|
+
as_cells = []
|
|
691
|
+
collect_cell_names(node, addressed, as_cells)
|
|
692
|
+
(handed.uniq - as_cells - scan_local_array_names(node))
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
# The names standing bare where a declaration wants a pointer at
|
|
696
|
+
# numbers, and the argument nodes they stand in.
|
|
697
|
+
def collect_address_arguments (node, c_functions, handed, addressed)
|
|
698
|
+
return unless node
|
|
699
|
+
return if raise_call?(node)
|
|
700
|
+
if node.is_a?(Prism::CallNode) &&
|
|
701
|
+
C_FUNCTION_CALL_NAMES.include?(node.name) &&
|
|
702
|
+
(function = captured_name(node.receiver)) &&
|
|
703
|
+
c_functions.key?(function)
|
|
704
|
+
parameters = c_functions.fetch(function).parameters
|
|
705
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
706
|
+
arguments.each_with_index do |argument, position|
|
|
707
|
+
next unless parameters[position]&.indexable?
|
|
708
|
+
given = captured_name(argument)
|
|
709
|
+
next unless given
|
|
710
|
+
handed << given
|
|
711
|
+
addressed[argument] = true
|
|
712
|
+
end
|
|
713
|
+
end
|
|
714
|
+
node.compact_child_nodes.each { |child|
|
|
715
|
+
collect_address_arguments(child, c_functions, handed, addressed)
|
|
716
|
+
}
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
# Every other way a name is reached: subscripted, or bare where a bare
|
|
720
|
+
# name is a cell. One of these takes a name back out of the answer.
|
|
721
|
+
def collect_cell_names (node, addressed, as_cells)
|
|
722
|
+
return unless node
|
|
723
|
+
return if raise_call?(node)
|
|
724
|
+
return if addressed[node]
|
|
725
|
+
if node.is_a?(Prism::CallNode) && node.name == :[] &&
|
|
726
|
+
(subscripted = captured_name(node.receiver))
|
|
727
|
+
as_cells << subscripted
|
|
728
|
+
end
|
|
729
|
+
if (bare = captured_name(node)) &&
|
|
730
|
+
!(node.is_a?(Prism::CallNode) &&
|
|
731
|
+
C_FUNCTION_CALL_NAMES.include?(node.name))
|
|
732
|
+
as_cells << bare
|
|
733
|
+
end
|
|
734
|
+
node.compact_child_nodes.each { |child|
|
|
735
|
+
collect_cell_names(child, addressed, as_cells)
|
|
736
|
+
}
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
def scan_local_array_names (node)
|
|
740
|
+
return [] unless node
|
|
741
|
+
names = []
|
|
742
|
+
if node.is_a?(Prism::LocalVariableWriteNode) &&
|
|
743
|
+
self.class.local_array_constructor?(node.value)
|
|
744
|
+
names << node.name
|
|
745
|
+
end
|
|
746
|
+
node.compact_child_nodes.each { |child|
|
|
747
|
+
names.concat(scan_local_array_names(child))
|
|
748
|
+
}
|
|
749
|
+
names.uniq
|
|
750
|
+
end
|
|
751
|
+
|
|
455
752
|
def names_constants (node)
|
|
456
753
|
return [] unless node
|
|
457
754
|
return [] if raise_call?(node)
|
|
755
|
+
# The receiver of a constructor is part of a spelling rather than a
|
|
756
|
+
# name the block reached outside for: `CArray.double(9)` says what to
|
|
757
|
+
# declare, and the class in front of it is not a value to look up.
|
|
758
|
+
# Left in, it is looked up, found to be a Class, and refused for
|
|
759
|
+
# being one -- which says nothing about the line that was written.
|
|
760
|
+
if self.class.local_array_constructor?(node)
|
|
761
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
762
|
+
return arguments.flat_map { |argument| names_constants(argument) } +
|
|
763
|
+
names_constants(node.block)
|
|
764
|
+
end
|
|
458
765
|
# `Foo::TABLE` names one thing, and `Foo` on its own names none of
|
|
459
766
|
# it, so a path is read whole and not descended into.
|
|
460
767
|
return [node.slice.to_sym] if node.is_a?(Prism::ConstantPathNode)
|
|
@@ -509,13 +816,24 @@ class CArray
|
|
|
509
816
|
# A block that ends in an assignment still has that assignment's
|
|
510
817
|
# value, which is what Ruby says it has -- so jit_map keeps the
|
|
511
818
|
# statement and reads the cell back for the value.
|
|
512
|
-
map_assignment = @map && last.is_a?(Prism::LocalVariableWriteNode)
|
|
819
|
+
map_assignment = @map && (last.is_a?(Prism::LocalVariableWriteNode) ||
|
|
820
|
+
last.is_a?(Prism::LocalVariableOperatorWriteNode))
|
|
513
821
|
returns_value = (@function && @returns) || (@map && !map_assignment) ||
|
|
514
822
|
(@contract &&
|
|
515
823
|
!(last.is_a?(Prism::CallNode) && last.name == :[]=))
|
|
516
824
|
built = statements[0..-2].map { |node| build_statement(node) }
|
|
517
825
|
if map_assignment
|
|
518
826
|
built << build_statement(last)
|
|
827
|
+
# `w = CArray.double(4)` as the last line: the block's value in Ruby
|
|
828
|
+
# is the array, and a cell of the result has nowhere to put one.
|
|
829
|
+
if built.last.is_a?(LocalArrayDeclaration)
|
|
830
|
+
raise Unsupported.new(
|
|
831
|
+
"this block ends by making `#{built.last.name}`, so its value " \
|
|
832
|
+
"is an array and there is no cell for one to go in; end with " \
|
|
833
|
+
"the number you want -- a cell of it, or `sum(#{built.last.name})` " \
|
|
834
|
+
"and its kin",
|
|
835
|
+
last.location)
|
|
836
|
+
end
|
|
519
837
|
@map_value = build_name_read(last.name, last.location)
|
|
520
838
|
else
|
|
521
839
|
built << (returns_value ? build(last) : build_statement(last))
|
|
@@ -544,22 +862,52 @@ class CArray
|
|
|
544
862
|
end
|
|
545
863
|
end
|
|
546
864
|
|
|
547
|
-
# An array the kernel writes is written once per outer cell
|
|
548
|
-
#
|
|
549
|
-
#
|
|
865
|
+
# An array the kernel writes at `a[i]` is written once per outer cell,
|
|
866
|
+
# and reading it at `a[j]` for an inner `j` would reach cells other
|
|
867
|
+
# outer iterations own -- an order no extent states.
|
|
868
|
+
#
|
|
869
|
+
# What decides that is the axis the outer indices pick the cell by, not
|
|
870
|
+
# the array. `work[i, k] = ...` gives outer cell i a row of its own,
|
|
871
|
+
# and `work[i, k]`, `work[i, j]` or `work[i, position]` are that row
|
|
872
|
+
# being read back inside the same cell, in the order the body states.
|
|
873
|
+
# So a read carrying an inner index has to address every axis some
|
|
874
|
+
# write walks with an outer index with that same index -- at whatever
|
|
875
|
+
# offset, `work[i-1, k]` being the row before this one and a recurrence
|
|
876
|
+
# like any other displaced read. Where it does, the inner index is
|
|
877
|
+
# roaming inside the cell the outer ones already picked, and whatever
|
|
878
|
+
# the writes do along that axis they do there too.
|
|
550
879
|
def verify_written_arrays_are_not_read_through_inner_indices
|
|
551
880
|
@written_arrays.each do |array|
|
|
881
|
+
writes = @write_subscripts[array]
|
|
552
882
|
@subscripts[array].each do |per_axis|
|
|
553
|
-
per_axis.
|
|
554
|
-
|
|
883
|
+
inner = per_axis.map(&:first).find { |index|
|
|
884
|
+
@inner_names_seen.include?(index)
|
|
885
|
+
}
|
|
886
|
+
spelled = @index_sources.fetch(inner, inner) if inner
|
|
887
|
+
next unless inner
|
|
888
|
+
per_axis.each_with_index do |(index, _), axis|
|
|
889
|
+
name = outer_name_written(writes, axis)
|
|
890
|
+
next if name.nil? || name == index
|
|
555
891
|
raise Unsupported.new(
|
|
556
|
-
"`#{array}` is written
|
|
557
|
-
"read through the inner index
|
|
892
|
+
"`#{array}` is written on axis #{axis} through `#{name}`, " \
|
|
893
|
+
"so it cannot also be read through the inner index " \
|
|
894
|
+
"`#{spelled}`: a read that carries one addresses axis " \
|
|
895
|
+
"#{axis} through `#{name}` too, or it reaches cells another " \
|
|
896
|
+
"outer iteration owns")
|
|
558
897
|
end
|
|
559
898
|
end
|
|
560
899
|
end
|
|
561
900
|
end
|
|
562
901
|
|
|
902
|
+
# The outer index the writes walk one axis with, where they agree on
|
|
903
|
+
# one. Writes that pin the axis or work its position out say nothing
|
|
904
|
+
# about which cell is whose, and so name nothing here.
|
|
905
|
+
def outer_name_written (writes, axis)
|
|
906
|
+
names = writes.map { |written| written[axis]&.first }
|
|
907
|
+
.select { |name| @outer_names.include?(name) }.uniq
|
|
908
|
+
names.size == 1 ? names.first : nil
|
|
909
|
+
end
|
|
910
|
+
|
|
563
911
|
# Turns `c[i,j] = a[i,k] * b[k,j]` into the loops it stands for.
|
|
564
912
|
#
|
|
565
913
|
# Which indices are summed is not the assignment's business: a repeated
|
|
@@ -593,6 +941,23 @@ class CArray
|
|
|
593
941
|
"array of your own")
|
|
594
942
|
end
|
|
595
943
|
|
|
944
|
+
# And one expression is all of it: a local assigned before the summand
|
|
945
|
+
# is a run of statements, which a contraction has no room for -- the
|
|
946
|
+
# same reason it takes no local array. Said here, because what said
|
|
947
|
+
# it before was the type assignment, which walks the accumulator's
|
|
948
|
+
# zero -- taken from the summand, above the loop the local is
|
|
949
|
+
# assigned in -- and reported the local as read before it is assigned.
|
|
950
|
+
leading = statements[0..-2].grep(Assignment).map(&:name)
|
|
951
|
+
unless leading.empty?
|
|
952
|
+
names = leading.uniq.map { |name| "`#{name}`" }.join(" and ")
|
|
953
|
+
raise Unsupported.new(
|
|
954
|
+
"#{names} #{leading.uniq.size == 1 ? 'is' : 'are'} assigned " \
|
|
955
|
+
"before the summand, and a contraction is one expression: there " \
|
|
956
|
+
"is no run of statements to assign in. Write the value into the " \
|
|
957
|
+
"expression, or write the loop with jit_for",
|
|
958
|
+
statements.first.location)
|
|
959
|
+
end
|
|
960
|
+
|
|
596
961
|
write = writes.first
|
|
597
962
|
unless write
|
|
598
963
|
# The returned form: the free indices, in the order the block named
|
|
@@ -664,6 +1029,7 @@ class CArray
|
|
|
664
1029
|
def classify_indices (statements, write)
|
|
665
1030
|
counts = Hash.new(0)
|
|
666
1031
|
subscripts_of(statements, write).each { |index, _| counts[index] += 1 }
|
|
1032
|
+
refuse_a_gathered_index(statements, write)
|
|
667
1033
|
|
|
668
1034
|
missing = @index_names - counts.keys
|
|
669
1035
|
unless missing.empty?
|
|
@@ -671,25 +1037,84 @@ class CArray
|
|
|
671
1037
|
"#{missing.map { |name| "`#{name}`" }.join(', ')} " \
|
|
672
1038
|
"#{missing.size == 1 ? 'names no axis' : 'name no axis'} here")
|
|
673
1039
|
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
|
-
#
|
|
1040
|
+
# Naming the result's axes says which indices are free, and says it
|
|
1041
|
+
# of all of them: what is not named is summed, at however few
|
|
1042
|
+
# positions it sits. So a named index is free however often it
|
|
1043
|
+
# appears -- twice is what a point number does, and `square[a,a]` is
|
|
1044
|
+
# the diagonal rather than a trace -- and a parameter is summed
|
|
1045
|
+
# whether it repeats or not, which is what lets `"ik->i"` be written.
|
|
1046
|
+
#
|
|
1047
|
+
# Where nothing is named the convention decides instead, and there a
|
|
1048
|
+
# repetition is what makes a sum: an index at one position is free,
|
|
1049
|
+
# because a list that would have said otherwise was not given.
|
|
681
1050
|
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]
|
|
1051
|
+
return [@free_indices, @index_names - @free_indices]
|
|
688
1052
|
end
|
|
689
1053
|
[@index_names.select { |name| counts[name] == 1 },
|
|
690
1054
|
@index_names.select { |name| counts[name] > 1 }]
|
|
691
1055
|
end
|
|
692
1056
|
|
|
1057
|
+
# An index inside a computed subscript -- `m[i, idx[k]]` -- is read at a
|
|
1058
|
+
# position, but not at a position of the array it stands in: it
|
|
1059
|
+
# addresses an axis of `idx`. A contraction is a count of positions,
|
|
1060
|
+
# and this one cannot be counted either way. Counting it makes
|
|
1061
|
+
# `m[i, idx[k]] * v[k]` a sum over k, which is what the notation says,
|
|
1062
|
+
# and makes `a[i, k] * w[idx[i]]` a sum over i, which is not what
|
|
1063
|
+
# anybody writing a gather means. Not counting it leaves the first one
|
|
1064
|
+
# a free index and the answer an outer product -- a whole matrix where
|
|
1065
|
+
# the reader asked for a vector, quietly.
|
|
1066
|
+
#
|
|
1067
|
+
# So it is refused, and the loop it means is written with jit_for,
|
|
1068
|
+
# where the gather is the ordinary computed subscript it already is.
|
|
1069
|
+
def refuse_a_gathered_index (statements, write)
|
|
1070
|
+
summand = write.is_a?(ElementWrite) ? write.expression : write
|
|
1071
|
+
found = []
|
|
1072
|
+
# Inside a gathering subscript every index is one of these, however
|
|
1073
|
+
# deep: `a[idx[jdx[k]]]` gathers twice and k is no more a position of
|
|
1074
|
+
# `a` for it.
|
|
1075
|
+
inside = lambda do |node|
|
|
1076
|
+
return unless node.is_a?(Node)
|
|
1077
|
+
subscript_indices(node).each do |index|
|
|
1078
|
+
found << index if @index_names.include?(index)
|
|
1079
|
+
end
|
|
1080
|
+
gathered_subscripts(node).each { |offset| inside.call(offset) }
|
|
1081
|
+
node.children.each { |child| inside.call(child) }
|
|
1082
|
+
end
|
|
1083
|
+
walk = lambda do |node|
|
|
1084
|
+
return unless node.is_a?(Node)
|
|
1085
|
+
gathered_subscripts(node).each { |offset| inside.call(offset) }
|
|
1086
|
+
node.children.each { |child| walk.call(child) }
|
|
1087
|
+
end
|
|
1088
|
+
(statements[0..-2] + [summand]).each { |statement| walk.call(statement) }
|
|
1089
|
+
return if found.empty?
|
|
1090
|
+
names = found.uniq.map { |name| "`#{name}`" }.join(" and ")
|
|
1091
|
+
raise Unsupported.new(
|
|
1092
|
+
"#{names} #{found.uniq.size == 1 ? 'stands' : 'stand'} inside a " \
|
|
1093
|
+
"subscript the kernel works out, where a contraction cannot count " \
|
|
1094
|
+
"it: an index there addresses an axis of the array doing the " \
|
|
1095
|
+
"gathering, not of the array being read, so whether it is summed " \
|
|
1096
|
+
"or free is not something the notation says. Write the loop with " \
|
|
1097
|
+
"jit_for",
|
|
1098
|
+
summand.location)
|
|
1099
|
+
end
|
|
1100
|
+
|
|
1101
|
+
# The indices a read names directly, as the symbols a subscript pair
|
|
1102
|
+
# carries rather than as nodes.
|
|
1103
|
+
def subscript_indices (node)
|
|
1104
|
+
return [] unless node.is_a?(ElementRead) || node.is_a?(MaskTest)
|
|
1105
|
+
node.subscripts.filter_map { |index, _| index }
|
|
1106
|
+
end
|
|
1107
|
+
|
|
1108
|
+
# The subscript expressions a read works out, which are the ones an
|
|
1109
|
+
# index can hide in. A plain `a[i]` or `a[i-1]` carries its index in
|
|
1110
|
+
# the pair itself and has none of these.
|
|
1111
|
+
def gathered_subscripts (node)
|
|
1112
|
+
return [] unless node.is_a?(ElementRead) || node.is_a?(MaskTest)
|
|
1113
|
+
node.subscripts.filter_map { |index, offset|
|
|
1114
|
+
offset if index.nil? && offset.is_a?(Node)
|
|
1115
|
+
}
|
|
1116
|
+
end
|
|
1117
|
+
|
|
693
1118
|
# Every subscript on the right-hand side: the summand, and whatever the
|
|
694
1119
|
# locals before it read.
|
|
695
1120
|
def subscripts_of (statements, write)
|
|
@@ -706,22 +1131,6 @@ class CArray
|
|
|
706
1131
|
collected.reject { |index, _| index.nil? }
|
|
707
1132
|
end
|
|
708
1133
|
|
|
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
1134
|
def describe_index_mismatch (written, free, summed)
|
|
726
1135
|
summed_on_left = written & summed
|
|
727
1136
|
unless summed_on_left.empty?
|
|
@@ -729,6 +1138,16 @@ class CArray
|
|
|
729
1138
|
# the result's axes, and the one that repeats is only the reason
|
|
730
1139
|
# the convention could not see it.
|
|
731
1140
|
named = written.map { |name| ":#{name}" }.join(", ")
|
|
1141
|
+
if @free_indices
|
|
1142
|
+
# A list was given, so what is summed is what the list left out.
|
|
1143
|
+
# An axis of the left-hand side that is not in it is the list
|
|
1144
|
+
# falling short of the result, not the index being a sum.
|
|
1145
|
+
listed = summed_on_left.map { |name| "`#{name}`" }.join(", ")
|
|
1146
|
+
return "#{listed} #{summed_on_left.size == 1 ? 'is an axis' : 'are axes'} " \
|
|
1147
|
+
"of the left-hand side and #{summed_on_left.size == 1 ? 'was' : 'were'} " \
|
|
1148
|
+
"not named. Naming the result's axes names all of them, and " \
|
|
1149
|
+
"what is left out is summed: `CArray.jit_contract(#{named})`"
|
|
1150
|
+
end
|
|
732
1151
|
return "#{summed_on_left.map { |name| "`#{name}`" }.join(', ')} " \
|
|
733
1152
|
"#{summed_on_left.size == 1 ? 'is repeated' : 'are repeated'} " \
|
|
734
1153
|
"on the right, so #{summed_on_left.size == 1 ? 'it is' : 'they are'} " \
|
|
@@ -768,6 +1187,19 @@ class CArray
|
|
|
768
1187
|
# block's locals, and the indices -- an index called `contraction` would
|
|
769
1188
|
# have shared the identifier with the accumulator and the sum would have
|
|
770
1189
|
# come out zero, with nothing said.
|
|
1190
|
+
# The identifier an inner index gets in the generated C: its own name
|
|
1191
|
+
# the first time it is written, and a numbered one after that. Every
|
|
1192
|
+
# name the block has said is avoided, so nothing collides with a local
|
|
1193
|
+
# or another index.
|
|
1194
|
+
def free_index_name (index)
|
|
1195
|
+
return index unless @inner_sources_seen.include?(index)
|
|
1196
|
+
taken = @local_names + @index_names + @outer_names + @inner_names +
|
|
1197
|
+
@inner_names_seen + @contracted_names
|
|
1198
|
+
suffix = 2
|
|
1199
|
+
suffix += 1 while taken.include?(:"#{index}__#{suffix}")
|
|
1200
|
+
:"#{index}__#{suffix}"
|
|
1201
|
+
end
|
|
1202
|
+
|
|
771
1203
|
def free_local_name
|
|
772
1204
|
taken = @local_names + @index_names + @outer_names + @inner_names +
|
|
773
1205
|
@contracted_names
|
|
@@ -785,10 +1217,22 @@ class CArray
|
|
|
785
1217
|
extract_block(result.value)
|
|
786
1218
|
end
|
|
787
1219
|
|
|
1220
|
+
# Every spelling that binds a local: `x = e`, the operator forms, which
|
|
1221
|
+
# read the name and write it back, and a name on the left of a parallel
|
|
1222
|
+
# assignment. A name the block assigns is not a name it captures, and
|
|
1223
|
+
# one that is an array outside is the array the whole-array spelling
|
|
1224
|
+
# writes -- so an operator form left out here would have `out += a`
|
|
1225
|
+
# reaching for a capture that is not there.
|
|
1226
|
+
ASSIGNMENT_NODES = [Prism::LocalVariableWriteNode,
|
|
1227
|
+
Prism::LocalVariableOperatorWriteNode,
|
|
1228
|
+
Prism::LocalVariableOrWriteNode,
|
|
1229
|
+
Prism::LocalVariableAndWriteNode,
|
|
1230
|
+
Prism::LocalVariableTargetNode].freeze
|
|
1231
|
+
|
|
788
1232
|
def collect_assigned_names (node)
|
|
789
1233
|
return [] unless node
|
|
790
1234
|
names = []
|
|
791
|
-
names << node.name if node.is_a?(
|
|
1235
|
+
names << node.name if ASSIGNMENT_NODES.any? { |kind| node.is_a?(kind) }
|
|
792
1236
|
node.compact_child_nodes.each do |child|
|
|
793
1237
|
names.concat(collect_assigned_names(child))
|
|
794
1238
|
end
|
|
@@ -827,7 +1271,23 @@ class CArray
|
|
|
827
1271
|
end
|
|
828
1272
|
|
|
829
1273
|
if @function
|
|
830
|
-
|
|
1274
|
+
# A body written for `jit_call` takes no parameters: the names are
|
|
1275
|
+
# in the declaration, where they also name the locals the call
|
|
1276
|
+
# reads. Written in both places they could disagree, so the block
|
|
1277
|
+
# is refused for naming any.
|
|
1278
|
+
if @declared_parameters
|
|
1279
|
+
unless requireds.empty?
|
|
1280
|
+
raise Unsupported.new(
|
|
1281
|
+
"this body's parameters are the ones its declaration named " \
|
|
1282
|
+
"-- #{@declared_parameters.map { |n| "`#{n}`" }.join(', ')} " \
|
|
1283
|
+
"-- and the block names #{requireds.map { |p| "`#{p.name}`" }.join(', ')} " \
|
|
1284
|
+
"again. Written in two places they could disagree; leave " \
|
|
1285
|
+
"the block's off")
|
|
1286
|
+
end
|
|
1287
|
+
@parameter_names = @declared_parameters
|
|
1288
|
+
else
|
|
1289
|
+
@parameter_names = requireds.map(&:name)
|
|
1290
|
+
end
|
|
831
1291
|
@index_names = []
|
|
832
1292
|
@outer_names = []
|
|
833
1293
|
return
|
|
@@ -955,31 +1415,34 @@ class CArray
|
|
|
955
1415
|
@uses_undef = true
|
|
956
1416
|
return MaskWrite.new(node.name, node.location)
|
|
957
1417
|
end
|
|
958
|
-
|
|
959
|
-
#
|
|
960
|
-
#
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
return whole_array_write(node.name, expression, node.location)
|
|
966
|
-
end
|
|
967
|
-
# An index is the loop's, not the block's. Ruby reads `i = 2` as
|
|
968
|
-
# rebinding the parameter and leaves the loop alone; the C would
|
|
969
|
-
# assign to the counter, so the loop would walk somewhere else --
|
|
970
|
-
# to cells outside the array, given a value outside its extent.
|
|
971
|
-
# The two do not mean the same thing, so this is not compiled.
|
|
972
|
-
if index_in_scope?(node.name)
|
|
973
|
-
raise Unsupported.new(
|
|
974
|
-
"`#{node.name}` is a loop index, and assigning to it here would " \
|
|
975
|
-
"move the loop rather than the value: in Ruby the same line " \
|
|
976
|
-
"rebinds the parameter and the loop runs on. Use a local of " \
|
|
977
|
-
"another name",
|
|
978
|
-
node.location)
|
|
1418
|
+
# A constructor is read before the right-hand side is built, the way
|
|
1419
|
+
# `out = UNDEF` above is: what it says is a declaration, not a value
|
|
1420
|
+
# to compute and store.
|
|
1421
|
+
if (made = read_local_array_constructor(node.value))
|
|
1422
|
+
storage, shape, zeroed = made
|
|
1423
|
+
return declare_local_array(node.name, storage, shape, zeroed,
|
|
1424
|
+
node.location, node.depth)
|
|
979
1425
|
end
|
|
980
|
-
|
|
981
|
-
|
|
1426
|
+
expression = build(node.value)
|
|
1427
|
+
assign_local(node.name, expression, node.location, node.depth)
|
|
1428
|
+
when Prism::LocalVariableOperatorWriteNode
|
|
1429
|
+
build_operator_assignment(node)
|
|
1430
|
+
when Prism::MultiWriteNode
|
|
1431
|
+
build_parallel_assignment(node)
|
|
1432
|
+
when Prism::IndexOperatorWriteNode
|
|
1433
|
+
build_operator_element_write(node)
|
|
1434
|
+
when Prism::LocalVariableOrWriteNode, Prism::LocalVariableAndWriteNode,
|
|
1435
|
+
Prism::IndexOrWriteNode, Prism::IndexAndWriteNode
|
|
1436
|
+
raise Unsupported.new(
|
|
1437
|
+
"`#{node.is_a?(Prism::LocalVariableOrWriteNode) ||
|
|
1438
|
+
node.is_a?(Prism::IndexOrWriteNode) ? '||=' : '&&='}` asks " \
|
|
1439
|
+
"whether the value is already nil or false, which a number in a " \
|
|
1440
|
+
"kernel never is; write the assignment out",
|
|
1441
|
+
node.location)
|
|
982
1442
|
when Prism::CallNode
|
|
1443
|
+
if (intrinsic = build_intrinsic_statement(node))
|
|
1444
|
+
return intrinsic
|
|
1445
|
+
end
|
|
983
1446
|
if node.receiver.nil? && node.name == :printf
|
|
984
1447
|
return build_print(node)
|
|
985
1448
|
end
|
|
@@ -989,7 +1452,8 @@ class CArray
|
|
|
989
1452
|
# pointed -- which is the whole reason C has statements that are
|
|
990
1453
|
# calls. Everything else keeps the refusal below: a computation
|
|
991
1454
|
# standing where a statement stands is a line that does nothing.
|
|
992
|
-
if (call = recursive_call(node) || c_function_call(node)
|
|
1455
|
+
if (call = recursive_call(node) || c_function_call(node) ||
|
|
1456
|
+
random_call(node))
|
|
993
1457
|
@calls_for_effect = true
|
|
994
1458
|
return CallStatement.new(call, node.location)
|
|
995
1459
|
end
|
|
@@ -1003,13 +1467,58 @@ class CArray
|
|
|
1003
1467
|
when Prism::BreakNode
|
|
1004
1468
|
build_loop_jump(node, LoopStop, "break")
|
|
1005
1469
|
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)
|
|
1470
|
+
refuse_as_a_statement(node, statement_description(node))
|
|
1010
1471
|
end
|
|
1011
1472
|
end
|
|
1012
1473
|
|
|
1474
|
+
# What a body may hold, said in one place: it was said in two, and they
|
|
1475
|
+
# had already come apart -- one of them listing `step` and the other
|
|
1476
|
+
# not.
|
|
1477
|
+
def refuse_as_a_statement (node, description)
|
|
1478
|
+
hint = STATEMENT_HINTS[node.class]
|
|
1479
|
+
raise Unsupported.new(
|
|
1480
|
+
"a kernel body holds assignments, `if`, `while`, `(a...b).each`, " \
|
|
1481
|
+
"`n.times` and `a.step(b, s)` only, got #{description}" +
|
|
1482
|
+
(hint ? " -- #{hint}" : ""),
|
|
1483
|
+
node.location)
|
|
1484
|
+
end
|
|
1485
|
+
|
|
1486
|
+
# A refusal names what was written rather than the node this compiler
|
|
1487
|
+
# read it as: `Prism::UnlessNode` is not a thing anyone typed.
|
|
1488
|
+
STATEMENT_DESCRIPTIONS = {
|
|
1489
|
+
Prism::UnlessNode => "`unless`",
|
|
1490
|
+
Prism::UntilNode => "`until`",
|
|
1491
|
+
Prism::CaseNode => "`case`",
|
|
1492
|
+
Prism::CaseMatchNode => "`case ... in`",
|
|
1493
|
+
Prism::ForNode => "`for`",
|
|
1494
|
+
Prism::ReturnNode => "`return`",
|
|
1495
|
+
Prism::BeginNode => "`begin`",
|
|
1496
|
+
Prism::RescueModifierNode => "`rescue`",
|
|
1497
|
+
Prism::MultiWriteNode => "a parallel assignment",
|
|
1498
|
+
Prism::InstanceVariableWriteNode => "an instance variable",
|
|
1499
|
+
Prism::GlobalVariableWriteNode => "a global variable",
|
|
1500
|
+
Prism::ConstantWriteNode => "an assignment to a constant",
|
|
1501
|
+
Prism::DefNode => "a method definition",
|
|
1502
|
+
}.freeze
|
|
1503
|
+
|
|
1504
|
+
STATEMENT_HINTS = {
|
|
1505
|
+
Prism::UnlessNode => "write it as `if` with the condition negated",
|
|
1506
|
+
Prism::UntilNode => "write it as `while` with the condition negated",
|
|
1507
|
+
Prism::CaseNode => "write the branches out with `if` and `elsif`",
|
|
1508
|
+
Prism::ReturnNode => "a body is one expression, and its value is " \
|
|
1509
|
+
"the last thing in it",
|
|
1510
|
+
# A statement, and a supported one; what it has not got is a value.
|
|
1511
|
+
# Ruby's is the array of what it wrote, and a cell holds a number.
|
|
1512
|
+
Prism::MultiWriteNode =>
|
|
1513
|
+
"its value in Ruby is the array of what it " \
|
|
1514
|
+
"wrote, and a cell holds a number; end with " \
|
|
1515
|
+
"the one you want",
|
|
1516
|
+
}.freeze
|
|
1517
|
+
|
|
1518
|
+
def statement_description (node)
|
|
1519
|
+
STATEMENT_DESCRIPTIONS.fetch(node.class) { node_name(node) }
|
|
1520
|
+
end
|
|
1521
|
+
|
|
1013
1522
|
# `next` skips the rest of this iteration, `break` leaves the loop. Both
|
|
1014
1523
|
# mean in the generated loop what they mean in the Ruby loop it replaces
|
|
1015
1524
|
# -- with one exception: Ruby's `break` in the kernel block is not a
|
|
@@ -1116,7 +1625,12 @@ class CArray
|
|
|
1116
1625
|
def build_inner_loop (node)
|
|
1117
1626
|
counted = node.name == :times
|
|
1118
1627
|
range = unwrap(node.receiver)
|
|
1119
|
-
|
|
1628
|
+
# Two spellings carry a stride, and they are Ruby's two: a range
|
|
1629
|
+
# steps within the ends it already states, and an integer steps to
|
|
1630
|
+
# one it is given. Which is which is the receiver.
|
|
1631
|
+
stepped = node.name == :step && !range.is_a?(Prism::RangeNode)
|
|
1632
|
+
walked = node.name == :step && range.is_a?(Prism::RangeNode)
|
|
1633
|
+
if !counted && !stepped && !range.is_a?(Prism::RangeNode)
|
|
1120
1634
|
raise Unsupported.new(
|
|
1121
1635
|
"an inner loop runs over a range, as in `(0...n).each { |j| ... }`",
|
|
1122
1636
|
node.location)
|
|
@@ -1132,7 +1646,7 @@ class CArray
|
|
|
1132
1646
|
if index_in_scope?(index)
|
|
1133
1647
|
raise Unsupported.new("`#{index}` is already an index here", node.location)
|
|
1134
1648
|
end
|
|
1135
|
-
unless counted || (range.left && range.right)
|
|
1649
|
+
unless counted || stepped || (range.left && range.right)
|
|
1136
1650
|
raise Unsupported.new("an inner loop needs both ends of its range",
|
|
1137
1651
|
node.location)
|
|
1138
1652
|
end
|
|
@@ -1140,26 +1654,102 @@ class CArray
|
|
|
1140
1654
|
# `n.times` is `(0...n).each`: the count is the exclusive end, and it
|
|
1141
1655
|
# is built from the same vocabulary a range end is, so a literal and a
|
|
1142
1656
|
# captured integer both serve.
|
|
1657
|
+
#
|
|
1658
|
+
# `from.step(to, s)` is the spelling an extent already says a stride
|
|
1659
|
+
# or a downward sweep with, and it means here what it means there --
|
|
1660
|
+
# `to` included, which is Ruby's own reading of it and the opposite of
|
|
1661
|
+
# a `...` range's. It is turned into an exclusive end at once, so
|
|
1662
|
+
# everything below this line counts the way `each` does.
|
|
1663
|
+
step = 1
|
|
1143
1664
|
if counted
|
|
1144
1665
|
from = IntegerLiteral.new(0, node.location)
|
|
1145
1666
|
to = build(node.receiver)
|
|
1667
|
+
elsif stepped
|
|
1668
|
+
from = build(node.receiver)
|
|
1669
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
1670
|
+
unless arguments.size == 2
|
|
1671
|
+
raise Unsupported.new(
|
|
1672
|
+
"an inner loop written with `step` gives it both the last " \
|
|
1673
|
+
"index and the stride, as in `(n-1).step(0, -1) { |k| ... }`",
|
|
1674
|
+
node.location)
|
|
1675
|
+
end
|
|
1676
|
+
step = inner_loop_step(arguments.last)
|
|
1677
|
+
to = build(arguments.first)
|
|
1678
|
+
to = BinaryOperation.new(step.positive? ? :+ : :-, to,
|
|
1679
|
+
IntegerLiteral.new(1, node.location),
|
|
1680
|
+
node.location)
|
|
1146
1681
|
else
|
|
1147
1682
|
from = build(range.left)
|
|
1148
1683
|
to = build(range.right)
|
|
1149
1684
|
to = BinaryOperation.new(:+, to, IntegerLiteral.new(1, node.location),
|
|
1150
1685
|
node.location) unless range.exclude_end?
|
|
1686
|
+
if walked
|
|
1687
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
1688
|
+
unless arguments.size == 1
|
|
1689
|
+
raise Unsupported.new(
|
|
1690
|
+
"a range steps by one stride, as in `(0...n).step(2) " \
|
|
1691
|
+
"{ |j| ... }`", node.location)
|
|
1692
|
+
end
|
|
1693
|
+
step = inner_loop_step(arguments.first)
|
|
1694
|
+
if step.negative?
|
|
1695
|
+
raise Unsupported.new(
|
|
1696
|
+
"a range cannot step backwards, in Ruby either -- count " \
|
|
1697
|
+
"down with `(n-1).step(0, -1) { |j| ... }`, which is the " \
|
|
1698
|
+
"spelling an extent takes too",
|
|
1699
|
+
node.location)
|
|
1700
|
+
end
|
|
1701
|
+
end
|
|
1151
1702
|
end
|
|
1152
1703
|
|
|
1704
|
+
# Two sibling loops may both be written `{ |k| ... }`, and each has
|
|
1705
|
+
# a range of its own -- a forward sweep from 1 and the sweep back
|
|
1706
|
+
# down from the far end are exactly that pair. They are one name in
|
|
1707
|
+
# the block and cannot be one variable here, because the reach of
|
|
1708
|
+
# `a[k-1]` is checked against the loop it sits in. So the second
|
|
1709
|
+
# gets an identifier of its own, and the name in the block goes on
|
|
1710
|
+
# meaning whichever loop it is inside.
|
|
1711
|
+
internal = free_index_name(index)
|
|
1153
1712
|
@inner_names << index
|
|
1154
|
-
@
|
|
1155
|
-
@
|
|
1713
|
+
@inner_sources_seen << index
|
|
1714
|
+
@inner_names_seen << internal
|
|
1715
|
+
@inner_aliases[index] = internal
|
|
1716
|
+
@index_sources[internal] = index
|
|
1717
|
+
@inner_ranges[internal] = [from, to, step]
|
|
1156
1718
|
@loop_depth += 1
|
|
1719
|
+
@scopes.push([])
|
|
1157
1720
|
statements = statements_of(node.block.body).map { |inner|
|
|
1158
1721
|
build_statement(inner)
|
|
1159
1722
|
}
|
|
1723
|
+
@scopes.pop
|
|
1160
1724
|
@loop_depth -= 1
|
|
1161
1725
|
@inner_names.pop
|
|
1162
|
-
|
|
1726
|
+
@inner_aliases.delete(index)
|
|
1727
|
+
InnerLoop.new(internal, from, to, statements, node.location, step)
|
|
1728
|
+
end
|
|
1729
|
+
|
|
1730
|
+
# The stride is read off the page rather than computed, because it is
|
|
1731
|
+
# what says which way the loop runs and the C has to be written one way
|
|
1732
|
+
# or the other before anything is known. A captured integer would
|
|
1733
|
+
# leave that open.
|
|
1734
|
+
def inner_loop_step (node)
|
|
1735
|
+
value = case node
|
|
1736
|
+
when Prism::IntegerNode then node.value
|
|
1737
|
+
when Prism::CallNode
|
|
1738
|
+
if node.name == :-@ && node.receiver.is_a?(Prism::IntegerNode)
|
|
1739
|
+
-node.receiver.value
|
|
1740
|
+
end
|
|
1741
|
+
end
|
|
1742
|
+
if value.nil?
|
|
1743
|
+
raise Unsupported.new(
|
|
1744
|
+
"an inner loop's stride says which way it runs, so it is an " \
|
|
1745
|
+
"integer literal -- `step(0, -1)` for a downward sweep",
|
|
1746
|
+
node.location)
|
|
1747
|
+
end
|
|
1748
|
+
if value.zero?
|
|
1749
|
+
raise Unsupported.new("an inner loop's stride cannot be zero",
|
|
1750
|
+
node.location)
|
|
1751
|
+
end
|
|
1752
|
+
value
|
|
1163
1753
|
end
|
|
1164
1754
|
|
|
1165
1755
|
def unwrap (node)
|
|
@@ -1192,8 +1782,1014 @@ class CArray
|
|
|
1192
1782
|
node ? node.body : []
|
|
1193
1783
|
end
|
|
1194
1784
|
|
|
1785
|
+
# Handing a local array to a C function, with the declaration matched
|
|
1786
|
+
# against what the block wrote.
|
|
1787
|
+
#
|
|
1788
|
+
# The length check is the one that matters, and it is not decoration: a
|
|
1789
|
+
# compiled function does not check a subscript on a pointer parameter
|
|
1790
|
+
# (unchecked by design -- the caller's business, as in C), and this
|
|
1791
|
+
# array is on the stack of the block that made it. A declaration that
|
|
1792
|
+
# reads more cells than the array holds would walk over the frame, and
|
|
1793
|
+
# the return address is in it.
|
|
1794
|
+
def build_local_array_address (name, argument, parameter, c_function)
|
|
1795
|
+
storage, shape = local_array_in_sight(name)
|
|
1796
|
+
# The callee is handed a pointer to the cells, and the shadow beside
|
|
1797
|
+
# them travels in no C declaration there is a spelling for here: a
|
|
1798
|
+
# signature says what it points at, and "these cells, and a byte each
|
|
1799
|
+
# saying whether they are there" is not one of the things it can say.
|
|
1800
|
+
if @masked
|
|
1801
|
+
raise Unsupported.new(
|
|
1802
|
+
"`#{name}` is a local array of this kernel, and the kernel " \
|
|
1803
|
+
"carries masks -- so `#{name}` carries one beside each of its " \
|
|
1804
|
+
"cells, and there is no way to hand that to `#{c_function}`: a " \
|
|
1805
|
+
"signature says what a pointer points at, and a mask travels in " \
|
|
1806
|
+
"no C declaration. Copy the cells the function is to see into a " \
|
|
1807
|
+
"captured array, or answer the mask in the kernel",
|
|
1808
|
+
argument.location)
|
|
1809
|
+
end
|
|
1810
|
+
unless parameter&.pointer
|
|
1811
|
+
raise Unsupported.new(
|
|
1812
|
+
"`#{c_function}` takes #{parameter ? "`#{parameter.text}`" : 'a number'} " \
|
|
1813
|
+
"there, and `#{name}` is an array; pass a cell of it, as in " \
|
|
1814
|
+
"`#{name}[0]`",
|
|
1815
|
+
argument.location)
|
|
1816
|
+
end
|
|
1817
|
+
unless parameter.indexable?
|
|
1818
|
+
raise Unsupported.new(
|
|
1819
|
+
"`#{c_function}` takes `#{parameter.text}` there, which points at " \
|
|
1820
|
+
"nothing in particular, so there is no element type to hold " \
|
|
1821
|
+
"`#{name}` to; declare what it points at",
|
|
1822
|
+
argument.location)
|
|
1823
|
+
end
|
|
1824
|
+
wanted = CDeclaration::DATA_TYPES.fetch(parameter.element.fiddle).to_s
|
|
1825
|
+
unless storage == wanted
|
|
1826
|
+
raise Unsupported.new(
|
|
1827
|
+
"`#{c_function}` takes `#{parameter.text}` there, which is a " \
|
|
1828
|
+
"#{wanted} array, and `#{name}` is #{storage}; make it with " \
|
|
1829
|
+
"`CArray.new(:#{wanted}, [#{shape.first}])` -- an element type is " \
|
|
1830
|
+
"matched exactly here, as it is for a captured array",
|
|
1831
|
+
argument.location)
|
|
1832
|
+
end
|
|
1833
|
+
# The cells over every axis, because that is what the callee is handed:
|
|
1834
|
+
# a local array is packed row-major, so `double[3][4]` is the twelve
|
|
1835
|
+
# cells `const double a[12]` asks for.
|
|
1836
|
+
#
|
|
1837
|
+
# A declaration that names a length can only be held to a shape that
|
|
1838
|
+
# is numbers: the two are matched as the block is read, and a length
|
|
1839
|
+
# the kernel works out is not there yet. A pointer with no length is
|
|
1840
|
+
# the C that says "however many you have", and takes one.
|
|
1841
|
+
unless literal_shape?(shape)
|
|
1842
|
+
if parameter.sized?
|
|
1843
|
+
raise Unsupported.new(
|
|
1844
|
+
"`#{c_function}` takes `#{parameter.text}` there, which reads " \
|
|
1845
|
+
"#{parameter.array} cells, and the shape of `#{name}` is " \
|
|
1846
|
+
"worked out when the kernel runs -- so the two cannot be " \
|
|
1847
|
+
"matched here, and the function checks no subscript of its " \
|
|
1848
|
+
"own. Declare the parameter without a length " \
|
|
1849
|
+
"(`#{parameter.element.text} *`), which promises nothing about " \
|
|
1850
|
+
"how many cells there are, or give `#{name}` a shape written " \
|
|
1851
|
+
"out",
|
|
1852
|
+
argument.location)
|
|
1853
|
+
end
|
|
1854
|
+
return LocalArrayAddress.new(name, storage, shape, argument.location)
|
|
1855
|
+
end
|
|
1856
|
+
cells = shape.inject(1, :*)
|
|
1857
|
+
if parameter.sized? && cells < parameter.array
|
|
1858
|
+
shaped = shape.size == 1 ? "" :
|
|
1859
|
+
" (#{shape.join(' x ')}, packed row-major)"
|
|
1860
|
+
raise Unsupported.new(
|
|
1861
|
+
"`#{c_function}` takes `#{parameter.text}` there, which reads " \
|
|
1862
|
+
"#{parameter.array} cells, and `#{name}` has #{cells}#{shaped}. A " \
|
|
1863
|
+
"local array is on this block's stack and the function does not " \
|
|
1864
|
+
"check a subscript of its own, so the missing cells would be the " \
|
|
1865
|
+
"frame; give `#{name}` at least #{parameter.array}",
|
|
1866
|
+
argument.location)
|
|
1867
|
+
end
|
|
1868
|
+
LocalArrayAddress.new(name, storage, shape, argument.location)
|
|
1869
|
+
end
|
|
1870
|
+
|
|
1871
|
+
# ---- the functions the compiler brings with it ----
|
|
1872
|
+
|
|
1873
|
+
# Written as bare calls, the way `random(rng: r)` is: names of this
|
|
1874
|
+
# compiler's own rather than methods on an array, so that what each one
|
|
1875
|
+
# means is settled here instead of promising CArray's method semantics
|
|
1876
|
+
# -- `axis:`, masks skipped, an identity for an empty array, a CScalar
|
|
1877
|
+
# or a number back, one type promoted to another.
|
|
1878
|
+
#
|
|
1879
|
+
# A bare name with no arguments is a capture (`sum = 0.0` beside
|
|
1880
|
+
# `sum(w)` is a local and reads as one), and a name with a receiver is a
|
|
1881
|
+
# call to whatever holds it (`sum.call(x)`), so neither collides.
|
|
1882
|
+
INTRINSIC_VALUES = [:sum, :min, :max].freeze
|
|
1883
|
+
INTRINSIC_STATEMENTS = [:sort].freeze
|
|
1884
|
+
INTRINSIC_NAMES = (INTRINSIC_VALUES + INTRINSIC_STATEMENTS).freeze
|
|
1885
|
+
|
|
1886
|
+
# `sum(w)` / `min(w)` / `max(w)` in an expression, or nil where this
|
|
1887
|
+
# call is not one of them.
|
|
1888
|
+
def build_intrinsic_call (node)
|
|
1889
|
+
return nil unless intrinsic_spelling?(node)
|
|
1890
|
+
if INTRINSIC_STATEMENTS.include?(node.name)
|
|
1891
|
+
raise Unsupported.new(
|
|
1892
|
+
"`#{node.name}` rearranges the array and has no value to put " \
|
|
1893
|
+
"anywhere; write it on a line of its own, then read the cells " \
|
|
1894
|
+
"you want",
|
|
1895
|
+
node.location)
|
|
1896
|
+
end
|
|
1897
|
+
return nil unless INTRINSIC_VALUES.include?(node.name)
|
|
1898
|
+
name, storage, shape = intrinsic_array(node)
|
|
1899
|
+
IntrinsicCall.new(node.name, name, storage, shape, node.location)
|
|
1900
|
+
end
|
|
1901
|
+
|
|
1902
|
+
# `sort(w)` standing as a statement.
|
|
1903
|
+
def build_intrinsic_statement (node)
|
|
1904
|
+
return nil unless intrinsic_spelling?(node)
|
|
1905
|
+
if INTRINSIC_VALUES.include?(node.name)
|
|
1906
|
+
raise Unsupported.new(
|
|
1907
|
+
"`#{node.name}` works out a value, and standing on a line of its " \
|
|
1908
|
+
"own it puts that value nowhere; assign it, as in " \
|
|
1909
|
+
"`total = #{node.name}(...)`",
|
|
1910
|
+
node.location)
|
|
1911
|
+
end
|
|
1912
|
+
return nil unless INTRINSIC_STATEMENTS.include?(node.name)
|
|
1913
|
+
name, storage, shape = intrinsic_array(node)
|
|
1914
|
+
# Deliberately not `@calls_for_effect`: rearranging an array the body
|
|
1915
|
+
# made leaves nothing outside the kernel, so a kernel whose only work
|
|
1916
|
+
# is a `sort` has still put its work nowhere and is told so.
|
|
1917
|
+
IntrinsicStatement.new(node.name, name, storage, shape, node.location)
|
|
1918
|
+
end
|
|
1919
|
+
|
|
1920
|
+
def intrinsic_spelling? (node)
|
|
1921
|
+
node.is_a?(Prism::CallNode) && node.receiver.nil? && node.block.nil? &&
|
|
1922
|
+
INTRINSIC_NAMES.include?(node.name) && !node.arguments.nil?
|
|
1923
|
+
end
|
|
1924
|
+
|
|
1925
|
+
# The one argument, which is a local array of one axis.
|
|
1926
|
+
def intrinsic_array (node)
|
|
1927
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
1928
|
+
unless arguments.size == 1
|
|
1929
|
+
raise Unsupported.new(
|
|
1930
|
+
"`#{node.name}` takes one local array, and #{arguments.size} " \
|
|
1931
|
+
"#{arguments.size == 1 ? 'argument was' : 'arguments were'} " \
|
|
1932
|
+
"given" +
|
|
1933
|
+
(node.name == :min || node.name == :max ?
|
|
1934
|
+
". For the #{node.name} of two numbers write " \
|
|
1935
|
+
"`x #{node.name == :min ? '<' : '>'} y ? x : y`, and to hold " \
|
|
1936
|
+
"a value between two bounds write `x.clamp(lo, hi)`" : ""),
|
|
1937
|
+
node.location)
|
|
1938
|
+
end
|
|
1939
|
+
argument = arguments.first
|
|
1940
|
+
name = local_array_name(argument)
|
|
1941
|
+
refuse_an_intrinsic_argument(node, argument) unless name
|
|
1942
|
+
# What the four should do with a missing cell is not decided, and
|
|
1943
|
+
# there is more than one defensible answer: skip it, gather it at the
|
|
1944
|
+
# end the way `sort` gathers NaN, count it where a median is counted.
|
|
1945
|
+
# So the array is refused rather than given a meaning here.
|
|
1946
|
+
if @masked
|
|
1947
|
+
raise Unsupported.new(
|
|
1948
|
+
"`#{node.name}(#{name})` is over a local array of this kernel, " \
|
|
1949
|
+
"and the kernel carries masks -- so `#{name}` carries one beside " \
|
|
1950
|
+
"each of its cells, and what `#{node.name}` should do with a " \
|
|
1951
|
+
"missing one is not decided: whether to pass over it, to gather " \
|
|
1952
|
+
"it at the end as `sort` gathers NaN, or to count it where a " \
|
|
1953
|
+
"median counts. Write the loop yourself, deciding it there",
|
|
1954
|
+
node.location)
|
|
1955
|
+
end
|
|
1956
|
+
storage, shape = local_array_in_sight(name)
|
|
1957
|
+
unless shape.size == 1
|
|
1958
|
+
raise Unsupported.new(
|
|
1959
|
+
"`#{name}` has #{shape.size} axes, and `#{node.name}` takes an " \
|
|
1960
|
+
"array of one axis. A sweep over every cell would read well " \
|
|
1961
|
+
"enough for `sum`, and `sort` over one has no obvious meaning at " \
|
|
1962
|
+
"all, so the four keep one rule; walk the axes yourself, or copy " \
|
|
1963
|
+
"the cells you want into an array of one axis",
|
|
1964
|
+
node.location)
|
|
1965
|
+
end
|
|
1966
|
+
[name, storage, shape]
|
|
1967
|
+
end
|
|
1968
|
+
|
|
1969
|
+
# What was passed instead, said in the terms it was written in.
|
|
1970
|
+
def refuse_an_intrinsic_argument (node, argument)
|
|
1971
|
+
passed = captured_name(argument)
|
|
1972
|
+
if passed && @array_names.include?(passed)
|
|
1973
|
+
raise Unsupported.new(
|
|
1974
|
+
"`#{node.name}` takes a local array -- one this block made with " \
|
|
1975
|
+
"`CArray.double(n)` or its kin -- and `#{passed}` is an array " \
|
|
1976
|
+
"the block closed over. Walking all of it at every cell is a " \
|
|
1977
|
+
"pass over the whole array per cell, which is not what the line " \
|
|
1978
|
+
"looks like it costs; `#{passed}.#{node.name}` outside the " \
|
|
1979
|
+
"kernel says what it costs, and a loop written out says it too",
|
|
1980
|
+
node.location)
|
|
1981
|
+
end
|
|
1982
|
+
raise Unsupported.new(
|
|
1983
|
+
"`#{node.name}` takes a local array -- one this block made with " \
|
|
1984
|
+
"`CArray.double(n)` or its kin -- named on its own, and " \
|
|
1985
|
+
"`#{argument.slice}` is not one",
|
|
1986
|
+
node.location)
|
|
1987
|
+
end
|
|
1988
|
+
|
|
1989
|
+
# ---- the arrays a body makes for itself ----
|
|
1990
|
+
|
|
1991
|
+
LOCAL_ARRAY_SUBSCRIPTS = [:[], :[]=].freeze
|
|
1992
|
+
|
|
1993
|
+
# A constructor stands on the right of an assignment and nowhere else.
|
|
1994
|
+
# Everywhere else it is an array standing where a number goes -- in an
|
|
1995
|
+
# argument, under a subscript, as an operand -- and there is no name for
|
|
1996
|
+
# the C array to be declared under.
|
|
1997
|
+
def refuse_a_constructor_in_an_expression (node)
|
|
1998
|
+
return unless node
|
|
1999
|
+
return unless self.class.local_array_constructor?(node)
|
|
2000
|
+
raise Unsupported.new(
|
|
2001
|
+
"`#{node.slice}` makes an array, and a constructor stands on the " \
|
|
2002
|
+
"right of an assignment and nowhere else: write " \
|
|
2003
|
+
"`w = #{node.slice}` on a line of its own, then reach `w` at a " \
|
|
2004
|
+
"subscript",
|
|
2005
|
+
node.location)
|
|
2006
|
+
end
|
|
2007
|
+
|
|
2008
|
+
# `[storage type, shape, zeroed]` for a constructor, or nil where this
|
|
2009
|
+
# call is not one. Nil is the answer for everything else a body may
|
|
2010
|
+
# write, so nothing outside this vocabulary is touched; once the answer
|
|
2011
|
+
# is not nil the call is a constructor, and a spelling that cannot be
|
|
2012
|
+
# taken is refused here rather than further along.
|
|
2013
|
+
def read_local_array_constructor (node)
|
|
2014
|
+
return nil unless self.class.local_array_constructor?(node)
|
|
2015
|
+
refuse_a_local_array_in_this_spelling(node)
|
|
2016
|
+
if node.block
|
|
2017
|
+
raise Unsupported.new(
|
|
2018
|
+
"`#{node.slice}` fills the array from a block, and a local " \
|
|
2019
|
+
"array is either zero-filled or left as it stands; write the " \
|
|
2020
|
+
"fill out as a loop over its cells",
|
|
2021
|
+
node.location)
|
|
2022
|
+
end
|
|
2023
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
2024
|
+
if arguments.any? { |argument| argument.is_a?(Prism::KeywordHashNode) }
|
|
2025
|
+
raise Unsupported.new(
|
|
2026
|
+
"`#{node.slice}` passes a keyword, and a local array is the " \
|
|
2027
|
+
"type and the shape only", node.location)
|
|
2028
|
+
end
|
|
2029
|
+
if node.receiver.is_a?(Prism::ConstantPathNode) &&
|
|
2030
|
+
node.receiver.slice != "::CArray"
|
|
2031
|
+
return refuse_a_type_class_constructor(node, arguments)
|
|
2032
|
+
end
|
|
2033
|
+
case node.name
|
|
2034
|
+
when :new then read_carray_new(node, arguments, true)
|
|
2035
|
+
when :empty then read_carray_empty(node, arguments)
|
|
2036
|
+
else read_type_constructor(node, arguments)
|
|
2037
|
+
end
|
|
2038
|
+
end
|
|
2039
|
+
|
|
2040
|
+
# `CArray.double(9)` and the rest of the singleton methods CArray's own
|
|
2041
|
+
# `Init` defines: the type is the name and the shape is the arguments.
|
|
2042
|
+
def read_type_constructor (node, arguments)
|
|
2043
|
+
if LOCAL_ARRAY_REFUSED_TYPES.include?(node.name)
|
|
2044
|
+
raise Unsupported.new(
|
|
2045
|
+
"`#{node.name}` holds #{LOCAL_ARRAY_REFUSED_TYPES_HOLD.fetch(node.name)} " \
|
|
2046
|
+
"rather than numbers, and a kernel computes with numbers; a " \
|
|
2047
|
+
"local array is one of " \
|
|
2048
|
+
"#{LOCAL_ARRAY_TYPES.keys.take(13).map { |name| "`#{name}`" }.join(', ')}",
|
|
2049
|
+
node.location)
|
|
2050
|
+
end
|
|
2051
|
+
if LOCAL_ARRAY_COMPATIBILITY_NAMES.include?(node.name)
|
|
2052
|
+
return refuse_a_compatibility_constructor(node, arguments, nil)
|
|
2053
|
+
end
|
|
2054
|
+
storage = LOCAL_ARRAY_TYPES.fetch(node.name)
|
|
2055
|
+
if arguments.empty?
|
|
2056
|
+
raise Unsupported.new(
|
|
2057
|
+
"`#{node.slice}` says no shape; a local array is written " \
|
|
2058
|
+
"`CArray.#{node.name}(n)` with the length written out",
|
|
2059
|
+
node.location)
|
|
2060
|
+
end
|
|
2061
|
+
[storage, read_local_array_shape(node, arguments), true]
|
|
2062
|
+
end
|
|
2063
|
+
|
|
2064
|
+
# `CArray.new(:int64, [256])` -- `rb_ca_initialize`, whose
|
|
2065
|
+
# `carray_safe_setup` is what fills the cells with zero.
|
|
2066
|
+
def read_carray_new (node, arguments, zeroed)
|
|
2067
|
+
unless arguments.size == 2
|
|
2068
|
+
raise Unsupported.new(
|
|
2069
|
+
"`CArray.#{node.name}` takes the type and the shape, as in " \
|
|
2070
|
+
"`CArray.#{node.name}(:float64, [9])`", node.location)
|
|
2071
|
+
end
|
|
2072
|
+
[read_local_array_type(node, arguments.first),
|
|
2073
|
+
read_local_array_shape(node, read_shape_arguments(node, arguments.last)),
|
|
2074
|
+
zeroed]
|
|
2075
|
+
end
|
|
2076
|
+
|
|
2077
|
+
# Two spellings share the name. `CArray.empty(:float64, [9])` is
|
|
2078
|
+
# carray's, and stands beside `CArray.new` with the fill left out;
|
|
2079
|
+
# `CArray.empty(9)` is the compatibility layer's NumPy form, where the
|
|
2080
|
+
# type is float64 by convention rather than by anything written down.
|
|
2081
|
+
def read_carray_empty (node, arguments)
|
|
2082
|
+
carray_form = arguments.size == 2 &&
|
|
2083
|
+
arguments.first.is_a?(Prism::SymbolNode) &&
|
|
2084
|
+
arguments.last.is_a?(Prism::ArrayNode)
|
|
2085
|
+
return read_carray_new(node, arguments, false) if carray_form
|
|
2086
|
+
refuse_a_compatibility_constructor(node, arguments, nil)
|
|
2087
|
+
end
|
|
2088
|
+
|
|
2089
|
+
def read_shape_arguments (node, argument)
|
|
2090
|
+
unless argument.is_a?(Prism::ArrayNode)
|
|
2091
|
+
raise Unsupported.new(
|
|
2092
|
+
"the shape `CArray.#{node.name}` takes is an Array, as in " \
|
|
2093
|
+
"`CArray.#{node.name}(:float64, [9])`", node.location)
|
|
2094
|
+
end
|
|
2095
|
+
argument.elements
|
|
2096
|
+
end
|
|
2097
|
+
|
|
2098
|
+
# The type is a Symbol written out. One of CArray's constants would be
|
|
2099
|
+
# a name looked up outside the block, and one kernel serves every value
|
|
2100
|
+
# of a capture -- so a type that arrived that way would not be in the C
|
|
2101
|
+
# at all.
|
|
2102
|
+
def read_local_array_type (node, argument)
|
|
2103
|
+
unless argument.is_a?(Prism::SymbolNode)
|
|
2104
|
+
raise Unsupported.new(
|
|
2105
|
+
"the type `CArray.#{node.name}` takes is a Symbol written out, " \
|
|
2106
|
+
"as in `CArray.#{node.name}(:float64, [9])`; " \
|
|
2107
|
+
"`#{argument.slice}` is looked up where the block was written, " \
|
|
2108
|
+
"and one compiled kernel serves every value of that",
|
|
2109
|
+
node.location)
|
|
2110
|
+
end
|
|
2111
|
+
name = argument.unescaped.to_sym
|
|
2112
|
+
if LOCAL_ARRAY_REFUSED_TYPES.include?(name)
|
|
2113
|
+
raise Unsupported.new(
|
|
2114
|
+
"`:#{name}` holds #{LOCAL_ARRAY_REFUSED_TYPES_HOLD.fetch(name)} " \
|
|
2115
|
+
"rather than numbers, and a kernel computes with numbers",
|
|
2116
|
+
node.location)
|
|
2117
|
+
end
|
|
2118
|
+
LOCAL_ARRAY_TYPES.fetch(name) do
|
|
2119
|
+
raise Unsupported.new(
|
|
2120
|
+
"`#{argument.slice}` is not a type a local array holds; they are " \
|
|
2121
|
+
"#{LOCAL_ARRAY_TYPES.keys.take(13).map { |n| "`:#{n}`" }.join(', ')}",
|
|
2122
|
+
node.location)
|
|
2123
|
+
end
|
|
2124
|
+
end
|
|
2125
|
+
|
|
2126
|
+
# The shape, one entry an axis: an Integer where the block wrote a
|
|
2127
|
+
# number, and a LocalArrayExtent where it wrote an expression over the
|
|
2128
|
+
# integers it captured. A number is what the C declares and what a
|
|
2129
|
+
# subscript is checked against as the block is read; an expression is
|
|
2130
|
+
# worked out once at the kernel's entry, and every check against it
|
|
2131
|
+
# happens where the cell is reached.
|
|
2132
|
+
def read_local_array_shape (node, arguments)
|
|
2133
|
+
if arguments.empty?
|
|
2134
|
+
raise Unsupported.new(
|
|
2135
|
+
"`#{node.slice}` says no shape; a local array is written " \
|
|
2136
|
+
"`CArray.#{node.name}(n)` with the length written out",
|
|
2137
|
+
node.location)
|
|
2138
|
+
end
|
|
2139
|
+
arguments.map { |argument|
|
|
2140
|
+
extent = literal_integer(argument)
|
|
2141
|
+
next captured_extent(node, argument) unless extent
|
|
2142
|
+
unless extent.positive?
|
|
2143
|
+
raise Unsupported.new(
|
|
2144
|
+
"a local array holds at least one cell, and `#{argument.slice}` " \
|
|
2145
|
+
"asks for #{extent}", node.location)
|
|
2146
|
+
end
|
|
2147
|
+
extent
|
|
2148
|
+
}
|
|
2149
|
+
end
|
|
2150
|
+
|
|
2151
|
+
# A length the kernel works out at its entry, from the integers it
|
|
2152
|
+
# captured.
|
|
2153
|
+
#
|
|
2154
|
+
# It may read nothing that varies from cell to cell: the array is one
|
|
2155
|
+
# allocation for the whole pass, made before the first cell, so an
|
|
2156
|
+
# extent over a loop index or an array's cell is a length that would
|
|
2157
|
+
# have to change underneath it. That is what is refused here, and the
|
|
2158
|
+
# message says which of the two was written.
|
|
2159
|
+
def captured_extent (node, argument)
|
|
2160
|
+
built = build(argument)
|
|
2161
|
+
varies = extent_varies_by_cell(built)
|
|
2162
|
+
if varies
|
|
2163
|
+
raise Unsupported.new(
|
|
2164
|
+
"`#{argument.slice}` is the shape of a local array, and " \
|
|
2165
|
+
"#{varies} -- the array is one allocation made before the first " \
|
|
2166
|
+
"cell, so its length cannot change from cell to cell. Take the " \
|
|
2167
|
+
"widest length the pass needs, or pass a captured array of " \
|
|
2168
|
+
"workspace",
|
|
2169
|
+
node.location)
|
|
2170
|
+
end
|
|
2171
|
+
LocalArrayExtent.new(argument.slice, built)
|
|
2172
|
+
end
|
|
2173
|
+
|
|
2174
|
+
# What in an extent expression is not settled before the loop runs, in
|
|
2175
|
+
# the words it was written in, or nil where everything in it is.
|
|
2176
|
+
def extent_varies_by_cell (node)
|
|
2177
|
+
case node
|
|
2178
|
+
when IntegerLiteral, CaptureRead then nil
|
|
2179
|
+
when UnaryMinus then extent_varies_by_cell(node.operand)
|
|
2180
|
+
when BinaryOperation
|
|
2181
|
+
unless [:+, :-, :*].include?(node.operator)
|
|
2182
|
+
return "`#{node.operator}` is not one of `+`, `-` and `*`"
|
|
2183
|
+
end
|
|
2184
|
+
extent_varies_by_cell(node.left) || extent_varies_by_cell(node.right)
|
|
2185
|
+
when IndexVariable then "`#{node.name}` is a loop index"
|
|
2186
|
+
when LocalRead then "`#{node.name}` is worked out inside the block"
|
|
2187
|
+
when ElementRead then "`#{node.array}` is a cell of an array"
|
|
2188
|
+
when LocalArrayRead then "`#{node.name}` is a cell of an array"
|
|
2189
|
+
else "it is not an integer expression over the captured integers"
|
|
2190
|
+
end
|
|
2191
|
+
end
|
|
2192
|
+
|
|
2193
|
+
# `CArray.zeros(4)`, `CArray.empty(4)`, `CArray.ones(4)`: the
|
|
2194
|
+
# compatibility layer, which `data_type_extension.rb` opens by calling
|
|
2195
|
+
# itself "Numo / NumPy-style" and "not the 'main' carray API". The
|
|
2196
|
+
# words inside a block are carray's own.
|
|
2197
|
+
def refuse_a_compatibility_constructor (node, arguments, type)
|
|
2198
|
+
storage = type || "float64"
|
|
2199
|
+
length = arguments.empty? ? nil : literal_integer(arguments.first)
|
|
2200
|
+
shape = length ? "[#{length}]" : "[n]"
|
|
2201
|
+
raise Unsupported.new(
|
|
2202
|
+
"`#{node.slice}` is CArray's Numo/NumPy compatibility layer, and " \
|
|
2203
|
+
"a kernel body is written in carray's own words: " \
|
|
2204
|
+
"`CArray.new(:#{storage}, #{shape})` for cells that start at zero, " \
|
|
2205
|
+
"or `CArray.empty(:#{storage}, #{shape})` where every cell is " \
|
|
2206
|
+
"written before it is read",
|
|
2207
|
+
node.location)
|
|
2208
|
+
end
|
|
2209
|
+
|
|
2210
|
+
# `CArray::Int64.empty(4)` and `CArray::Int64.zeros(4)`: the same layer
|
|
2211
|
+
# reached through a type class, which is the Numo spelling of it.
|
|
2212
|
+
def refuse_a_type_class_constructor (node, arguments)
|
|
2213
|
+
tail = node.receiver.slice.split("::").last.to_s
|
|
2214
|
+
type = LOCAL_ARRAY_TYPES[tail.downcase.to_sym]
|
|
2215
|
+
refuse_a_compatibility_constructor(node, arguments, type)
|
|
2216
|
+
end
|
|
2217
|
+
|
|
2218
|
+
# Which entry points take one, and which release takes the rest. The
|
|
2219
|
+
# shape of the check is the mode this analyzer was made in, because that
|
|
2220
|
+
# is what says how the block is being read.
|
|
2221
|
+
LOCAL_ARRAY_ELSEWHERE = {
|
|
2222
|
+
:contract => ["a contraction's body", nil],
|
|
2223
|
+
}.freeze
|
|
2224
|
+
|
|
2225
|
+
def refuse_a_local_array_in_this_spelling (node)
|
|
2226
|
+
mode = if @contract then :contract
|
|
2227
|
+
end
|
|
2228
|
+
return unless mode
|
|
2229
|
+
where, when_ = LOCAL_ARRAY_ELSEWHERE.fetch(mode)
|
|
2230
|
+
if mode == :contract
|
|
2231
|
+
raise Unsupported.new(
|
|
2232
|
+
"`#{node.slice}` makes a local array, and #{where} is one " \
|
|
2233
|
+
"expression -- with, at most, an assignment into an array of its " \
|
|
2234
|
+
"own -- so there is no run of statements for a workspace to be " \
|
|
2235
|
+
"used by. Write the body as `CArray.jit_for`",
|
|
2236
|
+
node.location)
|
|
2237
|
+
end
|
|
2238
|
+
raise Unsupported.new(
|
|
2239
|
+
"`#{node.slice}` makes a local array, which #{where} takes in " \
|
|
2240
|
+
"#{when_}; `CArray.jit_for` takes one now, and a captured array of " \
|
|
2241
|
+
"workspace works here today",
|
|
2242
|
+
node.location)
|
|
2243
|
+
end
|
|
2244
|
+
|
|
2245
|
+
# The declaration itself.
|
|
2246
|
+
def declare_local_array (name, storage, shape, zeroed, location, depth)
|
|
2247
|
+
if index_in_scope?(name)
|
|
2248
|
+
raise Unsupported.new(
|
|
2249
|
+
"`#{name}` is a loop index, and the loop is what owns it; give " \
|
|
2250
|
+
"the array a name of its own",
|
|
2251
|
+
location)
|
|
2252
|
+
end
|
|
2253
|
+
if @local_names.include?(name) && local_in_sight?(name)
|
|
2254
|
+
raise Unsupported.new(
|
|
2255
|
+
"`#{name}` holds a number here, and a local array is not a " \
|
|
2256
|
+
"number; the two cannot be one C variable, so give the array a " \
|
|
2257
|
+
"name of its own",
|
|
2258
|
+
location)
|
|
2259
|
+
end
|
|
2260
|
+
heap = heap_array?(storage, shape)
|
|
2261
|
+
refuse_a_heap_array_in_a_body(name, storage, shape, location) if heap
|
|
2262
|
+
@local_array_names << name unless @local_array_names.include?(name)
|
|
2263
|
+
@local_arrays[name] = [storage, shape]
|
|
2264
|
+
scope = [@scopes.size - 1 - depth, 0].max
|
|
2265
|
+
@scopes[scope] << name unless @scopes[scope].include?(name)
|
|
2266
|
+
entry = [scope, name, storage, shape, heap]
|
|
2267
|
+
@local_array_declarations << entry unless @local_array_declarations.include?(entry)
|
|
2268
|
+
@clears_a_local_array ||= zeroed
|
|
2269
|
+
LocalArrayDeclaration.new(name, storage, shape, zeroed, location, scope,
|
|
2270
|
+
heap)
|
|
2271
|
+
end
|
|
2272
|
+
|
|
2273
|
+
# Where the array goes. The stack while it is small enough to be a
|
|
2274
|
+
# frame's business, and the heap otherwise -- which is a placement and
|
|
2275
|
+
# not a refusal: nothing is turned away for its size any more.
|
|
2276
|
+
#
|
|
2277
|
+
# Three ways onto the heap: a length the kernel works out, an array
|
|
2278
|
+
# larger than one frame should hold, and an array that fits on its own
|
|
2279
|
+
# but not beside the ones this kernel already has.
|
|
2280
|
+
def heap_array? (storage, shape)
|
|
2281
|
+
return true unless literal_shape?(shape)
|
|
2282
|
+
bytes = local_array_bytes(storage, shape)
|
|
2283
|
+
return true if bytes > LOCAL_ARRAY_BYTE_LIMIT
|
|
2284
|
+
bytes + stack_array_bytes > LOCAL_ARRAY_TOTAL_BYTE_LIMIT
|
|
2285
|
+
end
|
|
2286
|
+
|
|
2287
|
+
def literal_shape? (shape)
|
|
2288
|
+
shape.all? { |extent| extent.is_a?(Integer) }
|
|
2289
|
+
end
|
|
2290
|
+
|
|
2291
|
+
# What the arrays declared so far take on the stack. Two sibling
|
|
2292
|
+
# loops that each declare `w` are one entry: their blocks do not
|
|
2293
|
+
# overlap, so neither do their frames.
|
|
2294
|
+
def stack_array_bytes
|
|
2295
|
+
@local_array_declarations.sum { |_scope, _name, storage, shape, heap|
|
|
2296
|
+
heap ? 0 : local_array_bytes(storage, shape)
|
|
2297
|
+
}
|
|
2298
|
+
end
|
|
2299
|
+
|
|
2300
|
+
# A compiled function's body is called once per cell, so an allocation
|
|
2301
|
+
# in it would be one per cell -- and the body has no entry of its own
|
|
2302
|
+
# outside the kernel's loop to make it at instead.
|
|
2303
|
+
def refuse_a_heap_array_in_a_body (name, storage, shape, location)
|
|
2304
|
+
return unless @function
|
|
2305
|
+
reason =
|
|
2306
|
+
if literal_shape?(shape)
|
|
2307
|
+
"`#{name}` asks for " \
|
|
2308
|
+
"#{local_array_bytes_phrase(storage, shape)}, which is past the " \
|
|
2309
|
+
"#{LOCAL_ARRAY_BYTE_LIMIT} a frame here holds"
|
|
2310
|
+
else
|
|
2311
|
+
"the shape of `#{name}` is worked out when the kernel runs"
|
|
2312
|
+
end
|
|
2313
|
+
raise Unsupported.new(
|
|
2314
|
+
"#{reason}, so `#{name}` would have to be allocated -- and a " \
|
|
2315
|
+
"function's body is called once per cell, which would be one " \
|
|
2316
|
+
"allocation per cell. Make the array in the kernel that calls " \
|
|
2317
|
+
"this and pass it in, as a pointer parameter",
|
|
2318
|
+
location)
|
|
2319
|
+
end
|
|
2320
|
+
|
|
2321
|
+
# A shadow byte a cell where the kernel carries masks: it is on the
|
|
2322
|
+
# same stack as the cells, so it is counted with them.
|
|
2323
|
+
def local_array_bytes (storage, shape)
|
|
2324
|
+
cells = shape.inject(1) { |product, extent| product * extent }
|
|
2325
|
+
(LOCAL_ARRAY_STORAGE_BYTES.fetch(storage) + (@masked ? 1 : 0)) * cells
|
|
2326
|
+
end
|
|
2327
|
+
|
|
2328
|
+
# How the refusal accounts for a size the block did not write out.
|
|
2329
|
+
def local_array_bytes_phrase (storage, shape)
|
|
2330
|
+
cells = shape.inject(1) { |product, extent| product * extent }
|
|
2331
|
+
values = LOCAL_ARRAY_STORAGE_BYTES.fetch(storage) * cells
|
|
2332
|
+
return "#{values} bytes of #{storage}" unless @masked
|
|
2333
|
+
"#{values + cells} bytes -- #{values} of #{storage} and #{cells} of " \
|
|
2334
|
+
"the mask beside it, a byte a cell, since this kernel carries masks"
|
|
2335
|
+
end
|
|
2336
|
+
|
|
2337
|
+
# A name is in sight where some scope still standing holds it. The
|
|
2338
|
+
# scopes are pushed and popped with the inner loops' blocks, so a name
|
|
2339
|
+
# an inner loop introduced is out of sight after it -- which is what
|
|
2340
|
+
# Ruby says about it too.
|
|
2341
|
+
def local_in_sight? (name)
|
|
2342
|
+
@scopes.any? { |scope| scope.include?(name) }
|
|
2343
|
+
end
|
|
2344
|
+
|
|
2345
|
+
# The name of the local array this receiver is, or nil.
|
|
2346
|
+
#
|
|
2347
|
+
# A name the body did make an array under, but in a block that has since
|
|
2348
|
+
# closed, is neither: it says so rather than falling through to be
|
|
2349
|
+
# refused for not being a captured array, which is not what happened.
|
|
2350
|
+
def local_array_name (node)
|
|
2351
|
+
name = case node
|
|
2352
|
+
when Prism::LocalVariableReadNode
|
|
2353
|
+
node.name
|
|
2354
|
+
when Prism::CallNode
|
|
2355
|
+
node.name if node.receiver.nil? && node.arguments.nil? &&
|
|
2356
|
+
node.block.nil?
|
|
2357
|
+
end
|
|
2358
|
+
return nil unless name && @local_array_names.include?(name)
|
|
2359
|
+
unless local_in_sight?(name)
|
|
2360
|
+
raise Unsupported.new(
|
|
2361
|
+
"`#{name}` belongs to the loop block it was assigned in, and is " \
|
|
2362
|
+
"not there after it; make the array before the loop",
|
|
2363
|
+
node.location)
|
|
2364
|
+
end
|
|
2365
|
+
name
|
|
2366
|
+
end
|
|
2367
|
+
|
|
2368
|
+
# `[storage, shape]` for the declaration in sight under this name.
|
|
2369
|
+
def local_array_in_sight (name)
|
|
2370
|
+
@local_arrays.fetch(name)
|
|
2371
|
+
end
|
|
2372
|
+
|
|
2373
|
+
# A local array's subscripts.
|
|
2374
|
+
#
|
|
2375
|
+
# Not `read_subscripts`. That one answers for the operand tables: it
|
|
2376
|
+
# refuses an index where the block takes none, records the array's rank
|
|
2377
|
+
# in a table shared with every other array, and hands a window its
|
|
2378
|
+
# offsets instead. A local array is in none of that -- its rank is its
|
|
2379
|
+
# shape's, and its subscripts are written wherever the block stands.
|
|
2380
|
+
# What the two share is only the two ways a position is checked.
|
|
2381
|
+
def local_array_subscripts (name, shape, arguments, location)
|
|
2382
|
+
unless arguments.size == shape.size
|
|
2383
|
+
# A block may give one of its own arrays the name of the window it
|
|
2384
|
+
# was handed. Ruby reads that as rebinding the parameter, and so
|
|
2385
|
+
# does this -- but then the window's own spelling stops working, and
|
|
2386
|
+
# the axis count alone is a poor account of why.
|
|
2387
|
+
if @windows.include?(name)
|
|
2388
|
+
raise Unsupported.new(
|
|
2389
|
+
"`#{name}` is the window this block was given, and the block " \
|
|
2390
|
+
"also makes an array of its own under that name -- which " \
|
|
2391
|
+
"rebinds it, here as in Ruby, so from that line on `#{name}` " \
|
|
2392
|
+
"is the array and the window has no name left. " \
|
|
2393
|
+
"`#{name}[#{Array.new(rank, 0).join(', ')}]` is the window's " \
|
|
2394
|
+
"spelling and wants #{rank} #{rank == 1 ? 'offset' : 'offsets'}; " \
|
|
2395
|
+
"give the array a name of its own",
|
|
2396
|
+
location)
|
|
2397
|
+
end
|
|
2398
|
+
raise Unsupported.new(
|
|
2399
|
+
"`#{name}` has #{shape.size == 1 ? 'one axis' : "#{shape.size} axes"}, " \
|
|
2400
|
+
"so it takes #{shape.size == 1 ? 'one subscript' : "#{shape.size} subscripts"}, " \
|
|
2401
|
+
"and #{arguments.size} #{arguments.size == 1 ? 'was' : 'were'} written",
|
|
2402
|
+
location)
|
|
2403
|
+
end
|
|
2404
|
+
arguments.each_with_index.map { |argument, axis|
|
|
2405
|
+
local_array_subscript(name, shape, axis, argument, location)
|
|
2406
|
+
}
|
|
2407
|
+
end
|
|
2408
|
+
|
|
2409
|
+
# How a message names the thing that is out of range.
|
|
2410
|
+
#
|
|
2411
|
+
# With one axis the whole subscript is the thing, and quoting it back is
|
|
2412
|
+
# exactly right. With more than one it is not: the block wrote
|
|
2413
|
+
# `m[r, c]`, so quoting `m[c]` would be a spelling nobody typed, and an
|
|
2414
|
+
# extent on its own ("a local array of 4 cells") is a poor account of a
|
|
2415
|
+
# 3 x 4 one. So the axis is named instead, and the shape with it.
|
|
2416
|
+
def local_array_subject (name, shape, axis, argument)
|
|
2417
|
+
if shape.size == 1
|
|
2418
|
+
"`#{name}[#{argument.slice}]`"
|
|
2419
|
+
else
|
|
2420
|
+
"the subscript `#{argument.slice}` on axis #{axis} of `#{name}`, " \
|
|
2421
|
+
"which is #{shape.join(' x ')},"
|
|
2422
|
+
end
|
|
2423
|
+
end
|
|
2424
|
+
|
|
2425
|
+
# What the cells of the axis in question are, said whole rather than as
|
|
2426
|
+
# a fragment to glue on: the two ranks want different sentences and
|
|
2427
|
+
# composing them produced "is outside and that axis has 4 cells".
|
|
2428
|
+
def local_array_cells_phrase (shape, axis)
|
|
2429
|
+
extent = shape[axis]
|
|
2430
|
+
unless extent.is_a?(Integer)
|
|
2431
|
+
length = "a length of `#{extent}`, which the kernel works out"
|
|
2432
|
+
return shape.size == 1 ? "a local array of #{length}"
|
|
2433
|
+
: "that axis's #{length}"
|
|
2434
|
+
end
|
|
2435
|
+
cells = "#{extent} #{extent == 1 ? 'cell' : 'cells'}"
|
|
2436
|
+
run = extent == 1 ? "only cell is 0" : "cells are 0 to #{extent - 1}"
|
|
2437
|
+
shape.size == 1 ? "a local array of #{cells}, whose #{run}"
|
|
2438
|
+
: "that axis's #{cells}, whose #{run}"
|
|
2439
|
+
end
|
|
2440
|
+
|
|
2441
|
+
# (a) An index whose loop states its range in literals reaches a set of
|
|
2442
|
+
# positions that is known now, so it is checked now and the C carries no
|
|
2443
|
+
# test. (b) Anything else is checked where the cell is reached.
|
|
2444
|
+
#
|
|
2445
|
+
# A literal position is (a) as well, its range being itself.
|
|
2446
|
+
def local_array_subscript (name, shape, axis, argument, location)
|
|
2447
|
+
extent = shape[axis]
|
|
2448
|
+
# An axis whose length the kernel works out has nothing for (a) to
|
|
2449
|
+
# compare against: a literal position included, since 3 is inside an
|
|
2450
|
+
# array of 4 cells and outside one of 2. So every position on such
|
|
2451
|
+
# an axis is checked where the cell is reached.
|
|
2452
|
+
return [nil, build(argument)] unless extent.is_a?(Integer)
|
|
2453
|
+
if (index = index_name(argument)) && index_in_scope?(index)
|
|
2454
|
+
settled = checked_index_subscript(name, shape, axis, index, 0,
|
|
2455
|
+
argument, location)
|
|
2456
|
+
return settled if settled
|
|
2457
|
+
elsif argument.is_a?(Prism::CallNode) &&
|
|
2458
|
+
[:+, :-].include?(argument.name) &&
|
|
2459
|
+
(receiver = index_name(argument.receiver)) &&
|
|
2460
|
+
index_in_scope?(receiver)
|
|
2461
|
+
arguments = argument.arguments ? argument.arguments.arguments : []
|
|
2462
|
+
if arguments.size == 1 && (offset = literal_integer(arguments.first))
|
|
2463
|
+
offset = -offset if argument.name == :-
|
|
2464
|
+
settled = checked_index_subscript(name, shape, axis, receiver,
|
|
2465
|
+
offset, argument, location)
|
|
2466
|
+
return settled if settled
|
|
2467
|
+
end
|
|
2468
|
+
end
|
|
2469
|
+
if (position = literal_integer(argument))
|
|
2470
|
+
unless (0...extent).cover?(position)
|
|
2471
|
+
raise Unsupported.new(
|
|
2472
|
+
"#{local_array_subject(name, shape, axis, argument)} is " \
|
|
2473
|
+
"outside #{local_array_cells_phrase(shape, axis)}" +
|
|
2474
|
+
(position.negative? ?
|
|
2475
|
+
". A subscript here counts from the start, so CArray's " \
|
|
2476
|
+
"`#{name}[-1]` for the last cell is written " \
|
|
2477
|
+
"`#{name}[#{extent - 1}]`" : ""),
|
|
2478
|
+
location)
|
|
2479
|
+
end
|
|
2480
|
+
return [nil, IntegerLiteral.new(position, location)]
|
|
2481
|
+
end
|
|
2482
|
+
[nil, build(argument)]
|
|
2483
|
+
end
|
|
2484
|
+
|
|
2485
|
+
# The subscript settled as the block is read, or nil where it cannot be.
|
|
2486
|
+
#
|
|
2487
|
+
# Nil is not "allow it": it sends the axis to the check at the access,
|
|
2488
|
+
# which is where every position this compiler cannot settle goes. The
|
|
2489
|
+
# two kinds it cannot settle are a loop whose bound is a captured
|
|
2490
|
+
# integer and one whose range is written over another index -- in both
|
|
2491
|
+
# the reach is not knowable until the kernel runs. Returning the index
|
|
2492
|
+
# unchecked would leave a write past the end of a stack array with
|
|
2493
|
+
# nothing at all looking at it.
|
|
2494
|
+
def checked_index_subscript (name, shape, axis, index, offset, argument,
|
|
2495
|
+
location)
|
|
2496
|
+
identifier = index_identifier(index)
|
|
2497
|
+
return nil unless index_reach(identifier)
|
|
2498
|
+
verify_index_reach(name, shape, axis, identifier, offset, argument,
|
|
2499
|
+
location)
|
|
2500
|
+
[identifier, offset]
|
|
2501
|
+
end
|
|
2502
|
+
|
|
2503
|
+
# The lowest and highest position an index takes, where its loop says so
|
|
2504
|
+
# in literals; nil where it does not, or where the loop runs no passes
|
|
2505
|
+
# at all and so reaches nothing.
|
|
2506
|
+
#
|
|
2507
|
+
# Computed rather than enumerated: `(0...10_000_000).each` states its
|
|
2508
|
+
# range in literals too.
|
|
2509
|
+
def index_reach (identifier)
|
|
2510
|
+
range = @inner_ranges[identifier]
|
|
2511
|
+
return nil unless range
|
|
2512
|
+
from, to, step = range
|
|
2513
|
+
return nil unless from.is_a?(IntegerLiteral) && to.is_a?(IntegerLiteral)
|
|
2514
|
+
first = from.value
|
|
2515
|
+
last_excluded = to.value
|
|
2516
|
+
if step.positive?
|
|
2517
|
+
return nil unless first < last_excluded
|
|
2518
|
+
[first, first + ((last_excluded - 1 - first) / step) * step]
|
|
2519
|
+
else
|
|
2520
|
+
return nil unless first > last_excluded
|
|
2521
|
+
stride = -step
|
|
2522
|
+
[first - ((first - (last_excluded + 1)) / stride) * stride, first]
|
|
2523
|
+
end
|
|
2524
|
+
end
|
|
2525
|
+
|
|
2526
|
+
# This looks at the loop's range and not at what stands around the
|
|
2527
|
+
# access, which is the character the captured arrays' own bounds check
|
|
2528
|
+
# already has: an `if` around the line does not make the reach smaller,
|
|
2529
|
+
# because the reach is the loop's. Narrowing the loop's range is what
|
|
2530
|
+
# makes it smaller.
|
|
2531
|
+
def verify_index_reach (name, shape, axis, identifier, offset, argument,
|
|
2532
|
+
location)
|
|
2533
|
+
extent = shape[axis]
|
|
2534
|
+
reach = index_reach(identifier)
|
|
2535
|
+
return unless reach
|
|
2536
|
+
low = reach.first + offset
|
|
2537
|
+
high = reach.last + offset
|
|
2538
|
+
return if low >= 0 && high < extent
|
|
2539
|
+
outside = low.negative? ? low : high
|
|
2540
|
+
spelled = @index_sources.fetch(identifier, identifier)
|
|
2541
|
+
raise Unsupported.new(
|
|
2542
|
+
"#{local_array_subject(name, shape, axis, argument)} reaches cell " \
|
|
2543
|
+
"#{outside} of #{local_array_cells_phrase(shape, axis)}: " \
|
|
2544
|
+
"`#{spelled}` runs #{reach.first} to #{reach.last} here. Narrow " \
|
|
2545
|
+
"the loop's range, or give `#{name}` more cells -- an `if` around " \
|
|
2546
|
+
"the line does not narrow the reach, which is the loop's",
|
|
2547
|
+
location)
|
|
2548
|
+
end
|
|
2549
|
+
|
|
2550
|
+
# In the whole-array spelling every name in the block is a cell, so an
|
|
2551
|
+
# assignment to a name that is an array outside writes that array's
|
|
2552
|
+
# cell -- the one the loop is on, the same cell every read in the block
|
|
2553
|
+
# is at. A name that is not an array is a local, as it is anywhere
|
|
2554
|
+
# else.
|
|
2555
|
+
#
|
|
2556
|
+
# `depth` is Prism's: how many scopes out the variable is. Counted from
|
|
2557
|
+
# the statement, it says which of the scopes here holds the local -- and
|
|
2558
|
+
# one past the kernel's block is a name of the method around it, which
|
|
2559
|
+
# the kernel holds as its own, as it always has.
|
|
2560
|
+
def assign_local (name, expression, location, depth = 0)
|
|
2561
|
+
if @whole_array && @array_names.include?(name)
|
|
2562
|
+
return whole_array_write(name, expression, location)
|
|
2563
|
+
end
|
|
2564
|
+
# An index is the loop's, not the block's. Ruby reads `i = 2` as
|
|
2565
|
+
# rebinding the parameter and leaves the loop alone; the C would
|
|
2566
|
+
# assign to the counter, so the loop would walk somewhere else -- to
|
|
2567
|
+
# cells outside the array, given a value outside its extent. The two
|
|
2568
|
+
# do not mean the same thing, so this is not compiled.
|
|
2569
|
+
if index_in_scope?(name)
|
|
2570
|
+
raise Unsupported.new(
|
|
2571
|
+
"`#{name}` is a loop index, and assigning to it here would " \
|
|
2572
|
+
"move the loop rather than the value: in Ruby the same line " \
|
|
2573
|
+
"rebinds the parameter and the loop runs on. Use a local of " \
|
|
2574
|
+
"another name",
|
|
2575
|
+
location)
|
|
2576
|
+
end
|
|
2577
|
+
if @local_array_names.include?(name)
|
|
2578
|
+
if local_in_sight?(name)
|
|
2579
|
+
raise Unsupported.new(
|
|
2580
|
+
"`#{name}` is a local array here, and cannot hold a number; the " \
|
|
2581
|
+
"two cannot be one C variable, so give the number a name of its " \
|
|
2582
|
+
"own",
|
|
2583
|
+
location)
|
|
2584
|
+
end
|
|
2585
|
+
# Out of sight, the array belonged to a block that has closed, and
|
|
2586
|
+
# this line introduces a variable that merely shares its name. The
|
|
2587
|
+
# record goes, so a read below is read as the number it now is.
|
|
2588
|
+
# The array itself stays counted: its C declaration is still in that
|
|
2589
|
+
# block, and still on that block's stack.
|
|
2590
|
+
@local_array_names.delete(name)
|
|
2591
|
+
@local_arrays.delete(name)
|
|
2592
|
+
end
|
|
2593
|
+
@local_names << name unless @local_names.include?(name)
|
|
2594
|
+
scope = [@scopes.size - 1 - depth, 0].max
|
|
2595
|
+
@scopes[scope] << name unless @scopes[scope].include?(name)
|
|
2596
|
+
Assignment.new(name, expression, location, scope)
|
|
2597
|
+
end
|
|
2598
|
+
|
|
2599
|
+
# What a name on the left of a parallel assignment may not be, said the
|
|
2600
|
+
# way it was written. A cell may: `a[i], a[j] = a[j], a[i]` is the swap
|
|
2601
|
+
# a sort is written with, and each half of it is the write `a[i] = ...`
|
|
2602
|
+
# already was.
|
|
2603
|
+
TARGET_DESCRIPTIONS = {
|
|
2604
|
+
Prism::MultiTargetNode => "a nested target",
|
|
2605
|
+
Prism::SplatNode => "a splat",
|
|
2606
|
+
Prism::ImplicitRestNode => "a bare comma",
|
|
2607
|
+
Prism::InstanceVariableTargetNode => "an instance variable",
|
|
2608
|
+
Prism::GlobalVariableTargetNode => "a global variable",
|
|
2609
|
+
Prism::ClassVariableTargetNode => "a class variable",
|
|
2610
|
+
Prism::ConstantTargetNode => "a constant",
|
|
2611
|
+
Prism::ConstantPathTargetNode => "a constant",
|
|
2612
|
+
Prism::CallTargetNode => "an attribute",
|
|
2613
|
+
}.freeze
|
|
2614
|
+
|
|
2615
|
+
TARGET_HINTS = {
|
|
2616
|
+
Prism::MultiTargetNode => "it unpacks one value into several, and " \
|
|
2617
|
+
"nothing in a kernel holds several; write " \
|
|
2618
|
+
"the names out flat",
|
|
2619
|
+
Prism::SplatNode => "it takes however many values are left " \
|
|
2620
|
+
"over, and how many there are is the one " \
|
|
2621
|
+
"thing a C variable cannot be",
|
|
2622
|
+
Prism::ImplicitRestNode => "it drops the rest of the values, and how " \
|
|
2623
|
+
"many that is has to be written out here",
|
|
2624
|
+
}.freeze
|
|
2625
|
+
|
|
2626
|
+
# `prev, cur = cur, prev + cur`, and the swap it is the general case of.
|
|
2627
|
+
#
|
|
2628
|
+
# Ruby settles every value on the right before it writes any of them,
|
|
2629
|
+
# and so does this: each value is assigned to a name of its own first,
|
|
2630
|
+
# and only then are the writes made. The other way round, the line
|
|
2631
|
+
# above would write `prev` and then read the new one back while working
|
|
2632
|
+
# out `cur` -- which is the one thing the spelling exists to prevent,
|
|
2633
|
+
# and why `jit_for` wanted it. A recurrence advances by a parallel
|
|
2634
|
+
# assignment in Ruby; until now it had to be unpicked by hand here, and
|
|
2635
|
+
# the temporary a reader had to introduce is where the bug lives.
|
|
2636
|
+
#
|
|
2637
|
+
# The names are written even where nothing on the right reads what the
|
|
2638
|
+
# left writes. A rule that dropped them sometimes would be one a reader
|
|
2639
|
+
# of the generated C had to know before they could tell what a line
|
|
2640
|
+
# meant, and a C compiler drops a name assigned once and read once
|
|
2641
|
+
# without being asked.
|
|
2642
|
+
def build_parallel_assignment (node)
|
|
2643
|
+
targets = parallel_targets(node)
|
|
2644
|
+
values = parallel_values(node, targets)
|
|
2645
|
+
temporaries = values.map { |value|
|
|
2646
|
+
assign_local(parallel_value_name, build(value), value.location)
|
|
2647
|
+
}
|
|
2648
|
+
writes = targets.each_with_index.map { |target, k|
|
|
2649
|
+
value = build_name_read(temporaries[k].name, target.location)
|
|
2650
|
+
if target.is_a?(Prism::LocalVariableTargetNode)
|
|
2651
|
+
assign_local(target.name, value, target.location, target.depth)
|
|
2652
|
+
else
|
|
2653
|
+
indices = target.arguments ? target.arguments.arguments : []
|
|
2654
|
+
element_write(target, indices, value, target.location)
|
|
2655
|
+
end
|
|
2656
|
+
}
|
|
2657
|
+
ParallelAssignment.new(temporaries + writes, node.location)
|
|
2658
|
+
end
|
|
2659
|
+
|
|
2660
|
+
def parallel_targets (node)
|
|
2661
|
+
targets = node.lefts + (node.rest ? [node.rest] : []) + node.rights
|
|
2662
|
+
targets.each do |target|
|
|
2663
|
+
next if target.is_a?(Prism::LocalVariableTargetNode) ||
|
|
2664
|
+
target.is_a?(Prism::IndexTargetNode)
|
|
2665
|
+
described = TARGET_DESCRIPTIONS[target.class]
|
|
2666
|
+
hint = TARGET_HINTS[target.class]
|
|
2667
|
+
raise Unsupported.new(
|
|
2668
|
+
"a parallel assignment writes names and cells here -- " \
|
|
2669
|
+
"`a, b = b, a`, `a[i], a[j] = a[j], a[i]` -- and this one writes " \
|
|
2670
|
+
"#{described || node_name(target)}#{hint ? " -- #{hint}" : ""}",
|
|
2671
|
+
target.location)
|
|
2672
|
+
end
|
|
2673
|
+
targets
|
|
2674
|
+
end
|
|
2675
|
+
|
|
2676
|
+
# The values, told apart from the one value Ruby would spread.
|
|
2677
|
+
#
|
|
2678
|
+
# `a, b = b, a` writes its values out, and Prism hands them over as an
|
|
2679
|
+
# array with no brackets around it. Brackets are Ruby's other spelling
|
|
2680
|
+
# -- `a, b = [b, a]` -- and it means something else: one value, taken
|
|
2681
|
+
# apart across the names. Nothing in a kernel is a value that can be
|
|
2682
|
+
# taken apart, so the two are told apart here rather than quietly read
|
|
2683
|
+
# as one.
|
|
2684
|
+
def parallel_values (node, targets)
|
|
2685
|
+
value = node.value
|
|
2686
|
+
unless value.is_a?(Prism::ArrayNode) && value.opening_loc.nil?
|
|
2687
|
+
raise Unsupported.new(
|
|
2688
|
+
"a parallel assignment takes one value per name, written out on " \
|
|
2689
|
+
"the right, as in `a, b = b, a`. Ruby takes a single value apart " \
|
|
2690
|
+
"across the names, and a kernel computes numbers -- there is " \
|
|
2691
|
+
"nothing here to take apart",
|
|
2692
|
+
value.location)
|
|
2693
|
+
end
|
|
2694
|
+
if (splat = value.elements.find { |element|
|
|
2695
|
+
element.is_a?(Prism::SplatNode) })
|
|
2696
|
+
raise Unsupported.new(
|
|
2697
|
+
"`*` spreads a value across the names, and how many it comes to " \
|
|
2698
|
+
"is the one thing a C variable cannot be; write the values out",
|
|
2699
|
+
splat.location)
|
|
2700
|
+
end
|
|
2701
|
+
unless value.elements.size == targets.size
|
|
2702
|
+
# Ruby has an answer for either way round, and neither is one a
|
|
2703
|
+
# kernel can keep: the names it runs out of values for are nil, and
|
|
2704
|
+
# a value it runs out of names for is dropped.
|
|
2705
|
+
raise Unsupported.new(
|
|
2706
|
+
"this writes #{count_of(targets.size, "name")} and has " \
|
|
2707
|
+
"#{count_of(value.elements.size, "value")} for them. Ruby " \
|
|
2708
|
+
"#{value.elements.size < targets.size ?
|
|
2709
|
+
"leaves the rest of the names nil, which is not a number a " \
|
|
2710
|
+
"local or a cell can hold" :
|
|
2711
|
+
"drops the values it has no name for, and a value computed " \
|
|
2712
|
+
"and dropped is a line that does nothing"}",
|
|
2713
|
+
node.location)
|
|
2714
|
+
end
|
|
2715
|
+
value.elements
|
|
2716
|
+
end
|
|
2717
|
+
|
|
2718
|
+
def count_of (n, noun)
|
|
2719
|
+
"#{n} #{n == 1 ? noun : noun + "s"}"
|
|
2720
|
+
end
|
|
2721
|
+
|
|
2722
|
+
# A name for a value on the right that no name in the block can be, and
|
|
2723
|
+
# that no other statement uses.
|
|
2724
|
+
#
|
|
2725
|
+
# The block's own text settles the first half: a local, a capture, an
|
|
2726
|
+
# index and an array all appear in it, so a name that appears nowhere
|
|
2727
|
+
# in it is none of them. An underscore is added until that holds,
|
|
2728
|
+
# which keeps the same block reading the same way every time -- it has
|
|
2729
|
+
# to, the generated C being part of the cache key.
|
|
2730
|
+
#
|
|
2731
|
+
# The second half is the counter. Two parallel assignments sharing a
|
|
2732
|
+
# name would be two statements sharing a C variable, and a variable
|
|
2733
|
+
# live at the head of a loop is one the loop carries: a body that
|
|
2734
|
+
# swapped Integers before the loop and Floats inside it was refused for
|
|
2735
|
+
# changing the type of `value1` on the way round -- a name its author
|
|
2736
|
+
# never wrote, about a value that is dead by the end of the line it is
|
|
2737
|
+
# on. A name of its own per statement cannot be carried anywhere.
|
|
2738
|
+
def parallel_value_name
|
|
2739
|
+
@parallel_value_prefix ||= begin
|
|
2740
|
+
text = (@source || @node&.slice).to_s
|
|
2741
|
+
prefix = "value"
|
|
2742
|
+
prefix = "_" + prefix while text.include?(prefix)
|
|
2743
|
+
prefix
|
|
2744
|
+
end
|
|
2745
|
+
@parallel_value_count = @parallel_value_count.to_i + 1
|
|
2746
|
+
:"#{@parallel_value_prefix}#{@parallel_value_count}"
|
|
2747
|
+
end
|
|
2748
|
+
|
|
2749
|
+
# `x += e` is `x = x + e`, and is read as exactly that: the name is
|
|
2750
|
+
# read where Ruby reads it -- before the right-hand side, and by the
|
|
2751
|
+
# same rule, so a name the block has not assigned yet says so rather
|
|
2752
|
+
# than starting from whatever C left in the variable.
|
|
2753
|
+
def build_operator_assignment (node)
|
|
2754
|
+
operator = assignment_operator(node)
|
|
2755
|
+
read = build_name_read(node.name, node.location)
|
|
2756
|
+
expression = combined(operator, read, build(node.value), node.location)
|
|
2757
|
+
assign_local(node.name, expression, node.location, node.depth)
|
|
2758
|
+
end
|
|
2759
|
+
|
|
2760
|
+
# `a[i] += e` is `a[i] = a[i] + e`, cell for cell -- including under a
|
|
2761
|
+
# mask, where the read is a read like any other and carries what it
|
|
2762
|
+
# finds into what is written.
|
|
2763
|
+
def build_operator_element_write (node)
|
|
2764
|
+
operator = assignment_operator(node)
|
|
2765
|
+
indices = node.arguments ? node.arguments.arguments : []
|
|
2766
|
+
read = build_element_read(node, indices)
|
|
2767
|
+
expression = combined(operator, read, build(node.value), node.location)
|
|
2768
|
+
element_write(node, indices, expression, node.location)
|
|
2769
|
+
end
|
|
2770
|
+
|
|
2771
|
+
# The operators an assignment may carry are the ones an expression may:
|
|
2772
|
+
# `||=` and `&&=` are refused where they are read, being about nil and
|
|
2773
|
+
# false rather than about arithmetic.
|
|
2774
|
+
def assignment_operator (node)
|
|
2775
|
+
operator = node.binary_operator
|
|
2776
|
+
unless (ARITHMETIC_OPERATORS + BIT_OPERATORS + [:**]).include?(operator)
|
|
2777
|
+
raise Unsupported.new(
|
|
2778
|
+
"`#{operator}=` is not one of the operators a kernel computes " \
|
|
2779
|
+
"with", node.location)
|
|
2780
|
+
end
|
|
2781
|
+
operator
|
|
2782
|
+
end
|
|
2783
|
+
|
|
2784
|
+
def combined (operator, left, right, location)
|
|
2785
|
+
return power_node(left, right, location) if operator == :**
|
|
2786
|
+
BinaryOperation.new(operator, left, right, location)
|
|
2787
|
+
end
|
|
2788
|
+
|
|
1195
2789
|
def build_element_write (node)
|
|
1196
|
-
|
|
2790
|
+
if node.name == :each || node.name == :times || node.name == :step
|
|
2791
|
+
return build_inner_loop(node)
|
|
2792
|
+
end
|
|
1197
2793
|
unless node.name == :[]=
|
|
1198
2794
|
# An expression standing alone in this spelling is a computation
|
|
1199
2795
|
# nobody can see: there is no index to have written it against, and
|
|
@@ -1205,54 +2801,98 @@ class CArray
|
|
|
1205
2801
|
"`CArray.jit_map`",
|
|
1206
2802
|
node.location)
|
|
1207
2803
|
end
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
2804
|
+
if INNER_LOOP_SPELLINGS.include?(node.name)
|
|
2805
|
+
raise Unsupported.new(
|
|
2806
|
+
"an inner loop is written `(a...b).each`, `n.times` or " \
|
|
2807
|
+
"`a.step(b, s)`, which is the spelling an extent takes a " \
|
|
2808
|
+
"stride and a direction in -- `#{node.name}` is not one of " \
|
|
2809
|
+
"them",
|
|
2810
|
+
node.location)
|
|
2811
|
+
end
|
|
2812
|
+
refuse_as_a_statement(node, "a call to `#{node.name}`")
|
|
1212
2813
|
end
|
|
1213
|
-
|
|
2814
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
2815
|
+
element_write(node, arguments[0..-2], nil, node.location,
|
|
2816
|
+
value_node: arguments.last)
|
|
2817
|
+
end
|
|
2818
|
+
|
|
2819
|
+
# The write itself, with the indices and the value already told apart:
|
|
2820
|
+
# `a[i] = e` puts the value last among the arguments, while `a[i] += e`
|
|
2821
|
+
# keeps it somewhere else, and everything from here down is the same
|
|
2822
|
+
# for both. `value_node` is passed where the value is still a Prism
|
|
2823
|
+
# node, since `a[i] = UNDEF` is a mark rather than a value and is read
|
|
2824
|
+
# off the node.
|
|
2825
|
+
def element_write (node, indices, expression, location, value_node: nil)
|
|
2826
|
+
if (pointer = pointer_subscript(node, write: true, indices: indices))
|
|
1214
2827
|
name, index = pointer
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
2828
|
+
return PointerWrite.new(name, index,
|
|
2829
|
+
expression || build(value_node), location)
|
|
2830
|
+
end
|
|
2831
|
+
refuse_a_constructor_in_an_expression(node.receiver)
|
|
2832
|
+
if (name = local_array_name(node.receiver))
|
|
2833
|
+
storage, shape = local_array_in_sight(name)
|
|
2834
|
+
if value_node && undef_constant?(value_node)
|
|
2835
|
+
# A kernel that writes UNDEF anywhere carries masks by that
|
|
2836
|
+
# alone, so this is a mark on the shadow beside the cell. In a
|
|
2837
|
+
# function body there is no shadow: a function is handed numbers
|
|
2838
|
+
# and pointers, and a mask reaches neither.
|
|
2839
|
+
unless @masked
|
|
2840
|
+
raise Unsupported.new(
|
|
2841
|
+
"`#{name}` is a local array in a compiled function's body, " \
|
|
2842
|
+
"and nothing there carries masks -- a function is handed " \
|
|
2843
|
+
"numbers and pointers, and a mask travels in neither. Keep " \
|
|
2844
|
+
"a value that stands for missing, or mark the cell in the " \
|
|
2845
|
+
"kernel that called this",
|
|
2846
|
+
location)
|
|
2847
|
+
end
|
|
2848
|
+
@uses_undef = true
|
|
2849
|
+
return LocalArrayMaskWrite.new(
|
|
2850
|
+
name, storage, shape,
|
|
2851
|
+
local_array_subscripts(name, shape, indices, location), location)
|
|
2852
|
+
end
|
|
2853
|
+
return LocalArrayWrite.new(
|
|
2854
|
+
name, storage, shape,
|
|
2855
|
+
local_array_subscripts(name, shape, indices, location),
|
|
2856
|
+
expression || build(value_node), location)
|
|
1218
2857
|
end
|
|
1219
|
-
array = array_name(node.receiver,
|
|
1220
|
-
arguments = node.arguments ? node.arguments.arguments : []
|
|
2858
|
+
array = array_name(node.receiver, location)
|
|
1221
2859
|
# `out[] = ...` was how a block that had to run as Ruby said "the
|
|
1222
2860
|
# whole array", `[]=` being the only spelling Ruby has for it. The
|
|
1223
2861
|
# block is read rather than run, and every name in it is a cell, so
|
|
1224
2862
|
# the assignment is Ruby's own: `out = ...`.
|
|
1225
|
-
if @whole_array &&
|
|
2863
|
+
if @whole_array && indices.empty?
|
|
1226
2864
|
raise Unsupported.new(
|
|
1227
2865
|
"`#{array}[] = ...` writes the cell the loop is on, which is what " \
|
|
1228
2866
|
"`#{array} = ...` says; the block is read rather than run, so the " \
|
|
1229
2867
|
"assignment is an ordinary one",
|
|
1230
|
-
|
|
2868
|
+
location)
|
|
1231
2869
|
end
|
|
1232
|
-
subscripts = read_subscripts(array,
|
|
1233
|
-
# A kernel writes a cell its own indices
|
|
1234
|
-
#
|
|
1235
|
-
#
|
|
1236
|
-
#
|
|
2870
|
+
subscripts = read_subscripts(array, indices, location)
|
|
2871
|
+
# A kernel writes a cell one of its own indices reaches -- an outer
|
|
2872
|
+
# one or an inner one -- or one it works out, which is a scatter and
|
|
2873
|
+
# is checked as it is reached rather than in advance. What it may not
|
|
2874
|
+
# do is write through a name that addresses no cell at all.
|
|
1237
2875
|
#
|
|
1238
2876
|
# In a contraction the left-hand side names which indices are free and
|
|
1239
2877
|
# which are summed over, so it is checked there instead.
|
|
1240
2878
|
unless @contract || walking_subscripts?(subscripts) ||
|
|
1241
2879
|
computed_subscripts?(subscripts)
|
|
1242
2880
|
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
|
-
|
|
2881
|
+
"a kernel writes through the indices it is running, so " \
|
|
2882
|
+
"`#{array}[...]` on the left of `=` is addressed by " \
|
|
2883
|
+
"#{available_indices}, by a position fixed before the loop runs " \
|
|
2884
|
+
"-- or by a value the kernel works out, which is a scatter",
|
|
2885
|
+
location)
|
|
1248
2886
|
end
|
|
1249
2887
|
record_subscripts(array, subscripts)
|
|
2888
|
+
record_write_subscripts(array, subscripts)
|
|
1250
2889
|
@written_arrays << array unless @written_arrays.include?(array)
|
|
1251
|
-
if undef_constant?(
|
|
2890
|
+
if value_node && undef_constant?(value_node)
|
|
1252
2891
|
@uses_undef = true
|
|
1253
|
-
return MaskWrite.new(array,
|
|
2892
|
+
return MaskWrite.new(array, location)
|
|
1254
2893
|
end
|
|
1255
|
-
ElementWrite.new(array, build(
|
|
2894
|
+
ElementWrite.new(array, expression || build(value_node), location,
|
|
2895
|
+
subscripts)
|
|
1256
2896
|
end
|
|
1257
2897
|
|
|
1258
2898
|
def build (node)
|
|
@@ -1301,8 +2941,19 @@ class CArray
|
|
|
1301
2941
|
# closes over nothing.
|
|
1302
2942
|
build_name_read(node.name, node.location)
|
|
1303
2943
|
else
|
|
1304
|
-
|
|
1305
|
-
|
|
2944
|
+
# Named as it was written where there is a name for it, and with
|
|
2945
|
+
# the same advice a statement in that position gets: `unless` in
|
|
2946
|
+
# value position was "unsupported expression Unless", which is
|
|
2947
|
+
# this compiler's reading of it rather than the reader's.
|
|
2948
|
+
described = STATEMENT_DESCRIPTIONS[node.class]
|
|
2949
|
+
hint = STATEMENT_HINTS[node.class]
|
|
2950
|
+
raise Unsupported.new(
|
|
2951
|
+
described ?
|
|
2952
|
+
"a kernel computes its value from arithmetic, comparisons, " \
|
|
2953
|
+
"cells and calls, and this one is #{described}" \
|
|
2954
|
+
"#{hint ? " -- #{hint}" : ""}" :
|
|
2955
|
+
"unsupported expression #{node_name(node)}",
|
|
2956
|
+
node.location)
|
|
1306
2957
|
end
|
|
1307
2958
|
end
|
|
1308
2959
|
|
|
@@ -1314,8 +2965,31 @@ class CArray
|
|
|
1314
2965
|
def build_name_read (name, location)
|
|
1315
2966
|
axis = @outer_names.index(name)
|
|
1316
2967
|
return IndexVariable.new(name, axis, location) if axis
|
|
1317
|
-
|
|
2968
|
+
if @inner_names.include?(name)
|
|
2969
|
+
return IndexVariable.new(@inner_aliases.fetch(name, name), nil, location)
|
|
2970
|
+
end
|
|
2971
|
+
if @local_array_names.include?(name)
|
|
2972
|
+
unless local_in_sight?(name)
|
|
2973
|
+
raise Unsupported.new(
|
|
2974
|
+
"`#{name}` belongs to the loop block it was assigned in, and is " \
|
|
2975
|
+
"not there after it; make the array before the loop",
|
|
2976
|
+
location)
|
|
2977
|
+
end
|
|
2978
|
+
raise Unsupported.new(
|
|
2979
|
+
"`#{name}` is a local array; index it, as in `#{name}[0]`. An " \
|
|
2980
|
+
"array has no value of its own in a kernel -- what the name " \
|
|
2981
|
+
"stands for is its cells",
|
|
2982
|
+
location)
|
|
2983
|
+
end
|
|
1318
2984
|
if @local_names.include?(name)
|
|
2985
|
+
# A local of an inner loop's block that has closed is not in sight:
|
|
2986
|
+
# Ruby reads the same name after the block as a method call.
|
|
2987
|
+
unless @scopes.any? { |scope| scope.include?(name) }
|
|
2988
|
+
raise Unsupported.new(
|
|
2989
|
+
"`#{name}` belongs to the loop block it was assigned in, and is " \
|
|
2990
|
+
"not there after it; give it a value before the loop",
|
|
2991
|
+
location)
|
|
2992
|
+
end
|
|
1319
2993
|
return LocalRead.new(name, location)
|
|
1320
2994
|
end
|
|
1321
2995
|
if @cell_names.include?(name)
|
|
@@ -1380,9 +3054,29 @@ class CArray
|
|
|
1380
3054
|
if node.receiver.nil? && node.arguments.nil? && node.block.nil?
|
|
1381
3055
|
return build_name_read(node.name, node.location)
|
|
1382
3056
|
end
|
|
3057
|
+
if (intrinsic = build_intrinsic_call(node))
|
|
3058
|
+
return intrinsic
|
|
3059
|
+
end
|
|
3060
|
+
refuse_a_constructor_in_an_expression(node)
|
|
3061
|
+
# A method on a local array. The operators are left alone so that
|
|
3062
|
+
# `w + 1` is refused for reading the array bare, which is what it
|
|
3063
|
+
# does; a named method is refused here, where the name can be said.
|
|
3064
|
+
if (holder = local_array_name(node.receiver)) &&
|
|
3065
|
+
!LOCAL_ARRAY_SUBSCRIPTS.include?(node.name) &&
|
|
3066
|
+
!(ARITHMETIC_OPERATORS + COMPARISON_OPERATORS +
|
|
3067
|
+
BIT_OPERATORS + [:**]).include?(node.name)
|
|
3068
|
+
raise Unsupported.new(
|
|
3069
|
+
"`#{holder}` is a local array, and `#{node.name}` is a method " \
|
|
3070
|
+
"CArray answers outside a kernel; inside one an array is reached " \
|
|
3071
|
+
"at a subscript. Write the loop out over `#{holder}[k]`",
|
|
3072
|
+
node.location)
|
|
3073
|
+
end
|
|
1383
3074
|
if (recursive = recursive_call(node))
|
|
1384
3075
|
return recursive
|
|
1385
3076
|
end
|
|
3077
|
+
if (draw = random_call(node))
|
|
3078
|
+
return draw
|
|
3079
|
+
end
|
|
1386
3080
|
if (c_function = c_function_call(node))
|
|
1387
3081
|
return c_function
|
|
1388
3082
|
end
|
|
@@ -1421,6 +3115,28 @@ class CArray
|
|
|
1421
3115
|
if node.name == :** && arguments.size == 1
|
|
1422
3116
|
return build_power(node.receiver, arguments.first, node.location)
|
|
1423
3117
|
end
|
|
3118
|
+
if node.name == :clamp
|
|
3119
|
+
unless arguments.size == 2
|
|
3120
|
+
raise Unsupported.new(
|
|
3121
|
+
"`clamp` takes the two bounds, as in `x.clamp(0.0, 1.0)`" +
|
|
3122
|
+
(arguments.size == 1 ? " -- the range form is not in the subset" : ""),
|
|
3123
|
+
node.location)
|
|
3124
|
+
end
|
|
3125
|
+
return Clamp.new(build(node.receiver), build(arguments.first),
|
|
3126
|
+
build(arguments.last), node.location)
|
|
3127
|
+
end
|
|
3128
|
+
if arguments.empty? && NUMERIC_PREDICATES.include?(node.name)
|
|
3129
|
+
return NumericPredicate.new(node.name, build(node.receiver),
|
|
3130
|
+
node.location)
|
|
3131
|
+
end
|
|
3132
|
+
if arguments.empty? && node.name == :infinite?
|
|
3133
|
+
raise Unsupported.new(
|
|
3134
|
+
"`infinite?` answers nil, 1 or -1 in Ruby rather than true or " \
|
|
3135
|
+
"false, and a kernel has no nil to answer with; ask " \
|
|
3136
|
+
"`x.abs == Float::INFINITY`, or `x == Float::INFINITY` where " \
|
|
3137
|
+
"the sign is the question",
|
|
3138
|
+
node.location)
|
|
3139
|
+
end
|
|
1424
3140
|
if arguments.empty?
|
|
1425
3141
|
if (function = postfix_math(node.name))
|
|
1426
3142
|
return MathCall.new(function, [build(node.receiver)], node.location)
|
|
@@ -1507,11 +3223,26 @@ class CArray
|
|
|
1507
3223
|
"only a cell can be compared with UNDEF, as in `a[i] == UNDEF`",
|
|
1508
3224
|
location)
|
|
1509
3225
|
end
|
|
3226
|
+
arguments = receiver.arguments ? receiver.arguments.arguments : []
|
|
3227
|
+
if (made = local_array_name(receiver.receiver))
|
|
3228
|
+
storage, shape = local_array_in_sight(made)
|
|
3229
|
+
unless @masked
|
|
3230
|
+
raise Unsupported.new(
|
|
3231
|
+
"`#{made}` is a local array in a compiled function's body, " \
|
|
3232
|
+
"and nothing there carries masks -- a function is handed " \
|
|
3233
|
+
"numbers and pointers, and a mask travels in neither, so " \
|
|
3234
|
+
"there is no shadow beside these cells to ask about",
|
|
3235
|
+
location)
|
|
3236
|
+
end
|
|
3237
|
+
@uses_undef = true
|
|
3238
|
+
return LocalArrayMaskTest.new(
|
|
3239
|
+
made, storage, shape,
|
|
3240
|
+
local_array_subscripts(made, shape, arguments, location),
|
|
3241
|
+
negated, location)
|
|
3242
|
+
end
|
|
1510
3243
|
array = array_name(receiver.receiver, location)
|
|
1511
3244
|
array_for_test = array
|
|
1512
|
-
subscripts = read_subscripts(array_for_test,
|
|
1513
|
-
receiver.arguments ? receiver.arguments.arguments : [],
|
|
1514
|
-
location)
|
|
3245
|
+
subscripts = read_subscripts(array_for_test, arguments, location)
|
|
1515
3246
|
# Recorded like any other read, because the cell still has to exist
|
|
1516
3247
|
# and still has to have been settled before it is asked about. What
|
|
1517
3248
|
# it does not do is feed the value-mask propagation, which is decided
|
|
@@ -1525,7 +3256,7 @@ class CArray
|
|
|
1525
3256
|
# does not. A pointer declared const is refused on the left rather than
|
|
1526
3257
|
# silently written through: the declaration is a promise to the caller,
|
|
1527
3258
|
# not decoration.
|
|
1528
|
-
def pointer_subscript (node, write: false)
|
|
3259
|
+
def pointer_subscript (node, write: false, indices: nil)
|
|
1529
3260
|
name = captured_name(node.receiver)
|
|
1530
3261
|
return nil unless name && @pointers.key?(name)
|
|
1531
3262
|
case @pointers.fetch(name)
|
|
@@ -1541,14 +3272,36 @@ class CArray
|
|
|
1541
3272
|
"not write through it", node.location)
|
|
1542
3273
|
end
|
|
1543
3274
|
end
|
|
1544
|
-
arguments =
|
|
1545
|
-
|
|
3275
|
+
arguments = indices
|
|
3276
|
+
unless arguments
|
|
3277
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
3278
|
+
arguments = arguments[0..-2] if write
|
|
3279
|
+
end
|
|
1546
3280
|
unless arguments.size == 1
|
|
1547
3281
|
raise Unsupported.new(
|
|
1548
3282
|
"`#{name}` is a pointer, so it takes one index", node.location)
|
|
1549
3283
|
end
|
|
1550
3284
|
@pointer_names << name unless @pointer_names.include?(name)
|
|
1551
|
-
|
|
3285
|
+
index = build(arguments.first)
|
|
3286
|
+
refuse_undeclared_cell(name, index, node.location)
|
|
3287
|
+
[name, index]
|
|
3288
|
+
end
|
|
3289
|
+
|
|
3290
|
+
# A subscript on a pointer is not checked when the function runs: that
|
|
3291
|
+
# is C's bargain, and a computed one could only be checked there. A
|
|
3292
|
+
# literal needs no running to be read, though, and where the
|
|
3293
|
+
# declaration said how many cells there are, one outside them is a
|
|
3294
|
+
# write or a read past what the caller was held to -- the caller is
|
|
3295
|
+
# checked for at least that many and no more. C says nothing about
|
|
3296
|
+
# `v[7]` against `double v[2]`; this says it as the body is read.
|
|
3297
|
+
def refuse_undeclared_cell (name, index, location)
|
|
3298
|
+
length = @pointer_lengths[name]
|
|
3299
|
+
return unless length && index.is_a?(IntegerLiteral)
|
|
3300
|
+
return if index.value >= 0 && index.value < length
|
|
3301
|
+
raise Unsupported.new(
|
|
3302
|
+
"`#{name}[#{index.value}]` is outside the #{length} " \
|
|
3303
|
+
"#{length == 1 ? 'cell' : 'cells'} `#{name}` is declared with; " \
|
|
3304
|
+
"a caller is held to that many and no more", location)
|
|
1552
3305
|
end
|
|
1553
3306
|
|
|
1554
3307
|
# `fact(n - 1)` inside the body of `double fact(double)`, and
|
|
@@ -1561,16 +3314,19 @@ class CArray
|
|
|
1561
3314
|
def recursive_call (node)
|
|
1562
3315
|
return nil unless @recursion
|
|
1563
3316
|
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
|
-
#
|
|
3317
|
+
# `fact.call(n - 1)`, the spelling every other C function takes,
|
|
3318
|
+
# borrowed or written. A bare `fact(n - 1)` reads better as C and is
|
|
3319
|
+
# refused all the same, because a bare call is a spelling this
|
|
3320
|
+
# compiler has taken for itself: `sum(w)` and `sort(w)` are its own
|
|
3321
|
+
# functions, reached by no name the block wrote. A function of yours
|
|
3322
|
+
# is reached through the name that holds it -- which is also what
|
|
3323
|
+
# makes a recursion read like a call to anything else.
|
|
1569
3324
|
if node.receiver.nil? && node.name == name && node.arguments
|
|
1570
3325
|
raise Unsupported.new(
|
|
1571
3326
|
"`#{name}` calls itself the way any C function is called here, " \
|
|
1572
|
-
"as `#{name}.call(...)` -- a bare `#{name}(...)` is
|
|
1573
|
-
"
|
|
3327
|
+
"as `#{name}.call(...)` -- a bare `#{name}(...)` is how this " \
|
|
3328
|
+
"compiler spells its own functions, such as `sum(w)`, and a " \
|
|
3329
|
+
"function of yours is reached through the name that holds it",
|
|
1574
3330
|
node.location)
|
|
1575
3331
|
end
|
|
1576
3332
|
return nil unless C_FUNCTION_CALL_NAMES.include?(node.name) &&
|
|
@@ -1590,6 +3346,111 @@ class CArray
|
|
|
1590
3346
|
RecursiveCall.new(name, built, parameters, result_type, node.location)
|
|
1591
3347
|
end
|
|
1592
3348
|
|
|
3349
|
+
# `int64_t s[4]`, the parameter the generator's own C takes. Held as a
|
|
3350
|
+
# parsed declaration rather than described here, so that what the state
|
|
3351
|
+
# array is checked against is the C the draw will actually be compiled
|
|
3352
|
+
# into -- the same check `poly.call(x, coef)` gets, from the same code.
|
|
3353
|
+
# Parsed on first use: the declaration parser is in c_function.rb,
|
|
3354
|
+
# which is required after this file.
|
|
3355
|
+
def self.random_state_parameter
|
|
3356
|
+
@random_state_parameter ||=
|
|
3357
|
+
CDeclaration.parse("double draw(int64_t state[4])").last.first
|
|
3358
|
+
end
|
|
3359
|
+
|
|
3360
|
+
# `r.rand` and `r.bits` -- a draw written as a method of the generator,
|
|
3361
|
+
# which is how Ruby writes one. `#rand` and `#bits` do these outside a
|
|
3362
|
+
# kernel, and the same names inside it mean the same two things.
|
|
3363
|
+
RANDOM_DRAW_METHODS =
|
|
3364
|
+
{ :random => :random, :randomn => :randomn, :bits => :bits }.freeze
|
|
3365
|
+
|
|
3366
|
+
# `random(rng: r)` -- the same draw as `r.rand`, written as the array
|
|
3367
|
+
# language writes it. `a.random!(rng: r)` fills an array from a
|
|
3368
|
+
# generator, and this is that sentence about one cell, so a reader
|
|
3369
|
+
# moving between the two reads one word rather than matching two up.
|
|
3370
|
+
#
|
|
3371
|
+
# It is the only keyword argument in the subset and the only bare name
|
|
3372
|
+
# in it that Ruby does not have; both are paid for that.
|
|
3373
|
+
RANDOM_KEYWORD_NAMES =
|
|
3374
|
+
{ :random => :random, :randomn => :randomn }.freeze
|
|
3375
|
+
RANDOM_DRAW_KEYWORD = "rng"
|
|
3376
|
+
|
|
3377
|
+
# Returns the node for a draw from a captured CArray::Rng, or nil when
|
|
3378
|
+
# this is not one.
|
|
3379
|
+
def random_call (node)
|
|
3380
|
+
if node.receiver.nil? && (kind = RANDOM_KEYWORD_NAMES[node.name])
|
|
3381
|
+
return build_random_draw(random_generator_name(node), kind, node)
|
|
3382
|
+
end
|
|
3383
|
+
name = captured_name(node.receiver)
|
|
3384
|
+
return nil unless name && @randoms.key?(name)
|
|
3385
|
+
kind = RANDOM_DRAW_METHODS[node.name]
|
|
3386
|
+
refuse_a_generator_call(name, node) unless kind
|
|
3387
|
+
unless (node.arguments ? node.arguments.arguments : []).empty?
|
|
3388
|
+
bounded = node.name == :random ?
|
|
3389
|
+
". `CArray#random!` takes a range for a whole array; a kernel " \
|
|
3390
|
+
"has no bounded draw, so scale the one you get" : ""
|
|
3391
|
+
raise Unsupported.new(
|
|
3392
|
+
"`#{name}.#{node.name}` in a kernel is one draw and takes no " \
|
|
3393
|
+
"arguments#{bounded}",
|
|
3394
|
+
node.location)
|
|
3395
|
+
end
|
|
3396
|
+
build_random_draw(name, kind, node)
|
|
3397
|
+
end
|
|
3398
|
+
|
|
3399
|
+
def build_random_draw (name, kind, node)
|
|
3400
|
+
state = @randoms.fetch(name)
|
|
3401
|
+
@random_names << name unless @random_names.include?(name)
|
|
3402
|
+
# The state travels the way any array handed to a C function whole
|
|
3403
|
+
# does: by address, in the `data` buffer, copied back after the call.
|
|
3404
|
+
# That copy-back is what leaves the generator advanced.
|
|
3405
|
+
@address_arrays << state unless @address_arrays.include?(state)
|
|
3406
|
+
(@address_parameters[state] ||= []) << self.class.random_state_parameter
|
|
3407
|
+
RandomDraw.new(name, state, kind, node.location)
|
|
3408
|
+
end
|
|
3409
|
+
|
|
3410
|
+
# Everything else reached on a generator. There are three spellings
|
|
3411
|
+
# and no fourth, so the message lists them rather than saying only that
|
|
3412
|
+
# this one is not among them.
|
|
3413
|
+
def refuse_a_generator_call (name, node)
|
|
3414
|
+
raise Unsupported.new(
|
|
3415
|
+
"`#{name}` is a generator, and `#{name}.#{node.name}` is not a way " \
|
|
3416
|
+
"to draw from one. A kernel has `#{name}.random` for a double in " \
|
|
3417
|
+
"[0.0, 1.0), `#{name}.randomn` for a standard normal, " \
|
|
3418
|
+
"`#{name}.bits` for the raw word, and `random(rng: #{name})` or " \
|
|
3419
|
+
"`randomn(rng: #{name})` for the first two written as " \
|
|
3420
|
+
"`CArray#random!` writes them",
|
|
3421
|
+
node.location)
|
|
3422
|
+
end
|
|
3423
|
+
|
|
3424
|
+
# Which generator `random` was pointed at, or a refusal saying how to
|
|
3425
|
+
# point it at one. Nothing else is accepted in the parentheses: a
|
|
3426
|
+
# range, a bound, a data type are all things `CArray#random!` takes and
|
|
3427
|
+
# none of them has an answer for one cell.
|
|
3428
|
+
def random_generator_name (node)
|
|
3429
|
+
arguments = node.arguments ? node.arguments.arguments : []
|
|
3430
|
+
keywords = arguments.last if arguments.last.is_a?(Prism::KeywordHashNode)
|
|
3431
|
+
pairs = keywords ? keywords.elements : []
|
|
3432
|
+
unless arguments.size == 1 && keywords && pairs.size == 1 &&
|
|
3433
|
+
pairs.first.is_a?(Prism::AssocNode) &&
|
|
3434
|
+
pairs.first.key.is_a?(Prism::SymbolNode) &&
|
|
3435
|
+
pairs.first.key.unescaped == RANDOM_DRAW_KEYWORD
|
|
3436
|
+
raise Unsupported.new(
|
|
3437
|
+
"`#{node.name}` in a kernel draws one number from a generator " \
|
|
3438
|
+
"and takes `rng:` and nothing else -- `#{node.name}(rng: r)`, " \
|
|
3439
|
+
"where `r` is a `CArray::Rng` the block closed over",
|
|
3440
|
+
node.location)
|
|
3441
|
+
end
|
|
3442
|
+
name = captured_name(pairs.first.value)
|
|
3443
|
+
unless name && @randoms.key?(name)
|
|
3444
|
+
reached = name ? "`#{name}`" : "what was written there"
|
|
3445
|
+
raise Unsupported.new(
|
|
3446
|
+
"`rng:` takes a `CArray::Rng` the block closed over, and " \
|
|
3447
|
+
"#{reached} is not one. A Ruby `Random` cannot be reached from a " \
|
|
3448
|
+
"kernel at all; `CArray::Rng.new` makes one that can",
|
|
3449
|
+
pairs.first.value.location)
|
|
3450
|
+
end
|
|
3451
|
+
name
|
|
3452
|
+
end
|
|
3453
|
+
|
|
1593
3454
|
# Returns the node for a call on a captured C function, or nil when this
|
|
1594
3455
|
# is not one.
|
|
1595
3456
|
def c_function_call (node)
|
|
@@ -1600,7 +3461,8 @@ class CArray
|
|
|
1600
3461
|
arguments = node.arguments ? node.arguments.arguments : []
|
|
1601
3462
|
unless arguments.size == c_function.arity
|
|
1602
3463
|
raise Unsupported.new(
|
|
1603
|
-
"`#{name}` is `#{c_function}`, so it takes
|
|
3464
|
+
"`#{name}` is `#{c_function.declaration_as(name)}`, so it takes " \
|
|
3465
|
+
"#{c_function.arity} " \
|
|
1604
3466
|
"#{c_function.arity == 1 ? 'argument' : 'arguments'}; " \
|
|
1605
3467
|
"#{arguments.size} #{arguments.size == 1 ? 'was' : 'were'} given",
|
|
1606
3468
|
node.location)
|
|
@@ -1618,6 +3480,16 @@ class CArray
|
|
|
1618
3480
|
# name means a cell in one argument position and the whole array in
|
|
1619
3481
|
# another -- `poly.call(x[i], coef)` says both.
|
|
1620
3482
|
def build_c_function_argument (argument, parameter, c_function)
|
|
3483
|
+
# A local array decays to the pointer the callee wants, as it does in
|
|
3484
|
+
# C -- the name its declaration was written under is already the
|
|
3485
|
+
# address. What the declaration says about it is matched here, as the
|
|
3486
|
+
# block is read, because both halves are written in the block: the
|
|
3487
|
+
# element type is the constructor and the length is a literal. A
|
|
3488
|
+
# captured array is matched at the call instead (`address_buffer`),
|
|
3489
|
+
# neither being knowable until there is an array.
|
|
3490
|
+
if (name = local_array_name(argument))
|
|
3491
|
+
return build_local_array_address(name, argument, parameter, c_function)
|
|
3492
|
+
end
|
|
1621
3493
|
# Inside a function, one of its own pointer parameters is already the
|
|
1622
3494
|
# address the callee wants, and handing it on is what C does -- for a
|
|
1623
3495
|
# `void *` slot as much as for a run of numbers, since neither is
|
|
@@ -1670,12 +3542,24 @@ class CArray
|
|
|
1670
3542
|
ElementWrite.new(name, expression, location, subscripts)
|
|
1671
3543
|
end
|
|
1672
3544
|
|
|
1673
|
-
|
|
1674
|
-
|
|
3545
|
+
# `indices` is given where the node is not a plain `a[i]` -- `a[i] += e`
|
|
3546
|
+
# carries its indices and its value in different places, and the read
|
|
3547
|
+
# it stands for is the same read either way.
|
|
3548
|
+
def build_element_read (node, indices = nil)
|
|
3549
|
+
if (pointer = pointer_subscript(node, indices: indices))
|
|
1675
3550
|
return PointerRead.new(pointer.first, pointer.last, node.location)
|
|
1676
3551
|
end
|
|
3552
|
+
refuse_a_constructor_in_an_expression(node.receiver)
|
|
3553
|
+
if (name = local_array_name(node.receiver))
|
|
3554
|
+
storage, shape = local_array_in_sight(name)
|
|
3555
|
+
arguments = indices || (node.arguments ? node.arguments.arguments : [])
|
|
3556
|
+
return LocalArrayRead.new(
|
|
3557
|
+
name, storage, shape,
|
|
3558
|
+
local_array_subscripts(name, shape, arguments, node.location),
|
|
3559
|
+
node.location)
|
|
3560
|
+
end
|
|
1677
3561
|
array = array_name(node.receiver, node.location)
|
|
1678
|
-
arguments = node.arguments ? node.arguments.arguments : []
|
|
3562
|
+
arguments = indices || (node.arguments ? node.arguments.arguments : [])
|
|
1679
3563
|
subscripts = read_subscripts(array, arguments, node.location)
|
|
1680
3564
|
record_subscripts(array, subscripts)
|
|
1681
3565
|
ElementRead.new(array, subscripts, node.location)
|
|
@@ -1685,6 +3569,15 @@ class CArray
|
|
|
1685
3569
|
@subscripts[array] << subscripts unless @subscripts[array].include?(subscripts)
|
|
1686
3570
|
end
|
|
1687
3571
|
|
|
3572
|
+
# Kept apart from the reads because the two are asked different
|
|
3573
|
+
# questions: every use of an array decides the box a view is
|
|
3574
|
+
# transferred in, while only the writes say which axes the kernel owns
|
|
3575
|
+
# a cell of.
|
|
3576
|
+
def record_write_subscripts (array, subscripts)
|
|
3577
|
+
list = @write_subscripts[array]
|
|
3578
|
+
list << subscripts unless list.include?(subscripts)
|
|
3579
|
+
end
|
|
3580
|
+
|
|
1688
3581
|
def array_name (receiver, location)
|
|
1689
3582
|
name = captured_name(receiver)
|
|
1690
3583
|
unless name && @array_names.include?(name)
|
|
@@ -1769,7 +3662,7 @@ class CArray
|
|
|
1769
3662
|
# in scope, so that the offset is a compile-time constant.
|
|
1770
3663
|
def read_subscript (node, location)
|
|
1771
3664
|
name = index_name(node)
|
|
1772
|
-
return [name, 0] if name && index_in_scope?(name)
|
|
3665
|
+
return [index_identifier(name), 0] if name && index_in_scope?(name)
|
|
1773
3666
|
if node.is_a?(Prism::CallNode) && [:+, :-].include?(node.name) &&
|
|
1774
3667
|
(receiver = index_name(node.receiver)) && index_in_scope?(receiver)
|
|
1775
3668
|
return walked_subscript(node)
|
|
@@ -1840,10 +3733,39 @@ class CArray
|
|
|
1840
3733
|
raise Unsupported.new("write the offset as `j - c` with c >= 0",
|
|
1841
3734
|
node.location)
|
|
1842
3735
|
end
|
|
1843
|
-
return [receiver.name,
|
|
3736
|
+
return [index_identifier(receiver.name),
|
|
3737
|
+
node.name == :+ ? constant : -constant]
|
|
1844
3738
|
end
|
|
1845
3739
|
offset = pinned_subscript(argument)
|
|
1846
|
-
|
|
3740
|
+
refuse_an_index_in_the_offset(node, receiver.name, offset)
|
|
3741
|
+
[index_identifier(receiver.name),
|
|
3742
|
+
node.name == :+ ? offset : UnaryMinus.new(offset)]
|
|
3743
|
+
end
|
|
3744
|
+
|
|
3745
|
+
# `a[i + r]`, where `r` is an inner loop's index: the subscript walks
|
|
3746
|
+
# with `i`, and an offset is worked out once per call, before any cell,
|
|
3747
|
+
# which an index cannot be. Left to the call, it was refused there
|
|
3748
|
+
# for a reason about an inner loop's range -- true of the code that
|
|
3749
|
+
# happened to read it, and nothing to do with what was written. The
|
|
3750
|
+
# same sum put in a local first is a subscript the kernel works out
|
|
3751
|
+
# and checks at the cell, which is what the message points to.
|
|
3752
|
+
def refuse_an_index_in_the_offset (node, walker, offset)
|
|
3753
|
+
others = []
|
|
3754
|
+
visit = lambda { |item|
|
|
3755
|
+
next unless item.is_a?(Node)
|
|
3756
|
+
others << item.name if item.is_a?(IndexVariable)
|
|
3757
|
+
item.children.each(&visit)
|
|
3758
|
+
}
|
|
3759
|
+
visit.call(offset)
|
|
3760
|
+
return if others.empty?
|
|
3761
|
+
names = others.uniq.map { |name| "`#{name}`" }.join(" and ")
|
|
3762
|
+
raise Unsupported.new(
|
|
3763
|
+
"`#{node.slice}` walks with `#{walker}` and is offset by #{names}, " \
|
|
3764
|
+
"an index as well; an offset is worked out once per call, from " \
|
|
3765
|
+
"literals and captured integers -- put the sum in a local first " \
|
|
3766
|
+
"(`k = #{node.slice}`, then `[k]`) and it is worked out and " \
|
|
3767
|
+
"checked at each cell",
|
|
3768
|
+
node.location)
|
|
1847
3769
|
end
|
|
1848
3770
|
|
|
1849
3771
|
# An index the block names as a parameter reads as a local variable.
|
|
@@ -1860,6 +3782,19 @@ class CArray
|
|
|
1860
3782
|
end
|
|
1861
3783
|
end
|
|
1862
3784
|
|
|
3785
|
+
# An index as this compiler knows it: the name the block wrote, unless
|
|
3786
|
+
# that name belongs to a second loop of the same name, which has an
|
|
3787
|
+
# identifier of its own.
|
|
3788
|
+
def index_identifier (name)
|
|
3789
|
+
@inner_aliases.fetch(name, name)
|
|
3790
|
+
end
|
|
3791
|
+
|
|
3792
|
+
# The same question asked of an identifier a subscript carries, which
|
|
3793
|
+
# for a second loop of the same name is not the name the block wrote.
|
|
3794
|
+
def index_identifier_in_scope? (name)
|
|
3795
|
+
@outer_names.include?(name) || @inner_aliases.value?(name)
|
|
3796
|
+
end
|
|
3797
|
+
|
|
1863
3798
|
def index_in_scope? (name)
|
|
1864
3799
|
@outer_names.include?(name) || @inner_names.include?(name)
|
|
1865
3800
|
end
|
|
@@ -1930,8 +3865,10 @@ class CArray
|
|
|
1930
3865
|
# A non-negative literal exponent can still be squared out; anything
|
|
1931
3866
|
# else has to be asked for in floating point.
|
|
1932
3867
|
def build_power (receiver, exponent, location)
|
|
1933
|
-
|
|
1934
|
-
|
|
3868
|
+
power_node(build(receiver), build(exponent), location)
|
|
3869
|
+
end
|
|
3870
|
+
|
|
3871
|
+
def power_node (base, power, location)
|
|
1935
3872
|
if power.is_a?(IntegerLiteral) && power.value < 0
|
|
1936
3873
|
raise Unsupported.new(
|
|
1937
3874
|
"a negative exponent gives a Rational in Ruby; write `1.0 / x ** n`",
|
|
@@ -1943,8 +3880,12 @@ class CArray
|
|
|
1943
3880
|
def build_math_call (node)
|
|
1944
3881
|
function = MATH_FUNCTIONS[node.name]
|
|
1945
3882
|
unless function
|
|
1946
|
-
|
|
1947
|
-
|
|
3883
|
+
if (reason = REFUSED_MATH[node.name])
|
|
3884
|
+
raise Unsupported.new("Math.#{node.name} is not compiled: " \
|
|
3885
|
+
"#{reason}", node.location)
|
|
3886
|
+
end
|
|
3887
|
+
raise Unsupported.new(
|
|
3888
|
+
"Math.#{node.name} is not a name this compiles", node.location)
|
|
1948
3889
|
end
|
|
1949
3890
|
arguments = node.arguments ? node.arguments.arguments : []
|
|
1950
3891
|
expected = [:atan2, :hypot].include?(node.name) ? 2 : 1
|