carray-jit 0.1.3 → 0.1.5
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 +165 -0
- data/README.md +11 -10
- data/carray-jit.gemspec +1 -1
- data/docs/00_Introduction.md +2 -1
- data/docs/02_KernelShapes.md +1 -1
- data/docs/03_SupportedFeatures.md +88 -11
- data/docs/04_Compiling.md +2 -2
- data/docs/05_DesignNotes.md +1 -1
- data/docs/06_Cheatsheet.md +3 -3
- data/docs/07_StepByStep.ja.md +14 -15
- data/docs/07_StepByStep.md +14 -15
- data/docs/08_LanguageReference.md +428 -0
- data/lib/carray/jit/analyzer.rb +144 -7
- data/lib/carray/jit/c_function.rb +359 -21
- data/lib/carray/jit/c_generator.rb +398 -58
- data/lib/carray/jit/compiler.rb +27 -3
- data/lib/carray/jit/expression.rb +271 -33
- data/lib/carray/jit/kernel.rb +48 -9
- data/lib/carray/jit/node.rb +15 -0
- data/lib/carray/jit/type_assignment.rb +53 -1
- data/lib/carray/jit/version.rb +1 -1
- data/lib/carray/jit.rb +223 -87
- metadata +4 -3
|
@@ -361,6 +361,8 @@ class CArray
|
|
|
361
361
|
when MaskTest
|
|
362
362
|
walk_subscripts(node.subscripts)
|
|
363
363
|
node.type = :boolean
|
|
364
|
+
when PointerTest
|
|
365
|
+
node.type = :boolean
|
|
364
366
|
when NumericPredicate
|
|
365
367
|
walk(node.operand)
|
|
366
368
|
node.type = :boolean
|
|
@@ -430,7 +432,9 @@ class CArray
|
|
|
430
432
|
node.alternative.each { |statement| walk(statement) }
|
|
431
433
|
@bindings = merge_bindings(from_consequent, @bindings)
|
|
432
434
|
when IntegerLiteral
|
|
433
|
-
|
|
435
|
+
# Above 2**63-1 a literal is a uint64, as a captured Integer is;
|
|
436
|
+
# where it then meets an int64 it is refused (#refuse_wide_literal).
|
|
437
|
+
node.type = TypeAssignment.integer_type(node.value)
|
|
434
438
|
when FloatLiteral
|
|
435
439
|
node.type = :double
|
|
436
440
|
when ImaginaryLiteral
|
|
@@ -565,6 +569,14 @@ class CArray
|
|
|
565
569
|
walk(node.right)
|
|
566
570
|
node.type =
|
|
567
571
|
if Analyzer::COMPARISON_OPERATORS.include?(node.operator)
|
|
572
|
+
# Compared in the wider of the two, which for a uint64 against a
|
|
573
|
+
# negative literal is a uint64 the literal does not fit.
|
|
574
|
+
if [node.left.type, node.right.type].include?(:uint64)
|
|
575
|
+
refuse_negative_literal(node.left, node.right)
|
|
576
|
+
refuse_negative_literal(node.right, node.left)
|
|
577
|
+
refuse_wide_literal(node.left, node.right)
|
|
578
|
+
refuse_wide_literal(node.right, node.left)
|
|
579
|
+
end
|
|
568
580
|
:boolean
|
|
569
581
|
elsif [:<<, :>>].include?(node.operator)
|
|
570
582
|
# A shift takes its count as a number and keeps the type of the
|
|
@@ -813,6 +825,11 @@ class CArray
|
|
|
813
825
|
"too. Store `.real`, `.imag` or `.abs`",
|
|
814
826
|
location)
|
|
815
827
|
end
|
|
828
|
+
if wanted == :int64 && expression.is_a?(IntegerLiteral) &&
|
|
829
|
+
expression.value > INT64_MAX
|
|
830
|
+
raise Unsupported.new(wide_literal_message(expression.value),
|
|
831
|
+
location)
|
|
832
|
+
end
|
|
816
833
|
return if !boolean?(wanted) && numeric?(given)
|
|
817
834
|
if boolean?(wanted) && expression.is_a?(IntegerLiteral) &&
|
|
818
835
|
[0, 1].include?(expression.value)
|
|
@@ -873,6 +890,8 @@ class CArray
|
|
|
873
890
|
return scalar.type if KIND_RANK[scalar_kind] > KIND_RANK[typed_kind]
|
|
874
891
|
if scalar_kind == typed_kind
|
|
875
892
|
refuse_wide_capture(scalar, typed)
|
|
893
|
+
refuse_negative_literal(scalar, typed)
|
|
894
|
+
refuse_wide_literal(scalar, typed)
|
|
876
895
|
# The scalar becomes that type rather than being widened to meet it,
|
|
877
896
|
# and says so: a literal emitted as a double would take the C
|
|
878
897
|
# expression back to double however this node is typed.
|
|
@@ -881,6 +900,37 @@ class CArray
|
|
|
881
900
|
typed.type
|
|
882
901
|
end
|
|
883
902
|
|
|
903
|
+
# A negative literal meeting a uint64 would take that width and compute
|
|
904
|
+
# as 2**64 minus its magnitude. CArray refuses the same operand on a
|
|
905
|
+
# uint64 array (`RangeError: -1 is out of range for uint64`); a captured
|
|
906
|
+
# one is refused when the kernel is called, since its value is not part
|
|
907
|
+
# of the kernel.
|
|
908
|
+
def refuse_negative_literal (scalar, typed)
|
|
909
|
+
return unless scalar.is_a?(IntegerLiteral) && scalar.value.negative? &&
|
|
910
|
+
typed.type == :uint64
|
|
911
|
+
raise Unsupported.new(
|
|
912
|
+
"#{scalar.value} is out of range for uint64: a negative Integer " \
|
|
913
|
+
"meeting a uint64 would compute as 2**64#{scalar.value}. CArray " \
|
|
914
|
+
"refuses the same operand. Convert the uint64 side first, " \
|
|
915
|
+
"`x.to_f` or a signed capture", scalar.location)
|
|
916
|
+
end
|
|
917
|
+
|
|
918
|
+
# An integer literal above 2**63-1 computed as an int64, where it would
|
|
919
|
+
# wrap to a negative number. In Ruby it is an Integer, which has no
|
|
920
|
+
# width; meeting a uint64 or a Float it keeps its value and is accepted.
|
|
921
|
+
def refuse_wide_literal (scalar, typed)
|
|
922
|
+
return unless scalar.is_a?(IntegerLiteral) &&
|
|
923
|
+
scalar.value > INT64_MAX && typed.type == :int64
|
|
924
|
+
raise Unsupported.new(wide_literal_message(scalar.value),
|
|
925
|
+
scalar.location)
|
|
926
|
+
end
|
|
927
|
+
|
|
928
|
+
def wide_literal_message (value)
|
|
929
|
+
"#{value} does not fit an int64, which is what it is computed in " \
|
|
930
|
+
"here, and would wrap to a negative number. Meet it with a uint64 " \
|
|
931
|
+
"or a Float, or store it into a uint64 array"
|
|
932
|
+
end
|
|
933
|
+
|
|
884
934
|
# A captured Integer above int64 meeting an integer, which absorption
|
|
885
935
|
# has no answer for: the scalar is supposed to take the other side's
|
|
886
936
|
# width, and this value did not come from a width -- it came from a Ruby
|
|
@@ -932,6 +982,8 @@ class CArray
|
|
|
932
982
|
# Nothing to check: no value is computed.
|
|
933
983
|
when MaskTest
|
|
934
984
|
# Nothing to check: it reads a mask byte.
|
|
985
|
+
when PointerTest
|
|
986
|
+
# Nothing to check: it compares an address with NULL.
|
|
935
987
|
when Clamp
|
|
936
988
|
node.children.each { |child| verify(child) }
|
|
937
989
|
types = node.children.map(&:type)
|
data/lib/carray/jit/version.rb
CHANGED
data/lib/carray/jit.rb
CHANGED
|
@@ -37,9 +37,9 @@ class CArray
|
|
|
37
37
|
# no extent, and is written with CArray.jit_each or CArray.jit_map instead.
|
|
38
38
|
#
|
|
39
39
|
# Arrays and scalars are the variables the block closes over, so nothing has
|
|
40
|
-
# to be named twice. Which way each axis runs is
|
|
41
|
-
#
|
|
42
|
-
#
|
|
40
|
+
# to be named twice. Which way each axis runs is the extent's: a Range runs
|
|
41
|
+
# up, a step of -1 runs down, and the kernel runs the order it is given
|
|
42
|
+
# without checking it against the cells the body reads.
|
|
43
43
|
#
|
|
44
44
|
# A block outside the compilable subset raises CArray::JIT::Unsupported
|
|
45
45
|
# rather than falling back to a Ruby loop. Nobody calls this method except
|
|
@@ -638,10 +638,10 @@ class CArray
|
|
|
638
638
|
"the indices of a term are symbols naming its axes, as in " \
|
|
639
639
|
"`[a, [:i, :k]]`; got #{subscripts.inspect}"
|
|
640
640
|
end
|
|
641
|
-
unless subscripts.size == array.
|
|
641
|
+
unless subscripts.size == array.ndim
|
|
642
642
|
raise Unsupported,
|
|
643
643
|
"a term names one index per axis: this array has rank " \
|
|
644
|
-
"#{array.
|
|
644
|
+
"#{array.ndim} and #{subscripts.size} " \
|
|
645
645
|
"#{subscripts.size == 1 ? 'index' : 'indices'} were given"
|
|
646
646
|
end
|
|
647
647
|
end
|
|
@@ -680,9 +680,9 @@ class CArray
|
|
|
680
680
|
raise Unsupported, "`into:` is an array to write into"
|
|
681
681
|
end
|
|
682
682
|
expected = free.empty? ? 1 : free.size
|
|
683
|
-
unless into.
|
|
683
|
+
unless into.ndim == expected
|
|
684
684
|
raise Unsupported,
|
|
685
|
-
"`into:` has rank #{into.
|
|
685
|
+
"`into:` has rank #{into.ndim}, and the result's axes are " \
|
|
686
686
|
"#{free.empty? ? 'none, which is one cell' : free.map { |name| "`#{name}`" }.join(', ')}"
|
|
687
687
|
end
|
|
688
688
|
end
|
|
@@ -909,7 +909,7 @@ class CArray
|
|
|
909
909
|
return false if masked
|
|
910
910
|
return false if arrays.empty? || arrays.size > Sweep::MAX_ARITY
|
|
911
911
|
return false unless arrays.each_value.all? { |array|
|
|
912
|
-
array.
|
|
912
|
+
array.ndim.zero? || array.dim == shape
|
|
913
913
|
}
|
|
914
914
|
walkable_in_place?(arrays.each_value)
|
|
915
915
|
end
|
|
@@ -933,7 +933,7 @@ class CArray
|
|
|
933
933
|
# 0.9 ns/cell with the driver here against 6.1 ns/cell swept, and no
|
|
934
934
|
# scratch either way -- there was nothing the re-gather was buying.
|
|
935
935
|
def walkable_in_place? (arrays)
|
|
936
|
-
tiers = arrays.map { |array| Access.
|
|
936
|
+
tiers = arrays.map { |array| Access.tier(array) }
|
|
937
937
|
return true if tiers.include?(Access::TIER_XFER)
|
|
938
938
|
tiers.none? { |tier| tier == Access::TIER_STRIDE }
|
|
939
939
|
end
|
|
@@ -1214,6 +1214,23 @@ class CArray
|
|
|
1214
1214
|
# common case allocates nothing.
|
|
1215
1215
|
EMPTY_ARRAYS = {}.freeze
|
|
1216
1216
|
|
|
1217
|
+
# What an element-wise pass works out from its operands' shapes and
|
|
1218
|
+
# types, and not from their data: which of them are walked and which
|
|
1219
|
+
# are handed to a C function whole, the shape the pass covers, whether
|
|
1220
|
+
# lining the walked ones up to it changes any of them, which loop runs
|
|
1221
|
+
# it, the kernel, and what type a map's result is made as.
|
|
1222
|
+
#
|
|
1223
|
+
# @private
|
|
1224
|
+
RunPlan = Data.define(:walked, :addressed, :stretched, :shape,
|
|
1225
|
+
:sweeping, :kernel, :result_type)
|
|
1226
|
+
|
|
1227
|
+
# How many plans one block keeps. Not one: a caller alternating
|
|
1228
|
+
# between two model states would miss on every call, and pay for the
|
|
1229
|
+
# asking on top of what every call paid before. Eight, as a kernel
|
|
1230
|
+
# keeps of its own calls (CompiledKernel::PLAN_LIMIT), and for no
|
|
1231
|
+
# better-measured reason.
|
|
1232
|
+
RUN_PLAN_LIMIT = 8
|
|
1233
|
+
|
|
1217
1234
|
# @private
|
|
1218
1235
|
def run_over_whole_arrays (block, map: false)
|
|
1219
1236
|
node, source, origin = read_block(block)
|
|
@@ -1238,11 +1255,185 @@ class CArray
|
|
|
1238
1255
|
# is before the broadcast.
|
|
1239
1256
|
made = local_array_names(source, node)
|
|
1240
1257
|
refuse_a_shadowed_array(made, outside)
|
|
1241
|
-
|
|
1258
|
+
written = assigned_arrays(made.empty? ? assigned : assigned - made,
|
|
1259
|
+
outside)
|
|
1260
|
+
arrays = arrays.merge(written) unless written.empty?
|
|
1242
1261
|
if arrays.empty?
|
|
1243
1262
|
raise Unsupported, "the block reaches no array"
|
|
1244
1263
|
end
|
|
1245
1264
|
|
|
1265
|
+
# Everything from here to the kernel answers the same for two calls
|
|
1266
|
+
# whose operands agree on shape and type, so it is worked out once
|
|
1267
|
+
# per such agreement rather than once per call (#plan_run). What the
|
|
1268
|
+
# plan leaves to the call is what it has to: the operands themselves,
|
|
1269
|
+
# the views the broadcast makes of them, a generator's state, the
|
|
1270
|
+
# map's result, and the masks.
|
|
1271
|
+
key = run_key(arrays, scalars, c_functions, randoms, map)
|
|
1272
|
+
plan = key && found_run_plan(source, key)
|
|
1273
|
+
unless plan
|
|
1274
|
+
plan = plan_run(source, node, origin, arrays, scalars, c_functions,
|
|
1275
|
+
randoms, map)
|
|
1276
|
+
keep_run_plan(source, key, plan) if key
|
|
1277
|
+
end
|
|
1278
|
+
|
|
1279
|
+
walked = plan.addressed.empty? ? arrays : arrays.slice(*plan.walked)
|
|
1280
|
+
# Where no operand is stretched the broadcast hands back the operands
|
|
1281
|
+
# it was given, so it is not asked. Where one is, the views it makes
|
|
1282
|
+
# are views of these arrays and are made again: a view is exactly
|
|
1283
|
+
# what a plan must not keep.
|
|
1284
|
+
#
|
|
1285
|
+
# Unstretched, `aligned` can be `arrays` itself. That is sound only
|
|
1286
|
+
# because every write into it below puts back the array already
|
|
1287
|
+
# there.
|
|
1288
|
+
aligned = plan.stretched ? broadcast(walked).first : walked
|
|
1289
|
+
plan.addressed.each { |name| aligned[name] = arrays.fetch(name) }
|
|
1290
|
+
# A generator's state joins after the broadcast, never before it.
|
|
1291
|
+
# It is passed whole rather than walked, so the expression's shape
|
|
1292
|
+
# has nothing to say about it -- and it has a shape of its own that
|
|
1293
|
+
# would not line up anyway: four cells, against however many the
|
|
1294
|
+
# expression covers. Lining it up first is what `jit_each` did to a
|
|
1295
|
+
# one-cell state before 314d6cb, and a four-cell one does not even
|
|
1296
|
+
# stretch.
|
|
1297
|
+
unless randoms.empty?
|
|
1298
|
+
states = random_states(randoms)
|
|
1299
|
+
arrays = arrays.merge(states)
|
|
1300
|
+
aligned = aligned.merge(states)
|
|
1301
|
+
end
|
|
1302
|
+
shape = plan.shape
|
|
1303
|
+
if map
|
|
1304
|
+
result = CArray.send(plan.result_type, *(shape.empty? ? [1] : shape))
|
|
1305
|
+
arrays = arrays.merge(MAP_RESULT => result)
|
|
1306
|
+
aligned = aligned.merge(MAP_RESULT => result)
|
|
1307
|
+
end
|
|
1308
|
+
|
|
1309
|
+
kernel = plan.kernel
|
|
1310
|
+
# The shape of each of these was checked against the pass's when the
|
|
1311
|
+
# plan was made, and the key holds the shapes.
|
|
1312
|
+
kernel.written_arrays.each do |name|
|
|
1313
|
+
next if name == MAP_RESULT
|
|
1314
|
+
aligned[name] = arrays.fetch(name)
|
|
1315
|
+
end
|
|
1316
|
+
|
|
1317
|
+
aligned = read_once_where_the_pass_writes(aligned, kernel.written_arrays)
|
|
1318
|
+
|
|
1319
|
+
# An array handed to a C function by address is passed whole rather
|
|
1320
|
+
# than walked, so the expression's shape has nothing to say about it.
|
|
1321
|
+
# Broadcasting it would stretch a one-cell state array into a
|
|
1322
|
+
# read-only `CARepeat`, which the copy-back after the call cannot
|
|
1323
|
+
# write through -- and a state array is exactly the shape a caller
|
|
1324
|
+
# reaches for.
|
|
1325
|
+
kernel.address_arrays.each do |name|
|
|
1326
|
+
aligned[name] = arrays.fetch(name)
|
|
1327
|
+
end
|
|
1328
|
+
|
|
1329
|
+
if kernel.masked
|
|
1330
|
+
kernel.written_arrays.each do |name|
|
|
1331
|
+
array = arrays.fetch(name)
|
|
1332
|
+
array.mask = 0 unless array.has_mask?
|
|
1333
|
+
end
|
|
1334
|
+
end
|
|
1335
|
+
|
|
1336
|
+
result.mask = 0 if map && kernel.masked && !result.has_mask?
|
|
1337
|
+
drive(kernel, aligned, scalars, c_functions, shape, plan.sweeping)
|
|
1338
|
+
map ? result : kernel
|
|
1339
|
+
end
|
|
1340
|
+
|
|
1341
|
+
# What two calls of one block have to agree on for one to be run as
|
|
1342
|
+
# the other was.
|
|
1343
|
+
#
|
|
1344
|
+
# Each operand's name, type and shape, because that is what the kernel
|
|
1345
|
+
# is compiled for and the broadcast lines up; whether it is a CScalar,
|
|
1346
|
+
# because the broadcast stretches one where it leaves a one-cell array
|
|
1347
|
+
# standing. Whether it has a mask, because a mask is runtime state that
|
|
1348
|
+
# leaves the shape and the type alone -- one appearing makes a masked
|
|
1349
|
+
# kernel, and nothing else here would notice it. And the tier it would
|
|
1350
|
+
# be opened at, because that decides whether CArray's sweep or the
|
|
1351
|
+
# tiers here run the pass (#walkable_in_place?). All of it read off
|
|
1352
|
+
# the operand on this call; none of it kept from the last.
|
|
1353
|
+
#
|
|
1354
|
+
# Then the type of each scalar, each C function's signature and each
|
|
1355
|
+
# generator's kind, which is what the kernel is compiled for of those.
|
|
1356
|
+
# Not a scalar's value: a clock or a coefficient changes every step,
|
|
1357
|
+
# and keying on it would miss on every call of the loop this is for.
|
|
1358
|
+
#
|
|
1359
|
+
# Values only, never an operand: a block's plans live as long as the
|
|
1360
|
+
# process, and holding an array would hold its memory as long. Shapes
|
|
1361
|
+
# from `ndim`, `elements` and `dim0`.. rather than `dim`, which builds
|
|
1362
|
+
# an Array on every call.
|
|
1363
|
+
#
|
|
1364
|
+
# nil where a scalar is not one a kernel takes, so that the call is
|
|
1365
|
+
# refused where and as it always was rather than here.
|
|
1366
|
+
def run_key (arrays, scalars, c_functions, randoms, map)
|
|
1367
|
+
key = [map, arrays.size]
|
|
1368
|
+
arrays.each do |name, array|
|
|
1369
|
+
rank = array.ndim
|
|
1370
|
+
key << name << array.data_type << rank << array.is_a?(CScalar) <<
|
|
1371
|
+
array.has_mask? << Access.tier(array)
|
|
1372
|
+
case rank
|
|
1373
|
+
when 1 then key << array.elements
|
|
1374
|
+
when 2 then key << array.dim0 << array.dim1
|
|
1375
|
+
when 3 then key << array.dim0 << array.dim1 << array.dim2
|
|
1376
|
+
else key.concat(array.dim)
|
|
1377
|
+
end
|
|
1378
|
+
end
|
|
1379
|
+
key << scalars.size
|
|
1380
|
+
scalars.each do |name, value|
|
|
1381
|
+
key << name << TypeAssignment.scalar_type(value)
|
|
1382
|
+
end
|
|
1383
|
+
key << c_functions.size
|
|
1384
|
+
c_functions.each { |name, function| key << name << function.kernel_key }
|
|
1385
|
+
randoms.each { |name, generator| key << name << generator.generator }
|
|
1386
|
+
key
|
|
1387
|
+
rescue Unsupported
|
|
1388
|
+
nil
|
|
1389
|
+
end
|
|
1390
|
+
|
|
1391
|
+
# @private
|
|
1392
|
+
def run_plans
|
|
1393
|
+
@run_plans ||= {}
|
|
1394
|
+
end
|
|
1395
|
+
|
|
1396
|
+
# The block's plan for this key, if it has one. The list a plan sits
|
|
1397
|
+
# in is never changed once it is published -- a kernel's call lets go
|
|
1398
|
+
# of the GVL, so another thread may be reading it -- and bringing a
|
|
1399
|
+
# plan to the front replaces the list rather than editing it.
|
|
1400
|
+
def found_run_plan (source, key)
|
|
1401
|
+
plans = run_plans[source]
|
|
1402
|
+
return nil unless plans
|
|
1403
|
+
found = plans.assoc(key)
|
|
1404
|
+
return nil unless found
|
|
1405
|
+
unless plans.first.equal?(found)
|
|
1406
|
+
run_plans[source] =
|
|
1407
|
+
[found, *plans.reject { |entry| entry.equal?(found) }].freeze
|
|
1408
|
+
end
|
|
1409
|
+
found[1]
|
|
1410
|
+
end
|
|
1411
|
+
|
|
1412
|
+
# @private
|
|
1413
|
+
def keep_run_plan (source, key, plan)
|
|
1414
|
+
entry = [frozen_throughout(key), plan].freeze
|
|
1415
|
+
run_plans[source] =
|
|
1416
|
+
[entry, *run_plans[source]].first(RUN_PLAN_LIMIT).freeze
|
|
1417
|
+
end
|
|
1418
|
+
|
|
1419
|
+
# A copy that nothing outside can change under it, and that changes
|
|
1420
|
+
# nothing outside by being frozen: a C function's signature is its own
|
|
1421
|
+
# strings, and freezing those in place would freeze them in the
|
|
1422
|
+
# function.
|
|
1423
|
+
def frozen_throughout (value)
|
|
1424
|
+
case value
|
|
1425
|
+
when Array then value.map { |item| frozen_throughout(item) }.freeze
|
|
1426
|
+
when String then value.frozen? ? value : value.dup.freeze
|
|
1427
|
+
else value
|
|
1428
|
+
end
|
|
1429
|
+
end
|
|
1430
|
+
|
|
1431
|
+
# Works out what #run_over_whole_arrays keeps between calls, the way it
|
|
1432
|
+
# always worked it out on every call. Every refusal a pass can meet is
|
|
1433
|
+
# met here, before a plan exists, so a plan is only ever of a call that
|
|
1434
|
+
# ran.
|
|
1435
|
+
def plan_run (source, node, origin, arrays, scalars, c_functions,
|
|
1436
|
+
randoms, map)
|
|
1246
1437
|
# An array the block only hands to a C function whole is not walked,
|
|
1247
1438
|
# so the expression's shape has nothing to say about it -- and it has
|
|
1248
1439
|
# a shape of its own that need not line up with anything. Left in,
|
|
@@ -1250,15 +1441,10 @@ class CArray
|
|
|
1250
1441
|
# axis rather than the call it was written for.
|
|
1251
1442
|
#
|
|
1252
1443
|
# So it joins after the broadcast, which is what a generator's state
|
|
1253
|
-
# already does and for the same reason. The re-substitution
|
|
1254
|
-
#
|
|
1255
|
-
# the final one.
|
|
1444
|
+
# already does and for the same reason. The re-substitution in the
|
|
1445
|
+
# caller stays: this is a static reading, and the analyzer's answer
|
|
1446
|
+
# is the final one.
|
|
1256
1447
|
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
1448
|
addressed = handed_over.empty? ? EMPTY_ARRAYS
|
|
1263
1449
|
: arrays.slice(*handed_over)
|
|
1264
1450
|
walked = addressed.empty? ? arrays
|
|
@@ -1274,22 +1460,13 @@ class CArray
|
|
|
1274
1460
|
end
|
|
1275
1461
|
|
|
1276
1462
|
aligned, shape = broadcast(walked)
|
|
1277
|
-
|
|
1278
|
-
|
|
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)
|
|
1463
|
+
stretched = aligned.any? { |name, array| !array.equal?(walked[name]) }
|
|
1464
|
+
arrays = arrays.merge(random_states(randoms))
|
|
1288
1465
|
if map
|
|
1289
|
-
|
|
1290
|
-
|
|
1466
|
+
result_type = probe_map(source, node, arrays, scalars, c_functions,
|
|
1467
|
+
randoms)
|
|
1468
|
+
result = CArray.send(result_type, *(shape.empty? ? [1] : shape))
|
|
1291
1469
|
arrays = arrays.merge(MAP_RESULT => result)
|
|
1292
|
-
aligned = aligned.merge(MAP_RESULT => result)
|
|
1293
1470
|
end
|
|
1294
1471
|
# Both questions are about the arrays the loop walks. An array
|
|
1295
1472
|
# handed over whole is not one of them: its mask is not something the
|
|
@@ -1321,31 +1498,13 @@ class CArray
|
|
|
1321
1498
|
"expression covers #{shape.inspect}; a stretched array " \
|
|
1322
1499
|
"cannot be written to"
|
|
1323
1500
|
end
|
|
1324
|
-
aligned[name] = written
|
|
1325
|
-
end
|
|
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
|
-
|
|
1339
|
-
if kernel.masked
|
|
1340
|
-
kernel.written_arrays.each do |name|
|
|
1341
|
-
array = arrays.fetch(name)
|
|
1342
|
-
array.mask = 0 unless array.has_mask?
|
|
1343
|
-
end
|
|
1344
1501
|
end
|
|
1345
1502
|
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1503
|
+
RunPlan.new(walked: walked.keys.freeze,
|
|
1504
|
+
addressed: addressed.keys.freeze,
|
|
1505
|
+
stretched: stretched, shape: shape.freeze,
|
|
1506
|
+
sweeping: sweeping, kernel: kernel,
|
|
1507
|
+
result_type: result_type)
|
|
1349
1508
|
end
|
|
1350
1509
|
|
|
1351
1510
|
# `a = b * 2.0` reads the whole of the right-hand side and then assigns,
|
|
@@ -1394,7 +1553,7 @@ class CArray
|
|
|
1394
1553
|
return if written.empty?
|
|
1395
1554
|
roots = written.map { |array| root_of(array) }
|
|
1396
1555
|
arrays.each do |name, array|
|
|
1397
|
-
next unless Access.
|
|
1556
|
+
next unless Access.tier(array) == Access::TIER_XFER
|
|
1398
1557
|
root = root_of(array)
|
|
1399
1558
|
# Another operand on the same storage, and one of the two written:
|
|
1400
1559
|
# a copy taken before the loop cannot answer for either of them.
|
|
@@ -1434,7 +1593,7 @@ class CArray
|
|
|
1434
1593
|
def broadcast (arrays)
|
|
1435
1594
|
values = arrays.values
|
|
1436
1595
|
rank = values.reject { |value| value.is_a?(CScalar) }
|
|
1437
|
-
.map(&:
|
|
1596
|
+
.map(&:ndim).max || 1
|
|
1438
1597
|
values = values.map { |value|
|
|
1439
1598
|
value.is_a?(CScalar) ? value.refer(value.data_type, [1] * rank) : value
|
|
1440
1599
|
}
|
|
@@ -1695,6 +1854,9 @@ class CArray
|
|
|
1695
1854
|
@undef_cache = {}
|
|
1696
1855
|
@probe_cache = {}
|
|
1697
1856
|
@local_array_name_cache = {}
|
|
1857
|
+
# A plan holds the kernel it runs, so one left behind would hand the
|
|
1858
|
+
# next call a kernel the registry no longer has.
|
|
1859
|
+
@run_plans = {}
|
|
1698
1860
|
end
|
|
1699
1861
|
|
|
1700
1862
|
# @return [String] the directory this environment's kernels are kept in,
|
|
@@ -1837,31 +1999,13 @@ class CArray
|
|
|
1837
1999
|
case value
|
|
1838
2000
|
when CArray then arrays[name] = value
|
|
1839
2001
|
when CFunction then c_functions[name] = value
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
randoms[name] = refuse_an_unpasteable(name, value)
|
|
1843
|
-
else
|
|
1844
|
-
scalars[name] = value
|
|
1845
|
-
end
|
|
2002
|
+
when CArray::Rng then randoms[name] = refuse_an_unpasteable(name, value)
|
|
2003
|
+
else scalars[name] = value
|
|
1846
2004
|
end
|
|
1847
2005
|
end
|
|
1848
2006
|
[arrays, scalars, c_functions, randoms]
|
|
1849
2007
|
end
|
|
1850
2008
|
|
|
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
2009
|
# A generator CArray can run but cannot hand the source of.
|
|
1866
2010
|
#
|
|
1867
2011
|
# Checked here rather than where a generator is made, because a
|
|
@@ -2071,13 +2215,5 @@ end
|
|
|
2071
2215
|
# installed, and the same answer without it.
|
|
2072
2216
|
# ---------------------------------------------------------------------------
|
|
2073
2217
|
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
# somewhere to register. Without them the Prism front end above is the
|
|
2077
|
-
# whole of this gem, exactly as before.
|
|
2078
|
-
if CArray.respond_to?(:expression_evaluator) &&
|
|
2079
|
-
CArray.respond_to?(:__kernel_body__) &&
|
|
2080
|
-
CArray.const_defined?(:BUILD_FLAGS)
|
|
2081
|
-
require "carray/jit/expression"
|
|
2082
|
-
CArray.expression_evaluator = CArray::JIT::Expression.new
|
|
2083
|
-
end
|
|
2218
|
+
require "carray/jit/expression"
|
|
2219
|
+
CArray.expression_evaluator = CArray::JIT::Expression.new
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: carray-jit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- himotoyoshi
|
|
@@ -15,7 +15,7 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 3.0.
|
|
18
|
+
version: 3.0.4
|
|
19
19
|
- - "<"
|
|
20
20
|
- !ruby/object:Gem::Version
|
|
21
21
|
version: '3.1'
|
|
@@ -25,7 +25,7 @@ dependencies:
|
|
|
25
25
|
requirements:
|
|
26
26
|
- - ">="
|
|
27
27
|
- !ruby/object:Gem::Version
|
|
28
|
-
version: 3.0.
|
|
28
|
+
version: 3.0.4
|
|
29
29
|
- - "<"
|
|
30
30
|
- !ruby/object:Gem::Version
|
|
31
31
|
version: '3.1'
|
|
@@ -71,6 +71,7 @@ files:
|
|
|
71
71
|
- docs/06_Cheatsheet.md
|
|
72
72
|
- docs/07_StepByStep.ja.md
|
|
73
73
|
- docs/07_StepByStep.md
|
|
74
|
+
- docs/08_LanguageReference.md
|
|
74
75
|
- examples/README.md
|
|
75
76
|
- examples/applications/alarm.rb
|
|
76
77
|
- examples/applications/collatz.rb
|