carray-jit 0.1.0 → 0.1.2

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.
data/lib/carray/jit.rb CHANGED
@@ -216,13 +216,14 @@ class CArray
216
216
  JIT.run_stencil(arrays, block, border, type, into)
217
217
  end
218
218
 
219
- # Returns the contraction the block writes: an index that appears twice
220
- # is summed.
219
+ # Returns the contraction the block writes: a repeated index is summed.
221
220
  #
222
221
  # CArray.jit_contract { |i, j, k| c[i,j] = a[i,k] * b[k,j] }
223
222
  #
224
223
  # Every block parameter is an index. The ones that appear on the left are
225
- # the cells written; the rest -- `k` here -- are summed over.
224
+ # the cells written; the rest -- `k` here -- are summed over. An index that
225
+ # repeats is summed however often it repeats: `q[i,i,i]` is one index read
226
+ # at three positions, and the sum runs along the cube's long diagonal.
226
227
  #
227
228
  # No extent is given, because every index's extent is fixed by the axes it
228
229
  # addresses; an index whose axes disagree is an error, which is the shape
@@ -236,17 +237,61 @@ class CArray
236
237
  # Assigning into an array of your own says where to put it, and in what
237
238
  # order its axes lie; it does not decide what is summed.
238
239
  #
240
+ # That a repeated index is summed is a statement about *dimensions*,
241
+ # which is the world the notation comes from: two dimensions met is an inner
242
+ # product, and there is no other reading. An index that numbers things --
243
+ # a point, a sample, a batch -- is not a dimension, and `x[p,k] * y[p,k]`
244
+ # repeating `p` says "the same point", not "sum over points". Naming the
245
+ # result's axes says which is meant:
246
+ #
247
+ # CArray.jit_contract(:p) { |k| x[p,k] * y[p,k] } # one number per point
248
+ # CArray.jit_contract(:a) { square[a,a] } # the diagonal, not the trace
249
+ # CArray.jit_contract(:b, :i, :j) { |k| u[b,i,k] * v[b,k,j] } # a batch of products
250
+ #
251
+ # The arguments are the result's axes, in that order. What they say is
252
+ # which indices are free; what a repetition means is unchanged. So the rule
253
+ # is the convention's, with a third clause: an index that repeats is summed,
254
+ # one that appears once is free, and a named one is free however often it
255
+ # appears -- which is what puts the diagonal and the per-point quantity
256
+ # inside the notation instead of outside it. A free index needs somewhere
257
+ # to go, so a parameter at a single position is refused once the axes are
258
+ # named: it is free, and the axes are already stated. The list is all of
259
+ # the result's axes rather than some of them -- name one and you have named
260
+ # them all -- which is what keeps the order it states complete. Naming is allowed
261
+ # even where the convention would have reached the same answer, which is how
262
+ # the result's axes are put in another order.
263
+ #
264
+ # The sum is split into partial ones, as `jit_for`'s reduction is: a
265
+ # contraction says which indices are summed and nothing about the order, so
266
+ # there is no order here to override. `CArray::JIT.reassociate = false`
267
+ # asks for the serial one, which is what a Ruby loop would take.
268
+ #
239
269
  # The block is read and compiled, never called, so it is not yielded to.
240
270
  #
271
+ # @param free_indices [Array<Symbol>] the result's axes, in order; empty to
272
+ # let the convention decide, which is an index appearing once.
241
273
  # @return [CArray, CArray::JIT::CompiledKernel] the allocated result when the block
242
274
  # assigns into nothing, otherwise the compiled kernel.
243
275
  # @raise [CArray::JIT::Unsupported] when the block falls outside the
244
276
  # recognized subset, or an index's axes disagree.
245
- def self.jit_contract (&block)
277
+ def self.jit_contract (*free_indices, &block)
246
278
  unless block
247
279
  raise JIT::Unsupported, "jit_contract needs a block"
248
280
  end
