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.rb
CHANGED
|
@@ -7,10 +7,14 @@ require "carray/jit/analyzer"
|
|
|
7
7
|
require "carray/jit/block_reader"
|
|
8
8
|
require "carray/jit/c_function"
|
|
9
9
|
require "carray/jit/type_assignment"
|
|
10
|
+
require "carray/jit/sorting_networks"
|
|
10
11
|
require "carray/jit/c_generator"
|
|
11
12
|
require "carray/jit/compiler"
|
|
12
13
|
require "carray/jit/access"
|
|
13
14
|
require "carray/jit/kernel"
|
|
15
|
+
# `jit_call` and the provider that may answer for it, which need no
|
|
16
|
+
# compiler to be defined -- see the file for why that matters.
|
|
17
|
+
require "carray/jit/call"
|
|
14
18
|
|
|
15
19
|
class CArray
|
|
16
20
|
|
|
@@ -42,12 +46,10 @@ class CArray
|
|
|
42
46
|
# to make a per-cell computation fast, so quietly doing the slow thing would
|
|
43
47
|
# answer a question that was not asked.
|
|
44
48
|
#
|
|
45
|
-
# The name is
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
# calls it either compiles or is told why it cannot -- an expression over
|
|
50
|
-
# whole arrays that needs no compiler is CArray.fuse's.
|
|
49
|
+
# The name is this gem's: CArray defines no jit_for of its own, so the
|
|
50
|
+
# method exists once carray/jit is required, and is documented where the
|
|
51
|
+
# subset is. An expression over whole arrays that needs no compiler is
|
|
52
|
+
# CArray.fuse's.
|
|
51
53
|
#
|
|
52
54
|
# `reassociate:` says whether a reduction's accumulator may be split into
|
|
53
55
|
# partial sums. It defaults to CArray::JIT.reassociate, which is true:
|
|
@@ -106,8 +108,8 @@ class CArray
|
|
|
106
108
|
# matter, while an element-wise block has neither. And it is jit_map's
|
|
107
109
|
# sibling in the other direction: the name says whether a value comes back.
|
|
108
110
|
#
|
|
109
|
-
# Like jit_for, the name is
|
|
110
|
-
#
|
|
111
|
+
# Like jit_for, the name is this gem's, and exists once carray/jit is
|
|
112
|
+
# required. Written without a compiler, the same computation is
|
|
111
113
|
# CArray.fuse's -- the expression itself, one pass per operation, with the
|
|
112
114
|
# intermediates this one does without.
|
|
113
115
|
#
|
|
@@ -253,14 +255,20 @@ class CArray
|
|
|
253
255
|
# is the convention's, with a third clause: an index that repeats is summed,
|
|
254
256
|
# one that appears once is free, and a named one is free however often it
|
|
255
257
|
# appears -- which is what puts the diagonal and the per-point quantity
|
|
256
|
-
# inside the notation instead of outside it.
|
|
257
|
-
#
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
#
|
|
258
|
+
# inside the notation instead of outside it. The list is all of the
|
|
259
|
+
# result's axes rather than some of them -- name one and you have named them
|
|
260
|
+
# all -- so what is left out of it is summed, at however few positions it
|
|
261
|
+
# sits: `jit_contract(:i) { |k| a[i,k] }` is the row sums, which the
|
|
262
|
+
# convention alone cannot say. (`sum(axis:)` is the faster way to write
|
|
263
|
+
# that one, being a reduction rather than a contraction.) Naming is allowed
|
|
261
264
|
# even where the convention would have reached the same answer, which is how
|
|
262
265
|
# the result's axes are put in another order.
|
|
263
266
|
#
|
|
267
|
+
# Where the block assigns into an array of yours, the left-hand side has the
|
|
268
|
+
# result's axes on it, and an axis there that the list left out is the list
|
|
269
|
+
# falling short rather than an index to sum -- which is refused, and is the
|
|
270
|
+
# one place a short list is caught.
|
|
271
|
+
#
|
|
264
272
|
# The sum is split into partial ones, as `jit_for`'s reduction is: a
|
|
265
273
|
# contraction says which indices are summed and nothing about the order, so
|
|
266
274
|
# there is no order here to override. `CArray::JIT.reassociate = false`
|
|
@@ -337,6 +345,77 @@ class CArray
|
|
|
337
345
|
JIT.function(prototype, &block)
|
|
338
346
|
end
|
|
339
347
|
|
|
348
|
+
# Gives every cell of `self` the block's value at its indices, and returns
|
|
349
|
+
# `self`.
|
|
350
|
+
#
|
|
351
|
+
# The block takes one parameter per axis, as a constructor block does, and
|
|
352
|
+
# its value is what the cell at those indices gets:
|
|
353
|
+
#
|
|
354
|
+
# CArray.int32(1000, 1000).jit_init { |i, j| (i + j) % 2 }
|
|
355
|
+
#
|
|
356
|
+
# That is what `CArray.int32(n, n) { |i, j| ... }` says, without the Ruby
|
|
357
|
+
# call per cell: the constructor form is `map_index!`, which leaves C for
|
|
358
|
+
# every element and comes back, and this one compiles the body and runs the
|
|
359
|
+
# whole loop as C.
|
|
360
|
+
#
|
|
361
|
+
# It is the only member of the family that is an instance method, because
|
|
362
|
+
# the array being written is the receiver. {CArray.jit_for} runs the same
|
|
363
|
+
# loop and has to be given both the index space and the array to write into
|
|
364
|
+
# -- `CArray.jit_for(n, n) { |i, j| z[i, j] = ... }` -- where here the
|
|
365
|
+
# extents are the receiver's shape and the target is the receiver, so
|
|
366
|
+
# neither is said twice.
|
|
367
|
+
#
|
|
368
|
+
# Reach for it when the values are a formula over the indices that will not
|
|
369
|
+
# go through whole-array arithmetic. Where it will, that needs no compiler
|
|
370
|
+
# and is faster still: a checkerboard is `(i[nil, :_] + i[:_, nil]) % 2`
|
|
371
|
+
# over an index vector, and a sequence or a random fill has `seq!` and
|
|
372
|
+
# `random!` already. What is left for this method is the formula that
|
|
373
|
+
# cannot be said that way -- a branch per cell, a value looked up, a
|
|
374
|
+
# recurrence along an axis.
|
|
375
|
+
#
|
|
376
|
+
# A block outside the compilable subset raises CArray::JIT::Unsupported, as
|
|
377
|
+
# every other entry point here does. Nobody writes `jit_init` except to
|
|
378
|
+
# have the compiled loop, so quietly running the slow one would answer a
|
|
379
|
+
# question that was not asked -- write the constructor block when the slow
|
|
380
|
+
# one is what is wanted.
|
|
381
|
+
#
|
|
382
|
+
# The block is read and compiled, never called, so it is not yielded to.
|
|
383
|
+
#
|
|
384
|
+
# @param reassociate [Boolean, nil] as {CArray.jit_for}; `nil` defers to
|
|
385
|
+
# {CArray::JIT.reassociate}.
|
|
386
|
+
# @return [CArray] `self`
|
|
387
|
+
# @raise [CArray::JIT::Unsupported] when the block falls outside the
|
|
388
|
+
# recognized subset, or names a number of indices other than `ndim`.
|
|
389
|
+
def jit_init (reassociate: nil, &block)
|
|
390
|
+
unless block
|
|
391
|
+
raise JIT::Unsupported, "jit_init needs a block"
|
|
392
|
+
end
|
|
393
|
+
if block.arity.zero?
|
|
394
|
+
raise JIT::Unsupported,
|
|
395
|
+
"jit_init's block takes the indices of the cell it computes, one " \
|
|
396
|
+
"per axis, as a constructor block does; a block that names none " \
|
|
397
|
+
"computes from other arrays and belongs to jit_each"
|
|
398
|
+
end
|
|
399
|
+
# A splat or an optional parameter makes the arity negative, and the
|
|
400
|
+
# count in the message below a number nobody wrote. The block reader
|
|
401
|
+
# refuses both a few steps later; here there is an arity to compare, and
|
|
402
|
+
# -1 is not one.
|
|
403
|
+
if block.arity.negative?
|
|
404
|
+
raise JIT::Unsupported,
|
|
405
|
+
"jit_init's block names one index per axis, and names them as " \
|
|
406
|
+
"required parameters; this one takes a splat or an optional " \
|
|
407
|
+
"parameter, which says nothing about how many axes it is for"
|
|
408
|
+
end
|
|
409
|
+
unless block.arity == ndim
|
|
410
|
+
raise JIT::Unsupported,
|
|
411
|
+
"this array has #{ndim} #{ndim == 1 ? 'axis' : 'axes'} and the " \
|
|
412
|
+
"block names #{block.arity} " \
|
|
413
|
+
"#{block.arity == 1 ? 'index' : 'indices'}; jit_init fills every " \
|
|
414
|
+
"cell, so there is one index per axis"
|
|
415
|
+
end
|
|
416
|
+
JIT.run(dim, block, reassociate, into: self)
|
|
417
|
+
end
|
|
418
|
+
|
|
340
419
|
# @!endgroup
|
|
341
420
|
|
|
342
421
|
# The compiler behind `CArray.jit_*`: it reads a block, generates C for it,
|
|
@@ -379,7 +458,11 @@ class CArray
|
|
|
379
458
|
# the block reaches for it the way it reaches for a captured value.
|
|
380
459
|
# It is neither: it is an index, and it is answered here.
|
|
381
460
|
names = capture_names(source, node) - (free_indices || [])
|
|
382
|
-
arrays, scalars, c_functions =
|
|
461
|
+
arrays, scalars, c_functions, randoms =
|
|
462
|
+
split_captures(names, binding_of(block))
|
|
463
|
+
refuse_a_generator(randoms, "a contraction", "the block is a summand " \
|
|
464
|
+
"and runs once per term, so a draw in it would be one per term " \
|
|
465
|
+
"rather than one per cell of the result")
|
|
383
466
|
contract(source, arrays, free_indices, node: node, origin: origin,
|
|
384
467
|
scalars: scalars, c_functions: c_functions)
|
|
385
468
|
end
|
|
@@ -423,7 +506,11 @@ class CArray
|
|
|
423
506
|
# is not the same as naming an empty list of axes.
|
|
424
507
|
free_indices = nil if free_indices.empty?
|
|
425
508
|
names = capture_names(source, node) - (free_indices || [])
|
|
426
|
-
arrays, scalars, c_functions =
|
|
509
|
+
arrays, scalars, c_functions, randoms =
|
|
510
|
+
split_captures(names, binding_of(block))
|
|
511
|
+
refuse_a_generator(randoms, "a contraction", "the block is a summand " \
|
|
512
|
+
"and runs once per term, so a draw in it would be one per term " \
|
|
513
|
+
"rather than one per cell of the result")
|
|
427
514
|
# A compiled function in the summand is not something the structure
|
|
428
515
|
# carries; a captured number is, as the scale.
|
|
429
516
|
return nil unless c_functions.empty?
|
|
@@ -620,7 +707,8 @@ class CArray
|
|
|
620
707
|
# `contract_terms` reach it with a source it wrote itself.
|
|
621
708
|
def contract (source, arrays, free_indices,
|
|
622
709
|
node: nil, origin: nil, scalars: {}, c_functions: {})
|
|
623
|
-
result = allocate_result(source, node, arrays, scalars, free_indices
|
|
710
|
+
result = allocate_result(source, node, arrays, scalars, free_indices,
|
|
711
|
+
c_functions)
|
|
624
712
|
arrays = arrays.merge(RESULT => result) if result
|
|
625
713
|
|
|
626
714
|
kernel = compile(source,
|
|
@@ -670,10 +758,13 @@ class CArray
|
|
|
670
758
|
arrays.each do |name, array|
|
|
671
759
|
next if name == written
|
|
672
760
|
next unless root_of(array).equal?(target)
|
|
761
|
+
same = arrays.fetch(written).equal?(array)
|
|
673
762
|
raise Unsupported,
|
|
674
|
-
"`#{written}` and `#{name}` are the same array
|
|
675
|
-
"both writes and reads; that is a recurrence
|
|
676
|
-
"contraction, and is written with jit_for"
|
|
763
|
+
"`#{written}` and `#{name}` are #{same ? 'the same array' : 'views of one array'}, " \
|
|
764
|
+
"which this both writes and reads; that is a recurrence " \
|
|
765
|
+
"rather than a contraction, and is written with jit_for. " \
|
|
766
|
+
"Two views are refused by the storage they share rather " \
|
|
767
|
+
"than by the cells, so two that share none are refused too"
|
|
677
768
|
end
|
|
678
769
|
end
|
|
679
770
|
end
|
|
@@ -686,8 +777,10 @@ class CArray
|
|
|
686
777
|
# typed before there is a kernel to ask, so the block is analyzed once
|
|
687
778
|
# without being compiled. Returns nil when the block assigns into an
|
|
688
779
|
# array of its own.
|
|
689
|
-
def allocate_result (source, node, arrays, scalars, free_indices = nil
|
|
690
|
-
|
|
780
|
+
def allocate_result (source, node, arrays, scalars, free_indices = nil,
|
|
781
|
+
c_functions = {})
|
|
782
|
+
probe = probe_contraction(source, node, arrays, scalars, free_indices,
|
|
783
|
+
c_functions)
|
|
691
784
|
return nil unless probe
|
|
692
785
|
free, index_axes, type = probe
|
|
693
786
|
shape = free.map do |index|
|
|
@@ -708,17 +801,23 @@ class CArray
|
|
|
708
801
|
end
|
|
709
802
|
|
|
710
803
|
# @private
|
|
711
|
-
def probe_contraction (source, node, arrays, scalars, free_indices = nil
|
|
804
|
+
def probe_contraction (source, node, arrays, scalars, free_indices = nil,
|
|
805
|
+
c_functions = {})
|
|
712
806
|
key = [source, arrays.transform_values(&:data_type_name),
|
|
713
807
|
scalars.transform_values { |value| TypeAssignment.scalar_type(value) },
|
|
714
808
|
# The result's axes are named at the call site rather than in
|
|
715
809
|
# the block, so the same source under another naming is another
|
|
716
810
|
# kernel -- and another probe.
|
|
717
811
|
free_indices,
|
|
812
|
+
# A compiled function the summand calls decides the type of
|
|
813
|
+
# what it hands back, which is the type the result is
|
|
814
|
+
# collected into.
|
|
815
|
+
c_functions.transform_values(&:signature),
|
|
718
816
|
cell_names(arrays)]
|
|
719
817
|
cached = probe_cache[key]
|
|
720
818
|
return cached unless cached.nil?
|
|
721
|
-
probe_cache[key] = build_probe(source, node, arrays, scalars,
|
|
819
|
+
probe_cache[key] = build_probe(source, node, arrays, scalars,
|
|
820
|
+
free_indices, c_functions)
|
|
722
821
|
end
|
|
723
822
|
|
|
724
823
|
# @private
|
|
@@ -727,14 +826,20 @@ class CArray
|
|
|
727
826
|
end
|
|
728
827
|
|
|
729
828
|
# @private
|
|
730
|
-
def build_probe (source, node, arrays, scalars, free_indices = nil
|
|
829
|
+
def build_probe (source, node, arrays, scalars, free_indices = nil,
|
|
830
|
+
c_functions = {})
|
|
731
831
|
storage_types = arrays.transform_values(&:data_type_name)
|
|
832
|
+
# The functions the summand calls are given to the probe as they are
|
|
833
|
+
# to the compile below it: a body that calls one is a body the probe
|
|
834
|
+
# has to be able to read, and what the call hands back is what the
|
|
835
|
+
# result is sized and typed from.
|
|
732
836
|
analyzer = Analyzer.new(source, node: node, array_names: arrays.keys,
|
|
733
837
|
contract: :probe, free_indices: free_indices,
|
|
838
|
+
c_functions: c_functions,
|
|
734
839
|
cell_names: cell_names(arrays))
|
|
735
840
|
return false if analyzer.body.statements.last.is_a?(ElementWrite)
|
|
736
841
|
|
|
737
|
-
TypeAssignment.new(analyzer.body, storage_types, scalars)
|
|
842
|
+
TypeAssignment.new(analyzer.body, storage_types, scalars, c_functions)
|
|
738
843
|
summand = analyzer.body.statements.last
|
|
739
844
|
axes = Hash.new { |hash, key| hash[key] = [] }
|
|
740
845
|
analyzer.subscripts.each do |array, uses|
|
|
@@ -776,6 +881,14 @@ class CArray
|
|
|
776
881
|
# @private
|
|
777
882
|
MAP_RESULT = :__map_result
|
|
778
883
|
|
|
884
|
+
# The array `jit_init` writes into is the receiver, which the block does
|
|
885
|
+
# not close over -- it is `self` there, and `self` is not a capture. So
|
|
886
|
+
# it joins the operands under a name this compiler made up, the way a
|
|
887
|
+
# generator's state does, and the block's value is written to it at the
|
|
888
|
+
# cell the loop is on.
|
|
889
|
+
# @private
|
|
890
|
+
INIT_TARGET = :__init_target
|
|
891
|
+
|
|
779
892
|
# Whether CArray's sweep can run this pass, decided before there is a
|
|
780
893
|
# kernel -- because the answer changes what is compiled.
|
|
781
894
|
#
|
|
@@ -808,7 +921,7 @@ class CArray
|
|
|
808
921
|
# but only where the tiers here would have to materialise it. So the
|
|
809
922
|
# tier each operand would be opened at is the rest of the decision:
|
|
810
923
|
#
|
|
811
|
-
#
|
|
924
|
+
# TIER_XFER neither can walk it, and the sweep holds 32KB where
|
|
812
925
|
# the tiers hold the whole box: the sweep runs
|
|
813
926
|
# TIER_STRIDE a column, a transpose, every other cell -- the tiers
|
|
814
927
|
# address it in place, and the sweep re-gathers it for
|
|
@@ -821,7 +934,7 @@ class CArray
|
|
|
821
934
|
# scratch either way -- there was nothing the re-gather was buying.
|
|
822
935
|
def walkable_in_place? (arrays)
|
|
823
936
|
tiers = arrays.map { |array| Access.classify(array)[:tier] }
|
|
824
|
-
return true if tiers.include?(Access::
|
|
937
|
+
return true if tiers.include?(Access::TIER_XFER)
|
|
825
938
|
tiers.none? { |tier| tier == Access::TIER_STRIDE }
|
|
826
939
|
end
|
|
827
940
|
|
|
@@ -906,8 +1019,11 @@ class CArray
|
|
|
906
1019
|
|
|
907
1020
|
windowed = windows.zip(arrays).to_h
|
|
908
1021
|
free, assigned = free_and_assigned_names(source, node)
|
|
909
|
-
captured, scalars, c_functions =
|
|
1022
|
+
captured, scalars, c_functions, randoms =
|
|
910
1023
|
split_captures(free - windows, binding_of(block))
|
|
1024
|
+
refuse_a_generator(randoms, "a stencil", "its border is a second loop " \
|
|
1025
|
+
"over the frame, so the draws would not run in one pass over the " \
|
|
1026
|
+
"cells")
|
|
911
1027
|
unless assigned.empty? || (assigned & captured.keys).empty?
|
|
912
1028
|
raise Unsupported,
|
|
913
1029
|
"a stencil's value is its block's, and the cells it is over " \
|
|
@@ -967,9 +1083,13 @@ class CArray
|
|
|
967
1083
|
}
|
|
968
1084
|
if aliased
|
|
969
1085
|
raise Unsupported,
|
|
970
|
-
"`into:`
|
|
971
|
-
"
|
|
972
|
-
"
|
|
1086
|
+
"`into:` and `#{aliased.first}` are views of one array, and " \
|
|
1087
|
+
"a cell written through the one could be a cell the other " \
|
|
1088
|
+
"reads through its window. Which cells two views have in " \
|
|
1089
|
+
"common is not a question asked here -- the storage is -- " \
|
|
1090
|
+
"so two slabs of one array are refused along with the " \
|
|
1091
|
+
"overlapping case. Give the stencil an array of its own, " \
|
|
1092
|
+
"or `#{aliased.first}.copy` to read from"
|
|
973
1093
|
end
|
|
974
1094
|
end
|
|
975
1095
|
result.mask = 0 if (kernel.masked || border == :mask) && !result.has_mask?
|
|
@@ -1067,50 +1187,118 @@ class CArray
|
|
|
1067
1187
|
# The result has to be sized and typed before there is a kernel to ask,
|
|
1068
1188
|
# so the block is analyzed once without being compiled -- the same thing
|
|
1069
1189
|
# a returned contraction does, for the same reason.
|
|
1070
|
-
def allocate_map_result (source, node, arrays, scalars, shape, c_functions
|
|
1071
|
-
|
|
1190
|
+
def allocate_map_result (source, node, arrays, scalars, shape, c_functions,
|
|
1191
|
+
randoms = {})
|
|
1192
|
+
type = probe_map(source, node, arrays, scalars, c_functions, randoms)
|
|
1072
1193
|
CArray.send(type, *(shape.empty? ? [1] : shape))
|
|
1073
1194
|
end
|
|
1074
1195
|
|
|
1075
1196
|
# @private
|
|
1076
|
-
def probe_map (source, node, arrays, scalars, c_functions)
|
|
1197
|
+
def probe_map (source, node, arrays, scalars, c_functions, randoms = {})
|
|
1077
1198
|
key = [:map, source, arrays.transform_values(&:data_type_name),
|
|
1078
1199
|
scalars.transform_values { |value| TypeAssignment.scalar_type(value) },
|
|
1079
|
-
c_functions.transform_values(&:signature)
|
|
1200
|
+
c_functions.transform_values(&:signature),
|
|
1201
|
+
randoms.transform_values(&:generator)]
|
|
1080
1202
|
cached = probe_cache[key]
|
|
1081
1203
|
return cached if cached
|
|
1082
1204
|
analyzer = Analyzer.new(source, node: node, array_names: arrays.keys,
|
|
1083
|
-
c_functions: c_functions, rank: 1, map: :probe
|
|
1205
|
+
c_functions: c_functions, rank: 1, map: :probe,
|
|
1206
|
+
randoms: random_state_names(randoms))
|
|
1084
1207
|
TypeAssignment.new(analyzer.body, arrays.transform_values(&:data_type_name),
|
|
1085
1208
|
scalars, c_functions)
|
|
1086
1209
|
value = analyzer.body.statements.last
|
|
1087
1210
|
probe_cache[key] = TypeAssignment.result_storage_type(value.type)
|
|
1088
1211
|
end
|
|
1089
1212
|
|
|
1213
|
+
# Handed where there is nothing to take out of the line-up, so that the
|
|
1214
|
+
# common case allocates nothing.
|
|
1215
|
+
EMPTY_ARRAYS = {}.freeze
|
|
1216
|
+
|
|
1090
1217
|
# @private
|
|
1091
1218
|
def run_over_whole_arrays (block, map: false)
|
|
1092
1219
|
node, source, origin = read_block(block)
|
|
1093
1220
|
free, assigned = free_and_assigned_names(source, node)
|
|
1094
|
-
|
|
1221
|
+
# One Binding for the three questions asked of it. `Proc#binding`
|
|
1222
|
+
# allocates one every time it is asked, and this method is re-entered
|
|
1223
|
+
# on every call of an element-wise pass, so asking three times was
|
|
1224
|
+
# three allocations a call for one answer.
|
|
1225
|
+
outside = binding_of(block)
|
|
1226
|
+
arrays, scalars, c_functions, randoms = split_captures(free, outside)
|
|
1095
1227
|
# `out = a + b` writes the array named `out` where the block was
|
|
1096
1228
|
# written. A name the block assigns is not free in it, so it is
|
|
1097
1229
|
# looked up here rather than by split_captures -- and a name that is
|
|
1098
1230
|
# not an array outside stays what it looks like, a local.
|
|
1099
|
-
|
|
1231
|
+
#
|
|
1232
|
+
# Except a name the block makes an array of its own under: there the
|
|
1233
|
+
# assignment is a declaration and not a write to anybody's cell, so it
|
|
1234
|
+
# is taken out before the lookup. Left in, and with an array of that
|
|
1235
|
+
# name outside, the broadcast either fails on a shape nobody wrote or
|
|
1236
|
+
# -- where the shapes happen to agree -- quietly makes the local array
|
|
1237
|
+
# an operand and part of the cache key. So it is refused here, which
|
|
1238
|
+
# is before the broadcast.
|
|
1239
|
+
made = local_array_names(source, node)
|
|
1240
|
+
refuse_a_shadowed_array(made, outside)
|
|
1241
|
+
arrays = arrays.merge(assigned_arrays(assigned - made, outside))
|
|
1100
1242
|
if arrays.empty?
|
|
1101
1243
|
raise Unsupported, "the block reaches no array"
|
|
1102
1244
|
end
|
|
1103
1245
|
|
|
1104
|
-
|
|
1246
|
+
# An array the block only hands to a C function whole is not walked,
|
|
1247
|
+
# so the expression's shape has nothing to say about it -- and it has
|
|
1248
|
+
# a shape of its own that need not line up with anything. Left in,
|
|
1249
|
+
# one of a different length dies in the broadcast below, naming an
|
|
1250
|
+
# axis rather than the call it was written for.
|
|
1251
|
+
#
|
|
1252
|
+
# So it joins after the broadcast, which is what a generator's state
|
|
1253
|
+
# already does and for the same reason. The re-substitution further
|
|
1254
|
+
# down stays: this is a static reading, and the analyzer's answer is
|
|
1255
|
+
# the final one.
|
|
1256
|
+
handed_over = address_array_names(source, node, c_functions)
|
|
1257
|
+
# The common case is that there are none, and it pays nothing: no
|
|
1258
|
+
# hash is built and the broadcast is handed the operands it always
|
|
1259
|
+
# was. Every call comes through here -- `jit_each` is re-entered per
|
|
1260
|
+
# call -- so the work that is only needed sometimes is done only
|
|
1261
|
+
# then.
|
|
1262
|
+
addressed = handed_over.empty? ? EMPTY_ARRAYS
|
|
1263
|
+
: arrays.slice(*handed_over)
|
|
1264
|
+
walked = addressed.empty? ? arrays
|
|
1265
|
+
: arrays.reject { |name, _| addressed.key?(name) }
|
|
1266
|
+
if walked.empty?
|
|
1267
|
+
handed = addressed.keys.first
|
|
1268
|
+
raise Unsupported,
|
|
1269
|
+
"the block reaches no array to walk: `#{handed}` is handed " \
|
|
1270
|
+
"to a C function whole rather than read by cell, so it does " \
|
|
1271
|
+
"not say how many cells there are to compute. " \
|
|
1272
|
+
"`CArray.jit_for` with a count says that, and so does an " \
|
|
1273
|
+
"operand the block reads a cell of"
|
|
1274
|
+
end
|
|
1275
|
+
|
|
1276
|
+
aligned, shape = broadcast(walked)
|
|
1277
|
+
addressed.each { |name, array| aligned[name] = array }
|
|
1278
|
+
# A generator's state joins after the broadcast, never before it.
|
|
1279
|
+
# It is passed whole rather than walked, so the expression's shape
|
|
1280
|
+
# has nothing to say about it -- and it has a shape of its own that
|
|
1281
|
+
# would not line up anyway: four cells, against however many the
|
|
1282
|
+
# expression covers. Lining it up first is what `jit_each` did to a
|
|
1283
|
+
# one-cell state before 314d6cb, and a four-cell one does not even
|
|
1284
|
+
# stretch.
|
|
1285
|
+
states = random_states(randoms)
|
|
1286
|
+
arrays = arrays.merge(states)
|
|
1287
|
+
aligned = aligned.merge(states)
|
|
1105
1288
|
if map
|
|
1106
1289
|
result = allocate_map_result(source, node, arrays, scalars, shape,
|
|
1107
|
-
c_functions)
|
|
1290
|
+
c_functions, randoms)
|
|
1108
1291
|
arrays = arrays.merge(MAP_RESULT => result)
|
|
1109
1292
|
aligned = aligned.merge(MAP_RESULT => result)
|
|
1110
1293
|
end
|
|
1111
|
-
|
|
1294
|
+
# Both questions are about the arrays the loop walks. An array
|
|
1295
|
+
# handed over whole is not one of them: its mask is not something the
|
|
1296
|
+
# C can read -- `address_buffer` refuses a masked one outright -- and
|
|
1297
|
+
# it is neither one of the sweep's operands nor a shape the sweep has
|
|
1298
|
+
# to agree with.
|
|
1299
|
+
masked = walked.each_value.any? { |array| array.has_mask? } ||
|
|
1112
1300
|
mentions_undef(source, node)
|
|
1113
|
-
sweeping = sweepable_pass?(
|
|
1301
|
+
sweeping = sweepable_pass?(walked, shape, masked)
|
|
1114
1302
|
kernel = compile(source,
|
|
1115
1303
|
node: node,
|
|
1116
1304
|
origin: origin,
|
|
@@ -1118,6 +1306,7 @@ class CArray
|
|
|
1118
1306
|
storage_types: arrays.transform_values(&:data_type_name),
|
|
1119
1307
|
scalar_values: scalars,
|
|
1120
1308
|
c_functions: c_functions,
|
|
1309
|
+
randoms: randoms,
|
|
1121
1310
|
masked: masked,
|
|
1122
1311
|
rank: sweeping ? 1 : shape.size,
|
|
1123
1312
|
map: map,
|
|
@@ -1135,6 +1324,18 @@ class CArray
|
|
|
1135
1324
|
aligned[name] = written
|
|
1136
1325
|
end
|
|
1137
1326
|
|
|
1327
|
+
aligned = read_once_where_the_pass_writes(aligned, kernel.written_arrays)
|
|
1328
|
+
|
|
1329
|
+
# An array handed to a C function by address is passed whole rather
|
|
1330
|
+
# than walked, so the expression's shape has nothing to say about it.
|
|
1331
|
+
# Broadcasting it would stretch a one-cell state array into a
|
|
1332
|
+
# read-only `CARepeat`, which the copy-back after the call cannot
|
|
1333
|
+
# write through -- and a state array is exactly the shape a caller
|
|
1334
|
+
# reaches for.
|
|
1335
|
+
kernel.address_arrays.each do |name|
|
|
1336
|
+
aligned[name] = arrays.fetch(name)
|
|
1337
|
+
end
|
|
1338
|
+
|
|
1138
1339
|
if kernel.masked
|
|
1139
1340
|
kernel.written_arrays.each do |name|
|
|
1140
1341
|
array = arrays.fetch(name)
|
|
@@ -1147,6 +1348,72 @@ class CArray
|
|
|
1147
1348
|
map ? result : kernel
|
|
1148
1349
|
end
|
|
1149
1350
|
|
|
1351
|
+
# `a = b * 2.0` reads the whole of the right-hand side and then assigns,
|
|
1352
|
+
# which is what the expression means in Ruby and what CArray's own
|
|
1353
|
+
# operators do. A kernel walks cell by cell instead, and that is the
|
|
1354
|
+
# same thing only while no cell it reads is a cell it has written: two
|
|
1355
|
+
# overlapping views of one array -- `hi = lo * 2.0` for blocks that
|
|
1356
|
+
# share six cells, a transpose written over itself -- read the output
|
|
1357
|
+
# back as input, cell by cell. A gather is copied before the loop and
|
|
1358
|
+
# keeps the meaning; a strided view is addressed in place, which is
|
|
1359
|
+
# what makes it fast and what leaves it exposed here.
|
|
1360
|
+
#
|
|
1361
|
+
# So an operand that is read, and shares a root with one that is
|
|
1362
|
+
# written without being that array itself, is copied once before the
|
|
1363
|
+
# loop. `a = a + 1.0` is not that case -- the cell read is the cell
|
|
1364
|
+
# written, as in Ruby -- and neither is a pass whose arrays are
|
|
1365
|
+
# separate, which is nearly all of them.
|
|
1366
|
+
def read_once_where_the_pass_writes (aligned, written_names)
|
|
1367
|
+
written = written_names.filter_map { |name| aligned[name] }
|
|
1368
|
+
return aligned if written.empty?
|
|
1369
|
+
roots = written.map { |array| root_of(array) }
|
|
1370
|
+
aligned.to_h do |name, array|
|
|
1371
|
+
next [name, array] if written_names.include?(name)
|
|
1372
|
+
next [name, array] if written.any? { |other| other.equal?(array) }
|
|
1373
|
+
shared = roots.any? { |root| root.equal?(root_of(array)) }
|
|
1374
|
+
[name, shared ? array.copy : array]
|
|
1375
|
+
end
|
|
1376
|
+
end
|
|
1377
|
+
|
|
1378
|
+
# An operand a kernel cannot walk -- a gather, a lazy array -- is copied
|
|
1379
|
+
# into a buffer before the loop and copied back after it. For an
|
|
1380
|
+
# expression over whole arrays that is what the expression means: the
|
|
1381
|
+
# right-hand side is read, then assigned. An indexed kernel means the
|
|
1382
|
+
# loop instead, and a loop reads a cell when it reaches it: where the
|
|
1383
|
+
# array copied is one the loop also writes, the copy is a photograph of
|
|
1384
|
+
# cells that go on changing. `y[i] = gy[i] + 1.0`, with `gy` a gather
|
|
1385
|
+
# of `y`, gave the reversal of the array it started with where the Ruby
|
|
1386
|
+
# loop sees its own writes; and a gather written beside a direct write
|
|
1387
|
+
# put the whole buffer back at the end, dropping the direct one.
|
|
1388
|
+
#
|
|
1389
|
+
# Neither is a thing to fix by copying harder, so it is refused, with
|
|
1390
|
+
# the loop that does mean something named: index the array itself and
|
|
1391
|
+
# let the subscript do the gathering.
|
|
1392
|
+
def refuse_a_transferred_alias (kernel, arrays)
|
|
1393
|
+
written = kernel.written_arrays.filter_map { |name| arrays[name] }
|
|
1394
|
+
return if written.empty?
|
|
1395
|
+
roots = written.map { |array| root_of(array) }
|
|
1396
|
+
arrays.each do |name, array|
|
|
1397
|
+
next unless Access.classify(array)[:tier] == Access::TIER_XFER
|
|
1398
|
+
root = root_of(array)
|
|
1399
|
+
# Another operand on the same storage, and one of the two written:
|
|
1400
|
+
# a copy taken before the loop cannot answer for either of them.
|
|
1401
|
+
# The array alone on its root is not that case -- what it scatters
|
|
1402
|
+
# back at the end, nothing in the loop was reading.
|
|
1403
|
+
others = arrays.each_value.reject { |other| other.equal?(array) }
|
|
1404
|
+
.select { |other| root.equal?(root_of(other)) }
|
|
1405
|
+
next if others.empty?
|
|
1406
|
+
next unless written.any? { |one| one.equal?(array) } ||
|
|
1407
|
+
others.any? { |other| written.any? { |one| one.equal?(other) } }
|
|
1408
|
+
raise Unsupported,
|
|
1409
|
+
"`#{name}` cannot be walked as it stands, so the kernel would " \
|
|
1410
|
+
"work on a copy of it taken before the loop -- and the loop " \
|
|
1411
|
+
"writes the array it is a view of, which the copy would not " \
|
|
1412
|
+
"see. Index that array directly and let the subscript gather: " \
|
|
1413
|
+
"`a[order[i]]` rather than a view of `a`"
|
|
1414
|
+
end
|
|
1415
|
+
end
|
|
1416
|
+
|
|
1150
1417
|
# CArray lines the shapes up; a stretched axis comes back as a stride of
|
|
1151
1418
|
# zero, which the kernel addresses like any other stride.
|
|
1152
1419
|
# CArray leaves a CScalar as it is, because its own kernels know to read
|
|
@@ -1189,10 +1456,20 @@ class CArray
|
|
|
1189
1456
|
end
|
|
1190
1457
|
|
|
1191
1458
|
# @private
|
|
1192
|
-
|
|
1459
|
+
# `into` turns the block's value into a write: with it, the last
|
|
1460
|
+
# statement is what the cell of `into` gets, as jit_map's is what the
|
|
1461
|
+
# cell of its own result gets. Without it the block writes the arrays
|
|
1462
|
+
# it names, which is jit_for.
|
|
1463
|
+
def run (extents, block, reassociate = nil, into: nil)
|
|
1193
1464
|
node, source, origin = read_block(block)
|
|
1194
1465
|
names = capture_names(source, node)
|
|
1195
|
-
arrays, scalars, c_functions =
|
|
1466
|
+
arrays, scalars, c_functions, randoms =
|
|
1467
|
+
split_captures(names, binding_of(block))
|
|
1468
|
+
# A generator's state is an operand like any other array, under a
|
|
1469
|
+
# name this compiler made up. `jit_for` lines nothing up, so it can
|
|
1470
|
+
# simply join the rest.
|
|
1471
|
+
arrays = arrays.merge(random_states(randoms))
|
|
1472
|
+
arrays = arrays.merge(INIT_TARGET => into) if into
|
|
1196
1473
|
|
|
1197
1474
|
# A plain CArray carries no mask; one exists only once a cell has
|
|
1198
1475
|
# actually been marked. So masks are touched at all only when some
|
|
@@ -1217,9 +1494,12 @@ class CArray
|
|
|
1217
1494
|
storage_types: arrays.transform_values(&:data_type_name),
|
|
1218
1495
|
scalar_values: scalars,
|
|
1219
1496
|
c_functions: c_functions,
|
|
1497
|
+
randoms: randoms,
|
|
1220
1498
|
masked: arrays.each_value.any? { |array| array.has_mask? },
|
|
1221
1499
|
steps: steps,
|
|
1222
1500
|
cell_names: cell_names(arrays),
|
|
1501
|
+
map: into ? true : false,
|
|
1502
|
+
result: into ? INIT_TARGET : nil,
|
|
1223
1503
|
reassociate: reassociate.nil? ? JIT.reassociate : reassociate)
|
|
1224
1504
|
# The kernel decides, not the caller: mentioning UNDEF makes it a
|
|
1225
1505
|
# masked kernel even when no array carries a mask yet.
|
|
@@ -1236,8 +1516,9 @@ class CArray
|
|
|
1236
1516
|
"#{pairs.size} #{pairs.size == 1 ? 'extent was' : 'extents were'} " \
|
|
1237
1517
|
"given"
|
|
1238
1518
|
end
|
|
1519
|
+
refuse_a_transferred_alias(kernel, arrays)
|
|
1239
1520
|
kernel.call(arrays, scalars, pairs.map(&:first), c_functions)
|
|
1240
|
-
kernel
|
|
1521
|
+
into || kernel
|
|
1241
1522
|
end
|
|
1242
1523
|
|
|
1243
1524
|
# Compiles for one set of array data types and one set of scalar types,
|
|
@@ -1248,7 +1529,7 @@ class CArray
|
|
|
1248
1529
|
scalar_values:, c_functions: {}, masked: false, rank: nil,
|
|
1249
1530
|
steps: nil, contract: false, result: nil, map: false,
|
|
1250
1531
|
reassociate: false, cell_names: [], windows: [], border: nil,
|
|
1251
|
-
free_indices: nil)
|
|
1532
|
+
free_indices: nil, randoms: {})
|
|
1252
1533
|
# A kernel that mentions UNDEF is a masked one whatever its arrays
|
|
1253
1534
|
# carry, and deciding that here means no caller has to remember it.
|
|
1254
1535
|
masked ||= mentions_undef(source, node)
|
|
@@ -1260,6 +1541,12 @@ class CArray
|
|
|
1260
1541
|
# kernel, and it is compiled once. One written here adds its
|
|
1261
1542
|
# symbol, which stands for its body -- see `CFunction#kernel_key`.
|
|
1262
1543
|
c_functions.transform_values(&:kernel_key),
|
|
1544
|
+
# Which generator each name is, and nothing about its state:
|
|
1545
|
+
# the C pasted for a draw is decided by the kind, and the seed
|
|
1546
|
+
# is data the kernel is handed at the call. So two runs that
|
|
1547
|
+
# differ only in seed are one kernel, and the same kernel
|
|
1548
|
+
# serves a generator reset between calls.
|
|
1549
|
+
randoms.transform_values(&:generator),
|
|
1263
1550
|
masked, rank, steps, contract, result, map,
|
|
1264
1551
|
# A contraction whose free indices were named is not the kernel
|
|
1265
1552
|
# the same source is without them, nor with them in another
|
|
@@ -1285,7 +1572,8 @@ class CArray
|
|
|
1285
1572
|
registry[key] = build(source, node, array_names, storage_types,
|
|
1286
1573
|
scalar_values, c_functions, masked, rank, steps,
|
|
1287
1574
|
contract, result, origin, map, reassociate,
|
|
1288
|
-
cell_names, windows, border, free_indices
|
|
1575
|
+
cell_names, windows, border, free_indices,
|
|
1576
|
+
randoms)
|
|
1289
1577
|
end
|
|
1290
1578
|
|
|
1291
1579
|
# A kernel that mentions UNDEF is a masked kernel whatever its arrays
|
|
@@ -1318,22 +1606,76 @@ class CArray
|
|
|
1318
1606
|
Analyzer.free_and_assigned_names(source, node: node)
|
|
1319
1607
|
end
|
|
1320
1608
|
|
|
1609
|
+
# Which names the block makes arrays of its own under. A property of
|
|
1610
|
+
# the source, so it is cached with the rest of them -- and for the
|
|
1611
|
+
# reason the captured names are: an element-wise pass re-enters on
|
|
1612
|
+
# every call, so a walk of the tree here is a walk per call. It was
|
|
1613
|
+
# the last of this family still being walked, at 3.3 us of a call.
|
|
1614
|
+
def local_array_names (source, node = nil)
|
|
1615
|
+
cached = local_array_name_cache[source]
|
|
1616
|
+
return cached if cached
|
|
1617
|
+
local_array_name_cache[source] =
|
|
1618
|
+
Analyzer.local_array_names(source, node: node)
|
|
1619
|
+
end
|
|
1620
|
+
|
|
1321
1621
|
# @private
|
|
1322
1622
|
def registry
|
|
1323
1623
|
@registry ||= {}
|
|
1324
1624
|
end
|
|
1325
1625
|
|
|
1626
|
+
# @private
|
|
1627
|
+
def local_array_name_cache
|
|
1628
|
+
@local_array_name_cache ||= {}
|
|
1629
|
+
end
|
|
1630
|
+
|
|
1326
1631
|
# @private
|
|
1327
1632
|
def capture_name_cache
|
|
1328
1633
|
@capture_name_cache ||= {}
|
|
1329
1634
|
end
|
|
1330
1635
|
|
|
1636
|
+
# Which names the block only hands to a C function whole. Cached for
|
|
1637
|
+
# the reason the captured names are: `jit_each` is re-entered on every
|
|
1638
|
+
# call, so anything read off the source here is read on every call, and
|
|
1639
|
+
# walking the tree again cost the element-wise pass 1.7x before this
|
|
1640
|
+
# was memoized.
|
|
1641
|
+
#
|
|
1642
|
+
# The answer depends on the declarations as well as the source -- a
|
|
1643
|
+
# parameter that takes a pointer is what makes an argument an address
|
|
1644
|
+
# pass -- so the signatures are part of the key, as they are for the
|
|
1645
|
+
# map probe. Nothing below the signature can change the answer: the
|
|
1646
|
+
# scan asks `indexable?` and nothing else.
|
|
1647
|
+
def address_array_names (source, node, c_functions)
|
|
1648
|
+
key = [source, c_functions.transform_values(&:signature)]
|
|
1649
|
+
cached = address_name_cache[key]
|
|
1650
|
+
return cached if cached
|
|
1651
|
+
address_name_cache[key] =
|
|
1652
|
+
Analyzer.address_array_names(source, node: node,
|
|
1653
|
+
c_functions: c_functions)
|
|
1654
|
+
end
|
|
1655
|
+
|
|
1656
|
+
# @private
|
|
1657
|
+
def address_name_cache
|
|
1658
|
+
@address_name_cache ||= {}
|
|
1659
|
+
end
|
|
1660
|
+
|
|
1331
1661
|
# Keyed by instruction sequence, which CRuby hands back as the same
|
|
1332
1662
|
# object for every Proc made from one block literal. That makes it a
|
|
1333
1663
|
# free identity for the block, and keeps the file from being read and
|
|
1334
1664
|
# parsed again on each call.
|
|
1665
|
+
#
|
|
1666
|
+
# The key is held weakly. A block literal in a file keeps its sequence
|
|
1667
|
+
# for the life of the process, but every `eval` makes a new one -- in a
|
|
1668
|
+
# console, or in code that builds its kernels as text -- and a Hash kept
|
|
1669
|
+
# each of them alive along with a parse of the whole script it came
|
|
1670
|
+
# from, for as long as the process ran. Before Ruby 3.3 there is no
|
|
1671
|
+
# WeakKeyMap and the cache is a Hash as it was.
|
|
1335
1672
|
def block_cache
|
|
1336
|
-
@block_cache ||=
|
|
1673
|
+
@block_cache ||= new_block_cache
|
|
1674
|
+
end
|
|
1675
|
+
|
|
1676
|
+
# @private
|
|
1677
|
+
def new_block_cache
|
|
1678
|
+
defined?(ObjectSpace::WeakKeyMap) ? ObjectSpace::WeakKeyMap.new : {}
|
|
1337
1679
|
end
|
|
1338
1680
|
|
|
1339
1681
|
# @!group Kernel cache
|
|
@@ -1345,8 +1687,14 @@ class CArray
|
|
|
1345
1687
|
def clear_registry
|
|
1346
1688
|
@registry = {}
|
|
1347
1689
|
@capture_name_cache = {}
|
|
1348
|
-
@
|
|
1690
|
+
@address_name_cache = {}
|
|
1691
|
+
@block_cache = new_block_cache
|
|
1692
|
+
@function_registry = {}
|
|
1693
|
+
# `jit_call`'s, keyed by the site rather than the body.
|
|
1694
|
+
@call_sites = {}.compare_by_identity
|
|
1349
1695
|
@undef_cache = {}
|
|
1696
|
+
@probe_cache = {}
|
|
1697
|
+
@local_array_name_cache = {}
|
|
1350
1698
|
end
|
|
1351
1699
|
|
|
1352
1700
|
# @return [String] the directory this environment's kernels are kept in,
|
|
@@ -1413,16 +1761,20 @@ class CArray
|
|
|
1413
1761
|
def build (source, node, array_names, storage_types, scalar_values, c_functions,
|
|
1414
1762
|
masked, rank = nil, steps = nil, contract = false, result = nil,
|
|
1415
1763
|
origin = nil, map = false, reassociate = false,
|
|
1416
|
-
cell_names = [], windows = [], border = nil, free_indices = nil
|
|
1764
|
+
cell_names = [], windows = [], border = nil, free_indices = nil,
|
|
1765
|
+
randoms = {})
|
|
1417
1766
|
analyzer = Analyzer.new(source, node: node, array_names: array_names,
|
|
1418
1767
|
c_functions: c_functions,
|
|
1419
1768
|
rank: rank, steps: steps, contract: contract,
|
|
1420
1769
|
result: result, map: map, free_indices: free_indices,
|
|
1421
|
-
cell_names: cell_names, windows: windows
|
|
1770
|
+
cell_names: cell_names, windows: windows,
|
|
1771
|
+
randoms: random_state_names(randoms),
|
|
1772
|
+
masked: masked)
|
|
1422
1773
|
assignment = TypeAssignment.new(analyzer.body, storage_types,
|
|
1423
1774
|
scalar_values, c_functions)
|
|
1424
1775
|
generator = CGenerator.new(analyzer, storage_types, assignment.scalar_types,
|
|
1425
1776
|
c_functions: c_functions,
|
|
1777
|
+
randoms: randoms,
|
|
1426
1778
|
masked: masked, reassociate: reassociate,
|
|
1427
1779
|
steps: steps, border: border,
|
|
1428
1780
|
origin: origin, block_source: source)
|
|
@@ -1448,6 +1800,25 @@ class CArray
|
|
|
1448
1800
|
# Of the names the block assigns, the ones that are arrays where it was
|
|
1449
1801
|
# written. Anything else -- a name that holds a number, or no name at
|
|
1450
1802
|
# all -- is a local of the block's own.
|
|
1803
|
+
# A name that is both an array this block makes and an array where the
|
|
1804
|
+
# block was written. In the whole-array spelling an assignment writes
|
|
1805
|
+
# the outer array's cell, so the one line says two different things
|
|
1806
|
+
# depending on which reading you take -- and neither of them is what the
|
|
1807
|
+
# other reader will assume. Refused rather than chosen between.
|
|
1808
|
+
def refuse_a_shadowed_array (made, binding)
|
|
1809
|
+
shadowed = made.find { |name|
|
|
1810
|
+
binding.local_variable_defined?(name) &&
|
|
1811
|
+
binding.local_variable_get(name).is_a?(CArray)
|
|
1812
|
+
}
|
|
1813
|
+
return unless shadowed
|
|
1814
|
+
raise Unsupported,
|
|
1815
|
+
"`#{shadowed}` is made in this block with `CArray.double` or " \
|
|
1816
|
+
"its kin, and the block also closes over an array named " \
|
|
1817
|
+
"`#{shadowed}`; in this spelling an assignment writes that " \
|
|
1818
|
+
"array's cell, so the line means one thing to a reader and " \
|
|
1819
|
+
"another to this compiler. Rename one of the two"
|
|
1820
|
+
end
|
|
1821
|
+
|
|
1451
1822
|
def assigned_arrays (names, binding)
|
|
1452
1823
|
names.each_with_object({}) do |name, found|
|
|
1453
1824
|
next unless binding.local_variable_defined?(name)
|
|
@@ -1460,15 +1831,84 @@ class CArray
|
|
|
1460
1831
|
arrays = {}
|
|
1461
1832
|
scalars = {}
|
|
1462
1833
|
c_functions = {}
|
|
1834
|
+
randoms = {}
|
|
1463
1835
|
names.each do |name|
|
|
1464
1836
|
value = capture_value(name, binding)
|
|
1465
1837
|
case value
|
|
1466
1838
|
when CArray then arrays[name] = value
|
|
1467
1839
|
when CFunction then c_functions[name] = value
|
|
1468
|
-
else
|
|
1840
|
+
else
|
|
1841
|
+
if generator_class && value.is_a?(generator_class)
|
|
1842
|
+
randoms[name] = refuse_an_unpasteable(name, value)
|
|
1843
|
+
else
|
|
1844
|
+
scalars[name] = value
|
|
1845
|
+
end
|
|
1469
1846
|
end
|
|
1470
1847
|
end
|
|
1471
|
-
[arrays, scalars, c_functions]
|
|
1848
|
+
[arrays, scalars, c_functions, randoms]
|
|
1849
|
+
end
|
|
1850
|
+
|
|
1851
|
+
# `CArray::Rng`, or nil where the CArray in use has none.
|
|
1852
|
+
#
|
|
1853
|
+
# Asked of the object rather than of the version, because the version
|
|
1854
|
+
# cannot answer: 3.0.2 is where the class arrived and 3.0.2 is also
|
|
1855
|
+
# what the release before it called itself. Asked once and remembered,
|
|
1856
|
+
# and reached only after a capture has failed to be an array or a
|
|
1857
|
+
# function -- a bare `when CArray::Rng` is evaluated for every captured
|
|
1858
|
+
# number, and against a CArray without the class that is a NameError
|
|
1859
|
+
# loose in a kernel that never mentions a generator.
|
|
1860
|
+
def generator_class
|
|
1861
|
+
return @generator_class unless @generator_class.nil?
|
|
1862
|
+
@generator_class = defined?(CArray::Rng) ? CArray::Rng : false
|
|
1863
|
+
end
|
|
1864
|
+
|
|
1865
|
+
# A generator CArray can run but cannot hand the source of.
|
|
1866
|
+
#
|
|
1867
|
+
# Checked here rather than where a generator is made, because a
|
|
1868
|
+
# generator is made by `CArray::Rng.new` and this gem is not on that
|
|
1869
|
+
# path -- it learns one is in play when a block closes over it, which
|
|
1870
|
+
# is here and is every route in. What it costs to check late is
|
|
1871
|
+
# nothing: a kernel that cannot paste the text cannot be compiled
|
|
1872
|
+
# either way, and the question is which of the two says so.
|
|
1873
|
+
#
|
|
1874
|
+
# Nothing reaches this today: CArray hands out the source of every
|
|
1875
|
+
# generator it has. It is the seam between two of its hashes, and the
|
|
1876
|
+
# failure without it is a `KeyError` out of the middle of code
|
|
1877
|
+
# generation.
|
|
1878
|
+
def refuse_an_unpasteable (name, generator)
|
|
1879
|
+
kind = generator.generator
|
|
1880
|
+
return generator if CArray::Rng::SOURCE.key?(kind)
|
|
1881
|
+
raise Unsupported,
|
|
1882
|
+
"`#{name}` is a #{kind.inspect} generator, and this CArray " \
|
|
1883
|
+
"does not hand out that generator's C " \
|
|
1884
|
+
"(CArray::Rng::SOURCE has " \
|
|
1885
|
+
"#{CArray::Rng::SOURCE.keys.map(&:inspect).join(', ')}), so a " \
|
|
1886
|
+
"kernel has nothing to paste. Fill an array with " \
|
|
1887
|
+
"`CArray#random!` and read a cell of it instead"
|
|
1888
|
+
end
|
|
1889
|
+
|
|
1890
|
+
# The name a generator's state array is an operand under.
|
|
1891
|
+
#
|
|
1892
|
+
# Made up here rather than taken from the caller, because the state is
|
|
1893
|
+
# not something the block named: it wrote `r.call`, and the array
|
|
1894
|
+
# behind that is machinery. The name is derived from the one the block
|
|
1895
|
+
# did write, so it is the same in the next process as in this one --
|
|
1896
|
+
# which is what lets a compiled kernel be found in the cache rather
|
|
1897
|
+
# than built again.
|
|
1898
|
+
def random_state_name (name)
|
|
1899
|
+
:"__random_state_#{name}"
|
|
1900
|
+
end
|
|
1901
|
+
|
|
1902
|
+
# Which state array each generator draws from, as operands.
|
|
1903
|
+
def random_states (randoms)
|
|
1904
|
+
randoms.to_h { |name, generator|
|
|
1905
|
+
[random_state_name(name), generator.state]
|
|
1906
|
+
}
|
|
1907
|
+
end
|
|
1908
|
+
|
|
1909
|
+
# Which made-up name each generator's state is under, for the analyzer.
|
|
1910
|
+
def random_state_names (randoms)
|
|
1911
|
+
randoms.to_h { |name, _| [name, random_state_name(name)] }
|
|
1472
1912
|
end
|
|
1473
1913
|
|
|
1474
1914
|
# A constant is looked up where the block was written, so it means what
|
|
@@ -1477,7 +1917,7 @@ class CArray
|
|
|
1477
1917
|
# a compiled function or a table held in a constant is how a method
|
|
1478
1918
|
# gets at one.
|
|
1479
1919
|
def capture_value (name, binding)
|
|
1480
|
-
if
|
|
1920
|
+
if constant_name?(name)
|
|
1481
1921
|
begin
|
|
1482
1922
|
binding.eval(name.to_s)
|
|
1483
1923
|
rescue NameError
|
|
@@ -1494,26 +1934,70 @@ class CArray
|
|
|
1494
1934
|
end
|
|
1495
1935
|
end
|
|
1496
1936
|
|
|
1937
|
+
# A capture spelled with a capital is a constant and is looked up as one.
|
|
1938
|
+
# Asked of the symbol's own name, which Ruby hands back frozen rather
|
|
1939
|
+
# than building, and by the byte rather than by `/[A-Z]/`: this runs for
|
|
1940
|
+
# every captured name on every call, and the String and the match it
|
|
1941
|
+
# used to make were 2.7 us of an element-wise pass. ASCII either way --
|
|
1942
|
+
# that is what the pattern matched too.
|
|
1943
|
+
def constant_name? (name)
|
|
1944
|
+
first = name.name.getbyte(0)
|
|
1945
|
+
!first.nil? && first >= 0x41 && first <= 0x5A
|
|
1946
|
+
end
|
|
1947
|
+
|
|
1497
1948
|
# `rand` is Kernel's, so "not defined" would be a lie, and the reason it
|
|
1498
1949
|
# is not here is worth saying where it is reached for.
|
|
1499
1950
|
DRAW_NAMES = [:rand, :srand].freeze
|
|
1500
1951
|
|
|
1501
1952
|
def refuse_a_draw (name)
|
|
1953
|
+
# `random` with no `rng:` is not a name that was left undefined, it is
|
|
1954
|
+
# a draw with its generator missing. It reaches here rather than
|
|
1955
|
+
# `random_call` because a bare name with no arguments is parsed as a
|
|
1956
|
+
# name read, and only the parentheses tell the two apart.
|
|
1957
|
+
if [:random, :randomn].include?(name.to_sym)
|
|
1958
|
+
raise Unsupported,
|
|
1959
|
+
"`#{name}` in a kernel draws from a generator and has to say " \
|
|
1960
|
+
"which: `#{name}(rng: r)`, where `r` is a `CArray::Rng` -- " \
|
|
1961
|
+
"`CArray::Rng.new(seed: 4)` makes one"
|
|
1962
|
+
end
|
|
1502
1963
|
return unless DRAW_NAMES.include?(name.to_sym)
|
|
1503
1964
|
raise Unsupported, draw_message("`#{name}`")
|
|
1504
1965
|
end
|
|
1505
1966
|
|
|
1506
|
-
# A generator
|
|
1507
|
-
#
|
|
1508
|
-
#
|
|
1509
|
-
#
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1967
|
+
# A generator reaches `jit_for`, `jit_each` and `jit_map`, and stops
|
|
1968
|
+
# there. The two that are left are not refused because a draw is
|
|
1969
|
+
# meaningless in them but because it would not mean what it looks like,
|
|
1970
|
+
# so the reason is given rather than the fact.
|
|
1971
|
+
def refuse_a_generator (randoms, what, because)
|
|
1972
|
+
return if randoms.empty?
|
|
1973
|
+
name = randoms.keys.first
|
|
1974
|
+
raise Unsupported,
|
|
1975
|
+
"`#{name}` is a generator, and #{what} does not draw from one: " \
|
|
1976
|
+
"#{because}. Fill an array with `CArray#random!` and read a " \
|
|
1977
|
+
"cell of it, or draw in a `jit_for` / `jit_each` / `jit_map` " \
|
|
1978
|
+
"block, where `random(rng: #{name})` works"
|
|
1979
|
+
end
|
|
1980
|
+
|
|
1981
|
+
# `rand` is Ruby's, and Ruby's generator is reached through the VM: the
|
|
1982
|
+
# loop runs with the GVL released, which is not where it may be reached
|
|
1983
|
+
# at all. That is why this one is refused, and it is the only reason --
|
|
1984
|
+
# a kernel *can* draw, from a generator whose C it can paste, which is
|
|
1985
|
+
# what `CArray::Rng` is.
|
|
1986
|
+
#
|
|
1987
|
+
# Both ways out are named because they are different answers. A
|
|
1988
|
+
# generator draws in the order it is asked, and a kernel does not fix
|
|
1989
|
+
# that order: a stencil's border is a second loop over the frame, and a
|
|
1990
|
+
# reduction may split its accumulator. Where which draw lands in which
|
|
1991
|
+
# cell has to be settled -- common random numbers, antithetic variates
|
|
1992
|
+
# -- an array filled before the call is the answer, and a draw in the
|
|
1993
|
+
# loop is not. Where it does not, drawing in the kernel saves the
|
|
1994
|
+
# array.
|
|
1513
1995
|
def draw_message (what)
|
|
1514
|
-
"#{what} draws from a generator
|
|
1515
|
-
"
|
|
1516
|
-
"
|
|
1996
|
+
"#{what} draws from a generator this compiler cannot reach: it is " \
|
|
1997
|
+
"Ruby's, and the loop runs without the GVL. Draw from " \
|
|
1998
|
+
"`CArray::Rng`, which a kernel can; or, where which draw lands " \
|
|
1999
|
+
"in which cell matters, fill an array with `CArray#random!` and read " \
|
|
2000
|
+
"a cell of it as the kernel reads any other array"
|
|
1517
2001
|
end
|
|
1518
2002
|
|
|
1519
2003
|
# An extent is a Range, an Integer standing for `0...n`, or an
|
|
@@ -1532,6 +2016,18 @@ class CArray
|
|
|
1532
2016
|
low = extent.begin || 0
|
|
1533
2017
|
high = extent.end
|
|
1534
2018
|
raise Unsupported, "an endless range has no extent" unless high
|
|
2019
|
+
# `(n-2)..0` reads like a downward sweep and is not one: Ruby gives
|
|
2020
|
+
# that Range no elements, so the loop it looks like would run no
|
|
2021
|
+
# passes and say nothing. A kernel handed one ran no passes too.
|
|
2022
|
+
# An empty range whose ends agree -- `0...0`, a slice that came out
|
|
2023
|
+
# empty -- is a count of zero and is taken as one.
|
|
2024
|
+
if low > high
|
|
2025
|
+
raise Unsupported,
|
|
2026
|
+
"`#{extent.inspect}` counts down and Ruby gives it no " \
|
|
2027
|
+
"elements, so this would run no passes at all. A downward " \
|
|
2028
|
+
"sweep is `#{low}.step(#{high}, -1)`, which is how Ruby " \
|
|
2029
|
+
"iterates backwards"
|
|
2030
|
+
end
|
|
1535
2031
|
[[low, extent.exclude_end? ? high : high + 1, 1], nil]
|
|
1536
2032
|
when Integer
|
|
1537
2033
|
[[0, extent, 1], nil]
|