249
- JIT.run_contraction(block)
281
+ unless free_indices.all? { |name| name.is_a?(Symbol) }
282
+ raise JIT::Unsupported,
283
+ "jit_contract's arguments are the result's axes, named as symbols, " \
284
+ "as in `CArray.jit_contract(:p) { |k| x[p,k] * y[p,k] }`"
285
+ end
286
+ repeated = free_indices.tally.select { |_, count| count > 1 }.keys
287
+ unless repeated.empty?
288
+ raise JIT::Unsupported,
289
+ "#{repeated.map { |name| "`#{name}`" }.join(', ')} names more than " \
290
+ "one axis of the result; each axis is one index"
291
+ end
292
+ # Nothing named is the convention; naming none of them is a contraction
293
+ # to a single number, and the two are different statements.
294
+ JIT.run_contraction(block, free_indices.empty? ? nil : free_indices)
250
295
  end
251
296
 
252
297
  # @!endgroup
@@ -328,12 +373,254 @@ class CArray
328
373
  RESULT = :__contraction_result
329
374
 
330
375
  # @private
331
- def run_contraction (block)
376
+ def run_contraction (block, free_indices = nil)
332
377
  node, source, origin = read_block(block)
333
- names = capture_names(source, node)
378
+ # An index named at the call site is not a parameter of the block, so
379
+ # the block reaches for it the way it reaches for a captured value.
380
+ # It is neither: it is an index, and it is answered here.
381
+ names = capture_names(source, node) - (free_indices || [])
382
+ arrays, scalars, c_functions = split_captures(names, binding_of(block))
383
+ contract(source, arrays, free_indices, node: node, origin: origin,
384
+ scalars: scalars, c_functions: c_functions)
385
+ end
386
+
387
+ # Reads a block as a contraction and returns the terms it is a product
388
+ # of, or nil when it is not one:
389
+ #
390
+ # CArray::JIT.contraction_of(proc { |i, j, k| a[i,k] * b[k,j] })
391
+ # #=> { :terms => [[a, [:i, :k]], [b, [:k, :j]]],
392
+ # # :free => [:i, :j], :summed => [:k], :scale => 1 }
393
+ #
394
+ # A number multiplied into the product is not a term -- it has no
395
+ # indices and no cell -- so it comes back as `:scale`, which is 1 where
396
+ # there is none. `a[i,k] * b[k,j] * 2.0` is the same contraction scaled,
397
+ # and a caller that rearranges it has to put the scale back: multiplying
398
+ # a sum by a number and multiplying each of its terms are the same
399
+ # arithmetic, but not the same rounding, and they are not the same
400
+ # computation type either where the number is wider than the cells.
401
+ #
402
+ # This is the half of `jit_contract` that decides *what* is being
403
+ # computed, without compiling anything, for a caller that wants to
404
+ # rearrange it -- to contract two terms at a time, say, in an order it
405
+ # chose -- and then reach `contract_terms` with the pieces.
406
+ #
407
+ # Nil means "there is nothing here to rearrange", not that the block is
408
+ # wrong: a summand that is more than a product of element reads
409
+ # (`Math.exp(a[i,k]) * b[k,j]`, a division, a captured scalar, an index
410
+ # with an offset), or one that assigns into an array of its own. Such a
411
+ # block is still a contraction and `jit_contract` still compiles it; it
412
+ # is just not a product to be taken apart. A block that is not a
413
+ # contraction at all raises here, as it would there.
414
+ #
415
+ # @param block [Proc] the block, read and not called.
416
+ # @param free_indices [Array<Symbol>] the result's axes, as
417
+ # `jit_contract` takes them.
418
+ # @return [Hash, nil] `{ terms:, free:, summed: }`, or nil.
419
+ # @raise [CArray::JIT::Unsupported] when the block is not a contraction.
420
+ def contraction_of (block, *free_indices)
421
+ node, source, = read_block(block)
422
+ # As `jit_contract` reads them: naming none is the convention, which
423
+ # is not the same as naming an empty list of axes.
424
+ free_indices = nil if free_indices.empty?
425
+ names = capture_names(source, node) - (free_indices || [])
334
426
  arrays, scalars, c_functions = split_captures(names, binding_of(block))
427
+ # A compiled function in the summand is not something the structure
428
+ # carries; a captured number is, as the scale.
429
+ return nil unless c_functions.empty?
430
+
431
+ analyzer = Analyzer.new(source, node: node, array_names: arrays.keys,
432
+ contract: :probe, free_indices: free_indices,
433
+ cell_names: cell_names(arrays))
434
+ statements = analyzer.body.statements
435
+ # Locals before the summand are computation the structure cannot
436
+ # carry either; an ElementWrite last is the assigned form.
437
+ return nil unless statements.size == 1
438
+ factors = product_terms(statements.last, arrays, scalars)
439
+ return nil unless factors
440
+ terms, scale = factors
441
+ return nil if terms.empty?
442
+
443
+ free = analyzer.probe_free_names
444
+ { :terms => terms, :free => free,
445
+ :summed => terms.flat_map(&:last).uniq - free, :scale => scale }
446
+ end
447
+
448
+ # The product's terms and what multiplies them, or nil where the tree is
449
+ # anything but a product of cells and numbers.
450
+ def product_terms (node, arrays, scalars)
451
+ case node
452
+ when BinaryOperation
453
+ return nil unless node.operator == :*
454
+ left = product_terms(node.left, arrays, scalars)
455
+ right = left && product_terms(node.right, arrays, scalars)
456
+ right && [left.first + right.first, left.last * right.last]
457
+ when ElementRead
458
+ subscripts = node.subscripts
459
+ return nil unless subscripts.all? { |index, offset|
460
+ index && offset.is_a?(Integer) && offset.zero?
461
+ }
462
+ [[[arrays.fetch(node.array), subscripts.map(&:first)]], 1]
463
+ when IntegerLiteral, FloatLiteral
464
+ [[], node.value]
465
+ when CaptureRead
466
+ value = scalars[node.name]
467
+ value.is_a?(Numeric) ? [[], value] : nil
468
+ end
469
+ end
470
+
471
+ # @private
472
+ TERM_PREFIX = "term"
473
+ # @private
474
+ TARGET = :target
475
+
476
+ # Runs the contraction the terms describe, where a term is an array and
477
+ # the indices it is read at:
478
+ #
479
+ # CArray::JIT.contract_terms([[a, [:i, :k]], [b, [:k, :j]]],
480
+ # free: [:i, :j]) # a matrix product
481
+ #
482
+ # It is `jit_contract` with the block already taken apart -- the same
483
+ # rules, the same errors, the same kernels -- for a caller that has the
484
+ # structure rather than a block to read. A contraction that was decided
485
+ # rather than written has no source, which is what this is for.
486
+ #
487
+ # `free:` is the result's axes in order, and is required: a structure has
488
+ # no parameter list, so there is nowhere else for the order to be said.
489
+ # Every index that is not named is summed, and must appear at more than
490
+ # one position, exactly as in a block whose axes are named.
491
+ #
492
+ # The terms are a product of element reads and nothing else. A summand
493
+ # that is more than that -- `Math.exp(a[i,k]) * b[k,j]`, a division, a
494
+ # captured scalar, an index with an offset -- is written as a block and
495
+ # compiled by `jit_contract`; there is no structure here that says it.
496
+ #
497
+ # @param terms [Array<Array>] `[array, [index, ...]]` pairs.
498
+ # @param free [Array<Symbol>] the result's axes, in order.
499
+ # @param into [CArray, nil] an array of yours to write into, which then
500
+ # decides the axis order and is what comes back.
501
+ # @return [CArray] `into` when it is given, otherwise a new array.
502
+ # @raise [CArray::JIT::Unsupported] when the terms are malformed, or the
503
+ # contraction they describe is one the compiler refuses.
504
+ def contract_terms (terms, free:, into: nil)
505
+ names = check_terms(terms)
506
+ indices = terms.flat_map { |_, subscripts| subscripts }
507
+ free = check_free_indices(free, indices)
508
+ arrays = names.zip(terms.map(&:first)).to_h
509
+
510
+ factors = terms.each_with_index.map { |(_, subscripts), position|
511
+ "#{names[position]}[#{subscripts.join(',')}]"
512
+ }
513
+ body = factors.join(" * ")
514
+ if into
515
+ check_destination(into, free)
516
+ arrays[TARGET] = into
517
+ # With every index summed the result is one number, which lives in a
518
+ # one-cell array at a fixed subscript -- as it is written in a block.
519
+ body = "#{TARGET}[#{free.empty? ? '0' : free.join(',')}] = #{body}"
520
+ end
521
+ summed = indices.uniq - free
522
+ parameters = summed.empty? ? "" : "|#{summed.join(', ')}| "
523
+
524
+ # The source the block would have had. It is what the kernel cache is
525
+ # keyed by, so the same terms under the same shapes reach the same
526
+ # kernel however they were arrived at -- and the string is canonical,
527
+ # which is the same service einsum's subscripts perform.
528
+ result = contract("proc { #{parameters}#{body} }", arrays, free)
529
+ into || result
530
+ end
335
531
 
336
- result = allocate_result(source, node, arrays, scalars)
532
+ # The names the synthesized source gives the terms. They are the
533
+ # source's own, so nothing outside chose them -- but an index may
534
+ # collide with one, and a collision would silently read an array as an
535
+ # index.
536
+ def check_terms (terms)
537
+ unless terms.is_a?(Array) && !terms.empty?
538
+ raise Unsupported,
539
+ "contract_terms takes the terms of a product, as in " \
540
+ "`[[a, [:i, :k]], [b, [:k, :j]]]`"
541
+ end
542
+ terms.each do |term|
543
+ unless term.is_a?(Array) && term.size == 2 && term.first.is_a?(CArray)
544
+ raise Unsupported,
545
+ "a term is an array and the indices it is read at, as in " \
546
+ "`[a, [:i, :k]]`; got #{term.inspect}"
547
+ end
548
+ array, subscripts = term
549
+ unless subscripts.is_a?(Array) && subscripts.all? { |name| index_name?(name) }
550
+ raise Unsupported,
551
+ "the indices of a term are symbols naming its axes, as in " \
552
+ "`[a, [:i, :k]]`; got #{subscripts.inspect}"
553
+ end
554
+ unless subscripts.size == array.rank
555
+ raise Unsupported,
556
+ "a term names one index per axis: this array has rank " \
557
+ "#{array.rank} and #{subscripts.size} " \
558
+ "#{subscripts.size == 1 ? 'index' : 'indices'} were given"
559
+ end
560
+ end
561
+ names = terms.each_index.map { |position| :"#{TERM_PREFIX}#{position}" }
562
+ reserved = (names + [TARGET]) & terms.flat_map { |_, subscripts| subscripts }
563
+ unless reserved.empty?
564
+ raise Unsupported,
565
+ "#{reserved.map { |name| "`#{name}`" }.join(', ')} names a term " \
566
+ "here and cannot also be an index"
567
+ end
568
+ names
569
+ end
570
+
571
+ def check_free_indices (free, indices)
572
+ unless free.is_a?(Array) && free.all? { |name| index_name?(name) }
573
+ raise Unsupported,
574
+ "`free:` is the result's axes in order, named as symbols"
575
+ end
576
+ repeated = free.tally.select { |_, count| count > 1 }.keys
577
+ unless repeated.empty?
578
+ raise Unsupported,
579
+ "#{repeated.map { |name| "`#{name}`" }.join(', ')} names more " \
580
+ "than one axis of the result; each axis is one index"
581
+ end
582
+ missing = free - indices
583
+ unless missing.empty?
584
+ raise Unsupported,
585
+ "#{missing.map { |name| "`#{name}`" }.join(', ')} " \
586
+ "#{missing.size == 1 ? 'names no axis' : 'name no axis'} here"
587
+ end
588
+ free
589
+ end
590
+
591
+ def check_destination (into, free)
592
+ unless into.is_a?(CArray)
593
+ raise Unsupported, "`into:` is an array to write into"
594
+ end
595
+ expected = free.empty? ? 1 : free.size
596
+ unless into.rank == expected
597
+ raise Unsupported,
598
+ "`into:` has rank #{into.rank}, and the result's axes are " \
599
+ "#{free.empty? ? 'none, which is one cell' : free.map { |name| "`#{name}`" }.join(', ')}"
600
+ end
601
+ end
602
+
603
+ # An index is interpolated into the source this writes, so it has to be
604
+ # a name Ruby reads back as a local variable. `:end` and `:do` would
605
+ # not parse at all, and `:nil` and `:self` would come back as something
606
+ # else -- all of them as an error about a source the caller never wrote.
607
+ def index_name? (name)
608
+ return false unless name.is_a?(Symbol)
609
+ text = name.to_s
610
+ return false unless text.match?(/\A[a-z_][A-Za-z0-9_]*\z/)
611
+ parsed = Prism.parse("#{text} = 1")
612
+ parsed.errors.empty? &&
613
+ parsed.value.statements.body.first.is_a?(Prism::LocalVariableWriteNode)
614
+ end
615
+
616
+ # What a contraction is once its block has been read: a source, the
617
+ # arrays that source names, and which indices are free. Everything
618
+ # before this is about recovering those from a block; everything after
619
+ # is the same whatever recovered them, which is what lets
620
+ # `contract_terms` reach it with a source it wrote itself.
621
+ def contract (source, arrays, free_indices,
622
+ node: nil, origin: nil, scalars: {}, c_functions: {})
623
+ result = allocate_result(source, node, arrays, scalars, free_indices)
337
624
  arrays = arrays.merge(RESULT => result) if result
338
625
 
339
626
  kernel = compile(source,
@@ -346,8 +633,15 @@ class CArray
346
633
  masked: arrays.each_value.any? { |array| array.has_mask? },
347
634
  contract: true,
348
635
  result: RESULT,
636
+ # A contraction sums an index; which order it sums it
637
+ # in is not something the caller wrote, so splitting
638
+ # the sum into partial ones does not change what the
639
+ # contraction means. Same licence `jit_for` takes.
640
+ reassociate: JIT.reassociate,
641
+ free_indices: free_indices,
349
642
  cell_names: cell_names(arrays))
350
643
 
644
+ refuse_aliased_result(kernel, arrays)
351
645
  extents = contraction_extents(kernel, arrays)
352
646
  if kernel.masked
353
647
  kernel.written_arrays.each do |name|
@@ -359,12 +653,41 @@ class CArray
359
653
  result || kernel
360
654
  end
361
655
 
656
+ # An array a contraction writes and also reads is a recurrence, which
657
+ # the analyzer refuses -- but it compares the names a block gave them,
658
+ # and two names may be one array. `x = a` is one, and so is a view of
659
+ # something being read: cells reached before the write see the old value
660
+ # and cells reached after see the new one, so the answer depends on the
661
+ # order and is not the contraction that was asked for.
662
+ #
663
+ # This is where the arrays themselves are known, so it is where the
664
+ # question can be asked of them rather than of their names. Views are
665
+ # followed to what they are views of, since that is the memory two names
666
+ # would share.
667
+ def refuse_aliased_result (kernel, arrays)
668
+ kernel.written_arrays.each do |written|
669
+ target = root_of(arrays.fetch(written))
670
+ arrays.each do |name, array|
671
+ next if name == written
672
+ next unless root_of(array).equal?(target)
673
+ raise Unsupported,
674
+ "`#{written}` and `#{name}` are the same array, which this " \
675
+ "both writes and reads; that is a recurrence rather than a " \
676
+ "contraction, and is written with jit_for"
677
+ end
678
+ end
679
+ end
680
+
681
+ def root_of (array)
682
+ array.respond_to?(:root_array) ? array.root_array : array
683
+ end
684
+
362
685
  # A contraction with nothing to assign into needs its result sized and
363
686
  # typed before there is a kernel to ask, so the block is analyzed once
364
687
  # without being compiled. Returns nil when the block assigns into an
365
688
  # array of its own.
366
- def allocate_result (source, node, arrays, scalars)
367
- probe = probe_contraction(source, node, arrays, scalars)
689
+ def allocate_result (source, node, arrays, scalars, free_indices = nil)
690
+ probe = probe_contraction(source, node, arrays, scalars, free_indices)
368
691
  return nil unless probe
369
692
  free, index_axes, type = probe
370
693
  shape = free.map do |index|
@@ -385,13 +708,17 @@ class CArray
385
708
  end
386
709
 
387
710
  # @private
388
- def probe_contraction (source, node, arrays, scalars)
711
+ def probe_contraction (source, node, arrays, scalars, free_indices = nil)
389
712
  key = [source, arrays.transform_values(&:data_type_name),
390
713
  scalars.transform_values { |value| TypeAssignment.scalar_type(value) },
714
+ # The result's axes are named at the call site rather than in
715
+ # the block, so the same source under another naming is another
716
+ # kernel -- and another probe.
717
+ free_indices,
391
718
  cell_names(arrays)]
392
719
  cached = probe_cache[key]
393
720
  return cached unless cached.nil?
394
- probe_cache[key] = build_probe(source, node, arrays, scalars)
721
+ probe_cache[key] = build_probe(source, node, arrays, scalars, free_indices)
395
722
  end
396
723
 
397
724
  # @private
@@ -400,10 +727,10 @@ class CArray
400
727
  end
401
728
 
402
729
  # @private
403
- def build_probe (source, node, arrays, scalars)
730
+ def build_probe (source, node, arrays, scalars, free_indices = nil)
404
731
  storage_types = arrays.transform_values(&:data_type_name)
405
732
  analyzer = Analyzer.new(source, node: node, array_names: arrays.keys,
406
- contract: :probe,
733
+ contract: :probe, free_indices: free_indices,
407
734
  cell_names: cell_names(arrays))
408
735
  return false if analyzer.body.statements.last.is_a?(ElementWrite)
409
736
 
@@ -417,9 +744,13 @@ class CArray
417
744
  end
418
745
  end
419
746
  end
747
+ # The type the value was computed in decides the array it is collected
748
+ # into, and that table lives with the computation types rather than
749
+ # here -- a copy kept at the allocation site answers after a type is
750
+ # added, and answers wrongly. This one did: float32 and cmplx64 fell
751
+ # through its default and a float contraction came back int64.
420
752
  [analyzer.probe_free_names, axes,
421
- { :double => :float64, :complex => :cmplx128 }
422
- .fetch(summand.type, :int64)]
753
+ TypeAssignment.result_storage_type(summand.type)]
423
754
  end
424
755
 
425
756
  # Each index's extent comes from the axes it addresses. Where it
@@ -621,6 +952,26 @@ class CArray
621
952
  "#{shape.inspect}, so there is no cell where the window is " \
622
953
  "inside the array"
623
954
  end
955
+ # Writing into an array a window reads is not a pass over the array:
956
+ # a cell written here is a neighbour a later cell reads, so what comes
957
+ # back depends on the order the cells were reached in. The question
958
+ # is about that window alone -- a window that reaches nowhere reads
959
+ # only the cell the loop is on, and may be written in place however
960
+ # far the other windows in the same kernel reach.
961
+ if into
962
+ aliased = given.find { |name, array|
963
+ next false unless root_of(array).equal?(root_of(into))
964
+ kernel.window_reaches[name].any? { |low, high|
965
+ !low.zero? || !high.zero?
966
+ }
967
+ }
968
+ if aliased
969
+ raise Unsupported,
970
+ "`into:` is the array `#{aliased.first}` reaches its window " \
971
+ "into, and a cell written there is one a later cell reads; " \
972
+ "a stencil writes into an array of its own"
973
+ end
974
+ end
624
975
  result.mask = 0 if (kernel.masked || border == :mask) && !result.has_mask?
625
976
  mark_frame(result, bounds, shape) if border == :mask
626
977
  operands = given.merge(MAP_RESULT => result)
@@ -896,7 +1247,8 @@ class CArray
896
1247
  def compile (source, node: nil, origin: nil, array_names:, storage_types:,
897
1248
  scalar_values:, c_functions: {}, masked: false, rank: nil,
898
1249
  steps: nil, contract: false, result: nil, map: false,
899
- reassociate: false, cell_names: [], windows: [], border: nil)
1250
+ reassociate: false, cell_names: [], windows: [], border: nil,
1251
+ free_indices: nil)
900
1252
  # A kernel that mentions UNDEF is a masked one whatever its arrays
901
1253
  # carry, and deciding that here means no caller has to remember it.
902
1254
  masked ||= mentions_undef(source, node)
@@ -909,6 +1261,10 @@ class CArray
909
1261
  # symbol, which stands for its body -- see `CFunction#kernel_key`.
910
1262
  c_functions.transform_values(&:kernel_key),
911
1263
  masked, rank, steps, contract, result, map,
1264
+ # A contraction whose free indices were named is not the kernel
1265
+ # the same source is without them, nor with them in another
1266
+ # order: the naming decides what is summed and what comes out.
1267
+ free_indices,
912
1268
  # Which names are read at their one cell rather than walked:
913
1269
  # the same source over a CScalar is a different kernel from
914
1270
  # the same source over a one-cell CArray.
@@ -929,7 +1285,7 @@ class CArray
929
1285
  registry[key] = build(source, node, array_names, storage_types,
930
1286
  scalar_values, c_functions, masked, rank, steps,
931
1287
  contract, result, origin, map, reassociate,
932
- cell_names, windows, border)
1288
+ cell_names, windows, border, free_indices)
933
1289
  end
934
1290
 
935
1291
  # A kernel that mentions UNDEF is a masked kernel whatever its arrays
@@ -1004,6 +1360,27 @@ class CArray
1004
1360
  Compiler.cache_root
1005
1361
  end
1006
1362
 
1363
+ # Puts this application's kernels somewhere of its own, rather than in
1364
+ # the cache shared under the home directory. Say it before the first
1365
+ # kernel is compiled -- at the top of the program, beside the other
1366
+ # requires:
1367
+ #
1368
+ # CArray::JIT.cache_root = File.expand_path("../.jit-cache", __dir__)
1369
+ #
1370
+ # Kernels already loaded keep working and what is already on disk stays
1371
+ # where it is; this says where the next one is looked for and written.
1372
+ # `CARRAY_JIT_CACHE` and `CARRAY_JIT_NO_CACHE` still come first, so
1373
+ # whoever runs the program can put the cache somewhere writable or do
1374
+ # without one. A directory inside a project wants to be ignored by the
1375
+ # version control it sits in.
1376
+ #
1377
+ # @param path [String, nil] the directory, relative to where the
1378
+ # program starts; `nil` restores the default.
1379
+ # @return [void]
1380
+ def cache_root= (path)
1381
+ Compiler.cache_root = path
1382
+ end
1383
+
1007
1384
  # @return [Array<String>] the environment directories no longer in use --
1008
1385
  # another version, or another architecture.
1009
1386
  def stale_cache_environments
@@ -1036,11 +1413,11 @@ class CArray
1036
1413
  def build (source, node, array_names, storage_types, scalar_values, c_functions,
1037
1414
  masked, rank = nil, steps = nil, contract = false, result = nil,
1038
1415
  origin = nil, map = false, reassociate = false,
1039
- cell_names = [], windows = [], border = nil)
1416
+ cell_names = [], windows = [], border = nil, free_indices = nil)
1040
1417
  analyzer = Analyzer.new(source, node: node, array_names: array_names,
1041
1418
  c_functions: c_functions,
1042
1419
  rank: rank, steps: steps, contract: contract,
1043
- result: result, map: map,
1420
+ result: result, map: map, free_indices: free_indices,
1044
1421
  cell_names: cell_names, windows: windows)
1045
1422
  assignment = TypeAssignment.new(analyzer.body, storage_types,
1046
1423
  scalar_values, c_functions)
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.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - himotoyoshi
@@ -133,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
133
133
  - !ruby/object:Gem::Version
134
134
  version: '0'
135
135
  requirements: []
136
- rubygems_version: 4.0.17
136
+ rubygems_version: 4.0.8
137
137
  specification_version: 4
138
138
  summary: JIT compilation of CArray kernels written in Ruby
139
139
  test_files: []