carray 3.0.2 → 3.0.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 +304 -0
- data/README.md +3 -4
- data/carray.gemspec +1 -1
- data/ext/ca_axis_dispatch.c +225 -0
- data/ext/ca_axis_group.c +177 -99
- data/ext/ca_binop_dispatch.c +54 -32
- data/ext/ca_binop_dispatch.h +1 -0
- data/ext/ca_categorical_iterator.c +66 -71
- data/ext/ca_compare.c +8 -2
- data/ext/ca_for_buffer.h +21 -68
- data/ext/ca_group_iter.c +27 -11
- data/ext/ca_kernel_iterator.c +663 -214
- data/ext/ca_kernel_iterator.h +231 -167
- data/ext/ca_monop_dispatch.c +4 -4
- data/ext/ca_monop_dispatch.h +4 -0
- data/ext/ca_obj_array.c +132 -22
- data/ext/ca_obj_bincmp.c +152 -121
- data/ext/ca_obj_binop.c +107 -210
- data/ext/ca_obj_bitarray.c +37 -24
- data/ext/ca_obj_bitfield.c +95 -62
- data/ext/ca_obj_block.c +1 -0
- data/ext/ca_obj_byte_swap.c +40 -40
- data/ext/ca_obj_const_string.c +96 -25
- data/ext/ca_obj_face.c +406 -64
- data/ext/ca_obj_face.h +92 -47
- data/ext/ca_obj_fake.c +21 -50
- data/ext/ca_obj_farray.c +1 -0
- data/ext/ca_obj_field.c +16 -3
- data/ext/ca_obj_fixlen_string.c +21 -21
- data/ext/ca_obj_grid.c +181 -124
- data/ext/ca_obj_meld.c +189 -107
- data/ext/ca_obj_moncmp.c +61 -61
- data/ext/ca_obj_monop.c +125 -107
- data/ext/ca_obj_object.c +543 -233
- data/ext/ca_obj_record.c +51 -16
- data/ext/ca_obj_reduce.c +5 -8
- data/ext/ca_obj_refer.c +117 -33
- data/ext/ca_obj_remap.c +57 -17
- data/ext/ca_obj_repeat.c +21 -5
- data/ext/ca_obj_roll.c +115 -11
- data/ext/ca_obj_select.c +47 -49
- data/ext/ca_obj_select_axis.c +58 -60
- data/ext/ca_obj_shift.c +22 -102
- data/ext/ca_obj_stack.c +223 -118
- data/ext/ca_obj_stride.c +195 -34
- data/ext/ca_obj_string.c +17 -18
- data/ext/ca_obj_tile.c +32 -16
- data/ext/ca_obj_time.c +23 -25
- data/ext/ca_obj_timedelta.c +21 -23
- data/ext/ca_obj_transpose.c +12 -3
- data/ext/ca_obj_triop.c +110 -117
- data/ext/ca_obj_window.c +149 -27
- data/ext/ca_op_ipower.c +24 -11
- data/ext/ca_op_powi.h +57 -30
- data/ext/ca_pow5_table.h +656 -0
- data/ext/ca_sort_kernels.h +70 -0
- data/ext/ca_sweep_engine.c +102 -79
- data/ext/ca_sweep_engine.h +7 -20
- data/ext/ca_triop_dispatch.c +4 -1
- data/ext/ca_triop_dispatch.h +5 -2
- data/ext/caframe_csv_reader.c +463 -0
- data/ext/carray.h +70 -27
- data/ext/carray_access.c +455 -211
- data/ext/carray_address_basis.c +177 -36
- data/ext/carray_attribute.c +45 -3
- data/ext/carray_bincount.c +32 -2
- data/ext/carray_broadcast.c +46 -14
- data/ext/carray_call_cfunc.c +0 -28
- data/ext/carray_cast.c +1444 -209
- data/ext/carray_cast_func.rb +34 -21
- data/ext/carray_conversion.c +197 -73
- data/ext/carray_copy.c +32 -11
- data/ext/carray_core.c +514 -122
- data/ext/carray_count.c +140 -87
- data/ext/carray_element.c +18 -27
- data/ext/carray_factorize.c +569 -277
- data/ext/carray_generate.c +76 -40
- data/ext/carray_histogram.c +152 -94
- data/ext/carray_hold.c +2 -1
- data/ext/carray_index_classifier.c +63 -96
- data/ext/carray_internal.h +385 -3
- data/ext/carray_lazy.c +834 -79
- data/ext/carray_loop.c +81 -65
- data/ext/carray_mask.c +682 -316
- data/ext/carray_math_kernel.h +1 -0
- data/ext/carray_median_percentile.c +221 -76
- data/ext/carray_memory_view.c +254 -248
- data/ext/carray_operator.c +1089 -359
- data/ext/carray_order.c +133 -69
- data/ext/carray_partition.c +102 -56
- data/ext/carray_random.c +273 -239
- data/ext/carray_scatter.c +308 -65
- data/ext/carray_segment.c +257 -0
- data/ext/carray_slab.c +170 -70
- data/ext/carray_sort.c +215 -218
- data/ext/carray_sort_kernel.c +8 -1
- data/ext/carray_struct.c +99 -21
- data/ext/carray_test.c +167 -64
- data/ext/carray_time_text.c +1182 -0
- data/ext/carray_utils.c +363 -98
- data/ext/extconf.rb +27 -2
- data/ext/mk_call_cfunc.rb +0 -2
- data/ext/mk_pow5_table.rb +35 -0
- data/ext/mkkernel.rb +1076 -846
- data/ext/ruby_carray.c +7 -1
- data/ext/version.h +4 -4
- data/ext/version.rb +4 -2
- data/lib/carray/attribute.rb +39 -0
- data/lib/carray/autoload_carray.rb +19 -0
- data/lib/carray/autoload_method_extension.rb +6 -0
- data/lib/carray/axis_group.rb +174 -26
- data/lib/carray/basics.rb +91 -25
- data/lib/carray/bincount_nd.rb +6 -0
- data/lib/carray/block_iterator.rb +118 -48
- data/lib/carray/boolean_reduce.rb +21 -29
- data/lib/carray/categorical.rb +94 -26
- data/lib/carray/categorical_iterator.rb +333 -826
- data/lib/carray/complex.rb +36 -29
- data/lib/carray/conditional.rb +94 -7
- data/lib/carray/const_string.rb +46 -0
- data/lib/carray/frame/concat.rb +157 -6
- data/lib/carray/frame/convert.rb +13 -7
- data/lib/carray/frame/csv_parser.rb +589 -82
- data/lib/carray/frame/describe.rb +79 -0
- data/lib/carray/frame/frame.rb +124 -25
- data/lib/carray/frame/group.rb +189 -63
- data/lib/carray/frame/io.rb +412 -72
- data/lib/carray/frame/join.rb +134 -46
- data/lib/carray/frame/records.rb +37 -16
- data/lib/carray/frame/reshape.rb +448 -0
- data/lib/carray/frame/sort.rb +26 -2
- data/lib/carray/frame/verbs.rb +715 -48
- data/lib/carray/frame.rb +2 -0
- data/lib/carray/fuse_source.rb +111 -2
- data/lib/carray/fusion.rb +229 -38
- data/lib/carray/inspect.rb +29 -11
- data/lib/carray/iterator.rb +14 -2
- data/lib/carray/lazy.rb +54 -30
- data/lib/carray/mask_gap_fill.rb +66 -52
- data/lib/carray/math.rb +32 -19
- data/lib/carray/meld_reduce.rb +40 -7
- data/lib/carray/methods/align_addr.rb +8 -4
- data/lib/carray/methods/bincount.rb +2 -2
- data/lib/carray/methods/bit_string.rb +14 -3
- data/lib/carray/methods/composition.rb +16 -10
- data/lib/carray/methods/factorize.rb +2 -2
- data/lib/carray/methods/gather_nd.rb +13 -2
- data/lib/carray/methods/insert_block.rb +9 -14
- data/lib/carray/methods/is_in.rb +117 -4
- data/lib/carray/methods/join.rb +1 -1
- data/lib/carray/methods/locate_addr.rb +177 -36
- data/lib/carray/methods/mode.rb +18 -1
- data/lib/carray/methods/repeat.rb +1 -20
- data/lib/carray/methods/resize.rb +21 -23
- data/lib/carray/methods/snap.rb +34 -12
- data/lib/carray/methods/unique.rb +11 -2
- data/lib/carray/record_order.rb +208 -0
- data/lib/carray/runtime.rb +14 -11
- data/lib/carray/segment_iterator.rb +990 -0
- data/lib/carray/serialize.rb +197 -27
- data/lib/carray/slab_iterator.rb +51 -27
- data/lib/carray/stack.rb +10 -4
- data/lib/carray/string_operation_extension.rb +4 -3
- data/lib/carray/struct.rb +142 -51
- data/lib/carray/struct_builder.rb +110 -14
- data/lib/carray/table.rb +2 -2
- data/lib/carray/time.rb +517 -103
- data/lib/carray/window_iterator.rb +127 -72
- data/lib/carray.rb +2 -2
- data/yard-stubs/ca_obj_array.rb +4 -4
- data/yard-stubs/ca_obj_bitarray.rb +7 -0
- data/yard-stubs/ca_obj_bitfield.rb +23 -12
- data/yard-stubs/ca_obj_fake.rb +1 -1
- data/yard-stubs/ca_obj_field.rb +1 -1
- data/yard-stubs/ca_obj_refer.rb +3 -3
- data/yard-stubs/ca_obj_roll.rb +0 -1
- data/yard-stubs/ca_obj_stride.rb +10 -7
- data/yard-stubs/ca_obj_transpose.rb +1 -1
- data/yard-stubs/ca_obj_window.rb +1 -2
- data/yard-stubs/carray_access.rb +5 -4
- data/yard-stubs/carray_broadcast.rb +1 -1
- data/yard-stubs/carray_cast.rb +78 -43
- data/yard-stubs/carray_conversion.rb +13 -1
- data/yard-stubs/carray_copy.rb +8 -3
- data/yard-stubs/carray_core.rb +3 -3
- data/yard-stubs/carray_count.rb +9 -0
- data/yard-stubs/carray_lazy.rb +5 -1
- data/yard-stubs/carray_loop.rb +4 -1
- data/yard-stubs/carray_mask.rb +34 -10
- data/yard-stubs/carray_math.rb +25 -13
- data/yard-stubs/carray_median_percentile.rb +10 -6
- data/yard-stubs/carray_order.rb +34 -13
- data/yard-stubs/carray_scatter.rb +10 -3
- data/yard-stubs/carray_segment.rb +72 -0
- data/yard-stubs/carray_slab.rb +2 -1
- data/yard-stubs/carray_sort.rb +43 -3
- data/yard-stubs/carray_test.rb +10 -0
- data/yard-stubs/carray_utils.rb +0 -26
- metadata +11 -19
- data/ext/ca_for_each_element.h +0 -239
- data/ext/carray_build_flags.h +0 -3
- data/ext/carray_config.h +0 -28
- data/ext/carray_kernels_bincmp.c +0 -4446
- data/ext/carray_kernels_binop.c +0 -11001
- data/ext/carray_kernels_init.c +0 -1131
- data/ext/carray_kernels_map.c +0 -3467
- data/ext/carray_kernels_moncmp.c +0 -2097
- data/ext/carray_kernels_monop.c +0 -18313
- data/ext/carray_kernels_reduce_aggregate.c +0 -26005
- data/ext/carray_kernels_reduce_boolean.c +0 -330
- data/ext/carray_kernels_reduce_cumulative.c +0 -14862
- data/ext/carray_kernels_reduce_extreme.c +0 -17494
- data/ext/carray_kernels_reduce_variance.c +0 -3910
- data/ext/carray_kernels_scan.c +0 -3693
- data/ext/carray_kernels_search.c +0 -32218
- data/ext/carray_kernels_sort.c +0 -10626
- data/ext/carray_kernels_triop.c +0 -1392
data/ext/mkkernel.rb
CHANGED
|
@@ -406,8 +406,8 @@ module MkKernel
|
|
|
406
406
|
# only, output (an axis-local index) needs no re-lift.
|
|
407
407
|
# :relift -- value-returning kernels (min/max): descend, then re-lift the
|
|
408
408
|
# output back into the Face -- a full-reduction scalar via the
|
|
409
|
-
# registered
|
|
410
|
-
#
|
|
409
|
+
# registered storage_to_element hook, a per-axis CArray via
|
|
410
|
+
# ca_face_relift_entity (carries the subclass tail, e.g. unit).
|
|
411
411
|
raise "reduce: unknown face_gate #{face_gate} (expected nil, :strip, or :relift)" \
|
|
412
412
|
unless [nil, :strip, :relift].include?(face_gate)
|
|
413
413
|
# object_escape: a Symbol naming a Ruby instance method that handles the
|
|
@@ -659,41 +659,6 @@ module MkKernel
|
|
|
659
659
|
}
|
|
660
660
|
end
|
|
661
661
|
|
|
662
|
-
# Register a map kernel (element-wise transform: input shape == output
|
|
663
|
-
# shape, one cell at a time). Each generated helper walks the input
|
|
664
|
-
# and output slabs in lockstep using CA_SLAB_MAP_T(T_LOAD, T_OUT, ...).
|
|
665
|
-
#
|
|
666
|
-
# DSL:
|
|
667
|
-
# MkKernel.map :name,
|
|
668
|
-
# source: MkKernel::ALL_NUMERIC,
|
|
669
|
-
# output: :f64 | :preserve,
|
|
670
|
-
# expr: "r = sqrt((double) v)", # binds v (input) + r (output)
|
|
671
|
-
# fallback: :wrap_to_f64 | :raise
|
|
672
|
-
#
|
|
673
|
-
# Generated method takes no arguments (`a.sqrt_ki` not `a.sqrt_ki(0)`)
|
|
674
|
-
# since the operation is per-cell across the whole array.
|
|
675
|
-
def self.map(name, source:, output:, expr:, fallback: :raise)
|
|
676
|
-
raise "duplicate kernel #{name}" if KERNELS.any? { |k| k[:name] == name }
|
|
677
|
-
source.each do |s|
|
|
678
|
-
raise "unknown source data_type #{s}" unless DTYPES.key?(s)
|
|
679
|
-
end
|
|
680
|
-
raise "unknown output #{output}" unless output == :preserve || DTYPES.key?(output) || output.is_a?(Hash)
|
|
681
|
-
raise "unknown fallback #{fallback}" unless %i[raise wrap_to_f64].include?(fallback)
|
|
682
|
-
if output == :preserve && fallback == :wrap_to_f64
|
|
683
|
-
raise "#{name}: :preserve + :wrap_to_f64 is a semantic conflict " \
|
|
684
|
-
"(fallback path produces f64 output, contradicting :preserve)"
|
|
685
|
-
end
|
|
686
|
-
|
|
687
|
-
KERNELS << {
|
|
688
|
-
kind: :map,
|
|
689
|
-
name: name,
|
|
690
|
-
source: source,
|
|
691
|
-
output: output,
|
|
692
|
-
expr: expr,
|
|
693
|
-
fallback: fallback,
|
|
694
|
-
}
|
|
695
|
-
end
|
|
696
|
-
|
|
697
662
|
# Register a scan kernel (cumulative / prefix-scan: input shape ==
|
|
698
663
|
# output shape, running accumulator along one axis). The generated
|
|
699
664
|
# method takes exactly one axis argument (e.g., `a.cumsum_ki(0)`).
|
|
@@ -704,7 +669,11 @@ module MkKernel
|
|
|
704
669
|
# output: :f64 | :preserve,
|
|
705
670
|
# init: "0", # initial acc value (per fiber)
|
|
706
671
|
# step: "acc += v; r = acc", # binds v (T_LOAD), r (T_OUT), acc
|
|
707
|
-
# fallback: :wrap_to_f64 | :raise
|
|
672
|
+
# fallback: :wrap_to_f64 | :raise | :mask_count
|
|
673
|
+
#
|
|
674
|
+
# :mask_count is for a scan that reads only the mask (cumcount): a data
|
|
675
|
+
# type outside source: is answered as is_not_masked.cumsum(axis:) in
|
|
676
|
+
# int64, so the values are never converted.
|
|
708
677
|
#
|
|
709
678
|
# Init supports the same special tokens as reduce: T_LIMIT_HI /
|
|
710
679
|
# T_LIMIT_LO / T_ACC / T_OUT. Each scan slab (= fiber along the
|
|
@@ -738,13 +707,13 @@ module MkKernel
|
|
|
738
707
|
# the reduction contract (empty max / min = UNDEF, no
|
|
739
708
|
# identity) and the axis-group scan family.
|
|
740
709
|
def self.scan(name, source:, output:, init:, step:, fallback: :raise,
|
|
741
|
-
acc_type: nil, axis_default: nil, empty: nil)
|
|
710
|
+
acc_type: nil, axis_default: nil, empty: nil, face_gate: nil)
|
|
742
711
|
raise "duplicate kernel #{name}" if KERNELS.any? { |k| k[:name] == name }
|
|
743
712
|
source.each do |s|
|
|
744
713
|
raise "unknown source data_type #{s}" unless DTYPES.key?(s)
|
|
745
714
|
end
|
|
746
715
|
raise "unknown output #{output}" unless output == :preserve || DTYPES.key?(output) || output.is_a?(Hash)
|
|
747
|
-
raise "unknown fallback #{fallback}" unless %i[raise wrap_to_f64].include?(fallback)
|
|
716
|
+
raise "unknown fallback #{fallback}" unless %i[raise wrap_to_f64 mask_count].include?(fallback)
|
|
748
717
|
if output == :preserve && fallback == :wrap_to_f64
|
|
749
718
|
raise "#{name}: :preserve + :wrap_to_f64 is a semantic conflict"
|
|
750
719
|
end
|
|
@@ -760,6 +729,13 @@ module MkKernel
|
|
|
760
729
|
if empty == :undef && acc_type == :load_type
|
|
761
730
|
raise "#{name}: empty: :undef is not supported with acc_type: :load_type"
|
|
762
731
|
end
|
|
732
|
+
# face_gate: :relift descends an ORDERABLE Face to its storage, scans
|
|
733
|
+
# the storage and puts the Face back on the result (the reduce family's
|
|
734
|
+
# policy). Only a scan whose output is the source's own values may take
|
|
735
|
+
# it: a running sum of instants is not an instant.
|
|
736
|
+
unless face_gate.nil? || face_gate == :relift
|
|
737
|
+
raise "#{name}: unknown face_gate #{face_gate.inspect} (nil | :relift)"
|
|
738
|
+
end
|
|
763
739
|
|
|
764
740
|
KERNELS << {
|
|
765
741
|
kind: :scan,
|
|
@@ -772,6 +748,7 @@ module MkKernel
|
|
|
772
748
|
acc_type: acc_type,
|
|
773
749
|
axis_default: axis_default,
|
|
774
750
|
empty: empty,
|
|
751
|
+
face_gate: face_gate,
|
|
775
752
|
}
|
|
776
753
|
end
|
|
777
754
|
|
|
@@ -1446,6 +1423,12 @@ module MkKernel
|
|
|
1446
1423
|
true
|
|
1447
1424
|
end
|
|
1448
1425
|
|
|
1426
|
+
# The CArray names of the data types a kernel loads, for its refusal
|
|
1427
|
+
# message ("int8, uint8, ..."), not the generator's own short keys.
|
|
1428
|
+
def self.type_names(sources)
|
|
1429
|
+
sources.map { |s| s == :fixlen ? "fixlen" : DTYPES[s][:ca].sub(/\ACA_/, "").downcase }.join(", ")
|
|
1430
|
+
end
|
|
1431
|
+
|
|
1449
1432
|
def self.reduce_macro_suffix(k, src = nil)
|
|
1450
1433
|
# CA_OBJECT cannot ride the SIMD-licensed macros (= _PLUS / _MIN / _MAX
|
|
1451
1434
|
# / _STAR), which assume C operators (= acc is a VALUE, so
|
|
@@ -1624,9 +1607,11 @@ module MkKernel
|
|
|
1624
1607
|
require "fileutils"
|
|
1625
1608
|
FileUtils.mkdir_p(outdir)
|
|
1626
1609
|
tags = file_tags
|
|
1610
|
+
written = ["carray_kernels_init.c"]
|
|
1627
1611
|
tags.each do |kind, subgroup|
|
|
1628
1612
|
suffix = file_suffix(kind, subgroup)
|
|
1629
1613
|
path = File.join(outdir, "carray_kernels_#{suffix}.c")
|
|
1614
|
+
written << File.basename(path)
|
|
1630
1615
|
File.open(path, "w") do |io|
|
|
1631
1616
|
io.puts header
|
|
1632
1617
|
KERNELS.each do |k|
|
|
@@ -1640,6 +1625,11 @@ module MkKernel
|
|
|
1640
1625
|
File.open(File.join(outdir, "carray_kernels_init.c"), "w") do |io|
|
|
1641
1626
|
emit_aggregator_init(io, tags)
|
|
1642
1627
|
end
|
|
1628
|
+
# extconf compiles every carray_kernels_*.c it finds, so a file left
|
|
1629
|
+
# by a kind or sub-group that no longer exists has to go.
|
|
1630
|
+
Dir.glob(File.join(outdir, "carray_kernels_*.c")).each do |f|
|
|
1631
|
+
File.delete(f) unless written.include?(File.basename(f))
|
|
1632
|
+
end
|
|
1643
1633
|
end
|
|
1644
1634
|
|
|
1645
1635
|
# Which file a kernel belongs to. The three alias kinds live with the kind
|
|
@@ -1658,7 +1648,6 @@ module MkKernel
|
|
|
1658
1648
|
def self.emit_kernel_dispatch(io, k)
|
|
1659
1649
|
case k[:kind]
|
|
1660
1650
|
when :reduce then emit_reduce(io, k)
|
|
1661
|
-
when :map then emit_map(io, k)
|
|
1662
1651
|
when :scan then emit_scan(io, k)
|
|
1663
1652
|
when :sort then emit_sort(io, k)
|
|
1664
1653
|
when :search then emit_search(io, k)
|
|
@@ -1675,7 +1664,7 @@ module MkKernel
|
|
|
1675
1664
|
end
|
|
1676
1665
|
end
|
|
1677
1666
|
|
|
1678
|
-
KINDS = [:reduce, :
|
|
1667
|
+
KINDS = [:reduce, :scan, :sort, :search,
|
|
1679
1668
|
:monop, :binop, :triop, :moncmp, :bincmp].freeze
|
|
1680
1669
|
|
|
1681
1670
|
# Sub-split map for the reduce kind. As one file, reduce accounted for 71%
|
|
@@ -1686,17 +1675,16 @@ module MkKernel
|
|
|
1686
1675
|
# rather than the guess that monop and binop would dominate. A kernel added
|
|
1687
1676
|
# later needs an entry here; emit asserts, so a missing one is loud.
|
|
1688
1677
|
REDUCE_SUB_GROUPS = {
|
|
1689
|
-
# aggregate: arithmetic folds (sum / prod / mean and their
|
|
1690
|
-
#
|
|
1678
|
+
# aggregate: arithmetic folds (sum / prod / mean and their weighted
|
|
1679
|
+
# variants)
|
|
1691
1680
|
sum: :aggregate, prod: :aggregate, mean: :aggregate,
|
|
1692
|
-
sum_strict: :aggregate, mean_safe: :aggregate,
|
|
1693
1681
|
wsum: :aggregate, wmean: :aggregate,
|
|
1694
1682
|
# extreme: order-based (min / max, argmin / argmax, minmax)
|
|
1695
1683
|
min: :extreme, max: :extreme, minmax: :extreme,
|
|
1696
1684
|
argmin: :extreme, argmax: :extreme,
|
|
1697
1685
|
argmin_addr: :extreme, argmax_addr: :extreme,
|
|
1698
1686
|
# cumulative: counter accumulation (the count family and accumulate)
|
|
1699
|
-
|
|
1687
|
+
accumulate: :cumulative,
|
|
1700
1688
|
count_true: :cumulative, count_false: :cumulative,
|
|
1701
1689
|
count_equal: :cumulative,
|
|
1702
1690
|
# variance family: multi-state (Chan / Welford), and the one that
|
|
@@ -1784,11 +1772,116 @@ module MkKernel
|
|
|
1784
1772
|
base + HEADER_BLOCKS.join("\n")
|
|
1785
1773
|
end
|
|
1786
1774
|
|
|
1775
|
+
# Emits the native function of one source data type. The object lane
|
|
1776
|
+
# calls Ruby for every cell, so any cell can raise in the middle of a walk;
|
|
1777
|
+
# its function is rebuilt around ca_iter_ensure (see guard_object_native).
|
|
1778
|
+
# Every other lane is emitted as the emitter writes it.
|
|
1779
|
+
def self.emit_native_for(io, k, src)
|
|
1780
|
+
if src == :object
|
|
1781
|
+
buf = StringIO.new
|
|
1782
|
+
yield buf
|
|
1783
|
+
io.print guard_object_native(buf.string, "#{k[:name]}_ki_native_object")
|
|
1784
|
+
else
|
|
1785
|
+
yield io
|
|
1786
|
+
end
|
|
1787
|
+
end
|
|
1788
|
+
|
|
1789
|
+
# Index of the brace that closes the one at text[open], skipping string
|
|
1790
|
+
# and character literals and comments.
|
|
1791
|
+
def self.matching_brace(text, open)
|
|
1792
|
+
depth = 0
|
|
1793
|
+
i = open
|
|
1794
|
+
while i < text.length
|
|
1795
|
+
c = text[i]
|
|
1796
|
+
case c
|
|
1797
|
+
when '"', "'"
|
|
1798
|
+
i += 1
|
|
1799
|
+
i += (text[i] == "\\") ? 2 : 1 while i < text.length && text[i] != c
|
|
1800
|
+
when "/"
|
|
1801
|
+
if text[i + 1] == "/"
|
|
1802
|
+
i = text.index("\n", i) || text.length
|
|
1803
|
+
elsif text[i + 1] == "*"
|
|
1804
|
+
i = (text.index("*/", i + 2) || text.length) + 1
|
|
1805
|
+
end
|
|
1806
|
+
when "{"
|
|
1807
|
+
depth += 1
|
|
1808
|
+
when "}"
|
|
1809
|
+
depth -= 1
|
|
1810
|
+
return i if depth == 0
|
|
1811
|
+
end
|
|
1812
|
+
i += 1
|
|
1813
|
+
end
|
|
1814
|
+
raise "mkkernel: unbalanced braces"
|
|
1815
|
+
end
|
|
1816
|
+
|
|
1817
|
+
# Rebuilds the native function +fn+ found in +text+ so that its walks are
|
|
1818
|
+
# finished however it leaves. The body moves into a function of its own
|
|
1819
|
+
# that ca_iter_ensure runs; each `ca_iter_state` it declared becomes a
|
|
1820
|
+
# slot of a frame kept by the caller, since a state in the body's own
|
|
1821
|
+
# frame is gone once the body raises. The body text is kept as written:
|
|
1822
|
+
# the parameters are unpacked from the frame under their own names, and
|
|
1823
|
+
# each state name is defined to its slot for the length of the body.
|
|
1824
|
+
def self.guard_object_native(text, fn)
|
|
1825
|
+
header = /^static VALUE\n#{fn} \(([^)]*)\)\n\{\n/
|
|
1826
|
+
m = header.match(text) or raise "mkkernel: #{fn} not found"
|
|
1827
|
+
close = matching_brace(text, m.end(0) - 2)
|
|
1828
|
+
body = text[m.end(0)...close]
|
|
1829
|
+
rest = text[(close + 1)..]
|
|
1830
|
+
|
|
1831
|
+
states = []
|
|
1832
|
+
body = body.gsub(/^[ \t]*ca_iter_state[ \t]+(\w+(?:[ \t]*,[ \t]*\w+)*)[ \t]*;[ \t]*\n/) do
|
|
1833
|
+
states.concat($1.split(/[ \t]*,[ \t]*/))
|
|
1834
|
+
""
|
|
1835
|
+
end
|
|
1836
|
+
states.uniq!
|
|
1837
|
+
return text if states.empty?
|
|
1838
|
+
|
|
1839
|
+
params = m[1].split(",").map do |p|
|
|
1840
|
+
pm = p.strip.match(/\A(.*?)(\w+)\z/) or raise "mkkernel: #{fn} parameter #{p}"
|
|
1841
|
+
[pm[1].strip, pm[2]]
|
|
1842
|
+
end
|
|
1843
|
+
frame = "#{fn}_frame"
|
|
1844
|
+
|
|
1845
|
+
out = StringIO.new
|
|
1846
|
+
out.print m.pre_match
|
|
1847
|
+
out.puts "/* The object lane calls Ruby for every cell, so a walk can raise part"
|
|
1848
|
+
out.puts " way. Its states live in the caller's frame, and ca_iter_ensure"
|
|
1849
|
+
out.puts " finishes them however the body leaves. */"
|
|
1850
|
+
out.puts "typedef struct {"
|
|
1851
|
+
out.puts " ca_iter_state states[#{states.size}];"
|
|
1852
|
+
params.each { |type, name| out.puts " #{type} #{name};".sub(/\* /, "*") }
|
|
1853
|
+
out.puts "} #{frame};"
|
|
1854
|
+
out.puts
|
|
1855
|
+
out.puts "static VALUE"
|
|
1856
|
+
out.puts "#{fn}_body (VALUE arg)"
|
|
1857
|
+
out.puts "{"
|
|
1858
|
+
out.puts " #{frame} *fr = (#{frame} *) arg;"
|
|
1859
|
+
params.each do |type, name|
|
|
1860
|
+
out.puts " #{type} #{name} = fr->#{name};".sub(/\* /, "*")
|
|
1861
|
+
out.puts " (void) #{name};"
|
|
1862
|
+
end
|
|
1863
|
+
states.each_with_index { |s, i| out.puts "#define #{s} (fr->states[#{i}])" }
|
|
1864
|
+
out.print body.sub(/[ \t]*\z/, "")
|
|
1865
|
+
out.puts unless body.match?(/\n[ \t]*\z/)
|
|
1866
|
+
states.each { |s| out.puts "#undef #{s}" }
|
|
1867
|
+
out.puts "}"
|
|
1868
|
+
out.puts
|
|
1869
|
+
out.puts "static VALUE"
|
|
1870
|
+
out.puts "#{fn} (#{m[1]})"
|
|
1871
|
+
out.puts "{"
|
|
1872
|
+
out.puts " #{frame} fr;"
|
|
1873
|
+
params.each { |_, name| out.puts " fr.#{name} = #{name};" }
|
|
1874
|
+
out.puts " return ca_iter_ensure(#{states.size}, fr.states, #{fn}_body, (VALUE) &fr);"
|
|
1875
|
+
out.puts "}"
|
|
1876
|
+
out.print rest
|
|
1877
|
+
out.string
|
|
1878
|
+
end
|
|
1879
|
+
|
|
1787
1880
|
def self.emit_reduce(io, k)
|
|
1788
1881
|
io.puts
|
|
1789
1882
|
io.puts "/* ===== #{k[:name]}_ki ============================================ */"
|
|
1790
1883
|
k[:source].each do |src|
|
|
1791
|
-
|
|
1884
|
+
emit_native_for(io, k, src) { |o| emit_reduce_native(o, k, src) }
|
|
1792
1885
|
end
|
|
1793
1886
|
emit_reduce_dispatch(io, k)
|
|
1794
1887
|
end
|
|
@@ -1860,6 +1953,11 @@ module MkKernel
|
|
|
1860
1953
|
|
|
1861
1954
|
view_flat = (k[:semantics] == :view_flat)
|
|
1862
1955
|
|
|
1956
|
+
if streamable
|
|
1957
|
+
emit_reduce_streaming(io, k, si, oi, ruby_wrap, acc_var, acc_init,
|
|
1958
|
+
decls, reduce_stmt, finish_expr, src)
|
|
1959
|
+
end
|
|
1960
|
+
|
|
1863
1961
|
io.puts
|
|
1864
1962
|
io.puts <<~C
|
|
1865
1963
|
static VALUE
|
|
@@ -1868,8 +1966,7 @@ module MkKernel
|
|
|
1868
1966
|
C
|
|
1869
1967
|
|
|
1870
1968
|
if streamable
|
|
1871
|
-
|
|
1872
|
-
decls, reduce_stmt, finish_expr, extra_args, src)
|
|
1969
|
+
emit_reduce_streaming_call(io, k, src)
|
|
1873
1970
|
end
|
|
1874
1971
|
|
|
1875
1972
|
# L.1 / L.3 / L.4 (PROPOSAL_REDUCTION_LOOP_INTERCHANGE):
|
|
@@ -1917,12 +2014,22 @@ module MkKernel
|
|
|
1917
2014
|
else
|
|
1918
2015
|
mixed_buf = false
|
|
1919
2016
|
end
|
|
1920
|
-
|
|
2017
|
+
# The object lane is left out: its cell is a Ruby call, so the
|
|
2018
|
+
# interchange's contiguous SIMD loads buy nothing, and each output cell
|
|
2019
|
+
# accumulates in the same order on the generic walk.
|
|
2020
|
+
li_eligible = src != :object &&
|
|
2021
|
+
!k[:array_arg] && !k[:value_arg] &&
|
|
1921
2022
|
buf_count >= 1 && induction_count <= 1 && other_count == 0 &&
|
|
1922
2023
|
!mixed_buf
|
|
1923
2024
|
if li_eligible
|
|
2025
|
+
# The interchange folds every cell of an unmasked source, so it can
|
|
2026
|
+
# answer only when the reduced axis holds at least min_count cells;
|
|
2027
|
+
# otherwise the general walk writes the UNDEF (or fill_value) cells.
|
|
2028
|
+
li_gate = k[:mask_policy] == :min_count
|
|
2029
|
+
io.puts " if ( naxes != 1 || ! ca_too_few_present(ca->dim[slab_axes[0]], min_count, 0) ) {" if li_gate
|
|
1924
2030
|
emit_reduce_loop_interchange(io, k, si, oi, ruby_wrap, src,
|
|
1925
2031
|
acc_var, acc_init, reduce_stmt, finish_expr)
|
|
2032
|
+
io.puts " }" if li_gate
|
|
1926
2033
|
end
|
|
1927
2034
|
|
|
1928
2035
|
if view_flat
|
|
@@ -1932,7 +2039,7 @@ module MkKernel
|
|
|
1932
2039
|
# (slab walks the whole array in view-flat row-major); other
|
|
1933
2040
|
# naxes are rejected at entry.
|
|
1934
2041
|
io.puts " if ( naxes != 1 && naxes != ca->ndim ) {"
|
|
1935
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
2042
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: view_flat semantics requires single-axis or full reduction (got naxes=%d for ndim=%d)", ca_calling_method_name(), (int)naxes, (int)ca->ndim);]
|
|
1936
2043
|
io.puts " }"
|
|
1937
2044
|
end
|
|
1938
2045
|
|
|
@@ -1946,7 +2053,7 @@ module MkKernel
|
|
|
1946
2053
|
slab_axes, naxes, 0);
|
|
1947
2054
|
if ( rc != CA_ITER_OK ) {
|
|
1948
2055
|
rb_raise(rb_eRuntimeError,
|
|
1949
|
-
"
|
|
2056
|
+
"%s: kernel_iterator init failed rc=%d", ca_calling_method_name(), rc);
|
|
1950
2057
|
}
|
|
1951
2058
|
char *p;
|
|
1952
2059
|
boolean8_t *m;
|
|
@@ -1984,7 +2091,7 @@ module MkKernel
|
|
|
1984
2091
|
io.puts " slab_axes, naxes, 0);"
|
|
1985
2092
|
io.puts " if ( rcw != CA_ITER_OK ) {"
|
|
1986
2093
|
io.puts " ca_iter_state_finish(&st);"
|
|
1987
|
-
io.puts %Q[ rb_raise(rb_eRuntimeError, "
|
|
2094
|
+
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: weights kernel_iterator init failed rc=%d", ca_calling_method_name(), rcw);]
|
|
1988
2095
|
io.puts " }"
|
|
1989
2096
|
io.puts " char *p_w;"
|
|
1990
2097
|
end
|
|
@@ -1999,7 +2106,7 @@ module MkKernel
|
|
|
1999
2106
|
io.puts " if ( ! ca_iter_state_next_slab_axes(&st_w, &p_w, NULL) ) {"
|
|
2000
2107
|
io.puts " ca_iter_state_finish(&st);"
|
|
2001
2108
|
io.puts " ca_iter_state_finish(&st_w);"
|
|
2002
|
-
io.puts %Q[ rb_raise(rb_eRuntimeError, "
|
|
2109
|
+
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: weights iter exhausted early (shape invariant violated)", ca_calling_method_name());]
|
|
2003
2110
|
io.puts " }"
|
|
2004
2111
|
else
|
|
2005
2112
|
io.puts " while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {"
|
|
@@ -2035,6 +2142,11 @@ module MkKernel
|
|
|
2035
2142
|
if k[:all_nan_result] == :undef && FLOAT_DTYPES.include?(src)
|
|
2036
2143
|
all_nan_flag = "__anf_all_nan"
|
|
2037
2144
|
io.puts " int #{all_nan_flag} = 0;"
|
|
2145
|
+
elsif k[:all_nan_result] == :undef && src == :object
|
|
2146
|
+
# The object body takes no NaN cell, so a slab of present NaN
|
|
2147
|
+
# cells leaves the position unset.
|
|
2148
|
+
all_nan_flag = "__anf_all_nan"
|
|
2149
|
+
io.puts " int #{all_nan_flag} = ( masked_cnt < st.slab_elements && (#{finish_expr}) < 0 );"
|
|
2038
2150
|
end
|
|
2039
2151
|
emit_all_nan_fixup(io, k, src, si, pairs,
|
|
2040
2152
|
"masked_cnt < st.slab_elements",
|
|
@@ -2042,24 +2154,12 @@ module MkKernel
|
|
|
2042
2154
|
end
|
|
2043
2155
|
trigger = case k[:mask_policy]
|
|
2044
2156
|
when :strict then "masked_cnt > 0"
|
|
2045
|
-
when :all_masked then "masked_cnt
|
|
2157
|
+
when :all_masked then "ca_too_few_present(st.slab_elements - masked_cnt, -1, 1)"
|
|
2046
2158
|
when :min_count
|
|
2047
|
-
#
|
|
2048
|
-
#
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
# ERI.0: identity_on_empty kernels do NOT fire UNDEF for the
|
|
2052
|
-
# default (min_count < 0) zero-contribution case; the trigger
|
|
2053
|
-
# is false there, so the else branch writes finish (= acc =
|
|
2054
|
-
# identity) for empty / all-masked slabs. The explicit
|
|
2055
|
-
# min_count >= 0 opt-in ("require K valid cells") is unchanged.
|
|
2056
|
-
if k[:identity_on_empty]
|
|
2057
|
-
"(min_count < 0 ? 0 " \
|
|
2058
|
-
": st.slab_elements - masked_cnt < min_count)"
|
|
2059
|
-
else
|
|
2060
|
-
"(min_count < 0 ? masked_cnt == st.slab_elements " \
|
|
2061
|
-
": st.slab_elements - masked_cnt < min_count)"
|
|
2062
|
-
end
|
|
2159
|
+
# An identity_on_empty kernel needs no cell (the else
|
|
2160
|
+
# branch writes finish = acc = identity for an empty slab).
|
|
2161
|
+
"ca_too_few_present(st.slab_elements - masked_cnt, min_count, " \
|
|
2162
|
+
"#{k[:identity_on_empty] ? 0 : 1})"
|
|
2063
2163
|
end
|
|
2064
2164
|
trigger = "(#{trigger}) || #{all_nan_flag}" if all_nan_flag
|
|
2065
2165
|
finish_emit = view_flat \
|
|
@@ -2166,7 +2266,7 @@ module MkKernel
|
|
|
2166
2266
|
io.puts " ca_iter_state st;"
|
|
2167
2267
|
io.puts " int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES, slab_axes, naxes, 0);"
|
|
2168
2268
|
io.puts " if ( rc != CA_ITER_OK ) {"
|
|
2169
|
-
io.puts %Q[ rb_raise(rb_eRuntimeError, "
|
|
2269
|
+
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: kernel_iterator init failed rc=%d", ca_calling_method_name(), rc);]
|
|
2170
2270
|
io.puts " }"
|
|
2171
2271
|
io.puts " char *p;"
|
|
2172
2272
|
io.puts " boolean8_t *m;"
|
|
@@ -2215,16 +2315,9 @@ module MkKernel
|
|
|
2215
2315
|
io.puts " sidx[sk] = 0;"
|
|
2216
2316
|
io.puts " }"
|
|
2217
2317
|
io.puts " }"
|
|
2218
|
-
#
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
if counting
|
|
2222
|
-
io.puts " if ( min_count < 0 ? 0"
|
|
2223
|
-
io.puts " : st.slab_elements - masked_cnt < min_count ) {"
|
|
2224
|
-
else
|
|
2225
|
-
io.puts " if ( min_count < 0 ? masked_cnt == st.slab_elements"
|
|
2226
|
-
io.puts " : st.slab_elements - masked_cnt < min_count ) {"
|
|
2227
|
-
end
|
|
2318
|
+
# A count over nothing is 0, not UNDEF: it needs no cell.
|
|
2319
|
+
io.puts " if ( ca_too_few_present(st.slab_elements - masked_cnt, min_count, " \
|
|
2320
|
+
"#{counting ? 0 : 1}) ) {"
|
|
2228
2321
|
io.puts " if ( ! op_mask ) {"
|
|
2229
2322
|
io.puts " ca_create_mask(co);"
|
|
2230
2323
|
io.puts " op_mask = (boolean8_t *) co->mask->ptr;"
|
|
@@ -2277,10 +2370,8 @@ module MkKernel
|
|
|
2277
2370
|
# Layout per slab:
|
|
2278
2371
|
# Pass 1: sum = Σ x_i via CA_SLAB_REDUCE_T_PLUS_EX (SIMD :plus)
|
|
2279
2372
|
# masked_cnt is populated as a side effect.
|
|
2280
|
-
# mask trigger:
|
|
2281
|
-
#
|
|
2282
|
-
# :n_minus_1 divisor) if n_valid < 2.
|
|
2283
|
-
# min_count >= 0 -> UNDEF iff n_valid < min_count.
|
|
2373
|
+
# mask trigger: ca_too_few_present(n_valid, min_count, 1 or 2), 2 for
|
|
2374
|
+
# the :n_minus_1 divisor.
|
|
2284
2375
|
# Pass 2: M2 = Σ (x_i - mean)² via the same macro with a centred
|
|
2285
2376
|
# REDUCE stmt.
|
|
2286
2377
|
# Finish: variance = M2 / DIVISOR (n or n-1); stddev variant applies
|
|
@@ -2387,9 +2478,9 @@ module MkKernel
|
|
|
2387
2478
|
finish_expr = if k[:output_transform] == :sqrt_clamp
|
|
2388
2479
|
if src == :object
|
|
2389
2480
|
# Object stddev outputs f64 (BigMath.sqrt is user's job).
|
|
2390
|
-
"
|
|
2481
|
+
"ca_sqrt_clamp(NUM2DBL(#{inner_var}))"
|
|
2391
2482
|
else
|
|
2392
|
-
"
|
|
2483
|
+
"ca_sqrt_clamp(#{inner_var})"
|
|
2393
2484
|
end
|
|
2394
2485
|
else
|
|
2395
2486
|
inner_var
|
|
@@ -2417,13 +2508,9 @@ module MkKernel
|
|
|
2417
2508
|
# min_count sentinel arg is always present (all four kernels use it).
|
|
2418
2509
|
extra_args = ", ca_size_t min_count"
|
|
2419
2510
|
|
|
2420
|
-
#
|
|
2421
|
-
#
|
|
2422
|
-
#
|
|
2423
|
-
# (not UNDEF) for n_valid < required_min. Preserve that: the
|
|
2424
|
-
# default trigger only checks the all-masked case; the sub-required
|
|
2425
|
-
# case falls through to the "return zero" branch below.
|
|
2426
|
-
default_trigger = "masked_cnt == st.slab_elements"
|
|
2511
|
+
# Fewer than required_min valid cells (2 for the n-1 divisor) leave the
|
|
2512
|
+
# statistic undefined, so the cell is UNDEF: the sample variance of one
|
|
2513
|
+
# value is 0/0.
|
|
2427
2514
|
|
|
2428
2515
|
# Reduce-call plumbing. :object keeps the scalar rb_funcall loop
|
|
2429
2516
|
# (CA_SLAB_REDUCE_T_EX). Every other src uses the 8-way accumulator
|
|
@@ -2464,7 +2551,7 @@ module MkKernel
|
|
|
2464
2551
|
slab_axes, naxes, 0);
|
|
2465
2552
|
if ( rc != CA_ITER_OK ) {
|
|
2466
2553
|
rb_raise(rb_eRuntimeError,
|
|
2467
|
-
"
|
|
2554
|
+
"%s: kernel_iterator init failed rc=%d", ca_calling_method_name(), rc);
|
|
2468
2555
|
}
|
|
2469
2556
|
char *p;
|
|
2470
2557
|
boolean8_t *m;
|
|
@@ -2476,7 +2563,7 @@ module MkKernel
|
|
|
2476
2563
|
ca_size_t masked_cnt = 0;
|
|
2477
2564
|
#{p1_reduce_call}
|
|
2478
2565
|
ca_size_t n_valid = st.slab_elements - masked_cnt;
|
|
2479
|
-
int __trigger = (min_count
|
|
2566
|
+
int __trigger = ca_too_few_present(n_valid, min_count, #{required_min});
|
|
2480
2567
|
if ( __trigger ) {
|
|
2481
2568
|
if ( ! op_mask ) {
|
|
2482
2569
|
ca_create_mask(co);
|
|
@@ -2484,11 +2571,6 @@ module MkKernel
|
|
|
2484
2571
|
}
|
|
2485
2572
|
op_mask[out_i] = 1;
|
|
2486
2573
|
op[out_i++] = (#{out_c}) #{out_zero_typed};
|
|
2487
|
-
} else if ( n_valid < #{required_min} ) {
|
|
2488
|
-
/* Legacy degenerate case (e.g. sample variance with 1 unmasked
|
|
2489
|
-
cell): return zero rather than UNDEF, matching pre-migration
|
|
2490
|
-
behaviour of `cnt > 1 ? formula : 0`. */
|
|
2491
|
-
op[out_i++] = (#{out_c}) #{out_zero_typed};
|
|
2492
2574
|
} else {
|
|
2493
2575
|
#{mean_setup}
|
|
2494
2576
|
/* Pass 2: M2 = Σ (x - mean)² */
|
|
@@ -2660,8 +2742,9 @@ module MkKernel
|
|
|
2660
2742
|
exposes ca->ptr as that buffer at O(1) cost (no materialise). */
|
|
2661
2743
|
if ( naxes == 1
|
|
2662
2744
|
&& slab_axes[0] != (int8_t)(ca->ndim - 1)
|
|
2663
|
-
&& ca
|
|
2664
|
-
&& ca_attach_is_alias(ca)
|
|
2745
|
+
&& ! ca_has_mask(ca)
|
|
2746
|
+
&& ca_attach_is_alias(ca)
|
|
2747
|
+
&& ca_fast_paths_enabled() ) {
|
|
2665
2748
|
int8_t __li_ax = slab_axes[0];
|
|
2666
2749
|
ca_size_t __li_M = ca->dim[__li_ax];
|
|
2667
2750
|
ca_size_t __li_INNER = 1;
|
|
@@ -2675,7 +2758,7 @@ module MkKernel
|
|
|
2675
2758
|
CArray *co;
|
|
2676
2759
|
GetCArray(vout, co);
|
|
2677
2760
|
#{oi[:c]} *op = (#{oi[:c]} *) co->ptr;
|
|
2678
|
-
ca_attach(ca);
|
|
2761
|
+
ca_attach(ca); /* window: numeric lanes only, nothing raises inside; O(1) alias attach */
|
|
2679
2762
|
const #{si[:c]} *p_root = (const #{si[:c]} *) ca->ptr;
|
|
2680
2763
|
|
|
2681
2764
|
/* Per-buffer stack accumulators (one per :plus state, sized
|
|
@@ -2709,9 +2792,10 @@ module MkKernel
|
|
|
2709
2792
|
if ( naxes == 1
|
|
2710
2793
|
&& slab_axes[0] >= 1
|
|
2711
2794
|
&& slab_axes[0] != (int8_t)(ca->ndim - 1)
|
|
2712
|
-
&& ca
|
|
2795
|
+
&& ! ca_has_mask(ca)
|
|
2713
2796
|
&& ca_func[ca->obj_type].attach == ca_stack_func.attach
|
|
2714
|
-
&& ((CAStack *) ca)->k_axis == 0
|
|
2797
|
+
&& ((CAStack *) ca)->k_axis == 0
|
|
2798
|
+
&& ca_fast_paths_enabled() ) {
|
|
2715
2799
|
CAStack *__li_st = (CAStack *) ca;
|
|
2716
2800
|
int8_t __li_ax = slab_axes[0];
|
|
2717
2801
|
ca_size_t __li_M = ca->dim[__li_ax];
|
|
@@ -2724,7 +2808,7 @@ module MkKernel
|
|
|
2724
2808
|
core reads parent->ptr without mask awareness). */
|
|
2725
2809
|
int __li_ok = 1;
|
|
2726
2810
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2727
|
-
if ( __li_st->parents[__kk]
|
|
2811
|
+
if ( ca_has_mask(__li_st->parents[__kk]) ) { __li_ok = 0; break; }
|
|
2728
2812
|
}
|
|
2729
2813
|
|
|
2730
2814
|
if ( __li_ok && __li_INNER >= 64 && __li_M * __li_INNER >= 1024 ) {
|
|
@@ -2739,7 +2823,7 @@ module MkKernel
|
|
|
2739
2823
|
ca_size_t __li_parent_out = __li_OUTER * __li_INNER;
|
|
2740
2824
|
const ca_size_t __li_TILE = 512;
|
|
2741
2825
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2742
|
-
ca_attach(__li_st->parents[__kk]);
|
|
2826
|
+
ca_attach(__li_st->parents[__kk]); /* window: numeric lanes only, nothing raises inside */
|
|
2743
2827
|
const #{si[:c]} *p_root = (const #{si[:c]} *) __li_st->parents[__kk]->ptr;
|
|
2744
2828
|
#{oi[:c]} *__li_op_k = op + (ca_size_t) __kk * __li_parent_out;
|
|
2745
2829
|
#{core.call("p_root", "__li_op_k", "__li_OUTER")}
|
|
@@ -2772,11 +2856,12 @@ module MkKernel
|
|
|
2772
2856
|
if ( naxes == 1
|
|
2773
2857
|
&& slab_axes[0] >= 1
|
|
2774
2858
|
&& slab_axes[0] != (int8_t)(ca->ndim - 1)
|
|
2775
|
-
&& ca
|
|
2859
|
+
&& ! ca_has_mask(ca)
|
|
2776
2860
|
&& ca_func[ca->obj_type].attach == ca_stack_func.attach
|
|
2777
2861
|
&& ((CAStack *) ca)->k_axis > 0
|
|
2778
2862
|
&& ( slab_axes[0] > ((CAStack *) ca)->k_axis
|
|
2779
|
-
|| slab_axes[0] + 1 == ((CAStack *) ca)->k_axis )
|
|
2863
|
+
|| slab_axes[0] + 1 == ((CAStack *) ca)->k_axis )
|
|
2864
|
+
&& ca_fast_paths_enabled() ) {
|
|
2780
2865
|
CAStack *__li_st = (CAStack *) ca;
|
|
2781
2866
|
int8_t __li_ax = slab_axes[0];
|
|
2782
2867
|
int8_t __li_kax = (int8_t) __li_st->k_axis;
|
|
@@ -2792,7 +2877,7 @@ module MkKernel
|
|
|
2792
2877
|
/* Bail to the generic path if any parent carries a mask. */
|
|
2793
2878
|
int __li_ok = 1;
|
|
2794
2879
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2795
|
-
if ( __li_st->parents[__kk]
|
|
2880
|
+
if ( ca_has_mask(__li_st->parents[__kk]) ) { __li_ok = 0; break; }
|
|
2796
2881
|
}
|
|
2797
2882
|
|
|
2798
2883
|
if ( __li_ok && __li_INNER >= 64 && __li_M * __li_INNER >= 1024 ) {
|
|
@@ -2833,7 +2918,7 @@ module MkKernel
|
|
|
2833
2918
|
ca_size_t __li_plane_elems = __li_M * __li_INNER;
|
|
2834
2919
|
const ca_size_t __li_TILE = 512;
|
|
2835
2920
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2836
|
-
ca_attach(__li_st->parents[__kk]);
|
|
2921
|
+
ca_attach(__li_st->parents[__kk]); /* window: numeric lanes only, nothing raises inside */
|
|
2837
2922
|
const #{si[:c]} *p_root = (const #{si[:c]} *) __li_st->parents[__kk]->ptr;
|
|
2838
2923
|
ca_size_t __li_k_base = (ca_size_t) __kk * __li_k_stride;
|
|
2839
2924
|
|
|
@@ -2915,10 +3000,11 @@ module MkKernel
|
|
|
2915
3000
|
if ( naxes == 1
|
|
2916
3001
|
&& slab_axes[0] >= 1
|
|
2917
3002
|
&& slab_axes[0] != (int8_t)(ca->ndim - 1)
|
|
2918
|
-
&& ca
|
|
3003
|
+
&& ! ca_has_mask(ca)
|
|
2919
3004
|
&& ca_func[ca->obj_type].attach == ca_stack_func.attach
|
|
2920
3005
|
&& ((CAStack *) ca)->k_axis > 0
|
|
2921
|
-
&& slab_axes[0] + 1 < ((CAStack *) ca)->k_axis
|
|
3006
|
+
&& slab_axes[0] + 1 < ((CAStack *) ca)->k_axis
|
|
3007
|
+
&& ca_fast_paths_enabled() ) {
|
|
2922
3008
|
CAStack *__li_st = (CAStack *) ca;
|
|
2923
3009
|
int8_t __li_ax = slab_axes[0];
|
|
2924
3010
|
int8_t __li_kax = (int8_t) __li_st->k_axis;
|
|
@@ -2936,7 +3022,7 @@ module MkKernel
|
|
|
2936
3022
|
/* Bail to the generic path if any parent carries a mask. */
|
|
2937
3023
|
int __li_ok = 1;
|
|
2938
3024
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2939
|
-
if ( __li_st->parents[__kk]
|
|
3025
|
+
if ( ca_has_mask(__li_st->parents[__kk]) ) { __li_ok = 0; break; }
|
|
2940
3026
|
}
|
|
2941
3027
|
|
|
2942
3028
|
if ( __li_ok && __li_eff_INNER >= 32 && __li_M * __li_eff_INNER >= 1024 ) {
|
|
@@ -2990,7 +3076,7 @@ module MkKernel
|
|
|
2990
3076
|
|
|
2991
3077
|
const ca_size_t __li_TILE = 512;
|
|
2992
3078
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2993
|
-
ca_attach(__li_st->parents[__kk]);
|
|
3079
|
+
ca_attach(__li_st->parents[__kk]); /* window: numeric lanes only, nothing raises inside */
|
|
2994
3080
|
const #{si[:c]} *p_root = (const #{si[:c]} *) __li_st->parents[__kk]->ptr;
|
|
2995
3081
|
ca_size_t __li_k_base = (ca_size_t) __kk * __li_k_stride;
|
|
2996
3082
|
|
|
@@ -3097,7 +3183,7 @@ module MkKernel
|
|
|
3097
3183
|
slab_axes, naxes, 0);
|
|
3098
3184
|
if ( rc != CA_ITER_OK ) {
|
|
3099
3185
|
rb_raise(rb_eRuntimeError,
|
|
3100
|
-
"
|
|
3186
|
+
"%s: kernel_iterator init failed rc=%d", ca_calling_method_name(), rc);
|
|
3101
3187
|
}
|
|
3102
3188
|
char *p;
|
|
3103
3189
|
boolean8_t *m;
|
|
@@ -3116,10 +3202,7 @@ module MkKernel
|
|
|
3116
3202
|
emit_all_nan_fixup(io, k, src, si, limit_init_state_vars(k, si, oi, src),
|
|
3117
3203
|
"masked_cnt < st.slab_elements", indent: " ")
|
|
3118
3204
|
end
|
|
3119
|
-
|
|
3120
|
-
# when min_count < 0, otherwise need at least min_count valid cells.
|
|
3121
|
-
trigger = "(min_count < 0 ? masked_cnt == st.slab_elements " \
|
|
3122
|
-
": st.slab_elements - masked_cnt < min_count)"
|
|
3205
|
+
trigger = "ca_too_few_present(st.slab_elements - masked_cnt, min_count, 1)"
|
|
3123
3206
|
io.puts " if ( #{trigger} ) {"
|
|
3124
3207
|
io.puts " if ( ! op_mask_a ) {"
|
|
3125
3208
|
io.puts " ca_create_mask(co_a);"
|
|
@@ -3153,10 +3236,11 @@ module MkKernel
|
|
|
3153
3236
|
# pair of [1,...,1] entities instead.
|
|
3154
3237
|
io.puts " if ( naxes == ca->ndim && !keep_axis ) {"
|
|
3155
3238
|
if min_count
|
|
3156
|
-
# Full reduction: if the sole output cell is masked,
|
|
3157
|
-
#
|
|
3158
|
-
#
|
|
3159
|
-
|
|
3239
|
+
# Full reduction: if the sole output cell is masked, both answers are
|
|
3240
|
+
# UNDEF, still as a pair -- the shape of the answer does not depend on
|
|
3241
|
+
# the data. (Both op_mask_a and op_mask_b share the same bit since
|
|
3242
|
+
# both reductions consume the same slab, so checking _a is sufficient.)
|
|
3243
|
+
io.puts " if ( op_mask_a && op_mask_a[0] ) return rb_assoc_new(CA_UNDEF, CA_UNDEF);"
|
|
3160
3244
|
end
|
|
3161
3245
|
io.puts " return rb_assoc_new(#{ruby_wrap}(op_a[0]), #{ruby_wrap}(op_b[0]));"
|
|
3162
3246
|
io.puts " }"
|
|
@@ -3164,42 +3248,40 @@ module MkKernel
|
|
|
3164
3248
|
io.puts " }"
|
|
3165
3249
|
end
|
|
3166
3250
|
|
|
3167
|
-
#
|
|
3168
|
-
# early-return branch at the top of name_ki_native_<src> that pulls
|
|
3169
|
-
# the lazy view in fixed-size chunks via ca_xfer_stride instead of
|
|
3170
|
-
# full-materialising via SRC_ATTACH. Falls through to the existing
|
|
3171
|
-
# path when conditions aren't met.
|
|
3172
|
-
#
|
|
3173
|
-
# Strategy: chunk along the outermost axis. inner = prod(dim[1..]);
|
|
3174
|
-
# rows_per_chunk = max(1, target_elems / inner). 1-D is the natural
|
|
3175
|
-
# sub-case (inner == 1, rows = target_elems).
|
|
3176
|
-
#
|
|
3177
|
-
# Memory peak: O(max(target_elems, inner) * sizeof T) instead of O(N).
|
|
3178
|
-
# For N-D, this drops the outermost dim from the materialised buffer,
|
|
3179
|
-
# which is typically the dominant factor (e.g. 1000x10000 f64 reduction:
|
|
3180
|
-
# 80MB -> 80KB). Wall-clock perf at large N matches or beats
|
|
3181
|
-
# SRC_ATTACH because each chunk fits in L1d (or L2 for wide last dims).
|
|
3182
|
-
#
|
|
3183
|
-
# Restrictions handled by short-circuit checks (= falls through to
|
|
3184
|
-
# SRC_ATTACH path):
|
|
3185
|
-
# - axis kwarg (= partial reduction): naxes != ca->ndim
|
|
3186
|
-
# (structural blocker: CABinOp/CABinCmp xfer_stride rebuilds
|
|
3187
|
-
# right_strides from counts, so non-rectangular slab pulls
|
|
3188
|
-
# produce wrong values for the right operand. See proposal
|
|
3189
|
-
# §future-work / partial streaming blocker.)
|
|
3190
|
-
# - non-lazy source: !ca_is_lazy_view(ca)
|
|
3191
|
-
# - mask present: ca_has_mask(ca)
|
|
3251
|
+
# The walk of the streaming reduce (see emit_reduce_streaming_call).
|
|
3192
3252
|
def self.emit_reduce_streaming(io, k, si, oi, ruby_wrap, acc_var,
|
|
3193
3253
|
acc_init, decls, reduce_stmt,
|
|
3194
|
-
finish_expr,
|
|
3254
|
+
finish_expr, src)
|
|
3195
3255
|
name = k[:name]
|
|
3196
|
-
min_count = (k[:mask_policy] == :min_count)
|
|
3197
3256
|
has_mp = !k[:mask_policy].nil?
|
|
3257
|
+
fn = "#{name}_ki_stream_#{src}"
|
|
3198
3258
|
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3259
|
+
# The walk runs under ca_lazy_arena_protect: the transfer from the lazy
|
|
3260
|
+
# source and the object lane's fold can both raise, and the arena must
|
|
3261
|
+
# be left however the walk ends. So it is a function of its own, with
|
|
3262
|
+
# the native's arguments carried in a context whose first member is
|
|
3263
|
+
# the chunk (released by ca_lazy_arena_release_held).
|
|
3264
|
+
io.puts
|
|
3265
|
+
io.puts "typedef struct {"
|
|
3266
|
+
io.puts " void *chunk;"
|
|
3267
|
+
io.puts " CArray *ca;"
|
|
3268
|
+
io.puts " #{si[:c]} value_arg;" if k[:value_arg]
|
|
3269
|
+
io.puts " ca_size_t min_count;" if k[:mask_policy] == :min_count
|
|
3270
|
+
io.puts "} #{fn}_t;"
|
|
3271
|
+
io.puts
|
|
3272
|
+
io.puts "static VALUE"
|
|
3273
|
+
io.puts "#{fn} (VALUE arg)"
|
|
3274
|
+
io.puts "{"
|
|
3275
|
+
io.puts " #{fn}_t *__ctx = (#{fn}_t *) arg;"
|
|
3276
|
+
io.puts " CArray *ca = __ctx->ca;"
|
|
3277
|
+
if k[:value_arg]
|
|
3278
|
+
io.puts " #{si[:c]} value_arg = __ctx->value_arg;"
|
|
3279
|
+
io.puts " (void) value_arg;"
|
|
3280
|
+
end
|
|
3281
|
+
if k[:mask_policy] == :min_count
|
|
3282
|
+
io.puts " ca_size_t min_count = __ctx->min_count;"
|
|
3283
|
+
io.puts " (void) min_count;"
|
|
3284
|
+
end
|
|
3203
3285
|
io.puts " ca_size_t __inner = 1;"
|
|
3204
3286
|
io.puts " int __k;"
|
|
3205
3287
|
io.puts " for ( __k = 1; __k < ca->ndim; __k++ ) __inner *= ca->dim[__k];"
|
|
@@ -3237,7 +3319,6 @@ module MkKernel
|
|
|
3237
3319
|
io.puts " ca_size_t masked_cnt = 0; /* unused on streaming (no mask) */"
|
|
3238
3320
|
io.puts " (void) masked_cnt;"
|
|
3239
3321
|
end
|
|
3240
|
-
io.puts " ca_lazy_arena_enter();"
|
|
3241
3322
|
if si[:c] == "VALUE"
|
|
3242
3323
|
# Object lane: the chunk holds VALUEs pulled from the source, and
|
|
3243
3324
|
# the fold below calls rb_funcall per cell. A collection there
|
|
@@ -3247,6 +3328,7 @@ module MkKernel
|
|
|
3247
3328
|
else
|
|
3248
3329
|
io.puts " #{si[:c]} *__chunk = (#{si[:c]} *) ca_lazy_arena_acquire(__chunk_elems * sizeof(#{si[:c]}));"
|
|
3249
3330
|
end
|
|
3331
|
+
io.puts " __ctx->chunk = __chunk;"
|
|
3250
3332
|
io.puts " while ( __outer_off < __outer ) {"
|
|
3251
3333
|
io.puts " ca_size_t __r = (__outer - __outer_off < __rows) ? (__outer - __outer_off) : __rows;"
|
|
3252
3334
|
io.puts " ca_size_t __n = __r * __inner;"
|
|
@@ -3261,21 +3343,84 @@ module MkKernel
|
|
|
3261
3343
|
io.puts " }"
|
|
3262
3344
|
io.puts " __outer_off += __r;"
|
|
3263
3345
|
io.puts " }"
|
|
3264
|
-
io.puts " ca_lazy_arena_release(__chunk);"
|
|
3265
|
-
io.puts " ca_lazy_arena_exit();"
|
|
3266
3346
|
if all_nan_stream
|
|
3267
3347
|
io.puts " if ( ! __anf_seen && ca->elements > 0 ) #{acc_var} = (#{si[:c]}) NAN;"
|
|
3268
3348
|
end
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
#
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3349
|
+
# Streaming has no mask source, so masked_cnt is 0 and none of the
|
|
3350
|
+
# mask-policy triggers fire: the answer is the finish expression.
|
|
3351
|
+
io.puts " return #{ruby_wrap}((#{oi[:c]}) (#{finish_expr}));"
|
|
3352
|
+
io.puts "}"
|
|
3353
|
+
end
|
|
3354
|
+
|
|
3355
|
+
# The streaming branch at the top of name_ki_native_<src>: a flat
|
|
3356
|
+
# reduction of an unmasked lazy view pulls the source in fixed-size
|
|
3357
|
+
# chunks via ca_xfer_stride instead of materialising it whole. Falls
|
|
3358
|
+
# through to the existing path when the conditions are not met.
|
|
3359
|
+
#
|
|
3360
|
+
# Strategy: chunk along the outermost axis. inner = prod(dim[1..]);
|
|
3361
|
+
# rows_per_chunk = max(1, target_elems / inner). 1-D is the natural
|
|
3362
|
+
# sub-case (inner == 1, rows = target_elems).
|
|
3363
|
+
#
|
|
3364
|
+
# Memory peak: O(max(target_elems, inner) * sizeof T) instead of O(N).
|
|
3365
|
+
# For N-D, this drops the outermost dim from the materialised buffer,
|
|
3366
|
+
# which is typically the dominant factor (e.g. 1000x10000 f64 reduction:
|
|
3367
|
+
# 80MB -> 80KB). Wall-clock perf at large N matches or beats
|
|
3368
|
+
# SRC_ATTACH because each chunk fits in L1d (or L2 for wide last dims).
|
|
3369
|
+
#
|
|
3370
|
+
# Restrictions handled by short-circuit checks (= falls through to
|
|
3371
|
+
# SRC_ATTACH path):
|
|
3372
|
+
# - axis kwarg (= partial reduction): naxes != ca->ndim
|
|
3373
|
+
# (structural blocker: CABinOp/CABinCmp xfer_stride rebuilds
|
|
3374
|
+
# right_strides from counts, so non-rectangular slab pulls
|
|
3375
|
+
# produce wrong values for the right operand.)
|
|
3376
|
+
# - non-lazy source: !ca_is_lazy_view(ca)
|
|
3377
|
+
# - mask present: ca_has_mask(ca)
|
|
3378
|
+
# - nothing to fold, or fewer cells than min_count asks for: the walk
|
|
3379
|
+
# has no mask, so it answers the finish expression, which is right
|
|
3380
|
+
# only when the mask policy cannot fire. An empty source and a
|
|
3381
|
+
# min_count above the cell count are the two ways it can without a
|
|
3382
|
+
# mask, and the full path answers them.
|
|
3383
|
+
def self.emit_reduce_streaming_call(io, k, src)
|
|
3384
|
+
fn = "#{k[:name]}_ki_stream_#{src}"
|
|
3385
|
+
io.puts " if ( naxes == ca->ndim && ca->ndim >= 1 && !keep_axis &&"
|
|
3386
|
+
io.puts " ca->elements > 0 &&"
|
|
3387
|
+
io.puts " ! ca_too_few_present(ca->elements, min_count, 0) &&" if k[:mask_policy] == :min_count
|
|
3388
|
+
io.puts " ca_is_lazy_view(ca) && ! ca_has_mask(ca) &&"
|
|
3389
|
+
io.puts " ca_fast_paths_enabled() ) {"
|
|
3390
|
+
io.puts " #{fn}_t __ctx;"
|
|
3391
|
+
io.puts " __ctx.chunk = NULL;"
|
|
3392
|
+
io.puts " __ctx.ca = ca;"
|
|
3393
|
+
io.puts " __ctx.value_arg = value_arg;" if k[:value_arg]
|
|
3394
|
+
io.puts " __ctx.min_count = min_count;" if k[:mask_policy] == :min_count
|
|
3395
|
+
io.puts " return ca_lazy_arena_protect(#{fn}, ca_lazy_arena_release_held,"
|
|
3396
|
+
io.puts " (VALUE) &__ctx);"
|
|
3397
|
+
io.puts " }"
|
|
3398
|
+
end
|
|
3399
|
+
|
|
3400
|
+
# Where the source is a lazy expression the kernel is about to compute
|
|
3401
|
+
# whole, a registered expression evaluator may compute it instead; the
|
|
3402
|
+
# dispatcher then goes on with that array. `unless_streaming` is the C
|
|
3403
|
+
# condition under which the reduction streams the expression in chunks
|
|
3404
|
+
# rather than making it whole -- there it is left to stream, since asking
|
|
3405
|
+
# would make it whole and take that much more memory.
|
|
3406
|
+
def self.emit_lazy_evaluated(io, unless_streaming: nil)
|
|
3407
|
+
cond = "ca_is_lazy_view(src)"
|
|
3408
|
+
cond += " && ! ( #{unless_streaming} )" if unless_streaming
|
|
3409
|
+
io.puts " if ( #{cond} ) {"
|
|
3410
|
+
io.puts " volatile VALUE evaluated = ca_lazy_evaluated(self);"
|
|
3411
|
+
io.puts " if ( ! NIL_P(evaluated) ) {"
|
|
3412
|
+
io.puts " self = evaluated;"
|
|
3413
|
+
io.puts " GetCArray(self, src);"
|
|
3278
3414
|
io.puts " }"
|
|
3415
|
+
io.puts " }"
|
|
3416
|
+
end
|
|
3417
|
+
|
|
3418
|
+
# Whether a reduction's native walk has the streaming branch
|
|
3419
|
+
# (emit_reduce_streaming_call): the conditions emit_reduce_native checks
|
|
3420
|
+
# before it emits one.
|
|
3421
|
+
def self.reduce_streams?(k)
|
|
3422
|
+
k[:outputs] != 2 && k[:algorithm] != :two_pass_centred &&
|
|
3423
|
+
!k[:array_arg] && !k[:reduce].to_s.include?("idx")
|
|
3279
3424
|
end
|
|
3280
3425
|
|
|
3281
3426
|
def self.emit_reduce_dispatch(io, k)
|
|
@@ -3303,12 +3448,13 @@ module MkKernel
|
|
|
3303
3448
|
io.puts " GetCArray(self, src);"
|
|
3304
3449
|
|
|
3305
3450
|
if k[:object_escape]
|
|
3306
|
-
# CA_OBJECT escape:
|
|
3307
|
-
#
|
|
3308
|
-
#
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
io.puts "
|
|
3451
|
+
# CA_OBJECT escape: the Ruby composition helper gets the original
|
|
3452
|
+
# argc/argv (weights + trailing options hash, untouched). The escape
|
|
3453
|
+
# itself comes after the options and axes are checked below, so a
|
|
3454
|
+
# wrong keyword or axis is reported here, in this method's name, as
|
|
3455
|
+
# on the numeric path.
|
|
3456
|
+
io.puts " int escape_argc = argc;"
|
|
3457
|
+
io.puts " VALUE *escape_argv = argv;"
|
|
3312
3458
|
end
|
|
3313
3459
|
|
|
3314
3460
|
if k[:face_gate]
|
|
@@ -3342,7 +3488,18 @@ module MkKernel
|
|
|
3342
3488
|
io.puts " int keep_axis = RTEST(rkeep_axis);"
|
|
3343
3489
|
io.puts " int8_t slab_axes[CA_RANK_MAX];"
|
|
3344
3490
|
io.puts " int8_t naxes = rb_ca_parse_reduce_axes_kw(raxis, src, slab_axes);"
|
|
3491
|
+
if k[:object_escape]
|
|
3492
|
+
io.puts " if ( src->data_type == CA_OBJECT ) {"
|
|
3493
|
+
if min_count
|
|
3494
|
+
io.puts " ca_min_count_arg(rmin_count, NULL);"
|
|
3495
|
+
end
|
|
3496
|
+
io.puts " return rb_funcallv(self, rb_intern(\"#{k[:object_escape]}\"), escape_argc, escape_argv);"
|
|
3497
|
+
io.puts " }"
|
|
3498
|
+
end
|
|
3499
|
+
emit_lazy_evaluated(io, unless_streaming:
|
|
3500
|
+
reduce_streams?(k) ? "naxes == src->ndim && src->ndim >= 1 && ! keep_axis && ! ca_has_mask(src)" : nil)
|
|
3345
3501
|
|
|
3502
|
+
weights_io = nil
|
|
3346
3503
|
if has_aarg
|
|
3347
3504
|
# Weight storage type: :promote materializes weights at the f64
|
|
3348
3505
|
# computation type (so a float weight against an int source is not
|
|
@@ -3357,7 +3514,7 @@ module MkKernel
|
|
|
3357
3514
|
# at dispatch (= simple, uniform kernel path). rev5 strict acceptance
|
|
3358
3515
|
# mirrors PROPOSAL_LINEAR_INTERP_PER_FIBER_MATCHED rev5 in spirit.
|
|
3359
3516
|
io.puts " if ( argc < 1 ) {"
|
|
3360
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
3517
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: missing weights argument (1+ required, got 0)", ca_calling_method_name());]
|
|
3361
3518
|
io.puts " }"
|
|
3362
3519
|
io.puts " volatile VALUE rweights = argv[0];"
|
|
3363
3520
|
io.puts " argc--; argv++;"
|
|
@@ -3370,98 +3527,103 @@ module MkKernel
|
|
|
3370
3527
|
io.puts " rweights = rb_funcall(rweights, rb_intern(\"[]\"), 1, INT2NUM(0));"
|
|
3371
3528
|
io.puts " }"
|
|
3372
3529
|
io.puts " }"
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3530
|
+
# The weights are attached from here to the end of the call, so they
|
|
3531
|
+
# are made only once every check that can raise has passed; see the
|
|
3532
|
+
# data type check below.
|
|
3533
|
+
wio = StringIO.new
|
|
3534
|
+
wio.puts " volatile VALUE vcw;"
|
|
3535
|
+
wio.puts " CArray *cw;"
|
|
3536
|
+
wio.puts " if ( ! rb_obj_is_carray(rweights) ) {"
|
|
3537
|
+
wio.puts " /* W-A1: Ruby Numeric scalar -> full-shape constant weight CArray */"
|
|
3538
|
+
wio.puts " vcw = rb_carray_new(#{waca}, src->ndim, src->dim, 0, NULL);"
|
|
3539
|
+
wio.puts " GetCArray(vcw, cw); /* a new entity: its ptr needs no attach */"
|
|
3540
|
+
wio.puts " rb_ca_obj2ptr(vcw, rweights, cw->ptr); /* fill first cell */"
|
|
3541
|
+
wio.puts " /* broadcast first cell to all elements (= constant fill) */"
|
|
3542
|
+
wio.puts " {"
|
|
3543
|
+
wio.puts " char *p0 = cw->ptr;"
|
|
3544
|
+
wio.puts " for ( ca_size_t i = 1; i < cw->elements; i++ ) {"
|
|
3545
|
+
wio.puts " memcpy(cw->ptr + i * cw->bytes, p0, cw->bytes);"
|
|
3546
|
+
wio.puts " }"
|
|
3547
|
+
wio.puts " }"
|
|
3548
|
+
wio.puts " }"
|
|
3549
|
+
wio.puts " else {"
|
|
3550
|
+
wio.puts " /* CArray val: coerce to the weight storage type (f64 for :promote, so a"
|
|
3551
|
+
wio.puts " float weight against an int source is not truncated) */"
|
|
3552
|
+
wio.puts " vcw = rb_ca_wrap_readonly(rweights, INT2NUM(#{waca}));"
|
|
3553
|
+
wio.puts " CArray *cv;"
|
|
3554
|
+
wio.puts " GetCArray(vcw, cv);"
|
|
3555
|
+
wio.puts ""
|
|
3556
|
+
wio.puts " /* rev5 form detection: A3 (same ndim) -> A2 (1-D axis-broadcast) -> raise */"
|
|
3557
|
+
wio.puts " if ( cv->ndim == src->ndim ) {"
|
|
3558
|
+
wio.puts " /* W-A3 commit-or-raise: shape strict match */"
|
|
3559
|
+
wio.puts " for ( int8_t i = 0; i < src->ndim; i++ ) {"
|
|
3560
|
+
wio.puts " if ( cv->dim[i] != src->dim[i] ) {"
|
|
3561
|
+
wio.puts %Q[ rb_raise(rb_eArgError, "%s: weights shape mismatch (dim[%d]=%ld != self.dim[%d]=%ld; expected a scalar, a 1-D array along the reduced axis, or self.shape)", ca_calling_method_name(), (int)i, (long)cv->dim[i], (int)i, (long)src->dim[i]);]
|
|
3562
|
+
wio.puts " }"
|
|
3563
|
+
wio.puts " }"
|
|
3564
|
+
wio.puts " /* W-A3 path: use cv directly (= existing) */"
|
|
3565
|
+
wio.puts " cw = cv;"
|
|
3566
|
+
wio.puts " }"
|
|
3567
|
+
wio.puts " else if ( naxes == 1 && cv->ndim == 1 && cv->dim[0] == src->dim[slab_axes[0]] ) {"
|
|
3568
|
+
wio.puts " /* W-A2: 1-D axis-broadcast -> materialize to full shape */"
|
|
3569
|
+
wio.puts " ca_attach(cv); /* window: nothing raises inside */"
|
|
3570
|
+
wio.puts " vcw = rb_carray_new(#{waca}, src->ndim, src->dim, 0, NULL);"
|
|
3571
|
+
wio.puts " GetCArray(vcw, cw);"
|
|
3572
|
+
wio.puts " /* broadcast cv (1-D, length src->dim[slab_axes[0]]) along reduce axis */"
|
|
3573
|
+
wio.puts " {"
|
|
3574
|
+
wio.puts " int8_t baxis = slab_axes[0];"
|
|
3575
|
+
wio.puts " ca_size_t inner_stride = 1;"
|
|
3576
|
+
wio.puts " for ( int8_t ii = baxis + 1; ii < src->ndim; ii++ ) inner_stride *= src->dim[ii];"
|
|
3577
|
+
wio.puts " ca_size_t axis_n = src->dim[baxis];"
|
|
3578
|
+
wio.puts " ca_size_t outer = src->elements / (axis_n * inner_stride);"
|
|
3579
|
+
wio.puts " for ( ca_size_t o = 0; o < outer; o++ ) {"
|
|
3580
|
+
wio.puts " for ( ca_size_t a = 0; a < axis_n; a++ ) {"
|
|
3581
|
+
wio.puts " for ( ca_size_t in = 0; in < inner_stride; in++ ) {"
|
|
3582
|
+
wio.puts " ca_size_t flat = (o * axis_n + a) * inner_stride + in;"
|
|
3583
|
+
wio.puts " memcpy(cw->ptr + flat * cw->bytes, cv->ptr + a * cv->bytes, cw->bytes);"
|
|
3584
|
+
wio.puts " }"
|
|
3585
|
+
wio.puts " }"
|
|
3586
|
+
wio.puts " }"
|
|
3587
|
+
wio.puts " }"
|
|
3588
|
+
wio.puts " /* Propagate cv mask via W-A3 overlay path below (= mask materialize) */"
|
|
3589
|
+
wio.puts " if ( ca_has_mask(cv) ) {"
|
|
3590
|
+
wio.puts " /* Allocate cw mask + broadcast cv mask along axis */"
|
|
3591
|
+
wio.puts " ca_create_mask(cw);"
|
|
3592
|
+
wio.puts " boolean8_t *cw_m = (boolean8_t *)cw->mask->ptr;"
|
|
3593
|
+
wio.puts " boolean8_t *cv_m = (boolean8_t *)cv->mask->ptr;"
|
|
3594
|
+
wio.puts " int8_t baxis = slab_axes[0];"
|
|
3595
|
+
wio.puts " ca_size_t inner_stride = 1;"
|
|
3596
|
+
wio.puts " for ( int8_t ii = baxis + 1; ii < src->ndim; ii++ ) inner_stride *= src->dim[ii];"
|
|
3597
|
+
wio.puts " ca_size_t axis_n = src->dim[baxis];"
|
|
3598
|
+
wio.puts " ca_size_t outer = src->elements / (axis_n * inner_stride);"
|
|
3599
|
+
wio.puts " for ( ca_size_t o = 0; o < outer; o++ ) {"
|
|
3600
|
+
wio.puts " for ( ca_size_t a = 0; a < axis_n; a++ ) {"
|
|
3601
|
+
wio.puts " for ( ca_size_t in = 0; in < inner_stride; in++ ) {"
|
|
3602
|
+
wio.puts " ca_size_t flat = (o * axis_n + a) * inner_stride + in;"
|
|
3603
|
+
wio.puts " cw_m[flat] = cv_m[a];"
|
|
3604
|
+
wio.puts " }"
|
|
3605
|
+
wio.puts " }"
|
|
3606
|
+
wio.puts " }"
|
|
3607
|
+
wio.puts " }"
|
|
3608
|
+
wio.puts " ca_detach(cv);"
|
|
3609
|
+
wio.puts " }"
|
|
3610
|
+
wio.puts " else {"
|
|
3611
|
+
wio.puts %Q[ rb_raise(rb_eArgError, "%s: w shape not accepted (val.ndim=%d, val.dim[0]=%ld; expected scalar / [M=%ld] / self.shape)", ca_calling_method_name(), (int)cv->ndim, (long)(cv->ndim >= 1 ? cv->dim[0] : 0), (long)(naxes == 1 ? src->dim[slab_axes[0]] : -1));]
|
|
3612
|
+
wio.puts " }"
|
|
3613
|
+
wio.puts " }"
|
|
3456
3614
|
# Mask overlay (= legacy W-A3 path; if the materialized cw carries a mask, overlay it)
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3615
|
+
wio.puts " volatile VALUE __vsrc_keep = self; /* GC guard */ (void) __vsrc_keep;"
|
|
3616
|
+
wio.puts " if ( ca_has_mask(cw) ) {"
|
|
3617
|
+
wio.puts " CArray *src_copy = ca_copy(src);"
|
|
3618
|
+
wio.puts " VALUE vsrc_copy = ca_wrap_struct(src_copy);"
|
|
3619
|
+
wio.puts " ca_copy_mask_overlay(src_copy, src_copy->elements, 1, cw);"
|
|
3620
|
+
wio.puts " src = src_copy;"
|
|
3621
|
+
wio.puts " self = vsrc_copy;"
|
|
3622
|
+
wio.puts " }"
|
|
3623
|
+
# Attached last, after the overlay's copy of the source (which can
|
|
3624
|
+
# raise), and detached right after the switch.
|
|
3625
|
+
wio.puts " ca_attach(cw); /* window: nothing raises inside; the kernels are numeric */"
|
|
3626
|
+
weights_io = wio
|
|
3465
3627
|
end
|
|
3466
3628
|
|
|
3467
3629
|
if has_varg
|
|
@@ -3469,7 +3631,7 @@ module MkKernel
|
|
|
3469
3631
|
# runtime value_arg (e.g. `a.count_equal(5, 0)` -- 5 is value_arg,
|
|
3470
3632
|
# 0 is axis). Per-src cast happens inside the switch case below.
|
|
3471
3633
|
io.puts " if ( argc < 1 ) {"
|
|
3472
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
3634
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: missing value argument (1+ required, got 0)", ca_calling_method_name());]
|
|
3473
3635
|
io.puts " }"
|
|
3474
3636
|
io.puts " volatile VALUE rval = argv[0];"
|
|
3475
3637
|
io.puts " argc--; argv++;"
|
|
@@ -3477,16 +3639,16 @@ module MkKernel
|
|
|
3477
3639
|
|
|
3478
3640
|
if min_count
|
|
3479
3641
|
# rmin_count / rfval were scanned up front (PoC scan-merge); here we
|
|
3480
|
-
# only interpret them
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
io.puts "
|
|
3486
|
-
io.puts
|
|
3487
|
-
io.puts "
|
|
3488
|
-
io.puts "
|
|
3489
|
-
io.puts "
|
|
3642
|
+
# only interpret them; min_count is -1 when not given.
|
|
3643
|
+
io.puts " ca_size_t min_count = ca_min_count_arg(rmin_count, NULL);"
|
|
3644
|
+
# fill_value: UNDEF leaves the result undefined, as no fill_value does.
|
|
3645
|
+
# A full reduction with a fill keeps its axes so the fill goes into a
|
|
3646
|
+
# typed cell, as it does per axis; ca_reduce_fill takes the cell out.
|
|
3647
|
+
io.puts " if ( rfval == CA_UNDEF ) rfval = Qnil;"
|
|
3648
|
+
io.puts " int fill_whole = 0;"
|
|
3649
|
+
io.puts " if ( ! NIL_P(rfval) && naxes == src->ndim && ! keep_axis ) {"
|
|
3650
|
+
io.puts " keep_axis = 1;"
|
|
3651
|
+
io.puts " fill_whole = 1;"
|
|
3490
3652
|
io.puts " }"
|
|
3491
3653
|
io.puts " VALUE result;"
|
|
3492
3654
|
elsif has_aarg
|
|
@@ -3502,9 +3664,23 @@ module MkKernel
|
|
|
3502
3664
|
# axis is allowed. Variadic `a.sum(0, 1)` raises here with a migration
|
|
3503
3665
|
# hint pointing at the kwarg form.
|
|
3504
3666
|
io.puts " if ( argc > 0 ) {"
|
|
3505
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
3667
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: positional axis arguments are no longer accepted (got %d); use the axis: keyword, e.g. a.%s(axis: 0) or a.%s(axis: [0, 1])", ca_calling_method_name(), argc, ca_calling_method_name(), ca_calling_method_name());]
|
|
3506
3668
|
io.puts " }"
|
|
3507
3669
|
|
|
3670
|
+
if weights_io
|
|
3671
|
+
# Refuse an unsupported source before the weights are attached: the
|
|
3672
|
+
# switch below would raise with them held.
|
|
3673
|
+
if k[:fallback] == :raise
|
|
3674
|
+
io.puts " switch ( src->data_type ) {"
|
|
3675
|
+
io.puts " " + k[:source].map { |s| "case #{DTYPES[s][:ca]}:" }.join(" ") + " break;"
|
|
3676
|
+
io.puts " default:"
|
|
3677
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
3678
|
+
io.puts %Q[ rb_raise(rb_eCADataTypeError, "%s: source data_type :%s not supported (expected one of: #{MkKernel.type_names(k[:source])})", ca_calling_method_name(), ca_type_name[src->data_type]);]
|
|
3679
|
+
io.puts " }"
|
|
3680
|
+
end
|
|
3681
|
+
io.print weights_io.string
|
|
3682
|
+
end
|
|
3683
|
+
|
|
3508
3684
|
io.puts " switch ( src->data_type ) {"
|
|
3509
3685
|
k[:source].each do |s|
|
|
3510
3686
|
si = DTYPES[s]
|
|
@@ -3515,8 +3691,14 @@ module MkKernel
|
|
|
3515
3691
|
# A fixlen query is a runtime-width byte blob with no
|
|
3516
3692
|
# scalar cast; pack it the way the search family does,
|
|
3517
3693
|
# which NUL-pads a short String to the cell width.
|
|
3518
|
-
|
|
3694
|
+
# ALLOCV, not the stack: a cell can be wide. The holder
|
|
3695
|
+
# keeps the buffer until the GC takes it.
|
|
3696
|
+
" volatile VALUE value_arg_holder = 0;\n" \
|
|
3697
|
+
" char *value_arg = ALLOCV_N(char, value_arg_holder, src->bytes);\n" \
|
|
3519
3698
|
" rb_ca_obj2ptr(self, rval, value_arg);\n"
|
|
3699
|
+
elsif INT_DTYPES.include?(s)
|
|
3700
|
+
" ca_check_integer_fits(rval, #{si[:ca]});\n" \
|
|
3701
|
+
" #{si[:c]} value_arg = (#{si[:c]}) #{si[:num2c]}(rval);\n"
|
|
3520
3702
|
else
|
|
3521
3703
|
" #{si[:c]} value_arg = (#{si[:c]}) #{si[:num2c]}(rval);\n"
|
|
3522
3704
|
end
|
|
@@ -3525,13 +3707,14 @@ module MkKernel
|
|
|
3525
3707
|
io.print varg_decl unless varg_decl.empty?
|
|
3526
3708
|
if use_result_var
|
|
3527
3709
|
io.puts " result = #{name}_ki_native_#{s}(self, src, slab_axes, naxes, keep_axis#{extra_call});"
|
|
3710
|
+
io.puts " RB_GC_GUARD(self);"
|
|
3528
3711
|
io.puts " break;"
|
|
3529
3712
|
else
|
|
3530
|
-
io.puts "
|
|
3713
|
+
io.puts " CA_RETURN_KEEPING(self, #{name}_ki_native_#{s}(self, src, slab_axes, naxes, keep_axis#{extra_call}));"
|
|
3531
3714
|
end
|
|
3532
3715
|
io.puts " }"
|
|
3533
3716
|
else
|
|
3534
|
-
io.puts " case #{si[:ca]}:
|
|
3717
|
+
io.puts " case #{si[:ca]}: CA_RETURN_KEEPING(self, #{name}_ki_native_#{s}(self, src, slab_axes, naxes, keep_axis));"
|
|
3535
3718
|
end
|
|
3536
3719
|
end
|
|
3537
3720
|
case k[:fallback]
|
|
@@ -3544,7 +3727,8 @@ module MkKernel
|
|
|
3544
3727
|
io.puts " switch ( src->data_type ) {"
|
|
3545
3728
|
io.puts " case CA_BOOLEAN: break; /* wrap: count-of-trues semantic */"
|
|
3546
3729
|
io.puts " default:"
|
|
3547
|
-
io.puts %Q[
|
|
3730
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
3731
|
+
io.puts %Q[ rb_raise(rb_eCADataTypeError, "%s: source data_type :%s not supported (expected one of: #{MkKernel.type_names(k[:source])}, or boolean)", ca_calling_method_name(), ca_type_name[src->data_type]);]
|
|
3548
3732
|
io.puts " }"
|
|
3549
3733
|
io.puts " VALUE vsrc = rb_ca_wrap_readonly(self, INT2NUM(CA_FLOAT64));"
|
|
3550
3734
|
io.puts " CArray *casted;"
|
|
@@ -3556,42 +3740,44 @@ module MkKernel
|
|
|
3556
3740
|
end
|
|
3557
3741
|
if use_result_var
|
|
3558
3742
|
io.puts " result = #{name}_ki_native_f64(vsrc, casted, slab_axes, naxes, keep_axis#{extra_call});"
|
|
3743
|
+
io.puts " RB_GC_GUARD(vsrc);"
|
|
3559
3744
|
io.puts " break;"
|
|
3560
3745
|
else
|
|
3561
|
-
io.puts "
|
|
3746
|
+
io.puts " CA_RETURN_KEEPING(vsrc, #{name}_ki_native_f64(vsrc, casted, slab_axes, naxes, keep_axis#{extra_call}));"
|
|
3562
3747
|
end
|
|
3563
3748
|
io.puts " }"
|
|
3564
3749
|
when :raise
|
|
3565
3750
|
io.puts " default:"
|
|
3566
|
-
io.puts %Q[
|
|
3751
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
3752
|
+
io.puts %Q[ rb_raise(rb_eCADataTypeError, "%s: source data_type :%s not supported (expected one of: #{MkKernel.type_names(k[:source])})", ca_calling_method_name(), ca_type_name[src->data_type]);]
|
|
3567
3753
|
end
|
|
3568
3754
|
io.puts " }"
|
|
3569
3755
|
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
# or per-axis CArray with mask bits.
|
|
3573
|
-
io.puts " if ( ! NIL_P(rfval) ) {"
|
|
3574
|
-
io.puts " if ( result == CA_UNDEF ) {"
|
|
3575
|
-
io.puts " result = rfval;"
|
|
3576
|
-
io.puts " } else if ( TYPE(result) != T_FLOAT && rb_obj_is_kind_of(result, rb_cCArray) ) {"
|
|
3577
|
-
io.puts " CArray *cr;"
|
|
3578
|
-
io.puts " GetCArray(result, cr);"
|
|
3579
|
-
io.puts " if ( ca_has_mask(cr) ) {"
|
|
3580
|
-
io.puts " result = rb_ca_mask_fill_copy(result, rfval);"
|
|
3581
|
-
io.puts " }"
|
|
3582
|
-
io.puts " }"
|
|
3583
|
-
io.puts " }"
|
|
3584
|
-
end
|
|
3585
|
-
|
|
3586
|
-
# array_arg cleanup: detach weights (paired with ca_attach above).
|
|
3756
|
+
# array_arg cleanup: detach weights (paired with ca_attach above),
|
|
3757
|
+
# ahead of the fill_value substitution, which can raise.
|
|
3587
3758
|
if has_aarg
|
|
3588
3759
|
io.puts " ca_detach(cw);"
|
|
3589
3760
|
end
|
|
3590
3761
|
|
|
3591
|
-
|
|
3762
|
+
relift = (k[:face_gate] == :relift)
|
|
3763
|
+
if min_count
|
|
3764
|
+
# Apply fill_value through the one rule the per-axis result uses. A
|
|
3765
|
+
# Face's fill is a value of the Face, so it goes in after the re-lift
|
|
3766
|
+
# below, as an assignment to the Face would put it.
|
|
3767
|
+
io.puts relift ? " if ( ! NIL_P(rfval) && _reduce_face == Qnil ) {" : " if ( ! NIL_P(rfval) ) {"
|
|
3768
|
+
io.puts " result = ca_reduce_fill(result, rfval, fill_whole);"
|
|
3769
|
+
io.puts " }"
|
|
3770
|
+
end
|
|
3771
|
+
|
|
3772
|
+
if relift
|
|
3592
3773
|
# Descended through emit_reduce_face_gate: re-lift the storage result
|
|
3593
3774
|
# back into the Face before returning.
|
|
3594
3775
|
emit_reduce_face_relift(io)
|
|
3776
|
+
if min_count
|
|
3777
|
+
io.puts " if ( ! NIL_P(rfval) && _reduce_face != Qnil ) {"
|
|
3778
|
+
io.puts " result = ca_reduce_fill(result, rfval, fill_whole);"
|
|
3779
|
+
io.puts " }"
|
|
3780
|
+
end
|
|
3595
3781
|
end
|
|
3596
3782
|
|
|
3597
3783
|
if use_result_var
|
|
@@ -3602,117 +3788,13 @@ module MkKernel
|
|
|
3602
3788
|
io.puts "}"
|
|
3603
3789
|
end
|
|
3604
3790
|
|
|
3605
|
-
# ---- map emitter ------------------------------------------------------
|
|
3606
|
-
|
|
3607
|
-
def self.emit_map(io, k)
|
|
3608
|
-
io.puts
|
|
3609
|
-
io.puts "/* ===== #{k[:name]}_ki ============================================ */"
|
|
3610
|
-
k[:source].each do |src|
|
|
3611
|
-
emit_map_native(io, k, src)
|
|
3612
|
-
end
|
|
3613
|
-
emit_map_dispatch(io, k)
|
|
3614
|
-
end
|
|
3615
|
-
|
|
3616
|
-
def self.emit_map_native(io, k, src)
|
|
3617
|
-
si = DTYPES[src]
|
|
3618
|
-
oi = output_info(k, src)
|
|
3619
|
-
name = k[:name]
|
|
3620
|
-
expr = resolve_expr(k[:expr], oi, src)
|
|
3621
|
-
|
|
3622
|
-
io.puts
|
|
3623
|
-
io.puts <<~C
|
|
3624
|
-
static VALUE
|
|
3625
|
-
#{name}_ki_native_#{src} (VALUE self, CArray *ca)
|
|
3626
|
-
{
|
|
3627
|
-
VALUE vout = rb_ca_template_with_type(self, INT2NUM(#{oi[:ca]}),
|
|
3628
|
-
INT2NUM(sizeof(#{oi[:c]})));
|
|
3629
|
-
CArray *co;
|
|
3630
|
-
GetCArray(vout, co);
|
|
3631
|
-
|
|
3632
|
-
/* All-axes slab = whole array as one K-D walk via CA_SLAB_AXES.
|
|
3633
|
-
CA_SLAB_WHOLE uses the flat next_slab path, which doesn't
|
|
3634
|
-
populate slab_dims; CA_SLAB_AXES with naxes == ndim is the
|
|
3635
|
-
K-D-walkable form. */
|
|
3636
|
-
int8_t slab_axes[CA_RANK_MAX];
|
|
3637
|
-
int8_t naxes = ca->ndim;
|
|
3638
|
-
for ( int8_t k = 0; k < naxes; k++ ) slab_axes[k] = k;
|
|
3639
|
-
|
|
3640
|
-
ca_iter_state st_in, st_out;
|
|
3641
|
-
int rc;
|
|
3642
|
-
rc = ca_iter_state_init_l2(&st_in, ca, CA_SLAB_AXES,
|
|
3643
|
-
slab_axes, naxes, 0);
|
|
3644
|
-
if ( rc != CA_ITER_OK ) {
|
|
3645
|
-
rb_raise(rb_eRuntimeError,
|
|
3646
|
-
"#{name}_ki: input init failed rc=%d", rc);
|
|
3647
|
-
}
|
|
3648
|
-
rc = ca_iter_state_init_l2(&st_out, co, CA_SLAB_AXES,
|
|
3649
|
-
slab_axes, naxes, CA_KERNEL_WRITE);
|
|
3650
|
-
if ( rc != CA_ITER_OK ) {
|
|
3651
|
-
ca_iter_state_finish(&st_in);
|
|
3652
|
-
rb_raise(rb_eRuntimeError,
|
|
3653
|
-
"#{name}_ki: output init failed rc=%d", rc);
|
|
3654
|
-
}
|
|
3655
|
-
|
|
3656
|
-
char *pi, *po;
|
|
3657
|
-
boolean8_t *mi, *mo;
|
|
3658
|
-
while ( ca_iter_state_next_slab_axes(&st_in, &pi, &mi) &&
|
|
3659
|
-
ca_iter_state_next_slab_axes(&st_out, &po, &mo) ) {
|
|
3660
|
-
CA_SLAB_MAP_T(#{si[:c]}, #{oi[:c]}, st_in, pi, st_out, po, #{expr});
|
|
3661
|
-
ca_iter_state_sync_slab(&st_out);
|
|
3662
|
-
}
|
|
3663
|
-
ca_iter_state_finish(&st_in);
|
|
3664
|
-
ca_iter_state_finish(&st_out);
|
|
3665
|
-
return vout;
|
|
3666
|
-
}
|
|
3667
|
-
C
|
|
3668
|
-
end
|
|
3669
|
-
|
|
3670
|
-
def self.emit_map_dispatch(io, k)
|
|
3671
|
-
name = k[:name]
|
|
3672
|
-
io.puts
|
|
3673
|
-
io.puts "static VALUE"
|
|
3674
|
-
io.puts "rb_ca_#{name}_ki (VALUE self)"
|
|
3675
|
-
io.puts "{"
|
|
3676
|
-
io.puts " CArray *src;"
|
|
3677
|
-
io.puts " GetCArray(self, src);"
|
|
3678
|
-
io.puts " switch ( src->data_type ) {"
|
|
3679
|
-
k[:source].each do |s|
|
|
3680
|
-
si = DTYPES[s]
|
|
3681
|
-
io.puts " case #{si[:ca]}: return #{name}_ki_native_#{s}(self, src);"
|
|
3682
|
-
end
|
|
3683
|
-
case k[:fallback]
|
|
3684
|
-
when :wrap_to_f64
|
|
3685
|
-
io.puts " default: {"
|
|
3686
|
-
io.puts " /* Phase E: only wrap data_types that have a meaningful float64"
|
|
3687
|
-
io.puts " representation. Complex / fixlen / object would lose"
|
|
3688
|
-
io.puts " structure (e.g. complex -> float strips imaginary part)"
|
|
3689
|
-
io.puts " so reject explicitly, matching legacy DataTypeError. */"
|
|
3690
|
-
io.puts " switch ( src->data_type ) {"
|
|
3691
|
-
io.puts " case CA_BOOLEAN: break; /* wrap: count-of-trues semantic */"
|
|
3692
|
-
io.puts " default:"
|
|
3693
|
-
io.puts %Q[ rb_raise(rb_eCADataTypeError, "#{name}_ki: source data_type :%s not supported (expected one of: #{k[:source].join(", ")}, or boolean)", ca_type_name[src->data_type]);]
|
|
3694
|
-
io.puts " }"
|
|
3695
|
-
io.puts " VALUE vsrc = rb_ca_wrap_readonly(self, INT2NUM(CA_FLOAT64));"
|
|
3696
|
-
io.puts " CArray *casted;"
|
|
3697
|
-
io.puts " GetCArray(vsrc, casted);"
|
|
3698
|
-
io.puts " return #{name}_ki_native_f64(vsrc, casted);"
|
|
3699
|
-
io.puts " }"
|
|
3700
|
-
when :raise
|
|
3701
|
-
io.puts " default:"
|
|
3702
|
-
io.puts %Q[ rb_raise(rb_eCADataTypeError, "#{name}_ki: source data_type :%s not supported (expected one of: #{k[:source].join(", ")})", ca_type_name[src->data_type]);]
|
|
3703
|
-
end
|
|
3704
|
-
io.puts " }"
|
|
3705
|
-
io.puts " return Qnil; /* unreachable */"
|
|
3706
|
-
io.puts "}"
|
|
3707
|
-
end
|
|
3708
|
-
|
|
3709
3791
|
# ---- scan emitter -----------------------------------------------------
|
|
3710
3792
|
|
|
3711
3793
|
def self.emit_scan(io, k)
|
|
3712
3794
|
io.puts
|
|
3713
3795
|
io.puts "/* ===== #{k[:name]}_ki ============================================ */"
|
|
3714
3796
|
k[:source].each do |src|
|
|
3715
|
-
|
|
3797
|
+
emit_native_for(io, k, src) { |o| emit_scan_native(o, k, src) }
|
|
3716
3798
|
end
|
|
3717
3799
|
emit_scan_dispatch(io, k)
|
|
3718
3800
|
end
|
|
@@ -3783,14 +3865,14 @@ module MkKernel
|
|
|
3783
3865
|
slab_axes, 1, 0);
|
|
3784
3866
|
if ( rc != CA_ITER_OK ) {
|
|
3785
3867
|
rb_raise(rb_eRuntimeError,
|
|
3786
|
-
"
|
|
3868
|
+
"%s: input init failed rc=%d", ca_calling_method_name(), rc);
|
|
3787
3869
|
}
|
|
3788
|
-
rc =
|
|
3870
|
+
rc = ca_iter_state_init_l2_paired(&st_out, &st_in, co, CA_SLAB_AXES,
|
|
3789
3871
|
slab_axes, 1, CA_KERNEL_WRITE);
|
|
3790
3872
|
if ( rc != CA_ITER_OK ) {
|
|
3791
3873
|
ca_iter_state_finish(&st_in);
|
|
3792
3874
|
rb_raise(rb_eRuntimeError,
|
|
3793
|
-
"
|
|
3875
|
+
"%s: output init failed rc=%d", ca_calling_method_name(), rc);
|
|
3794
3876
|
}
|
|
3795
3877
|
#{mask_setup}
|
|
3796
3878
|
char *pi, *po;
|
|
@@ -3815,16 +3897,38 @@ module MkKernel
|
|
|
3815
3897
|
# multi-axis scan is semantically ambiguous (flatten-and-cumsum vs
|
|
3816
3898
|
# 2D prefix sum); users must chain explicitly. Arity flips from 1
|
|
3817
3899
|
# (positional Integer axis) to -1 (kwarg dispatcher).
|
|
3818
|
-
|
|
3819
|
-
|
|
3900
|
+
if k[:face_gate]
|
|
3901
|
+
# The scan proper runs on the storage; the bound entry below gates the
|
|
3902
|
+
# Face and re-lifts the result.
|
|
3903
|
+
io.puts "static VALUE rb_ca_#{name}_ki_storage (int argc, VALUE *argv, VALUE self);"
|
|
3904
|
+
io.puts
|
|
3905
|
+
io.puts "static VALUE"
|
|
3906
|
+
io.puts "rb_ca_#{name}_ki (int argc, VALUE *argv, VALUE self)"
|
|
3907
|
+
io.puts "{"
|
|
3908
|
+
io.puts " CArray *src;"
|
|
3909
|
+
io.puts " VALUE result;"
|
|
3910
|
+
io.puts " GetCArray(self, src);"
|
|
3911
|
+
emit_reduce_face_gate(io, name, true)
|
|
3912
|
+
io.puts " result = rb_ca_#{name}_ki_storage(argc, argv, self);"
|
|
3913
|
+
emit_reduce_face_relift(io)
|
|
3914
|
+
io.puts " return result;"
|
|
3915
|
+
io.puts "}"
|
|
3916
|
+
io.puts
|
|
3917
|
+
io.puts "static VALUE"
|
|
3918
|
+
io.puts "rb_ca_#{name}_ki_storage (int argc, VALUE *argv, VALUE self)"
|
|
3919
|
+
else
|
|
3920
|
+
io.puts "static VALUE"
|
|
3921
|
+
io.puts "rb_ca_#{name}_ki (int argc, VALUE *argv, VALUE self)"
|
|
3922
|
+
end
|
|
3820
3923
|
io.puts "{"
|
|
3821
3924
|
io.puts " CArray *src;"
|
|
3822
3925
|
io.puts " GetCArray(self, src);"
|
|
3926
|
+
emit_lazy_evaluated(io)
|
|
3823
3927
|
io.puts " volatile VALUE ropt = rb_pop_options(&argc, &argv);"
|
|
3824
3928
|
io.puts " volatile VALUE raxis = Qnil;"
|
|
3825
3929
|
io.puts " rb_scan_options(ropt, \"axis\", &raxis);"
|
|
3826
3930
|
io.puts " if ( argc > 0 ) {"
|
|
3827
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
3931
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: positional axis is no longer accepted (got %d); use axis: kwarg, e.g. a.#{name}(axis: 0)", ca_calling_method_name(), argc);]
|
|
3828
3932
|
io.puts " }"
|
|
3829
3933
|
if k[:axis_default] == :flatten
|
|
3830
3934
|
# Legacy compat: nil/omitted axis: flatten source and scan axis 0.
|
|
@@ -3836,7 +3940,7 @@ module MkKernel
|
|
|
3836
3940
|
io.puts " switch ( fsrc->data_type ) {"
|
|
3837
3941
|
k[:source].each do |s|
|
|
3838
3942
|
si = DTYPES[s]
|
|
3839
|
-
io.puts " case #{si[:ca]}:
|
|
3943
|
+
io.puts " case #{si[:ca]}: CA_RETURN_KEEPING(vflat, #{name}_ki_native_#{s}(vflat, fsrc, 0));"
|
|
3840
3944
|
end
|
|
3841
3945
|
case k[:fallback]
|
|
3842
3946
|
when :wrap_to_f64
|
|
@@ -3844,34 +3948,35 @@ module MkKernel
|
|
|
3844
3948
|
io.puts " VALUE vsrc = rb_ca_wrap_readonly(vflat, INT2NUM(CA_FLOAT64));"
|
|
3845
3949
|
io.puts " CArray *casted;"
|
|
3846
3950
|
io.puts " GetCArray(vsrc, casted);"
|
|
3847
|
-
io.puts "
|
|
3951
|
+
io.puts " CA_RETURN_KEEPING(vsrc, #{name}_ki_native_f64(vsrc, casted, 0));"
|
|
3848
3952
|
io.puts " }"
|
|
3849
3953
|
when :raise
|
|
3850
3954
|
io.puts " default:"
|
|
3851
|
-
io.puts %Q[
|
|
3955
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
3956
|
+
io.puts %Q[ rb_raise(rb_eCADataTypeError, "%s: source data_type :%s not supported (expected one of: #{MkKernel.type_names(k[:source])})", ca_calling_method_name(), ca_type_name[fsrc->data_type]);]
|
|
3957
|
+
when :mask_count
|
|
3958
|
+
io.puts " default:"
|
|
3959
|
+
io.puts " return rb_ca_scan_mask_count(self, Qnil);"
|
|
3852
3960
|
end
|
|
3853
3961
|
io.puts " }"
|
|
3854
3962
|
io.puts " }"
|
|
3855
3963
|
else
|
|
3856
3964
|
io.puts " if ( NIL_P(raxis) ) {"
|
|
3857
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
3965
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: axis: is required (a single Integer; a scan over several axes is ambiguous)", ca_calling_method_name());]
|
|
3858
3966
|
io.puts " }"
|
|
3859
3967
|
end
|
|
3860
3968
|
io.puts " if ( TYPE(raxis) == T_ARRAY ) {"
|
|
3861
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
3862
|
-
io.puts " }"
|
|
3863
|
-
io.puts " if ( ! rb_obj_is_kind_of(raxis, rb_cInteger) ) {"
|
|
3864
|
-
io.puts %Q[ rb_raise(rb_eTypeError, "#{name}_ki: axis: must be Integer (got %"PRIsVALUE")", rb_obj_class(raxis));]
|
|
3969
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: axis must be a single Integer (got Array); a scan over several axes is ambiguous, chain explicitly: a.#{name}(axis: 0).#{name}(axis: 1)", ca_calling_method_name());]
|
|
3865
3970
|
io.puts " }"
|
|
3866
|
-
io.puts " int axis =
|
|
3971
|
+
io.puts " int axis = (int) ca_axis_integer(raxis, NULL);"
|
|
3867
3972
|
io.puts " if ( axis < 0 ) axis += src->ndim;"
|
|
3868
3973
|
io.puts " if ( axis < 0 || axis >= src->ndim ) {"
|
|
3869
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
3974
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: axis %d out of range for ndim %d", ca_calling_method_name(), NUM2INT(raxis), src->ndim);]
|
|
3870
3975
|
io.puts " }"
|
|
3871
3976
|
io.puts " switch ( src->data_type ) {"
|
|
3872
3977
|
k[:source].each do |s|
|
|
3873
3978
|
si = DTYPES[s]
|
|
3874
|
-
io.puts " case #{si[:ca]}:
|
|
3979
|
+
io.puts " case #{si[:ca]}: CA_RETURN_KEEPING(self, #{name}_ki_native_#{s}(self, src, axis));"
|
|
3875
3980
|
end
|
|
3876
3981
|
case k[:fallback]
|
|
3877
3982
|
when :wrap_to_f64
|
|
@@ -3879,11 +3984,15 @@ module MkKernel
|
|
|
3879
3984
|
io.puts " VALUE vsrc = rb_ca_wrap_readonly(self, INT2NUM(CA_FLOAT64));"
|
|
3880
3985
|
io.puts " CArray *casted;"
|
|
3881
3986
|
io.puts " GetCArray(vsrc, casted);"
|
|
3882
|
-
io.puts "
|
|
3987
|
+
io.puts " CA_RETURN_KEEPING(vsrc, #{name}_ki_native_f64(vsrc, casted, axis));"
|
|
3883
3988
|
io.puts " }"
|
|
3884
3989
|
when :raise
|
|
3885
3990
|
io.puts " default:"
|
|
3886
|
-
io.puts %Q[
|
|
3991
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
3992
|
+
io.puts %Q[ rb_raise(rb_eCADataTypeError, "%s: source data_type :%s not supported (expected one of: #{MkKernel.type_names(k[:source])})", ca_calling_method_name(), ca_type_name[src->data_type]);]
|
|
3993
|
+
when :mask_count
|
|
3994
|
+
io.puts " default:"
|
|
3995
|
+
io.puts " return rb_ca_scan_mask_count(self, INT2NUM(axis));"
|
|
3887
3996
|
end
|
|
3888
3997
|
io.puts " }"
|
|
3889
3998
|
io.puts " return Qnil; /* unreachable */"
|
|
@@ -3898,7 +4007,7 @@ module MkKernel
|
|
|
3898
4007
|
k[:source].each do |src|
|
|
3899
4008
|
emit_sort_cmp(io, k, src)
|
|
3900
4009
|
emit_sort_quickselect(io, k, src) if k[:algorithm] == :partition
|
|
3901
|
-
|
|
4010
|
+
emit_native_for(io, k, src) { |o| emit_sort_native(o, k, src) }
|
|
3902
4011
|
end
|
|
3903
4012
|
emit_sort_dispatch(io, k)
|
|
3904
4013
|
end
|
|
@@ -4030,14 +4139,21 @@ module MkKernel
|
|
|
4030
4139
|
# String, custom class with <=> defined) works. Raises Ruby
|
|
4031
4140
|
# TypeError mid-sort if a cell pair is uncomparable -- consistent
|
|
4032
4141
|
# with Array#sort behavior.
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
io.puts
|
|
4036
|
-
io.puts "
|
|
4142
|
+
# A Float NaN sorts after every other value, as it does in a float
|
|
4143
|
+
# array; two NaNs fall to the index tie-break.
|
|
4144
|
+
io.puts " int nan_a = CA_OBJ_ISNAN(pa->v);"
|
|
4145
|
+
io.puts " int nan_b = CA_OBJ_ISNAN(pb->v);"
|
|
4146
|
+
io.puts " if ( nan_a && !nan_b ) return 1;"
|
|
4147
|
+
io.puts " if ( nan_b && !nan_a ) return -1;"
|
|
4148
|
+
io.puts " if ( !nan_a ) {"
|
|
4149
|
+
io.puts %Q{ VALUE r = rb_funcall(pa->v, rb_intern("<=>"), 1, pb->v);}
|
|
4150
|
+
io.puts " if ( NIL_P(r) ) {"
|
|
4151
|
+
io.puts " ca_obj_compare_refused(pa->v, pb->v);"
|
|
4152
|
+
io.puts " }"
|
|
4153
|
+
io.puts " int c = NUM2INT(r);"
|
|
4154
|
+
io.puts " if ( c > 0 ) return 1;"
|
|
4155
|
+
io.puts " if ( c < 0 ) return -1;"
|
|
4037
4156
|
io.puts " }"
|
|
4038
|
-
io.puts " int c = NUM2INT(r);"
|
|
4039
|
-
io.puts " if ( c > 0 ) return 1;"
|
|
4040
|
-
io.puts " if ( c < 0 ) return -1;"
|
|
4041
4157
|
else
|
|
4042
4158
|
if is_fp
|
|
4043
4159
|
# NaN policy :end -- NaN sorts to the end (matches qcmp_f_type in
|
|
@@ -4168,19 +4284,32 @@ module MkKernel
|
|
|
4168
4284
|
io.puts " rc = ca_iter_state_init_l2(&st_in, ca, CA_SLAB_AXES,"
|
|
4169
4285
|
io.puts " slab_axes, 1, 0);"
|
|
4170
4286
|
io.puts " if ( rc != CA_ITER_OK ) {"
|
|
4171
|
-
io.puts %Q[ rb_raise(rb_eRuntimeError, "
|
|
4287
|
+
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: input init failed rc=%d", ca_calling_method_name(), rc);]
|
|
4172
4288
|
io.puts " }"
|
|
4173
|
-
io.puts " rc =
|
|
4289
|
+
io.puts " rc = ca_iter_state_init_l2_paired(&st_out, &st_in, co, CA_SLAB_AXES,"
|
|
4174
4290
|
io.puts " slab_axes, 1, CA_KERNEL_WRITE);"
|
|
4175
4291
|
io.puts " if ( rc != CA_ITER_OK ) {"
|
|
4176
4292
|
io.puts " ca_iter_state_finish(&st_in);"
|
|
4177
|
-
io.puts %Q[ rb_raise(rb_eRuntimeError, "
|
|
4293
|
+
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: output init failed rc=%d", ca_calling_method_name(), rc);]
|
|
4178
4294
|
io.puts " }"
|
|
4179
4295
|
io.puts
|
|
4180
4296
|
io.puts " ca_size_t fiber_n = st_in.slab_elements;"
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4297
|
+
if src == :object
|
|
4298
|
+
# The object comparator calls <=>, so the sort can raise part way.
|
|
4299
|
+
# ALLOCV frees these buffers whether the function returns or unwinds.
|
|
4300
|
+
# A full sort also needs aux: ca_sort_merge_pair_object is a mergesort.
|
|
4301
|
+
io.puts " volatile VALUE hbuf = 0, haux = 0;"
|
|
4302
|
+
io.puts " #{name}_pair_#{src} *buf = ALLOCV_N(#{name}_pair_#{src}, hbuf, fiber_n);"
|
|
4303
|
+
if partition
|
|
4304
|
+
io.puts " (void) haux;"
|
|
4305
|
+
else
|
|
4306
|
+
io.puts " #{name}_pair_#{src} *aux = ALLOCV_N(#{name}_pair_#{src}, haux, fiber_n);"
|
|
4307
|
+
end
|
|
4308
|
+
else
|
|
4309
|
+
io.puts " #{name}_pair_#{src} *buf ="
|
|
4310
|
+
io.puts " xmalloc(sizeof(#{name}_pair_#{src}) * (size_t) fiber_n);"
|
|
4311
|
+
end
|
|
4312
|
+
if has_kind && src != :object
|
|
4184
4313
|
# PROPOSAL_PORTABLE_TEXTBOOK_SORT §9.5.2 Option γ: aux pair buffer
|
|
4185
4314
|
# for ca_sort_merge_pair_<src> (= ping-pong bottom-up mergesort).
|
|
4186
4315
|
# Allocated once outside the fiber loop, reused across fibers.
|
|
@@ -4325,16 +4454,15 @@ module MkKernel
|
|
|
4325
4454
|
io.puts " */"
|
|
4326
4455
|
end
|
|
4327
4456
|
if is_object
|
|
4328
|
-
#
|
|
4329
|
-
#
|
|
4330
|
-
#
|
|
4331
|
-
#
|
|
4332
|
-
#
|
|
4333
|
-
#
|
|
4334
|
-
#
|
|
4335
|
-
|
|
4336
|
-
io.puts " (
|
|
4337
|
-
io.puts " qsort(#{buf_expr}, (size_t) #{sort_n_expr}, sizeof(#{name}_pair_#{src}), #{name}_cmp_#{src});"
|
|
4457
|
+
# CA_OBJECT sorts with the emitted _cmp_object (rb_funcall(<=>)
|
|
4458
|
+
# based) through ca_sort_merge_pair_object rather than libc qsort: the
|
|
4459
|
+
# comparator raises on a pair that does not compare, and the raise
|
|
4460
|
+
# must not unwind through libc's frames. The cmp tie-breaks on the
|
|
4461
|
+
# original index, so the order is the same for both kinds, and
|
|
4462
|
+
# do_stable is ignored. NaN partition is skipped (Float#<=> answers
|
|
4463
|
+
# nil for NaN, which the comparator reports as ArgumentError).
|
|
4464
|
+
io.puts " (void) do_stable; /* CA_OBJECT: one stable sort serves both kinds */"
|
|
4465
|
+
io.puts " ca_sort_merge_pair_object((ca_pair_object *) #{buf_expr}, (ca_pair_object *) aux, (ca_size_t) #{sort_n_expr}, #{name}_cmp_#{src});"
|
|
4338
4466
|
elsif is_fixlen
|
|
4339
4467
|
io.puts " /* CA_FIXLEN: libc qsort with the memcmp comparator. qsort isn't"
|
|
4340
4468
|
io.puts " stable, but #{name}_cmp_#{src} tie-breaks on the original index,"
|
|
@@ -4381,7 +4509,7 @@ module MkKernel
|
|
|
4381
4509
|
io.puts " if ( r > 0 ) {"
|
|
4382
4510
|
io.puts " if ( dense ) {"
|
|
4383
4511
|
if is_object
|
|
4384
|
-
io.puts " if ( !
|
|
4512
|
+
io.puts " if ( ! ca_obj_equal(buf[r].v, buf[r - 1].v) ) out_r++;"
|
|
4385
4513
|
elsif is_fixlen
|
|
4386
4514
|
io.puts " if ( memcmp(buf[r].vp, buf[r - 1].vp, (size_t) buf[r].nb) != 0 ) out_r++;"
|
|
4387
4515
|
else
|
|
@@ -4427,9 +4555,14 @@ module MkKernel
|
|
|
4427
4555
|
end
|
|
4428
4556
|
|
|
4429
4557
|
io.puts " }"
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
io.puts "
|
|
4558
|
+
if src == :object
|
|
4559
|
+
io.puts " ALLOCV_END(hbuf);"
|
|
4560
|
+
io.puts " ALLOCV_END(haux);"
|
|
4561
|
+
else
|
|
4562
|
+
io.puts " xfree(buf);"
|
|
4563
|
+
if has_kind
|
|
4564
|
+
io.puts " if ( aux ) xfree(aux);"
|
|
4565
|
+
end
|
|
4433
4566
|
end
|
|
4434
4567
|
io.puts " ca_iter_state_finish(&st_in);"
|
|
4435
4568
|
io.puts " ca_iter_state_finish(&st_out);"
|
|
@@ -4451,10 +4584,7 @@ module MkKernel
|
|
|
4451
4584
|
io.puts " CArray *_sc;"
|
|
4452
4585
|
io.puts " GetCArray(_st, _sc);"
|
|
4453
4586
|
io.puts " if ( _sc->data_type != CA_FIXLEN && ! _orderable ) {"
|
|
4454
|
-
io.puts %Q[
|
|
4455
|
-
io.puts %Q[ "#{name}_ki: Face-typed input (%s) is not orderable "]
|
|
4456
|
-
io.puts %Q[ "by storage; use ca.parent to descend to storage",]
|
|
4457
|
-
io.puts %Q[ rb_obj_classname(_face));]
|
|
4587
|
+
io.puts %Q[ ca_face_refuse_order(ca_calling_method_name(), _face, "order");]
|
|
4458
4588
|
io.puts " }"
|
|
4459
4589
|
io.puts " self = _st;"
|
|
4460
4590
|
io.puts " src = _sc;"
|
|
@@ -4477,10 +4607,7 @@ module MkKernel
|
|
|
4477
4607
|
io.puts " CArray *_sc;"
|
|
4478
4608
|
io.puts " GetCArray(_st, _sc);"
|
|
4479
4609
|
io.puts " if ( _sc->data_type != CA_FIXLEN && ! _orderable ) {"
|
|
4480
|
-
io.puts %Q[
|
|
4481
|
-
io.puts %Q[ "#{name}_ki: Face-typed input (%s) is not orderable "]
|
|
4482
|
-
io.puts %Q[ "by storage; use ca.parent to descend to storage",]
|
|
4483
|
-
io.puts %Q[ rb_obj_classname(_face));]
|
|
4610
|
+
io.puts %Q[ ca_face_refuse_order(ca_calling_method_name(), _face, "order");]
|
|
4484
4611
|
io.puts " }"
|
|
4485
4612
|
if relift
|
|
4486
4613
|
io.puts " _reduce_face = _face;"
|
|
@@ -4491,32 +4618,37 @@ module MkKernel
|
|
|
4491
4618
|
end
|
|
4492
4619
|
|
|
4493
4620
|
# Re-lift a value-returning reduction result back into the Face captured by
|
|
4494
|
-
# emit_reduce_face_gate. Full-reduction scalar ->
|
|
4495
|
-
# per-axis CArray ->
|
|
4621
|
+
# emit_reduce_face_gate. Full-reduction scalar -> storage_to_element hook;
|
|
4622
|
+
# per-axis CArray -> ca_face_relift_entity (tail, mask and copy_state).
|
|
4496
4623
|
# All-masked full reductions leave CA_UNDEF untouched.
|
|
4497
4624
|
def self.emit_reduce_face_relift(io)
|
|
4498
|
-
io.puts " if ( _reduce_face != Qnil && result
|
|
4499
|
-
io.puts "
|
|
4500
|
-
io.puts "
|
|
4501
|
-
io.puts "
|
|
4502
|
-
io.puts "
|
|
4503
|
-
io.puts "
|
|
4504
|
-
io.puts "
|
|
4505
|
-
io.puts "
|
|
4506
|
-
io.puts "
|
|
4507
|
-
io.puts "
|
|
4508
|
-
io.puts "
|
|
4509
|
-
io.puts "
|
|
4510
|
-
io.puts "
|
|
4511
|
-
io.puts " ca_copy_mask(_lca, _rca);"
|
|
4625
|
+
io.puts " if ( _reduce_face != Qnil && RB_TYPE_P(result, T_ARRAY) ) {"
|
|
4626
|
+
io.puts " /* a multi-output reduction (minmax): re-lift each output. */"
|
|
4627
|
+
io.puts " long _i;"
|
|
4628
|
+
io.puts " for ( _i = 0; _i < RARRAY_LEN(result); _i++ ) {"
|
|
4629
|
+
io.puts " VALUE _r = rb_ary_entry(result, _i);"
|
|
4630
|
+
io.puts " if ( _r == CA_UNDEF || _r == Qnil ) continue;"
|
|
4631
|
+
io.puts " if ( rb_obj_is_kind_of(_r, rb_cCArray) ) {"
|
|
4632
|
+
io.puts " _r = ca_face_relift_entity(_reduce_face, _r);"
|
|
4633
|
+
io.puts " }"
|
|
4634
|
+
io.puts " else {"
|
|
4635
|
+
io.puts " CArray *_fca;"
|
|
4636
|
+
io.puts " GetCArray(_reduce_face, _fca);"
|
|
4637
|
+
io.puts " CA_FACE_STORAGE_TO_ELEMENT_IF_FACE(_r, _reduce_face, _fca);"
|
|
4512
4638
|
io.puts " }"
|
|
4513
|
-
io.puts " result
|
|
4639
|
+
io.puts " rb_ary_store(result, _i, _r);"
|
|
4640
|
+
io.puts " }"
|
|
4641
|
+
io.puts " }"
|
|
4642
|
+
io.puts " else if ( _reduce_face != Qnil && result != CA_UNDEF && result != Qnil ) {"
|
|
4643
|
+
io.puts " if ( rb_obj_is_kind_of(result, rb_cCArray) ) {"
|
|
4644
|
+
io.puts " /* per-axis: re-lift the storage result as a Face of the same class. */"
|
|
4645
|
+
io.puts " result = ca_face_relift_entity(_reduce_face, result);"
|
|
4514
4646
|
io.puts " }"
|
|
4515
4647
|
io.puts " else {"
|
|
4516
4648
|
io.puts " /* full reduction: decode the scalar via the registered hook. */"
|
|
4517
4649
|
io.puts " CArray *_fca;"
|
|
4518
4650
|
io.puts " GetCArray(_reduce_face, _fca);"
|
|
4519
|
-
io.puts "
|
|
4651
|
+
io.puts " CA_FACE_STORAGE_TO_ELEMENT_IF_FACE(result, _reduce_face, _fca);"
|
|
4520
4652
|
io.puts " }"
|
|
4521
4653
|
io.puts " }"
|
|
4522
4654
|
end
|
|
@@ -4528,7 +4660,7 @@ module MkKernel
|
|
|
4528
4660
|
has_kind = !partition # :full and :rank gain kind: dispatch (Option γ)
|
|
4529
4661
|
mask_sentinel = (k[:mask_self] == :sentinel)
|
|
4530
4662
|
# bind_ruby: false kernels are consumed by other .c files at the C
|
|
4531
|
-
# level (
|
|
4663
|
+
# level (carray_sort.c / carray_partition.c call the _mp twins).
|
|
4532
4664
|
# Emit extern linkage so the symbol is reachable across translation
|
|
4533
4665
|
# units. bind_ruby: true kernels stay static (only referenced by
|
|
4534
4666
|
# Init_carray_kernels in this file).
|
|
@@ -4540,6 +4672,10 @@ module MkKernel
|
|
|
4540
4672
|
# partition_index / rank_index, which are consumed at the C level
|
|
4541
4673
|
# by carray_order.c (= ordering helpers like nlargest, order).
|
|
4542
4674
|
storage = (k[:bind_ruby] == false || k[:c_callable]) ? "" : "static "
|
|
4675
|
+
# bind_ruby: false + mask_self: :sentinel kernels are reached only
|
|
4676
|
+
# through their _mp twins, so the fixed-masked_last entries below
|
|
4677
|
+
# would have no caller.
|
|
4678
|
+
mp_only = (k[:mask_self] == :sentinel && k[:bind_ruby] == false)
|
|
4543
4679
|
# PROPOSAL_PORTABLE_TEXTBOOK_SORT §9.5.2 Option γ:
|
|
4544
4680
|
# For algorithm :full / :rank, dispatch emits THREE C-level entries:
|
|
4545
4681
|
# rb_ca_<name>_ki_quick (self, vaxis) -> do_stable=0
|
|
@@ -4576,6 +4712,7 @@ module MkKernel
|
|
|
4576
4712
|
io.puts "{"
|
|
4577
4713
|
io.puts " CArray *src;"
|
|
4578
4714
|
io.puts " GetCArray(self, src);"
|
|
4715
|
+
emit_lazy_evaluated(io)
|
|
4579
4716
|
io.puts " /* Face gate: descend to storage (fixlen -> memcmp default,"
|
|
4580
4717
|
io.puts " numeric -> ORDERABLE required). The strip also makes the"
|
|
4581
4718
|
io.puts " switch below and every index/addr template output build on"
|
|
@@ -4585,23 +4722,24 @@ module MkKernel
|
|
|
4585
4722
|
io.puts " /* sort family rejects masked sources globally (R3 / Q3): "
|
|
4586
4723
|
io.puts " masked elements would break the ordering invariant. */"
|
|
4587
4724
|
io.puts " if ( ca_has_mask(src) ) {"
|
|
4588
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
4725
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: masked input not supported (use ca.value or ca.strip_mask(fill))", ca_calling_method_name());]
|
|
4589
4726
|
io.puts " }"
|
|
4590
4727
|
end
|
|
4591
|
-
io.puts " int axis =
|
|
4728
|
+
io.puts " int axis = (int) ca_axis_integer(vaxis, NULL);"
|
|
4592
4729
|
io.puts " if ( axis < 0 ) axis += src->ndim;"
|
|
4593
4730
|
io.puts " if ( axis < 0 || axis >= src->ndim ) {"
|
|
4594
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
4731
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: axis %d out of range for ndim %d", ca_calling_method_name(), NUM2INT(vaxis), src->ndim);]
|
|
4595
4732
|
io.puts " }"
|
|
4596
4733
|
io.puts " switch ( src->data_type ) {"
|
|
4597
4734
|
k[:source].each do |s|
|
|
4598
4735
|
si = DTYPES[s]
|
|
4599
4736
|
native_extra = mask_sentinel ? ", masked_last" : ""
|
|
4600
4737
|
native_extra += rank ? ", dense" : ""
|
|
4601
|
-
io.puts " case #{si[:ca]}:
|
|
4738
|
+
io.puts " case #{si[:ca]}: CA_RETURN_KEEPING(self, #{name}_ki_native_#{s}(self, src, axis, do_stable#{native_extra}));"
|
|
4602
4739
|
end
|
|
4603
4740
|
io.puts " default:"
|
|
4604
|
-
io.puts %Q[
|
|
4741
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
4742
|
+
io.puts %Q[ rb_raise(rb_eCADataTypeError, "%s: source data_type :%s not supported (expected one of: #{MkKernel.type_names(k[:source])})", ca_calling_method_name(), ca_type_name[src->data_type]);]
|
|
4605
4743
|
io.puts " }"
|
|
4606
4744
|
io.puts " return Qnil; /* unreachable */"
|
|
4607
4745
|
io.puts "}"
|
|
@@ -4614,30 +4752,33 @@ module MkKernel
|
|
|
4614
4752
|
dispatch_call_last = mask_sentinel ? ", 1" : ""
|
|
4615
4753
|
dispatch_call_last += rank ? ", 0" : ""
|
|
4616
4754
|
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4755
|
+
unless mp_only
|
|
4756
|
+
# _ki_quick: arity 2 (self, vaxis). Linkage = storage.
|
|
4757
|
+
io.puts
|
|
4758
|
+
io.puts "#{storage}VALUE"
|
|
4759
|
+
io.puts "rb_ca_#{name}_ki_quick (VALUE self, VALUE vaxis)"
|
|
4760
|
+
io.puts "{"
|
|
4761
|
+
io.puts " return #{name}_ki_dispatch(self, vaxis, 0#{dispatch_call_last});"
|
|
4762
|
+
io.puts "}"
|
|
4624
4763
|
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4764
|
+
# _ki_stable: arity 2 (self, vaxis). Linkage = storage.
|
|
4765
|
+
io.puts
|
|
4766
|
+
io.puts "#{storage}VALUE"
|
|
4767
|
+
io.puts "rb_ca_#{name}_ki_stable (VALUE self, VALUE vaxis)"
|
|
4768
|
+
io.puts "{"
|
|
4769
|
+
io.puts " return #{name}_ki_dispatch(self, vaxis, 1#{dispatch_call_last});"
|
|
4770
|
+
io.puts "}"
|
|
4632
4771
|
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4772
|
+
# _ki: arity 2 alias of _ki_quick (= preserves existing arity-1
|
|
4773
|
+
# Ruby binding and existing C extern callers).
|
|
4774
|
+
io.puts
|
|
4775
|
+
io.puts "#{storage}VALUE"
|
|
4776
|
+
io.puts "rb_ca_#{name}_ki (VALUE self, VALUE vaxis)"
|
|
4777
|
+
io.puts "{"
|
|
4778
|
+
io.puts " return rb_ca_#{name}_ki_quick(self, vaxis);"
|
|
4779
|
+
io.puts "}"
|
|
4780
|
+
|
|
4781
|
+
end
|
|
4641
4782
|
|
|
4642
4783
|
if mask_sentinel && k[:bind_ruby] == false
|
|
4643
4784
|
# _mp twins: explicit masked_last, non-Ruby (plain C int, no
|
|
@@ -4659,16 +4800,16 @@ module MkKernel
|
|
|
4659
4800
|
end
|
|
4660
4801
|
|
|
4661
4802
|
if rank
|
|
4662
|
-
#
|
|
4663
|
-
# carray_order.c's `order`
|
|
4664
|
-
# sugar over rank_index, defined in a different file, so it needs
|
|
4803
|
+
# _kind_dense: explicit kind (0 :quick / 1 :stable) and dense
|
|
4804
|
+
# (0 :ordinal / 1 :dense), extern for carray_order.c's `order`
|
|
4805
|
+
# (sugar over rank_index, defined in a different file, so it needs
|
|
4665
4806
|
# a non-static entry point the same way the mask_self: :sentinel
|
|
4666
4807
|
# kernels' _mp twins do).
|
|
4667
4808
|
io.puts
|
|
4668
4809
|
io.puts "VALUE"
|
|
4669
|
-
io.puts "rb_ca_#{name}
|
|
4810
|
+
io.puts "rb_ca_#{name}_ki_kind_dense (VALUE self, VALUE vaxis, int stable, int dense)"
|
|
4670
4811
|
io.puts "{"
|
|
4671
|
-
io.puts " return #{name}_ki_dispatch(self, vaxis,
|
|
4812
|
+
io.puts " return #{name}_ki_dispatch(self, vaxis, stable, dense);"
|
|
4672
4813
|
io.puts "}"
|
|
4673
4814
|
end
|
|
4674
4815
|
else
|
|
@@ -4683,6 +4824,7 @@ module MkKernel
|
|
|
4683
4824
|
io.puts "{"
|
|
4684
4825
|
io.puts " CArray *src;"
|
|
4685
4826
|
io.puts " GetCArray(self, src);"
|
|
4827
|
+
emit_lazy_evaluated(io)
|
|
4686
4828
|
io.puts " /* Face gate (partition): same as the sort dispatcher --"
|
|
4687
4829
|
io.puts " fixlen -> memcmp default, numeric -> ORDERABLE required. */"
|
|
4688
4830
|
emit_sort_face_gate(io, name)
|
|
@@ -4690,38 +4832,42 @@ module MkKernel
|
|
|
4690
4832
|
io.puts " /* sort family rejects masked sources globally (R3 / Q3): "
|
|
4691
4833
|
io.puts " masked elements would break the ordering invariant. */"
|
|
4692
4834
|
io.puts " if ( ca_has_mask(src) ) {"
|
|
4693
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
4835
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: masked input not supported (use ca.value or ca.strip_mask(fill))", ca_calling_method_name());]
|
|
4694
4836
|
io.puts " }"
|
|
4695
4837
|
end
|
|
4696
|
-
io.puts " int axis =
|
|
4838
|
+
io.puts " int axis = (int) ca_axis_integer(vaxis, NULL);"
|
|
4697
4839
|
io.puts " if ( axis < 0 ) axis += src->ndim;"
|
|
4698
4840
|
io.puts " if ( axis < 0 || axis >= src->ndim ) {"
|
|
4699
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
4841
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: axis %d out of range for ndim %d", ca_calling_method_name(), NUM2INT(vaxis), src->ndim);]
|
|
4700
4842
|
io.puts " }"
|
|
4701
|
-
io.puts " ca_size_t
|
|
4843
|
+
io.puts " ca_size_t kth_raw = (ca_size_t) ca_integer_arg(vkth, \"kth\", NULL);"
|
|
4844
|
+
io.puts " ca_size_t kth = kth_raw;"
|
|
4702
4845
|
io.puts " ca_size_t axis_n = src->dim[axis];"
|
|
4703
4846
|
io.puts " if ( kth < 0 ) kth += axis_n;"
|
|
4704
4847
|
io.puts " if ( kth < 0 || kth >= axis_n ) {"
|
|
4705
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
4848
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: kth %lld out of range for length %lld", ca_calling_method_name(), (long long) kth_raw, (long long) axis_n);]
|
|
4706
4849
|
io.puts " }"
|
|
4707
4850
|
io.puts " switch ( src->data_type ) {"
|
|
4708
4851
|
k[:source].each do |s|
|
|
4709
4852
|
si = DTYPES[s]
|
|
4710
4853
|
native_extra = mask_sentinel ? ", masked_last" : ""
|
|
4711
|
-
io.puts " case #{si[:ca]}:
|
|
4854
|
+
io.puts " case #{si[:ca]}: CA_RETURN_KEEPING(self, #{name}_ki_native_#{s}(self, src, axis, kth#{native_extra}));"
|
|
4712
4855
|
end
|
|
4713
4856
|
io.puts " default:"
|
|
4714
|
-
io.puts %Q[
|
|
4857
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
4858
|
+
io.puts %Q[ rb_raise(rb_eCADataTypeError, "%s: source data_type :%s not supported (expected one of: #{MkKernel.type_names(k[:source])})", ca_calling_method_name(), ca_type_name[src->data_type]);]
|
|
4715
4859
|
io.puts " }"
|
|
4716
4860
|
io.puts " return Qnil; /* unreachable */"
|
|
4717
4861
|
io.puts "}"
|
|
4718
4862
|
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4863
|
+
unless mp_only
|
|
4864
|
+
io.puts
|
|
4865
|
+
io.puts "#{storage}VALUE"
|
|
4866
|
+
io.puts "rb_ca_#{name}_ki (VALUE self, VALUE vaxis, VALUE vkth)"
|
|
4867
|
+
io.puts "{"
|
|
4868
|
+
io.puts " return #{name}_ki_dispatch(self, vaxis, vkth#{mask_sentinel ? ", 1" : ""});"
|
|
4869
|
+
io.puts "}"
|
|
4870
|
+
end
|
|
4725
4871
|
|
|
4726
4872
|
if mask_sentinel && k[:bind_ruby] == false
|
|
4727
4873
|
# _mp twin: explicit masked_last, extern for carray_partition.c's
|
|
@@ -4739,10 +4885,10 @@ module MkKernel
|
|
|
4739
4885
|
# wrapper). When public_method: is set, emit a -1-argc
|
|
4740
4886
|
# entry that parses `axis:` (and `kind:` for has_kind, and
|
|
4741
4887
|
# `masked_position:` for mask_self: :sentinel, and `method:` for
|
|
4742
|
-
# :rank) kwarg with default
|
|
4743
|
-
# dispatches to the appropriate positional entry.
|
|
4744
|
-
# algorithm, kth is accepted as a single positional
|
|
4745
|
-
# before the kwarg.
|
|
4888
|
+
# :rank) kwarg with default nil (= whole array) / :quick / :last /
|
|
4889
|
+
# :ordinal and dispatches to the appropriate positional entry.
|
|
4890
|
+
# For :partition algorithm, kth is accepted as a single positional
|
|
4891
|
+
# argument before the kwarg.
|
|
4746
4892
|
if k[:public_method]
|
|
4747
4893
|
legacy_name = k[:public_method].to_s
|
|
4748
4894
|
io.puts
|
|
@@ -4769,7 +4915,21 @@ module MkKernel
|
|
|
4769
4915
|
scan_targets << "&rmasked_position" if mask_sentinel
|
|
4770
4916
|
scan_targets << "&rmethod" if rank
|
|
4771
4917
|
io.puts " rb_scan_options(ropt, \"#{scan_keys.join(",")}\", #{scan_targets.join(", ")});"
|
|
4772
|
-
|
|
4918
|
+
# No axis means the whole array, as for sort: flatten and walk the
|
|
4919
|
+
# single fiber. rank_index keeps self's shape (a rank belongs to a
|
|
4920
|
+
# cell, as for order); the position lists keep the flat shape.
|
|
4921
|
+
io.puts " volatile VALUE whole_src = Qnil;" if rank
|
|
4922
|
+
io.puts " if ( NIL_P(raxis) ) { /* no axis: the whole array */"
|
|
4923
|
+
io.puts " whole_src = self;" if rank
|
|
4924
|
+
# The flattened view of a lazy expression is not one itself, so the
|
|
4925
|
+
# dispatcher would not see an expression to hand over: ask here.
|
|
4926
|
+
io.puts " {"
|
|
4927
|
+
io.puts " volatile VALUE evaluated = ca_lazy_evaluated(self);"
|
|
4928
|
+
io.puts " if ( ! NIL_P(evaluated) ) self = evaluated;"
|
|
4929
|
+
io.puts " }"
|
|
4930
|
+
io.puts " self = rb_ca_flatten(self);"
|
|
4931
|
+
io.puts " raxis = INT2NUM(0);"
|
|
4932
|
+
io.puts " }"
|
|
4773
4933
|
if rank
|
|
4774
4934
|
# method: :ordinal (default) / :dense -> dense int. :ordinal
|
|
4775
4935
|
# assigns every cell a distinct rank (ties broken by original
|
|
@@ -4777,37 +4937,15 @@ module MkKernel
|
|
|
4777
4937
|
# the standard "dense rank" used to compose rank as a sort_addr
|
|
4778
4938
|
# priority key without silently dropping lower-priority keys on
|
|
4779
4939
|
# ties (see MASKED_POSITION's sibling proposal thread).
|
|
4780
|
-
io.puts " int dense = 0
|
|
4781
|
-
io.puts " if ( ! NIL_P(rmethod) ) {"
|
|
4782
|
-
io.puts " static ID sym_ordinal = 0, sym_dense = 0;"
|
|
4783
|
-
io.puts " if ( ! sym_ordinal ) sym_ordinal = rb_intern(\"ordinal\");"
|
|
4784
|
-
io.puts " if ( ! sym_dense ) sym_dense = rb_intern(\"dense\");"
|
|
4785
|
-
io.puts " ID method_id = SYM2ID(rmethod);"
|
|
4786
|
-
io.puts " if ( method_id == sym_ordinal ) dense = 0;"
|
|
4787
|
-
io.puts " else if ( method_id == sym_dense ) dense = 1;"
|
|
4788
|
-
io.puts " else {"
|
|
4789
|
-
io.puts %Q[ rb_raise(rb_eArgError, "#{legacy_name}: unknown method %s (expected :ordinal or :dense)", rb_id2name(method_id));]
|
|
4790
|
-
io.puts " }"
|
|
4791
|
-
io.puts " }"
|
|
4940
|
+
io.puts " int dense = NIL_P(rmethod) ? 0 : ca_symbol_choice(rmethod, \"method\", \"ordinal\", \"dense\", NULL);"
|
|
4792
4941
|
end
|
|
4793
4942
|
if mask_sentinel
|
|
4794
4943
|
# masked_position: :last (default) / :first -> masked_last int.
|
|
4795
4944
|
# Shared by both the has_kind and :partition branches below.
|
|
4796
|
-
io.puts " int masked_last = 1
|
|
4797
|
-
io.puts " if ( ! NIL_P(rmasked_position) ) {"
|
|
4798
|
-
io.puts " static ID sym_first = 0, sym_last = 0;"
|
|
4799
|
-
io.puts " if ( ! sym_first ) sym_first = rb_intern(\"first\");"
|
|
4800
|
-
io.puts " if ( ! sym_last ) sym_last = rb_intern(\"last\");"
|
|
4801
|
-
io.puts " ID mp_id = SYM2ID(rmasked_position);"
|
|
4802
|
-
io.puts " if ( mp_id == sym_last ) masked_last = 1;"
|
|
4803
|
-
io.puts " else if ( mp_id == sym_first ) masked_last = 0;"
|
|
4804
|
-
io.puts " else {"
|
|
4805
|
-
io.puts %Q[ rb_raise(rb_eArgError, "#{legacy_name}: unknown masked_position %s (expected :first or :last)", rb_id2name(mp_id));]
|
|
4806
|
-
io.puts " }"
|
|
4807
|
-
io.puts " }"
|
|
4945
|
+
io.puts " int masked_last = NIL_P(rmasked_position) ? 1 : ca_symbol_choice(rmasked_position, \"masked_position\", \"first\", \"last\", NULL);"
|
|
4808
4946
|
end
|
|
4809
4947
|
if partition
|
|
4810
|
-
# partition_index(kth, axis:
|
|
4948
|
+
# partition_index(kth, axis: nil, masked_position: :last) -- kth
|
|
4811
4949
|
# positional, axis/masked_position kwargs.
|
|
4812
4950
|
io.puts " if ( argc != 1 ) {"
|
|
4813
4951
|
io.puts %Q[ rb_raise(rb_eArgError, "#{legacy_name}: wrong number of positional arguments (given %d, expected 1: kth)", argc);]
|
|
@@ -4818,28 +4956,27 @@ module MkKernel
|
|
|
4818
4956
|
io.puts " return rb_ca_#{name}_ki(self, raxis, argv[0]);"
|
|
4819
4957
|
end
|
|
4820
4958
|
else
|
|
4821
|
-
# sort_index(axis:
|
|
4959
|
+
# sort_index(axis: nil, kind: :quick, masked_position: :last) --
|
|
4822
4960
|
# no positional, kwargs only.
|
|
4823
4961
|
io.puts " if ( argc != 0 ) {"
|
|
4824
4962
|
io.puts %Q[ rb_raise(rb_eArgError, "#{legacy_name}: positional args no longer accepted (given %d args); use axis:/kind: kwargs", argc);]
|
|
4825
4963
|
io.puts " }"
|
|
4826
4964
|
# kind: dispatch (:quick default, :stable optional).
|
|
4827
|
-
io.puts " int do_stable = 0;"
|
|
4828
|
-
io.puts " if ( ! NIL_P(rkind) ) {"
|
|
4829
|
-
io.puts " static ID sym_quick = 0, sym_stable = 0;"
|
|
4830
|
-
io.puts " if ( ! sym_quick ) sym_quick = rb_intern(\"quick\");"
|
|
4831
|
-
io.puts " if ( ! sym_stable ) sym_stable = rb_intern(\"stable\");"
|
|
4832
|
-
io.puts " ID kind_id = SYM2ID(rkind);"
|
|
4833
|
-
io.puts " if ( kind_id == sym_quick ) do_stable = 0;"
|
|
4834
|
-
io.puts " else if ( kind_id == sym_stable ) do_stable = 1;"
|
|
4835
|
-
io.puts " else {"
|
|
4836
|
-
io.puts %Q[ rb_raise(rb_eArgError, "#{legacy_name}: unknown kind %s (expected :quick or :stable)", rb_id2name(kind_id));]
|
|
4837
|
-
io.puts " }"
|
|
4838
|
-
io.puts " }"
|
|
4965
|
+
io.puts " int do_stable = NIL_P(rkind) ? 0 : ca_symbol_choice(rkind, \"kind\", \"quick\", \"stable\", NULL);"
|
|
4839
4966
|
if mask_sentinel
|
|
4840
4967
|
io.puts " return #{name}_ki_dispatch(self, raxis, do_stable, masked_last);"
|
|
4841
4968
|
elsif rank
|
|
4842
|
-
io.puts "
|
|
4969
|
+
io.puts " VALUE out = #{name}_ki_dispatch(self, raxis, do_stable, dense);"
|
|
4970
|
+
io.puts " if ( ! NIL_P(whole_src) ) {"
|
|
4971
|
+
io.puts " CArray *src;"
|
|
4972
|
+
io.puts " VALUE shape_argv[CA_RANK_MAX];"
|
|
4973
|
+
io.puts " GetCArray(whole_src, src);"
|
|
4974
|
+
io.puts " for ( int k = 0; k < src->ndim; k++ ) {"
|
|
4975
|
+
io.puts " shape_argv[k] = SIZE2NUM(src->dim[k]);"
|
|
4976
|
+
io.puts " }"
|
|
4977
|
+
io.puts " out = rb_ca_reshape((int) src->ndim, shape_argv, out);"
|
|
4978
|
+
io.puts " }"
|
|
4979
|
+
io.puts " return out;"
|
|
4843
4980
|
else
|
|
4844
4981
|
io.puts " return do_stable ? rb_ca_#{name}_ki_stable(self, raxis)"
|
|
4845
4982
|
io.puts " : rb_ca_#{name}_ki_quick(self, raxis);"
|
|
@@ -4880,7 +5017,7 @@ module MkKernel
|
|
|
4880
5017
|
io.puts
|
|
4881
5018
|
io.puts "/* ===== #{k[:name]}_ki ============================================ */"
|
|
4882
5019
|
k[:source].each do |src|
|
|
4883
|
-
|
|
5020
|
+
emit_native_for(io, k, src) { |o| emit_search_native(o, k, src) }
|
|
4884
5021
|
end
|
|
4885
5022
|
emit_search_dispatch(io, k)
|
|
4886
5023
|
end
|
|
@@ -4903,7 +5040,7 @@ module MkKernel
|
|
|
4903
5040
|
# body: Hash form picks per-data_type-kind branch (= :int / :float /
|
|
4904
5041
|
# :object / :fixlen); String form is uniform.
|
|
4905
5042
|
# PROPOSAL_MKKERNEL_OBJECT_DTYPE_BRANCH Phase 4: :object branch added for
|
|
4906
|
-
# CA_OBJECT (equality via
|
|
5043
|
+
# CA_OBJECT (equality via ca_obj_equal, ordering via rb_funcall(<=>)).
|
|
4907
5044
|
# PROPOSAL_SEARCH_SEMANTICS_UNIFY S2: :fixlen branch added for CA_FIXLEN
|
|
4908
5045
|
# (the cell is a runtime-width byte blob; comparison is memcmp over
|
|
4909
5046
|
# ca->bytes, the same total order fixlen bincmp uses).
|
|
@@ -4937,7 +5074,7 @@ module MkKernel
|
|
|
4937
5074
|
<<~C.chomp
|
|
4938
5075
|
int eps_is_default = NIL_P(reps);
|
|
4939
5076
|
double user_eps = eps_is_default ? 0.0 : NUM2DBL(reps);
|
|
4940
|
-
double query_eps = eps_is_default ? (double)(#{eps_def}) * fabs((double)query_val) : user_eps;
|
|
5077
|
+
double query_eps = eps_is_default ? (isfinite((double)query_val) ? (double)(#{eps_def}) * fabs((double)query_val) : 0.0) : user_eps;
|
|
4941
5078
|
C
|
|
4942
5079
|
elsif has_eps && no_eps
|
|
4943
5080
|
"(void) reps; /* #{is_fixlen ? "CA_FIXLEN" : "CA_OBJECT"}: eps ignored, exact match only */\n double query_eps = 0.0; (void) query_eps;"
|
|
@@ -4952,7 +5089,7 @@ module MkKernel
|
|
|
4952
5089
|
""
|
|
4953
5090
|
end
|
|
4954
5091
|
eps_percell = if has_eps && !no_eps
|
|
4955
|
-
"double query_eps = eps_is_default ? (double)(#{eps_def}) * fabs((double)query_val) : user_eps;"
|
|
5092
|
+
"double query_eps = eps_is_default ? (isfinite((double)query_val) ? (double)(#{eps_def}) * fabs((double)query_val) : 0.0) : user_eps;"
|
|
4956
5093
|
else
|
|
4957
5094
|
"double query_eps = 0.0; (void) query_eps;"
|
|
4958
5095
|
end
|
|
@@ -4964,14 +5101,36 @@ module MkKernel
|
|
|
4964
5101
|
# and let rb_ca_obj2ptr pack it; `query_val` is then a char* the memcmp
|
|
4965
5102
|
# body reads (mirrors the legacy flat bsearch ALLOCA_N path).
|
|
4966
5103
|
query_setup_a = if is_fixlen
|
|
4967
|
-
"
|
|
5104
|
+
"volatile VALUE query_val_holder = 0;\n" \
|
|
5105
|
+
" char *query_val = ALLOCV_N(char, query_val_holder, ca->bytes);\n" \
|
|
4968
5106
|
" rb_ca_obj2ptr(self, rval, query_val);"
|
|
4969
5107
|
else
|
|
5108
|
+
(INT_DTYPES.include?(src) ? "ca_check_integer_fits(rval, #{si[:ca]});\n " : "") +
|
|
4970
5109
|
"#{si[:c]} query_val_buf;\n" \
|
|
4971
5110
|
" rb_ca_obj2ptr(self, rval, &query_val_buf);\n" \
|
|
4972
5111
|
" #{si[:c]} query_val = query_val_buf;"
|
|
4973
5112
|
end
|
|
4974
5113
|
|
|
5114
|
+
# The object lane calls Ruby for every cell of the case B/C walk, which
|
|
5115
|
+
# runs between a direct attach and detach of self and val; a raise there
|
|
5116
|
+
# would leave both attached. An entity holds no attach, so the lane
|
|
5117
|
+
# reads an entity copy of whichever is a view.
|
|
5118
|
+
entity_bc = if is_object
|
|
5119
|
+
<<~C.chomp
|
|
5120
|
+
volatile VALUE vca_entity = self;
|
|
5121
|
+
if ( ! ca_is_entity(ca) ) {
|
|
5122
|
+
vca_entity = rb_ca_copy(self);
|
|
5123
|
+
GetCArray(vca_entity, ca);
|
|
5124
|
+
}
|
|
5125
|
+
if ( ! ca_is_entity(cv) ) {
|
|
5126
|
+
vsrc_val = rb_ca_copy(vsrc_val);
|
|
5127
|
+
GetCArray(vsrc_val, cv);
|
|
5128
|
+
}
|
|
5129
|
+
C
|
|
5130
|
+
else
|
|
5131
|
+
""
|
|
5132
|
+
end
|
|
5133
|
+
|
|
4975
5134
|
# case B/C query coercion. numeric/object: wrap-readonly to self's
|
|
4976
5135
|
# data_type when they differ. fixlen: require a CA_FIXLEN query of the
|
|
4977
5136
|
# same byte width (no scalar coercion exists for blobs), so the per-cell
|
|
@@ -4981,7 +5140,7 @@ module MkKernel
|
|
|
4981
5140
|
(void) vsrc_val; /* fixlen: no wrap-readonly coercion */
|
|
4982
5141
|
if ( cv->data_type != CA_FIXLEN || cv->bytes != ca->bytes ) {
|
|
4983
5142
|
rb_raise(rb_eArgError,
|
|
4984
|
-
"
|
|
5143
|
+
"%s: CA_FIXLEN query must be CA_FIXLEN of the same byte width (= %ld)", ca_calling_method_name(), (long) ca->bytes);
|
|
4985
5144
|
}
|
|
4986
5145
|
C
|
|
4987
5146
|
else
|
|
@@ -5140,7 +5299,7 @@ module MkKernel
|
|
|
5140
5299
|
slab_axes, 1, 0);
|
|
5141
5300
|
if ( rc != CA_ITER_OK ) {
|
|
5142
5301
|
rb_raise(rb_eRuntimeError,
|
|
5143
|
-
"
|
|
5302
|
+
"%s: kernel_iterator init failed rc=%d", ca_calling_method_name(), rc);
|
|
5144
5303
|
}
|
|
5145
5304
|
C
|
|
5146
5305
|
|
|
@@ -5222,7 +5381,7 @@ module MkKernel
|
|
|
5222
5381
|
# transformation in two more inner loops.
|
|
5223
5382
|
io.puts <<~C
|
|
5224
5383
|
/* view_flat semantics + CArray val: NotImpError (deferred). */
|
|
5225
|
-
rb_raise(rb_eNotImpError, "
|
|
5384
|
+
rb_raise(rb_eNotImpError, "%s: view_flat semantics with CArray val (broadcast) not yet supported", ca_calling_method_name());
|
|
5226
5385
|
C
|
|
5227
5386
|
end
|
|
5228
5387
|
|
|
@@ -5245,7 +5404,9 @@ module MkKernel
|
|
|
5245
5404
|
*/
|
|
5246
5405
|
CArray *cv;
|
|
5247
5406
|
GetCArray(rval, cv);
|
|
5248
|
-
|
|
5407
|
+
/* volatile: once the query is wrapped below, only cv is used, and
|
|
5408
|
+
the wrapper must stay reachable while its cells are read. */
|
|
5409
|
+
volatile VALUE vsrc_val = rval;
|
|
5249
5410
|
#{coerce_bc}
|
|
5250
5411
|
|
|
5251
5412
|
/* base_shape = self.shape with axis removed. base_to_self[bk] =
|
|
@@ -5280,13 +5441,13 @@ module MkKernel
|
|
|
5280
5441
|
if ( i == axis ) continue;
|
|
5281
5442
|
if ( cv->dim[i] != ca->dim[i] ) {
|
|
5282
5443
|
rb_raise(rb_eArgError,
|
|
5283
|
-
"
|
|
5444
|
+
"%s: val shape mismatch (A3 candidate, axis %d non-target dim[%d] = %ld != self.dim[%d] = %ld; expected scalar / [M] / base_shape (ndim>=2) / self.shape with axis dim free)", ca_calling_method_name(),
|
|
5284
5445
|
axis, (int) i, (long) cv->dim[i], (int) i, (long) ca->dim[i]);
|
|
5285
5446
|
}
|
|
5286
5447
|
}
|
|
5287
5448
|
if ( cv->dim[axis] < 1 ) {
|
|
5288
5449
|
rb_raise(rb_eArgError,
|
|
5289
|
-
"
|
|
5450
|
+
"%s: empty query (val.dim[%d] = 0)", ca_calling_method_name(), axis);
|
|
5290
5451
|
}
|
|
5291
5452
|
cv_form = CV_FORM_A3;
|
|
5292
5453
|
cv_form_set = 1;
|
|
@@ -5296,7 +5457,7 @@ module MkKernel
|
|
|
5296
5457
|
/* A2: 1-D shared M-query (M >= 1); placed above A2.5 in rev5 */
|
|
5297
5458
|
if ( cv->dim[0] < 1 ) {
|
|
5298
5459
|
rb_raise(rb_eArgError,
|
|
5299
|
-
"
|
|
5460
|
+
"%s: empty query (val.dim[0] = 0)", ca_calling_method_name());
|
|
5300
5461
|
}
|
|
5301
5462
|
cv_form = CV_FORM_A2;
|
|
5302
5463
|
cv_form_set = 1;
|
|
@@ -5317,7 +5478,7 @@ module MkKernel
|
|
|
5317
5478
|
|
|
5318
5479
|
if ( ! cv_form_set ) {
|
|
5319
5480
|
rb_raise(rb_eArgError,
|
|
5320
|
-
"
|
|
5481
|
+
"%s: val shape not accepted (val.ndim = %d; expected scalar / [M] / base_shape (ndim>=2, = %d) / self.shape with axis dim free (ndim = %d))", ca_calling_method_name(),
|
|
5321
5482
|
(int) cv->ndim, (int) base_ndim, (int) ca->ndim);
|
|
5322
5483
|
}
|
|
5323
5484
|
|
|
@@ -5352,9 +5513,9 @@ module MkKernel
|
|
|
5352
5513
|
#{oi[:c]} *op = (#{oi[:c]} *) co->ptr;
|
|
5353
5514
|
#{op_mask_decl}
|
|
5354
5515
|
|
|
5516
|
+
#{entity_bc}
|
|
5355
5517
|
/* Attach self + val; rev4 uses raw row-major byte addressing. */
|
|
5356
|
-
|
|
5357
|
-
ca_attach(cv);
|
|
5518
|
+
ca_attach_n(2, ca, cv); /* window: numeric bodies do not raise; the object lane reads entities */
|
|
5358
5519
|
|
|
5359
5520
|
/* self_byte_stride[j] = bytes to advance along self axis j (row-major). */
|
|
5360
5521
|
ca_size_t self_byte_stride[CA_RANK_MAX];
|
|
@@ -5482,25 +5643,18 @@ module MkKernel
|
|
|
5482
5643
|
io.puts "{"
|
|
5483
5644
|
io.puts " CArray *src;"
|
|
5484
5645
|
io.puts " VALUE self_ref = self; /* pre-strip Face, for to_comparable */"
|
|
5485
|
-
io.puts " int self_face_comparable = 0;"
|
|
5486
5646
|
io.puts " int self_was_face = 0;"
|
|
5487
5647
|
io.puts " GetCArray(self, src);"
|
|
5648
|
+
emit_lazy_evaluated(io)
|
|
5488
5649
|
io.puts " /* Face gate (self): descend a Face reference to storage so the"
|
|
5489
5650
|
io.puts " search runs on the numeric storage. ORDERABLE licenses the"
|
|
5490
|
-
io.puts " ordered/searchable descent;
|
|
5491
|
-
io.puts "
|
|
5492
|
-
io.puts " gate below). A unit-bearing Face (CATime) is ORDERABLE"
|
|
5493
|
-
io.puts " but not COMPARABLE, so its query is reconciled by to_comparable"
|
|
5494
|
-
io.puts " -- no per-Face Ruby override needed. */"
|
|
5651
|
+
io.puts " ordered/searchable descent; the query gate below brings the"
|
|
5652
|
+
io.puts " query into the same space. */"
|
|
5495
5653
|
io.puts " if ( ca_is_face(src) ) {"
|
|
5496
5654
|
io.puts " if ( ! ca_test_flag(src, CA_FLAG_FACE_ORDERABLE_STORAGE) ) {"
|
|
5497
|
-
io.puts %Q[
|
|
5498
|
-
io.puts %Q[ "#{name}_ki: Face-typed input (%s) is not orderable "]
|
|
5499
|
-
io.puts %Q[ "by storage; use ca.parent to descend to storage",]
|
|
5500
|
-
io.puts %Q[ rb_obj_classname(self));]
|
|
5655
|
+
io.puts %Q[ ca_face_refuse_order(ca_calling_method_name(), self, "order");]
|
|
5501
5656
|
io.puts " }"
|
|
5502
5657
|
io.puts " self_was_face = 1;"
|
|
5503
|
-
io.puts " self_face_comparable = ca_test_flag(src, CA_FLAG_FACE_COMPARABLE_STORAGE);"
|
|
5504
5658
|
io.puts " self = rb_ca_strip_face_value(self);"
|
|
5505
5659
|
io.puts " GetCArray(self, src);"
|
|
5506
5660
|
io.puts " }"
|
|
@@ -5514,64 +5668,61 @@ module MkKernel
|
|
|
5514
5668
|
io.puts " VALUE raxis = argv[1];"
|
|
5515
5669
|
io.puts " VALUE reps = (argc >= 3) ? argv[2] : Qnil;"
|
|
5516
5670
|
end
|
|
5517
|
-
io.puts " /* Query gate
|
|
5518
|
-
io.puts "
|
|
5519
|
-
io.puts "
|
|
5520
|
-
io.puts "
|
|
5521
|
-
io.puts "
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
io.puts "
|
|
5532
|
-
io.puts "
|
|
5533
|
-
io.puts "
|
|
5534
|
-
io.puts "
|
|
5535
|
-
io.puts " if (
|
|
5536
|
-
io.puts "
|
|
5537
|
-
io.puts
|
|
5538
|
-
io.puts "
|
|
5539
|
-
io.puts "
|
|
5540
|
-
io.puts "
|
|
5541
|
-
io.puts " rval = rb_funcall(self_ref, rb_intern(\"to_comparable\"), 1, rval);"
|
|
5542
|
-
io.puts " rval = rb_ca_strip_face_value(rval);"
|
|
5543
|
-
io.puts " } else {"
|
|
5544
|
-
io.puts %Q[ rb_raise(rb_eArgError,]
|
|
5545
|
-
io.puts %Q[ "#{name}_ki: non-comparable Face reference (%s) has no "]
|
|
5546
|
-
io.puts %Q[ "to_comparable to reconcile the query; use ca.parent "]
|
|
5547
|
-
io.puts %Q[ "to search the hidden storage explicitly",]
|
|
5548
|
-
io.puts %Q[ rb_obj_classname(self_ref));]
|
|
5671
|
+
io.puts " /* Query gate: the reference Face reconciles the query into its own"
|
|
5672
|
+
io.puts " space (ca_face_reconcile_operand). */"
|
|
5673
|
+
io.puts " if ( self_was_face ) {"
|
|
5674
|
+
io.puts " rval = ca_face_reconcile_operand(self_ref, rval, ca_calling_method_name());"
|
|
5675
|
+
io.puts " }"
|
|
5676
|
+
|
|
5677
|
+
# The query and the reference are compared in the type they share, as
|
|
5678
|
+
# `a.eq(q)` compares, not with the query cast to the reference's type:
|
|
5679
|
+
# 3.9 must not match 3, a float64 query must not be rounded to a float32
|
|
5680
|
+
# reference, and -1 must not read as 255. A scalar takes the type the
|
|
5681
|
+
# operators give it (ca_promote_scalar_type); a CArray query the type
|
|
5682
|
+
# the two match in (ca_value_match_type). The reference is read through
|
|
5683
|
+
# a cast view of that type. An Integer scalar keeps the reference's type
|
|
5684
|
+
# and is refused when it does not fit.
|
|
5685
|
+
io.puts " if ( ca_is_integer_type(src) || ca_is_float_type(src) || src->data_type == CA_BOOLEAN ) {"
|
|
5686
|
+
io.puts " int8_t qt = src->data_type;"
|
|
5687
|
+
io.puts " if ( RB_FLOAT_TYPE_P(rval) ) {"
|
|
5688
|
+
io.puts " qt = ca_promote_scalar_type(src->data_type, rval, CA_FLOAT64);"
|
|
5689
|
+
io.puts " } else if ( rb_obj_is_kind_of(rval, rb_cCArray) ) {"
|
|
5690
|
+
io.puts " CArray *qv;"
|
|
5691
|
+
io.puts " TypedData_Get_Struct(rval, CArray, &carray_data_type, qv);"
|
|
5692
|
+
io.puts " if ( ca_is_integer_type(qv) || ca_is_float_type(qv) || qv->data_type == CA_BOOLEAN ) {"
|
|
5693
|
+
io.puts " qt = ca_value_match_type(ca_promote_type(src->data_type, qv->data_type),"
|
|
5694
|
+
io.puts " src->data_type, qv->data_type);"
|
|
5549
5695
|
io.puts " }"
|
|
5550
5696
|
io.puts " }"
|
|
5697
|
+
io.puts " if ( qt != src->data_type ) {"
|
|
5698
|
+
io.puts " self = rb_ca_wrap_readonly(self, INT2NUM(qt));"
|
|
5699
|
+
io.puts " GetCArray(self, src);"
|
|
5700
|
+
io.puts " }"
|
|
5551
5701
|
io.puts " }"
|
|
5552
5702
|
|
|
5553
5703
|
case k[:mask_self]
|
|
5554
5704
|
when :raise
|
|
5555
5705
|
io.puts " /* mask_self: :raise -- global reject if self has any masked element. */"
|
|
5556
5706
|
io.puts " if ( ca_is_any_masked(src) ) {"
|
|
5557
|
-
io.puts %Q[ rb_raise(
|
|
5707
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: self should not have any masked elements", ca_calling_method_name());]
|
|
5558
5708
|
io.puts " }"
|
|
5559
5709
|
when :skip, :ignore
|
|
5560
5710
|
# body handles mask_in or ignores it
|
|
5561
5711
|
end
|
|
5562
5712
|
|
|
5563
|
-
io.puts " int axis =
|
|
5713
|
+
io.puts " int axis = (int) ca_axis_integer(raxis, NULL);"
|
|
5564
5714
|
io.puts " if ( axis < 0 ) axis += src->ndim;"
|
|
5565
5715
|
io.puts " if ( axis < 0 || axis >= src->ndim ) {"
|
|
5566
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
5716
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: axis %d out of range for ndim %d", ca_calling_method_name(), NUM2INT(raxis), src->ndim);]
|
|
5567
5717
|
io.puts " }"
|
|
5568
5718
|
io.puts " switch ( src->data_type ) {"
|
|
5569
5719
|
k[:source].each do |s|
|
|
5570
5720
|
si = DTYPES[s]
|
|
5571
|
-
io.puts " case #{si[:ca]}:
|
|
5721
|
+
io.puts " case #{si[:ca]}: CA_RETURN_KEEPING(self, #{name}_ki_native_#{s}(self, src, rval, axis#{extra_arg}));"
|
|
5572
5722
|
end
|
|
5573
5723
|
io.puts " default:"
|
|
5574
|
-
io.puts %Q[
|
|
5724
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
5725
|
+
io.puts %Q[ rb_raise(rb_eCADataTypeError, "%s: source data_type :%s not supported (expected one of: #{MkKernel.type_names(k[:source])})", ca_calling_method_name(), ca_type_name[src->data_type]);]
|
|
5575
5726
|
io.puts " }"
|
|
5576
5727
|
io.puts " return Qnil; /* unreachable */"
|
|
5577
5728
|
io.puts "}"
|
|
@@ -5821,11 +5972,15 @@ module MkKernel
|
|
|
5821
5972
|
int8_t in_dt;
|
|
5822
5973
|
TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
|
|
5823
5974
|
in_dt = ca->data_type;
|
|
5975
|
+
if ( ca_monop_#{name}_out_data_type[in_dt] <= CA_NONE ) {
|
|
5976
|
+
rb_raise(rb_eCADataTypeError,
|
|
5977
|
+
"#{name}!: source data_type :%s not supported", ca_type_name[in_dt]);
|
|
5978
|
+
}
|
|
5824
5979
|
if ( ca_monop_#{name}_out_data_type[in_dt] != in_dt ) {
|
|
5825
|
-
rb_raise(
|
|
5826
|
-
"#{name}!:
|
|
5827
|
-
"
|
|
5828
|
-
|
|
5980
|
+
rb_raise(rb_eCADataTypeError,
|
|
5981
|
+
"#{name}!: #{name} of :%s is :%s, which cannot be written back in place; "
|
|
5982
|
+
"use #{name}", ca_type_name[in_dt],
|
|
5983
|
+
ca_type_name[ca_monop_#{name}_out_data_type[in_dt]]);
|
|
5829
5984
|
}
|
|
5830
5985
|
return rb_ca_call_monop_bang(self, ca_monop_#{name});
|
|
5831
5986
|
}
|
|
@@ -6079,6 +6234,10 @@ module MkKernel
|
|
|
6079
6234
|
io.print <<~C
|
|
6080
6235
|
VALUE rb_ca_#{name} (VALUE self, VALUE other2, VALUE other3)
|
|
6081
6236
|
{ return rb_ca_call_triop(self, other2, other3, ca_triop_#{name}); }
|
|
6237
|
+
C
|
|
6238
|
+
# bang: false kernels get no bang method, so no bang entry either.
|
|
6239
|
+
return if k[:bang] == false
|
|
6240
|
+
io.print <<~C
|
|
6082
6241
|
|
|
6083
6242
|
VALUE rb_ca_#{name}_bang (VALUE self, VALUE other2, VALUE other3)
|
|
6084
6243
|
{ return rb_ca_call_triop_bang(self, other2, other3, ca_triop_#{name}); }
|
|
@@ -6350,15 +6509,56 @@ module MkKernel
|
|
|
6350
6509
|
# caller substitutes its own operands. Object-lane bodies are left out,
|
|
6351
6510
|
# since they call back into the interpreter and cannot be compiled apart
|
|
6352
6511
|
# from it.
|
|
6512
|
+
#
|
|
6513
|
+
# A comparison is keyed by the data type it compares, not by what it
|
|
6514
|
+
# answers, which is always boolean.
|
|
6353
6515
|
# ---------------------------------------------------------------------
|
|
6354
6516
|
|
|
6355
6517
|
BODY_TABLE_DTYPES = %i[i8 u8 i16 u16 i32 u32 i64 u64 f32 f64
|
|
6356
6518
|
bool cmplx64 cmplx128].freeze
|
|
6357
6519
|
|
|
6520
|
+
# Kernels written outside this generator that a lazy expression can still
|
|
6521
|
+
# hold. A Float or Complex array to an Integer power is CABinOp's ipow
|
|
6522
|
+
# (ext/ca_op_ipower.c, which applies op_powi_<type> cell by cell); its
|
|
6523
|
+
# body is that call.
|
|
6524
|
+
EXTRA_BODY_ROWS = %w[float32 float64 cmplx64 cmplx128].map { |t|
|
|
6525
|
+
["binop", "ipow", t, "(#3) = op_powi_<type>((#1), (#2));"]
|
|
6526
|
+
}.freeze
|
|
6527
|
+
|
|
6528
|
+
# The helpers the bodies call that are not in the C standard library:
|
|
6529
|
+
# the part of ca_op_powi.h between its markers, as it stands.
|
|
6530
|
+
def self.kernel_helpers
|
|
6531
|
+
text = File.read(File.join(__dir__, "ca_op_powi.h"))
|
|
6532
|
+
text[%r{/\* BEGIN kernel helpers \*/\n(.*)/\* END kernel helpers \*/}m, 1] or
|
|
6533
|
+
raise "ca_op_powi.h: kernel helper markers not found"
|
|
6534
|
+
end
|
|
6535
|
+
|
|
6536
|
+
# A lazy cast node (CAMonOp with op_id CAST_BASE + target) converts cell
|
|
6537
|
+
# by cell through ca_cast_block, which for a numeric target is a plain C
|
|
6538
|
+
# conversion (ext/carray_cast_func.rb). It is keyed as cast_<target> at
|
|
6539
|
+
# the source's data type. Left out: a boolean target, which checks its
|
|
6540
|
+
# input and raises, and a float or complex source to an integer target,
|
|
6541
|
+
# where an out-of-range value or NaN has no defined C result for a
|
|
6542
|
+
# second compilation to agree on.
|
|
6543
|
+
CAST_SOURCES = %i[bool i8 u8 i16 u16 i32 u32 i64 u64 f32 f64
|
|
6544
|
+
cmplx64 cmplx128].freeze
|
|
6545
|
+
CAST_TARGETS = %i[i8 u8 i16 u16 i32 u32 i64 u64 f32 f64
|
|
6546
|
+
cmplx64 cmplx128].freeze
|
|
6547
|
+
CAST_INEXACT = %i[f32 f64 cmplx64 cmplx128].freeze
|
|
6548
|
+
|
|
6549
|
+
def self.cast_body_rows
|
|
6550
|
+
CAST_SOURCES.product(CAST_TARGETS).filter_map { |src, dst|
|
|
6551
|
+
next if CAST_INEXACT.include?(src) && !CAST_INEXACT.include?(dst)
|
|
6552
|
+
name = DTYPES[dst][:ca].sub(/\ACA_/, "").downcase
|
|
6553
|
+
["monop", "cast_#{name}", DTYPES[src][:ca].sub(/\ACA_/, "").downcase,
|
|
6554
|
+
"(#2) = (#{DTYPES[dst][:c]})(#1);"]
|
|
6555
|
+
}
|
|
6556
|
+
end
|
|
6557
|
+
|
|
6358
6558
|
def self.body_table_rows
|
|
6359
|
-
rows =
|
|
6559
|
+
rows = EXTRA_BODY_ROWS.map(&:dup) + cast_body_rows
|
|
6360
6560
|
KERNELS.each do |k|
|
|
6361
|
-
next unless %i[monop binop triop].include?(k[:kind])
|
|
6561
|
+
next unless %i[monop binop triop moncmp bincmp].include?(k[:kind])
|
|
6362
6562
|
BODY_TABLE_DTYPES.each do |src|
|
|
6363
6563
|
body = monop_expr_for(k, src)
|
|
6364
6564
|
next unless body.is_a?(String)
|
|
@@ -6370,8 +6570,10 @@ module MkKernel
|
|
|
6370
6570
|
rows
|
|
6371
6571
|
end
|
|
6372
6572
|
|
|
6573
|
+
# The block forms keep gsub from reading the backslashes in the
|
|
6574
|
+
# replacement as references to the match.
|
|
6373
6575
|
def self.c_string_literal(text)
|
|
6374
|
-
'"' + text.gsub("\\"
|
|
6576
|
+
'"' + text.gsub("\\") { "\\\\" }.gsub('"') { '\\"' }.gsub("\n") { '\\n' } + '"'
|
|
6375
6577
|
end
|
|
6376
6578
|
|
|
6377
6579
|
def self.emit_kernel_bodies(io)
|
|
@@ -6420,6 +6622,17 @@ module MkKernel
|
|
|
6420
6622
|
}
|
|
6421
6623
|
return Qnil;
|
|
6422
6624
|
}
|
|
6625
|
+
|
|
6626
|
+
/* CArray.__kernel_helpers__ -> String: the C a body above may call
|
|
6627
|
+
beyond the standard library, for the caller to put ahead of it. */
|
|
6628
|
+
static const char ca_kernel_helpers[] =
|
|
6629
|
+
#{c_string_literal(kernel_helpers)};
|
|
6630
|
+
|
|
6631
|
+
static VALUE
|
|
6632
|
+
rb_ca_s_kernel_helpers (VALUE klass)
|
|
6633
|
+
{
|
|
6634
|
+
return rb_str_new_cstr(ca_kernel_helpers);
|
|
6635
|
+
}
|
|
6423
6636
|
C
|
|
6424
6637
|
end
|
|
6425
6638
|
|
|
@@ -6441,6 +6654,8 @@ module MkKernel
|
|
|
6441
6654
|
end
|
|
6442
6655
|
io.puts " rb_define_singleton_method(rb_cCArray, \"__kernel_body__\","
|
|
6443
6656
|
io.puts " rb_ca_s_kernel_body, 3);"
|
|
6657
|
+
io.puts " rb_define_singleton_method(rb_cCArray, \"__kernel_helpers__\","
|
|
6658
|
+
io.puts " rb_ca_s_kernel_helpers, 0);"
|
|
6444
6659
|
io.puts "}"
|
|
6445
6660
|
end
|
|
6446
6661
|
|
|
@@ -6455,6 +6670,8 @@ module MkKernel
|
|
|
6455
6670
|
KERNELS.each { |k| emit_init_line(io, k) }
|
|
6456
6671
|
io.puts " rb_define_singleton_method(rb_cCArray, \"__kernel_body__\","
|
|
6457
6672
|
io.puts " rb_ca_s_kernel_body, 3);"
|
|
6673
|
+
io.puts " rb_define_singleton_method(rb_cCArray, \"__kernel_helpers__\","
|
|
6674
|
+
io.puts " rb_ca_s_kernel_helpers, 0);"
|
|
6458
6675
|
io.puts "}"
|
|
6459
6676
|
end
|
|
6460
6677
|
|
|
@@ -6515,7 +6732,6 @@ module MkKernel
|
|
|
6515
6732
|
end
|
|
6516
6733
|
arity = case k[:kind]
|
|
6517
6734
|
when :reduce then -1 # axis: kwarg + optional weights/value positional
|
|
6518
|
-
when :map then 0 # no args
|
|
6519
6735
|
when :scan then -1 # axis: kwarg (Integer required)
|
|
6520
6736
|
when :sort
|
|
6521
6737
|
# SO.3+ (rev8): :partition algorithm takes (axis, kth) = arity 2
|
|
@@ -6540,7 +6756,7 @@ module MkKernel
|
|
|
6540
6756
|
# reduce: default false (E.8 retire), opt-in via bind_ruby: true
|
|
6541
6757
|
# (used by CF.1 count_equal smoke + future kernel testing)
|
|
6542
6758
|
# sort: default true, opt-out via bind_ruby: false
|
|
6543
|
-
#
|
|
6759
|
+
# scan: always bound (no opt-out)
|
|
6544
6760
|
bind = case k[:kind]
|
|
6545
6761
|
when :reduce then k[:bind_ruby] == true
|
|
6546
6762
|
when :sort then k[:bind_ruby] != false
|
|
@@ -6642,7 +6858,7 @@ MkKernel.reduce :min,
|
|
|
6642
6858
|
# bool acc is u64 (numeric output); cast the boolean8_t
|
|
6643
6859
|
# load to match and avoid a signed/unsigned compare.
|
|
6644
6860
|
bool: "acc = ((uint64_t) v < acc) ? (uint64_t) v : acc",
|
|
6645
|
-
object: 'if (acc == Qundef
|
|
6861
|
+
object: 'if (acc == Qundef || ca_obj_extremum_replaces(v, acc, rb_intern("<"))) acc = v;' },
|
|
6646
6862
|
reduction_kind: :min, # SL.1.2
|
|
6647
6863
|
all_nan_result: :nan,
|
|
6648
6864
|
# CA_FIXLEN: memcmp lexicographic min (byte order == the fixlen sort
|
|
@@ -6655,8 +6871,8 @@ MkKernel.reduce :min,
|
|
|
6655
6871
|
fallback: :raise,
|
|
6656
6872
|
mask_policy: :min_count, # Phase E
|
|
6657
6873
|
# An ORDERABLE Face descends to numeric storage, then the result is
|
|
6658
|
-
# re-lifted into the Face (scalar via
|
|
6659
|
-
#
|
|
6874
|
+
# re-lifted into the Face (scalar via storage_to_element, per-axis via
|
|
6875
|
+
# ca_face_relift_entity). This replaces per-Face lib overrides.
|
|
6660
6876
|
face_gate: :relift,
|
|
6661
6877
|
public_method: true # Phase E: rebind "min" -> rb_ca_min_ki
|
|
6662
6878
|
|
|
@@ -6667,7 +6883,7 @@ MkKernel.reduce :max,
|
|
|
6667
6883
|
# 0/1); the boolean-returning twin is `any` (= bool max).
|
|
6668
6884
|
reduce: { numeric: "acc = (v > acc) ? v : acc",
|
|
6669
6885
|
bool: "acc = ((uint64_t) v > acc) ? (uint64_t) v : acc",
|
|
6670
|
-
object: 'if (acc == Qundef
|
|
6886
|
+
object: 'if (acc == Qundef || ca_obj_extremum_replaces(v, acc, rb_intern(">"))) acc = v;' },
|
|
6671
6887
|
reduction_kind: :max, # SL.1.2
|
|
6672
6888
|
all_nan_result: :nan,
|
|
6673
6889
|
# CA_FIXLEN: memcmp lexicographic max (byte order == the fixlen sort order).
|
|
@@ -6680,20 +6896,6 @@ MkKernel.reduce :max,
|
|
|
6680
6896
|
face_gate: :relift,
|
|
6681
6897
|
public_method: true # Phase E: rebind "max" -> rb_ca_max_ki
|
|
6682
6898
|
|
|
6683
|
-
# count_ki — count unmasked elements per slab. The macro skips masked
|
|
6684
|
-
# cells, so REDUCE fires only on contributing cells; `acc += 1` ignores
|
|
6685
|
-
# the value entirely (cast to void to silence unused-variable warnings).
|
|
6686
|
-
# When the input has no mask the macro takes a faster path that loads
|
|
6687
|
-
# `v` but our REDUCE doesn't use it -- compiler optimises the load away.
|
|
6688
|
-
MkKernel.reduce :count,
|
|
6689
|
-
init: "0",
|
|
6690
|
-
reduce: "(void)v; acc += 1",
|
|
6691
|
-
reduction_kind: :plus, # SL.1.4
|
|
6692
|
-
source: MkKernel::ALL_NUMERIC,
|
|
6693
|
-
output: :i64,
|
|
6694
|
-
ruby_scalar: :LL2NUM,
|
|
6695
|
-
fallback: :wrap_to_f64
|
|
6696
|
-
|
|
6697
6899
|
# all / any reduce: bool input -> bool output, axis-aware. Replaces the
|
|
6698
6900
|
# flat-only hand-written rb_ca_all_equal_p / rb_ca_any_equal_p /
|
|
6699
6901
|
# all_close? / any_close? / all_equiv? / any_equiv? in carray_stat.c
|
|
@@ -6710,6 +6912,15 @@ MkKernel.reduce :count,
|
|
|
6710
6912
|
# modern CPUs. For dramatically lopsided inputs (= mostly-false `all`
|
|
6711
6913
|
# or mostly-true `any`) a profile-driven early-break variant can be
|
|
6712
6914
|
# added later as a separate kernel.
|
|
6915
|
+
MkKernel.header_block <<~C
|
|
6916
|
+
/* The square root of a variance: an ε-negative from rounding becomes 0,
|
|
6917
|
+
a NaN stays NaN (fmax(NaN, 0.0) is 0.0). */
|
|
6918
|
+
#ifndef CARRAY_SQRT_CLAMP_DEFINED
|
|
6919
|
+
#define CARRAY_SQRT_CLAMP_DEFINED
|
|
6920
|
+
static inline double ca_sqrt_clamp (double v) { return sqrt(v < 0.0 ? 0.0 : v); }
|
|
6921
|
+
#endif
|
|
6922
|
+
C
|
|
6923
|
+
|
|
6713
6924
|
MkKernel.header_block <<~C
|
|
6714
6925
|
/* BOOL2VAL: bool -> Ruby (Qtrue/Qfalse). Used as ruby_scalar wrapper
|
|
6715
6926
|
for the all/any flat-reduction Ruby surface so `a.all` / `a.any`
|
|
@@ -6893,7 +7104,7 @@ MkKernel.reduce :minmax,
|
|
|
6893
7104
|
# CA_OBJECT minmax via Qundef sentinel + first-cell-init.
|
|
6894
7105
|
# One sentinel guards both lo + hi (they go Qundef
|
|
6895
7106
|
# together on init, get set together on first reduce).
|
|
6896
|
-
object: 'if (lo == Qundef) { lo = v; hi = v; } else { if (
|
|
7107
|
+
object: 'if (lo == Qundef) { lo = v; hi = v; } else { if (ca_obj_extremum_replaces(v, lo, rb_intern("<"))) lo = v; if (ca_obj_extremum_replaces(v, hi, rb_intern(">"))) hi = v; }' },
|
|
6897
7108
|
outputs: 2,
|
|
6898
7109
|
all_nan_result: :nan,
|
|
6899
7110
|
finish: { min: "lo", max: "hi" },
|
|
@@ -6904,6 +7115,7 @@ MkKernel.reduce :minmax,
|
|
|
6904
7115
|
ruby_scalar: :auto,
|
|
6905
7116
|
fallback: :raise,
|
|
6906
7117
|
mask_policy: :min_count, # FM.1.5: match existing min / max behaviour
|
|
7118
|
+
face_gate: :relift,
|
|
6907
7119
|
public_method: :minmax # bind public name `minmax` -> rb_ca_minmax_ki
|
|
6908
7120
|
# (= surface-name override, no actual legacy)
|
|
6909
7121
|
|
|
@@ -6925,14 +7137,19 @@ MkKernel.reduce :minmax,
|
|
|
6925
7137
|
# replace the legacy `min_addr` / `max_addr` retired in E.7 stat_proc
|
|
6926
7138
|
# retire (commit f5c7ecd). 3.0 breaking: name change from `*_addr` to
|
|
6927
7139
|
# `*_index` is intentional.
|
|
7140
|
+
#
|
|
7141
|
+
# best_i starts at -1 (no cell taken yet), so the first unmasked non-NaN
|
|
7142
|
+
# cell is taken even when it equals the starting limit (an all-+Infinity
|
|
7143
|
+
# or all-INT_MAX fiber). `v == v` keeps a NaN from being taken first; a
|
|
7144
|
+
# fiber of NaN only is left to the all-NaN fixup (UNDEF).
|
|
6928
7145
|
|
|
6929
7146
|
MkKernel.reduce :argmin,
|
|
6930
7147
|
state: { best_v: :load_type, best_i: :int64_t },
|
|
6931
7148
|
init: { best_v: { numeric: "T_LIMIT_HI", bool: "T_LIMIT_HI", object: "Qundef" },
|
|
6932
|
-
best_i: "
|
|
6933
|
-
reduce: { numeric: "if (v < best_v) { best_v = v; best_i = idx; }",
|
|
6934
|
-
bool: "if (v < best_v) { best_v = v; best_i = idx; }",
|
|
6935
|
-
object: 'if (
|
|
7149
|
+
best_i: "-1" },
|
|
7150
|
+
reduce: { numeric: "if (v < best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7151
|
+
bool: "if (v < best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7152
|
+
object: 'if (!CA_OBJ_ISNAN(v) && (best_v == Qundef || ca_obj_cmp(v, best_v) < 0)) { best_v = v; best_i = idx; }' },
|
|
6936
7153
|
finish: "best_i",
|
|
6937
7154
|
# CA_FIXLEN: position of the memcmp-lexicographic min (first-wins on ties).
|
|
6938
7155
|
fixlen: :argmin,
|
|
@@ -6950,10 +7167,10 @@ MkKernel.reduce :argmin,
|
|
|
6950
7167
|
MkKernel.reduce :argmax,
|
|
6951
7168
|
state: { best_v: :load_type, best_i: :int64_t },
|
|
6952
7169
|
init: { best_v: { numeric: "T_LIMIT_LO", bool: "T_LIMIT_LO", object: "Qundef" },
|
|
6953
|
-
best_i: "
|
|
6954
|
-
reduce: { numeric: "if (v > best_v) { best_v = v; best_i = idx; }",
|
|
6955
|
-
bool: "if (v > best_v) { best_v = v; best_i = idx; }",
|
|
6956
|
-
object: 'if (
|
|
7170
|
+
best_i: "-1" },
|
|
7171
|
+
reduce: { numeric: "if (v > best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7172
|
+
bool: "if (v > best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7173
|
+
object: 'if (!CA_OBJ_ISNAN(v) && (best_v == Qundef || ca_obj_cmp(v, best_v) > 0)) { best_v = v; best_i = idx; }' },
|
|
6957
7174
|
finish: "best_i",
|
|
6958
7175
|
# CA_FIXLEN: position of the memcmp-lexicographic max (first-wins on ties).
|
|
6959
7176
|
fixlen: :argmax,
|
|
@@ -6995,10 +7212,10 @@ MkKernel.reduce :argmax,
|
|
|
6995
7212
|
MkKernel.reduce :argmin_addr,
|
|
6996
7213
|
state: { best_v: :load_type, best_i: :int64_t },
|
|
6997
7214
|
init: { best_v: { numeric: "T_LIMIT_HI", bool: "T_LIMIT_HI", object: "Qundef" },
|
|
6998
|
-
best_i: "
|
|
6999
|
-
reduce: { numeric: "if (v < best_v) { best_v = v; best_i = idx; }",
|
|
7000
|
-
bool: "if (v < best_v) { best_v = v; best_i = idx; }",
|
|
7001
|
-
object: 'if (
|
|
7215
|
+
best_i: "-1" },
|
|
7216
|
+
reduce: { numeric: "if (v < best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7217
|
+
bool: "if (v < best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7218
|
+
object: 'if (!CA_OBJ_ISNAN(v) && (best_v == Qundef || ca_obj_cmp(v, best_v) < 0)) { best_v = v; best_i = idx; }' },
|
|
7002
7219
|
finish: "best_i",
|
|
7003
7220
|
source: MkKernel::ALL_NUMERIC + [:bool, :object],
|
|
7004
7221
|
output: :i64,
|
|
@@ -7013,10 +7230,10 @@ MkKernel.reduce :argmin_addr,
|
|
|
7013
7230
|
MkKernel.reduce :argmax_addr,
|
|
7014
7231
|
state: { best_v: :load_type, best_i: :int64_t },
|
|
7015
7232
|
init: { best_v: { numeric: "T_LIMIT_LO", bool: "T_LIMIT_LO", object: "Qundef" },
|
|
7016
|
-
best_i: "
|
|
7017
|
-
reduce: { numeric: "if (v > best_v) { best_v = v; best_i = idx; }",
|
|
7018
|
-
bool: "if (v > best_v) { best_v = v; best_i = idx; }",
|
|
7019
|
-
object: 'if (
|
|
7233
|
+
best_i: "-1" },
|
|
7234
|
+
reduce: { numeric: "if (v > best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7235
|
+
bool: "if (v > best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7236
|
+
object: 'if (!CA_OBJ_ISNAN(v) && (best_v == Qundef || ca_obj_cmp(v, best_v) > 0)) { best_v = v; best_i = idx; }' },
|
|
7020
7237
|
finish: "best_i",
|
|
7021
7238
|
source: MkKernel::ALL_NUMERIC + [:bool, :object],
|
|
7022
7239
|
output: :i64,
|
|
@@ -7028,36 +7245,6 @@ MkKernel.reduce :argmax_addr,
|
|
|
7028
7245
|
all_nan_result: :undef,
|
|
7029
7246
|
public_method: :max_addr
|
|
7030
7247
|
|
|
7031
|
-
# ---- mask_policy demos ------------------------------------------------
|
|
7032
|
-
|
|
7033
|
-
# sum_strict_ki -- :strict policy. Any masked input cell makes the
|
|
7034
|
-
# output cell UNDEF. Use case: "this is sensor data, any gap invalidates
|
|
7035
|
-
# the aggregate" (= NaN-propagation semantics). Sibling of
|
|
7036
|
-
# sum_ki which silently treats masked as 0.
|
|
7037
|
-
MkKernel.reduce :sum_strict,
|
|
7038
|
-
init: "0",
|
|
7039
|
-
reduce: "acc += v",
|
|
7040
|
-
source: MkKernel::ALL_NUMERIC + MkKernel::CMPLX_DTYPES,
|
|
7041
|
-
output: { numeric: :f64, complex: :cmplx128 },
|
|
7042
|
-
ruby_scalar: :auto,
|
|
7043
|
-
fallback: :raise,
|
|
7044
|
-
mask_policy: :strict
|
|
7045
|
-
|
|
7046
|
-
# mean_safe_ki -- :all_masked policy. Output UNDEF only when every
|
|
7047
|
-
# input cell is masked, otherwise mean over visible cells. Use case:
|
|
7048
|
-
# "tolerate gaps but flag if nothing is observed". Sibling of mean_ki
|
|
7049
|
-
# which returns 0.0 for all-masked slabs.
|
|
7050
|
-
MkKernel.reduce :mean_safe,
|
|
7051
|
-
state: { acc: :acc_type, cnt: :int64_t },
|
|
7052
|
-
init: { acc: "0", cnt: "0" },
|
|
7053
|
-
reduce: "(acc += v, cnt++)",
|
|
7054
|
-
finish: "cnt ? acc / (T_OUT) cnt : 0",
|
|
7055
|
-
source: MkKernel::ALL_NUMERIC + MkKernel::CMPLX_DTYPES,
|
|
7056
|
-
output: { numeric: :f64, complex: :cmplx128 },
|
|
7057
|
-
ruby_scalar: :auto,
|
|
7058
|
-
fallback: :raise,
|
|
7059
|
-
mask_policy: :all_masked
|
|
7060
|
-
|
|
7061
7248
|
# accumulate -- sum that preserves input data_type (legacy rb_ca_accum,
|
|
7062
7249
|
# retired in E.7). Same machinery as sum_ki but output: :preserve so
|
|
7063
7250
|
# int8 stays int8 (will overflow on long arrays = legacy behaviour).
|
|
@@ -7153,10 +7340,11 @@ MkKernel.reduce :count_false,
|
|
|
7153
7340
|
|
|
7154
7341
|
MkKernel.reduce :count_equal,
|
|
7155
7342
|
init: "0",
|
|
7156
|
-
# The object lane compares with
|
|
7157
|
-
# on two VALUEs would ask whether they are
|
|
7343
|
+
# The object lane compares with ca_obj_equal (= Ruby ==, a NaN equal to
|
|
7344
|
+
# nothing), not the C ==, which on two VALUEs would ask whether they are
|
|
7345
|
+
# the same object.
|
|
7158
7346
|
reduce: { numeric: "if (v == value_arg) acc += 1",
|
|
7159
|
-
object: "if (
|
|
7347
|
+
object: "if (ca_obj_equal(v, value_arg)) acc += 1" },
|
|
7160
7348
|
reduction_kind: :plus, # SL.1.4 (conditional predication; clang predicates safely under reduction(+:acc))
|
|
7161
7349
|
source: MkKernel::ALL_NUMERIC + [:object, :fixlen],
|
|
7162
7350
|
fixlen: :count_equal, # bespoke memcmp walk (a blob has no scalar C type)
|
|
@@ -7248,69 +7436,6 @@ MkKernel.reduce :wmean,
|
|
|
7248
7436
|
bind_ruby: true,
|
|
7249
7437
|
public_method: true
|
|
7250
7438
|
|
|
7251
|
-
# ---- map kernels (element-wise transforms) ----------------------------
|
|
7252
|
-
|
|
7253
|
-
# Float-output transcendentals -- input widens to double inside the
|
|
7254
|
-
# expression, output is float64. The :wrap_to_f64 fallback handles
|
|
7255
|
-
# any other numeric data_type (int16, bool, etc.) by promoting to f64 first.
|
|
7256
|
-
|
|
7257
|
-
MkKernel.map :sqrt,
|
|
7258
|
-
source: MkKernel::ALL_NUMERIC,
|
|
7259
|
-
output: :f64,
|
|
7260
|
-
expr: "r = sqrt((double) v)",
|
|
7261
|
-
fallback: :wrap_to_f64
|
|
7262
|
-
|
|
7263
|
-
MkKernel.map :sin,
|
|
7264
|
-
source: MkKernel::ALL_NUMERIC,
|
|
7265
|
-
output: :f64,
|
|
7266
|
-
expr: "r = sin((double) v)",
|
|
7267
|
-
fallback: :wrap_to_f64
|
|
7268
|
-
|
|
7269
|
-
MkKernel.map :cos,
|
|
7270
|
-
source: MkKernel::ALL_NUMERIC,
|
|
7271
|
-
output: :f64,
|
|
7272
|
-
expr: "r = cos((double) v)",
|
|
7273
|
-
fallback: :wrap_to_f64
|
|
7274
|
-
|
|
7275
|
-
MkKernel.map :exp,
|
|
7276
|
-
source: MkKernel::ALL_NUMERIC,
|
|
7277
|
-
output: :f64,
|
|
7278
|
-
expr: "r = exp((double) v)",
|
|
7279
|
-
fallback: :wrap_to_f64
|
|
7280
|
-
|
|
7281
|
-
MkKernel.map :log,
|
|
7282
|
-
source: MkKernel::ALL_NUMERIC,
|
|
7283
|
-
output: :f64,
|
|
7284
|
-
expr: "r = log((double) v)",
|
|
7285
|
-
fallback: :wrap_to_f64
|
|
7286
|
-
|
|
7287
|
-
# Source-data_type-preserving transforms -- arithmetic ops that don't need
|
|
7288
|
-
# floating-point. Fallback raises rather than silently widening.
|
|
7289
|
-
|
|
7290
|
-
MkKernel.map :square,
|
|
7291
|
-
source: MkKernel::ALL_NUMERIC,
|
|
7292
|
-
output: :preserve,
|
|
7293
|
-
expr: "r = v * v",
|
|
7294
|
-
fallback: :raise
|
|
7295
|
-
|
|
7296
|
-
MkKernel.map :abs,
|
|
7297
|
-
# Signed-only: `v < 0` is always false on unsigned types (compiler
|
|
7298
|
-
# warning + identity result). Use `negate` semantics for unsigned
|
|
7299
|
-
# only if you genuinely want wrap-around.
|
|
7300
|
-
source: MkKernel::SIGNED_NUMERIC,
|
|
7301
|
-
output: :preserve,
|
|
7302
|
-
expr: "r = (v < 0) ? -v : v",
|
|
7303
|
-
fallback: :raise
|
|
7304
|
-
|
|
7305
|
-
MkKernel.map :negate,
|
|
7306
|
-
# Signed-only: -v on unsigned types wraps (C semantics), which is
|
|
7307
|
-
# mathematically wrong for "negate". If you want bitwise inversion
|
|
7308
|
-
# on unsigned, use a different kernel (~v).
|
|
7309
|
-
source: MkKernel::SIGNED_NUMERIC,
|
|
7310
|
-
output: :preserve,
|
|
7311
|
-
expr: "r = -v",
|
|
7312
|
-
fallback: :raise
|
|
7313
|
-
|
|
7314
7439
|
# ---- scan kernels (cumulative / prefix scan along one axis) ----------
|
|
7315
7440
|
|
|
7316
7441
|
# cumsum / cumprod: data_type-conditional output (numeric -> f64 widening,
|
|
@@ -7360,13 +7485,14 @@ MkKernel.scan :cumprod,
|
|
|
7360
7485
|
# so the f32 lane needs no narrowing. Integer and boolean have no NaN
|
|
7361
7486
|
# and keep the limit init and the plain compare.
|
|
7362
7487
|
MkKernel.scan :cummax,
|
|
7488
|
+
face_gate: :relift,
|
|
7363
7489
|
source: MkKernel::ALL_NUMERIC + [:bool, :object],
|
|
7364
7490
|
output: { bool: :u64, default: :preserve },
|
|
7365
7491
|
init: { float: "NAN", numeric: "T_LIMIT_LO", bool: "T_LIMIT_LO", object: "Qnil" },
|
|
7366
7492
|
step: { float: "acc = fmax(acc, v); r = acc",
|
|
7367
7493
|
numeric: "if (v > acc) acc = v; r = acc",
|
|
7368
7494
|
bool: "if ((uint64_t) v > acc) acc = v; r = acc",
|
|
7369
|
-
object: 'if (acc == Qnil
|
|
7495
|
+
object: 'if (acc == Qnil || ca_obj_extremum_replaces(v, acc, rb_intern(">"))) acc = v; r = acc' },
|
|
7370
7496
|
fallback: :raise,
|
|
7371
7497
|
axis_default: :flatten,
|
|
7372
7498
|
empty: :undef
|
|
@@ -7379,26 +7505,30 @@ MkKernel.scan :cummax,
|
|
|
7379
7505
|
# so the f32 lane needs no narrowing. Integer and boolean have no NaN
|
|
7380
7506
|
# and keep the limit init and the plain compare.
|
|
7381
7507
|
MkKernel.scan :cummin,
|
|
7508
|
+
face_gate: :relift,
|
|
7382
7509
|
source: MkKernel::ALL_NUMERIC + [:bool, :object],
|
|
7383
7510
|
output: { bool: :u64, default: :preserve },
|
|
7384
7511
|
init: { float: "NAN", numeric: "T_LIMIT_HI", bool: "T_LIMIT_HI", object: "Qnil" },
|
|
7385
7512
|
step: { float: "acc = fmin(acc, v); r = acc",
|
|
7386
7513
|
numeric: "if (v < acc) acc = v; r = acc",
|
|
7387
7514
|
bool: "if ((uint64_t) v < acc) acc = v; r = acc",
|
|
7388
|
-
object: 'if (acc == Qnil
|
|
7515
|
+
object: 'if (acc == Qnil || ca_obj_extremum_replaces(v, acc, rb_intern("<"))) acc = v; r = acc' },
|
|
7389
7516
|
fallback: :raise,
|
|
7390
7517
|
axis_default: :flatten,
|
|
7391
7518
|
empty: :undef
|
|
7392
7519
|
|
|
7393
7520
|
# cumcount: running count of unmasked cells. Output data_type = int64.
|
|
7394
7521
|
# `(void) v` silences unused-variable warning for the unmasked path
|
|
7395
|
-
# (compiler DCEs the v load when STEP doesn't reference it).
|
|
7522
|
+
# (compiler DCEs the v load when STEP doesn't reference it). The count
|
|
7523
|
+
# reads only the mask, so a data type outside source: (object, complex,
|
|
7524
|
+
# boolean, fixlen, a Face) is counted off the mask without converting its
|
|
7525
|
+
# values (fallback: :mask_count).
|
|
7396
7526
|
MkKernel.scan :cumcount,
|
|
7397
7527
|
source: MkKernel::ALL_NUMERIC,
|
|
7398
7528
|
output: :i64,
|
|
7399
7529
|
init: "0",
|
|
7400
7530
|
step: "(void) v; r = ++acc",
|
|
7401
|
-
fallback: :
|
|
7531
|
+
fallback: :mask_count,
|
|
7402
7532
|
axis_default: :flatten
|
|
7403
7533
|
|
|
7404
7534
|
# uniq_scan: per-axis adjacent-compare scan returning a boolean
|
|
@@ -7446,7 +7576,7 @@ MkKernel.sort :sort_index,
|
|
|
7446
7576
|
nan_policy: :end,
|
|
7447
7577
|
fallback: :raise,
|
|
7448
7578
|
mask_self: :sentinel, # masked cells sort to masked_position: :first/:last (default :last)
|
|
7449
|
-
public_method: :sort_index, # binds public sort_index(axis:
|
|
7579
|
+
public_method: :sort_index, # binds public sort_index(axis: nil, masked_position: :last) via _kw trampoline
|
|
7450
7580
|
c_callable: true # extern linkage for carray_order.c (nlargest / order family)
|
|
7451
7581
|
|
|
7452
7582
|
# sort_addr: per-axis argsort returning view-flat addresses (=
|
|
@@ -7454,7 +7584,7 @@ MkKernel.sort :sort_index,
|
|
|
7454
7584
|
# sort_index but the kernel writes view-flat addresses instead of
|
|
7455
7585
|
# fiber-local indices, so the output can feed directly into
|
|
7456
7586
|
# ca_remap_new as a CARemap.idx. Internal-only (bind_ruby: false):
|
|
7457
|
-
# used by `a.sort(axis: k)` at the C level via
|
|
7587
|
+
# used by `a.sort(axis: k)` at the C level via rb_ca_sort_addr_ki_*_mp.
|
|
7458
7588
|
# rev3 of PROPOSAL_SORT_AXIS defers `sort_addr(axis: k)` as a public
|
|
7459
7589
|
# method to a future phase; here we just need the kernel machinery.
|
|
7460
7590
|
MkKernel.sort :sort_addr,
|
|
@@ -7481,7 +7611,7 @@ MkKernel.sort :partition_index,
|
|
|
7481
7611
|
fallback: :raise,
|
|
7482
7612
|
algorithm: :partition,
|
|
7483
7613
|
mask_self: :sentinel, # masked cells sort to masked_position: :first/:last (default :last)
|
|
7484
|
-
public_method: :partition_index, # binds partition_index(kth, axis:
|
|
7614
|
+
public_method: :partition_index, # binds partition_index(kth, axis: nil, masked_position: :last) via _kw trampoline
|
|
7485
7615
|
c_callable: true # extern linkage for carray_order.c (nlargest / nsmallest)
|
|
7486
7616
|
|
|
7487
7617
|
# partition_addr: view-flat-address variant of partition_index, parallel
|
|
@@ -7523,7 +7653,7 @@ MkKernel.sort :rank_index,
|
|
|
7523
7653
|
fallback: :raise,
|
|
7524
7654
|
algorithm: :rank,
|
|
7525
7655
|
mask_self: :skip,
|
|
7526
|
-
public_method: :rank_index, # binds public rank_index(axis:
|
|
7656
|
+
public_method: :rank_index, # binds public rank_index(axis: nil) via _kw trampoline
|
|
7527
7657
|
c_callable: true # extern linkage for carray_order.c (order method)
|
|
7528
7658
|
|
|
7529
7659
|
# ---- search family ------------------------------------------------------
|
|
@@ -7560,7 +7690,7 @@ MkKernel.search :find_value_index,
|
|
|
7560
7690
|
result = (ca_size_t) -1;
|
|
7561
7691
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7562
7692
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7563
|
-
if (
|
|
7693
|
+
if ( ca_obj_equal(v, query_val) ) { result = i; break; }
|
|
7564
7694
|
}
|
|
7565
7695
|
C
|
|
7566
7696
|
fixlen: <<~C,
|
|
@@ -7626,19 +7756,18 @@ MkKernel.search :bsearch,
|
|
|
7626
7756
|
C
|
|
7627
7757
|
object: <<~C,
|
|
7628
7758
|
/* CA_OBJECT bsearch: ordering via rb_funcall(<=>). Assumes the
|
|
7629
|
-
slab is sorted by the same comparator used to build it
|
|
7630
|
-
|
|
7631
|
-
a Float-cell NaN would raise from <=>). */
|
|
7759
|
+
slab is sorted by the same comparator used to build it, which puts
|
|
7760
|
+
a Float NaN last; a NaN query matches nothing, as in float. */
|
|
7632
7761
|
result = (ca_size_t) -1;
|
|
7633
7762
|
ca_size_t lo = 0;
|
|
7634
|
-
ca_size_t hi = slab_n;
|
|
7763
|
+
ca_size_t hi = CA_OBJ_ISNAN(query_val) ? 0 : slab_n; /* NaN matches nothing */
|
|
7635
7764
|
while ( lo < hi ) {
|
|
7636
7765
|
ca_size_t mid = lo + (hi - lo) / 2;
|
|
7637
7766
|
T_LOAD v = *(T_LOAD *)(slab_ptr + mid * slab_stride);
|
|
7767
|
+
if ( CA_OBJ_ISNAN(v) ) { hi = mid; continue; } /* NaN sorts last */
|
|
7638
7768
|
VALUE r = rb_funcall(v, rb_intern("<=>"), 1, query_val);
|
|
7639
7769
|
if ( NIL_P(r) ) {
|
|
7640
|
-
|
|
7641
|
-
rb_obj_classname(v), rb_obj_classname(query_val));
|
|
7770
|
+
ca_obj_compare_refused(v, query_val);
|
|
7642
7771
|
}
|
|
7643
7772
|
int c = NUM2INT(r);
|
|
7644
7773
|
if ( c < 0 ) lo = mid + 1;
|
|
@@ -7675,7 +7804,7 @@ MkKernel.search :bsearch,
|
|
|
7675
7804
|
#
|
|
7676
7805
|
# Per-data_type body: int uses exact `v == query_val` (eps ignored); float
|
|
7677
7806
|
# uses `fabs(v - query_val) <= query_eps` (eps_default = FLT_EPSILON*|val|
|
|
7678
|
-
# or DBL_EPSILON*|val|); object uses
|
|
7807
|
+
# or DBL_EPSILON*|val|); object uses ca_obj_equal (eps ignored); fixlen uses
|
|
7679
7808
|
# memcmp exact match (eps ignored). Sole search implementation: the
|
|
7680
7809
|
# no-axis (flat) surface routes here via flatten + axis 0
|
|
7681
7810
|
# (PROPOSAL_SEARCH_SEMANTICS_UNIFY S2/S3; the legacy flat scan was removed).
|
|
@@ -7697,18 +7826,18 @@ MkKernel.search :search,
|
|
|
7697
7826
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7698
7827
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7699
7828
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7700
|
-
if ( fabs((double)(v - query_val)) <= query_eps ) { result = i; break; }
|
|
7829
|
+
if ( v == query_val || fabs((double)(v - query_val)) <= query_eps ) { result = i; break; }
|
|
7701
7830
|
}
|
|
7702
7831
|
C
|
|
7703
7832
|
object: <<~C,
|
|
7704
|
-
/* CA_OBJECT search: exact equality via
|
|
7833
|
+
/* CA_OBJECT search: exact equality via ca_obj_equal. eps is silently
|
|
7705
7834
|
ignored (= no meaningful tolerance on arbitrary Ruby objects). */
|
|
7706
7835
|
result = (ca_size_t) -1;
|
|
7707
7836
|
(void) query_eps;
|
|
7708
7837
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7709
7838
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7710
7839
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7711
|
-
if (
|
|
7840
|
+
if ( ca_obj_equal(v, query_val) ) { result = i; break; }
|
|
7712
7841
|
}
|
|
7713
7842
|
C
|
|
7714
7843
|
fixlen: <<~C,
|
|
@@ -7747,13 +7876,16 @@ MkKernel.search :search_nearest,
|
|
|
7747
7876
|
output: :ca_size,
|
|
7748
7877
|
body: {
|
|
7749
7878
|
int: <<~C,
|
|
7879
|
+
/* The distance is exact in uint64: a double loses the difference
|
|
7880
|
+
between neighbouring int64 values above 2**53. */
|
|
7750
7881
|
result = (ca_size_t) -1;
|
|
7751
|
-
|
|
7882
|
+
uint64_t best_dist = 0;
|
|
7752
7883
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7753
7884
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7754
7885
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7755
|
-
|
|
7756
|
-
|
|
7886
|
+
uint64_t dist = ( v >= query_val ) ? (uint64_t) v - (uint64_t) query_val
|
|
7887
|
+
: (uint64_t) query_val - (uint64_t) v;
|
|
7888
|
+
if ( result == (ca_size_t) -1 || dist < best_dist ) { best_dist = dist; result = i; }
|
|
7757
7889
|
}
|
|
7758
7890
|
(void) query_eps; /* unused: search_nearest has no eps semantics */
|
|
7759
7891
|
C
|
|
@@ -7763,8 +7895,11 @@ MkKernel.search :search_nearest,
|
|
|
7763
7895
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7764
7896
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7765
7897
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7766
|
-
|
|
7767
|
-
|
|
7898
|
+
/* An equal value is at distance 0 even when both are infinite
|
|
7899
|
+
(Inf - Inf is NaN); a NaN distance never wins. */
|
|
7900
|
+
double dist = ( v == query_val ) ? 0.0 : fabs((double)v - (double)query_val);
|
|
7901
|
+
if ( dist != dist ) continue;
|
|
7902
|
+
if ( result == (ca_size_t) -1 || dist < best_dist ) { best_dist = dist; result = i; }
|
|
7768
7903
|
}
|
|
7769
7904
|
(void) query_eps; /* unused: search_nearest has no eps semantics */
|
|
7770
7905
|
C
|
|
@@ -7783,20 +7918,22 @@ MkKernel.search :search_nearest,
|
|
|
7783
7918
|
int nearest_by_distance = rb_respond_to(query_val, nearest_id);
|
|
7784
7919
|
if ( ! nearest_by_distance && ! rb_obj_is_kind_of(query_val, rb_cNumeric) ) {
|
|
7785
7920
|
rb_raise(rb_eCADataTypeError,
|
|
7786
|
-
"
|
|
7921
|
+
"%s: nearest needs a distance, and %s is neither a "
|
|
7787
7922
|
"number nor answers #distance (define one on the stored "
|
|
7788
7923
|
"objects, or use search / bsearch for an exact match)",
|
|
7789
|
-
rb_obj_classname(query_val));
|
|
7924
|
+
ca_calling_method_name(), rb_obj_classname(query_val));
|
|
7790
7925
|
}
|
|
7791
7926
|
result = (ca_size_t) -1;
|
|
7792
7927
|
VALUE best = Qnil;
|
|
7793
7928
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7794
7929
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7795
7930
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7796
|
-
VALUE dist =
|
|
7931
|
+
VALUE dist = ca_obj_equal(v, query_val) ? INT2FIX(0)
|
|
7932
|
+
: nearest_by_distance
|
|
7797
7933
|
? rb_funcall(query_val, nearest_id, 1, v)
|
|
7798
7934
|
: rb_funcall(rb_funcall(query_val, '-', 1, v),
|
|
7799
7935
|
rb_intern("abs"), 0);
|
|
7936
|
+
if ( CA_OBJ_ISNAN(dist) ) continue; /* a NaN is no distance, as in float */
|
|
7800
7937
|
if ( NIL_P(best) || RTEST(rb_funcall(dist, rb_intern("<"), 1, best)) ) {
|
|
7801
7938
|
best = dist; result = i;
|
|
7802
7939
|
}
|
|
@@ -7859,14 +7996,14 @@ MkKernel.search :bsearch_addr,
|
|
|
7859
7996
|
object: <<~C,
|
|
7860
7997
|
result = (ca_size_t) -1;
|
|
7861
7998
|
ca_size_t lo = 0;
|
|
7862
|
-
ca_size_t hi = slab_n;
|
|
7999
|
+
ca_size_t hi = CA_OBJ_ISNAN(query_val) ? 0 : slab_n; /* NaN matches nothing */
|
|
7863
8000
|
while ( lo < hi ) {
|
|
7864
8001
|
ca_size_t mid = lo + (hi - lo) / 2;
|
|
7865
8002
|
T_LOAD v = *(T_LOAD *)(slab_ptr + mid * slab_stride);
|
|
8003
|
+
if ( CA_OBJ_ISNAN(v) ) { hi = mid; continue; } /* NaN sorts last */
|
|
7866
8004
|
VALUE r = rb_funcall(v, rb_intern("<=>"), 1, query_val);
|
|
7867
8005
|
if ( NIL_P(r) ) {
|
|
7868
|
-
|
|
7869
|
-
rb_obj_classname(v), rb_obj_classname(query_val));
|
|
8006
|
+
ca_obj_compare_refused(v, query_val);
|
|
7870
8007
|
}
|
|
7871
8008
|
int c = NUM2INT(r);
|
|
7872
8009
|
if ( c < 0 ) lo = mid + 1;
|
|
@@ -7913,7 +8050,7 @@ MkKernel.search :search_addr,
|
|
|
7913
8050
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7914
8051
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7915
8052
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7916
|
-
if ( fabs((double)v - (double)query_val) <= query_eps ) { result = i; break; }
|
|
8053
|
+
if ( v == query_val || fabs((double)v - (double)query_val) <= query_eps ) { result = i; break; }
|
|
7917
8054
|
}
|
|
7918
8055
|
C
|
|
7919
8056
|
object: <<~C,
|
|
@@ -7922,7 +8059,7 @@ MkKernel.search :search_addr,
|
|
|
7922
8059
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7923
8060
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7924
8061
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7925
|
-
if (
|
|
8062
|
+
if ( ca_obj_equal(v, query_val) ) { result = i; break; }
|
|
7926
8063
|
}
|
|
7927
8064
|
C
|
|
7928
8065
|
fixlen: <<~C,
|
|
@@ -7948,13 +8085,16 @@ MkKernel.search :search_nearest_addr,
|
|
|
7948
8085
|
output: :ca_size,
|
|
7949
8086
|
body: {
|
|
7950
8087
|
int: <<~C,
|
|
8088
|
+
/* The distance is exact in uint64: a double loses the difference
|
|
8089
|
+
between neighbouring int64 values above 2**53. */
|
|
7951
8090
|
result = (ca_size_t) -1;
|
|
7952
|
-
|
|
8091
|
+
uint64_t best_dist = 0;
|
|
7953
8092
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7954
8093
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7955
8094
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7956
|
-
|
|
7957
|
-
|
|
8095
|
+
uint64_t dist = ( v >= query_val ) ? (uint64_t) v - (uint64_t) query_val
|
|
8096
|
+
: (uint64_t) query_val - (uint64_t) v;
|
|
8097
|
+
if ( result == (ca_size_t) -1 || dist < best_dist ) { best_dist = dist; result = i; }
|
|
7958
8098
|
}
|
|
7959
8099
|
(void) query_eps;
|
|
7960
8100
|
C
|
|
@@ -7964,8 +8104,11 @@ MkKernel.search :search_nearest_addr,
|
|
|
7964
8104
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7965
8105
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7966
8106
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7967
|
-
|
|
7968
|
-
|
|
8107
|
+
/* An equal value is at distance 0 even when both are infinite
|
|
8108
|
+
(Inf - Inf is NaN); a NaN distance never wins. */
|
|
8109
|
+
double dist = ( v == query_val ) ? 0.0 : fabs((double)v - (double)query_val);
|
|
8110
|
+
if ( dist != dist ) continue;
|
|
8111
|
+
if ( result == (ca_size_t) -1 || dist < best_dist ) { best_dist = dist; result = i; }
|
|
7969
8112
|
}
|
|
7970
8113
|
(void) query_eps;
|
|
7971
8114
|
C
|
|
@@ -7984,20 +8127,22 @@ MkKernel.search :search_nearest_addr,
|
|
|
7984
8127
|
int nearest_by_distance = rb_respond_to(query_val, nearest_id);
|
|
7985
8128
|
if ( ! nearest_by_distance && ! rb_obj_is_kind_of(query_val, rb_cNumeric) ) {
|
|
7986
8129
|
rb_raise(rb_eCADataTypeError,
|
|
7987
|
-
"
|
|
8130
|
+
"%s: nearest needs a distance, and %s is neither a "
|
|
7988
8131
|
"number nor answers #distance (define one on the stored "
|
|
7989
8132
|
"objects, or use search / bsearch for an exact match)",
|
|
7990
|
-
rb_obj_classname(query_val));
|
|
8133
|
+
ca_calling_method_name(), rb_obj_classname(query_val));
|
|
7991
8134
|
}
|
|
7992
8135
|
result = (ca_size_t) -1;
|
|
7993
8136
|
VALUE best = Qnil;
|
|
7994
8137
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7995
8138
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7996
8139
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7997
|
-
VALUE dist =
|
|
8140
|
+
VALUE dist = ca_obj_equal(v, query_val) ? INT2FIX(0)
|
|
8141
|
+
: nearest_by_distance
|
|
7998
8142
|
? rb_funcall(query_val, nearest_id, 1, v)
|
|
7999
8143
|
: rb_funcall(rb_funcall(query_val, '-', 1, v),
|
|
8000
8144
|
rb_intern("abs"), 0);
|
|
8145
|
+
if ( CA_OBJ_ISNAN(dist) ) continue; /* a NaN is no distance, as in float */
|
|
8001
8146
|
if ( NIL_P(best) || RTEST(rb_funcall(dist, rb_intern("<"), 1, best)) ) {
|
|
8002
8147
|
best = dist; result = i;
|
|
8003
8148
|
}
|
|
@@ -8047,7 +8192,11 @@ MkKernel.search :linear_section_binary,
|
|
|
8047
8192
|
body: <<~C,
|
|
8048
8193
|
result = NAN;
|
|
8049
8194
|
if ( slab_n <= 1 ) {
|
|
8050
|
-
|
|
8195
|
+
/* A one-point axis has no segment: only the point itself is in
|
|
8196
|
+
* range, at 0. An empty one has nothing in range. */
|
|
8197
|
+
if ( slab_n == 1 && query_val == *(double *)slab_ptr ) {
|
|
8198
|
+
result = 0.0;
|
|
8199
|
+
}
|
|
8051
8200
|
}
|
|
8052
8201
|
else {
|
|
8053
8202
|
double y0 = *(double *)(slab_ptr + 0 * slab_stride);
|
|
@@ -8135,7 +8284,11 @@ MkKernel.search :linear_section_linear,
|
|
|
8135
8284
|
body: <<~C,
|
|
8136
8285
|
result = NAN;
|
|
8137
8286
|
if ( slab_n <= 1 ) {
|
|
8138
|
-
|
|
8287
|
+
/* A one-point axis has no segment: only the point itself is in
|
|
8288
|
+
* range, at 0. An empty one has nothing in range. */
|
|
8289
|
+
if ( slab_n == 1 && query_val == *(double *)slab_ptr ) {
|
|
8290
|
+
result = 0.0;
|
|
8291
|
+
}
|
|
8139
8292
|
}
|
|
8140
8293
|
else {
|
|
8141
8294
|
ca_size_t x1 = 0;
|
|
@@ -8265,9 +8418,10 @@ MkKernel.monop :abs_i,
|
|
|
8265
8418
|
# (= used by .real chain composition); abs is the user-facing op that
|
|
8266
8419
|
# returns the real-valued magnitude entity.
|
|
8267
8420
|
MkKernel.monop :abs,
|
|
8268
|
-
source: MkKernel::ALL_NUMERIC + MkKernel::CMPLX_DTYPES,
|
|
8269
|
-
output: { numeric: :preserve, complex: :real_of_source },
|
|
8421
|
+
source: MkKernel::ALL_NUMERIC + MkKernel::CMPLX_DTYPES + [:object],
|
|
8422
|
+
output: { numeric: :preserve, complex: :real_of_source, object: :preserve },
|
|
8270
8423
|
expr: {
|
|
8424
|
+
[:object] => '(#2) = rb_funcall((#1), rb_intern("abs"), 0);',
|
|
8271
8425
|
MkKernel::SINT_SMALL_DTYPES => "(#2) = abs(#1);",
|
|
8272
8426
|
MkKernel::SINT64_DTYPES => "(#2) = llabs(#1);",
|
|
8273
8427
|
MkKernel::UINT_DTYPES => "(#2) = (#1);",
|
|
@@ -8383,7 +8537,9 @@ MkKernel.monop :imag_i,
|
|
|
8383
8537
|
MkKernel.monop :arg_i,
|
|
8384
8538
|
source: MkKernel::FLOAT_DTYPES + MkKernel::CMPLX_DTYPES,
|
|
8385
8539
|
expr: {
|
|
8386
|
-
|
|
8540
|
+
# A real cell's argument is 0, pi or NaN. Taken in double and rounded,
|
|
8541
|
+
# as the eager arg does; cargf returns pi one ulp low.
|
|
8542
|
+
[:f32] => "(#2) = carg((cmplx128_t)(#1));",
|
|
8387
8543
|
[:f64] => "(#2) = carg((cmplx128_t)(#1));",
|
|
8388
8544
|
[:cmplx64] => "(#2) = cargf(#1);",
|
|
8389
8545
|
[:cmplx128] => "(#2) = carg(#1);",
|
|
@@ -8439,7 +8595,9 @@ MkKernel.monfunc :round,
|
|
|
8439
8595
|
MkKernel.monfunc :rcp,
|
|
8440
8596
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
8441
8597
|
expr: {
|
|
8442
|
-
|
|
8598
|
+
MkKernel::UINT_DTYPES => "if ((#1)==0) {ca_zerodiv();}; (#2) = 1/(#1);",
|
|
8599
|
+
# 1 / x floored: 1 for 1, -1 for every negative x, 0 otherwise.
|
|
8600
|
+
int: "if ((#1)==0) {ca_zerodiv();}; (#2) = ((#1)==1) ? 1 : ((#1) < 0) ? -1 : 0;",
|
|
8443
8601
|
float: "(#2) = 1/(#1);",
|
|
8444
8602
|
[:cmplx64] => "(#2) = op_crcp_cmplx64(#1);",
|
|
8445
8603
|
[:cmplx128] => "(#2) = 1/(#1);",
|
|
@@ -8548,11 +8706,13 @@ end
|
|
|
8548
8706
|
|
|
8549
8707
|
# expm1, log1p: float + object only (C99 doesn't standardise complex
|
|
8550
8708
|
# variants). Widening monfunc — integer input auto-casts to f64.
|
|
8709
|
+
# CAMath.expm1 / log1p are defined in lib/carray/math.rb instead: Ruby's
|
|
8710
|
+
# Math has neither, so the generated front-end could not take a Numeric.
|
|
8551
8711
|
{
|
|
8552
8712
|
expm1: "expm1",
|
|
8553
8713
|
log1p: "log1p",
|
|
8554
8714
|
}.each do |op, c_fn|
|
|
8555
|
-
MkKernel.monfunc op,
|
|
8715
|
+
MkKernel.monfunc op, cmath: false,
|
|
8556
8716
|
source: MkKernel::FLOAT_DTYPES + [:object],
|
|
8557
8717
|
expr: {
|
|
8558
8718
|
object: MkKernel.obj_float_math("#{c_fn}(<v>)", c_fn),
|
|
@@ -8737,14 +8897,15 @@ MkKernel.monfunc :rad_pi,
|
|
|
8737
8897
|
# NaN the result is NaN. Distinct kernel — cannot be
|
|
8738
8898
|
# aliased onto pmax.
|
|
8739
8899
|
#
|
|
8740
|
-
# Integer
|
|
8741
|
-
#
|
|
8900
|
+
# Integer branches are identical across all three (no NaN). The float
|
|
8901
|
+
# and object branches differ: pmax / pmin let a NaN lose (an object cell's
|
|
8902
|
+
# Float NaN included), maximum / minimum let it win.
|
|
8742
8903
|
MkKernel.binop :pmax,
|
|
8743
8904
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8744
8905
|
expr: {
|
|
8745
8906
|
int: "(#3) = (#1) > (#2) ? (#1) : (#2);",
|
|
8746
8907
|
float: "(#3) = fmax(#1, #2);",
|
|
8747
|
-
object: '(#3) =
|
|
8908
|
+
object: '(#3) = ca_obj_extremum_replaces((#2), (#1), rb_intern(">")) ? (#2) : (#1);',
|
|
8748
8909
|
}
|
|
8749
8910
|
|
|
8750
8911
|
MkKernel.binop :pmin,
|
|
@@ -8752,26 +8913,28 @@ MkKernel.binop :pmin,
|
|
|
8752
8913
|
expr: {
|
|
8753
8914
|
int: "(#3) = (#1) < (#2) ? (#1) : (#2);",
|
|
8754
8915
|
float: "(#3) = fmin(#1, #2);",
|
|
8755
|
-
object: '(#3) =
|
|
8916
|
+
object: '(#3) = ca_obj_extremum_replaces((#2), (#1), rb_intern("<")) ? (#2) : (#1);',
|
|
8756
8917
|
}
|
|
8757
8918
|
|
|
8758
8919
|
# NaN-propagate variants. Float
|
|
8759
8920
|
# branch tests both operands for NaN and short-circuits to NaN; non-
|
|
8760
|
-
# float branches reuse the comparison form.
|
|
8921
|
+
# float branches reuse the comparison form. Of two zeros, maximum is +0
|
|
8922
|
+
# and minimum is -0 in either order: a bare comparison would answer with
|
|
8923
|
+
# whichever came second.
|
|
8761
8924
|
MkKernel.binop :maximum,
|
|
8762
8925
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8763
8926
|
expr: {
|
|
8764
8927
|
int: "(#3) = (#1) > (#2) ? (#1) : (#2);",
|
|
8765
|
-
float: "(#3) = isnan(#1) ? (#1) :
|
|
8766
|
-
object: '(#3) =
|
|
8928
|
+
float: "(#3) = isnan(#1) ? (#1) : isnan(#2) ? (#2) : ((#1) > (#2) || ((#1) == (#2) && !signbit(#1))) ? (#1) : (#2);",
|
|
8929
|
+
object: '(#3) = CA_OBJ_ISNAN(#1) ? (#1) : CA_OBJ_ISNAN(#2) ? (#2) : ca_obj_cmp((#2), (#1)) > 0 ? (#2) : (#1);',
|
|
8767
8930
|
}
|
|
8768
8931
|
|
|
8769
8932
|
MkKernel.binop :minimum,
|
|
8770
8933
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8771
8934
|
expr: {
|
|
8772
8935
|
int: "(#3) = (#1) < (#2) ? (#1) : (#2);",
|
|
8773
|
-
float: "(#3) = isnan(#1) ? (#1) :
|
|
8774
|
-
object: '(#3) =
|
|
8936
|
+
float: "(#3) = isnan(#1) ? (#1) : isnan(#2) ? (#2) : ((#1) < (#2) || ((#1) == (#2) && signbit(#1))) ? (#1) : (#2);",
|
|
8937
|
+
object: '(#3) = CA_OBJ_ISNAN(#1) ? (#1) : CA_OBJ_ISNAN(#2) ? (#2) : ca_obj_cmp((#2), (#1)) < 0 ? (#2) : (#1);',
|
|
8775
8938
|
}
|
|
8776
8939
|
|
|
8777
8940
|
# + and - work on the parts independently, so one generic expression
|
|
@@ -8812,6 +8975,11 @@ MkKernel.binop :mul,
|
|
|
8812
8975
|
# sign, so they keep the bare C form. Float `/` is true division and is
|
|
8813
8976
|
# unchanged (matching Ruby `Float#/`); the identity above therefore holds
|
|
8814
8977
|
# for integers only, exactly as in Ruby.
|
|
8978
|
+
#
|
|
8979
|
+
# A signed divisor of -1 is taken apart from C's `/`: MIN / -1 does not
|
|
8980
|
+
# fit, and the hardware's answer to it differs (x86 traps, ARM returns
|
|
8981
|
+
# MIN). The quotient is the negation wrapped like any other overflow, so
|
|
8982
|
+
# MIN / -1 is MIN and MIN % -1 is 0 on every machine.
|
|
8815
8983
|
MkKernel.binop :div,
|
|
8816
8984
|
op: "/",
|
|
8817
8985
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
@@ -8819,7 +8987,8 @@ MkKernel.binop :div,
|
|
|
8819
8987
|
MkKernel::UINT_DTYPES => "if ((#2)==0) {ca_zerodiv();}; (#3) = (#1) / (#2);",
|
|
8820
8988
|
int: %{
|
|
8821
8989
|
if ((#2)==0) {ca_zerodiv();};
|
|
8822
|
-
{
|
|
8990
|
+
if ((#2)==-1) { (#3) = (<type>) (0 - (uint64_t) (#1)); }
|
|
8991
|
+
else {
|
|
8823
8992
|
<type> _q = (#1) / (#2);
|
|
8824
8993
|
if ( (#1) % (#2) != 0 && (((#1) < 0) != ((#2) < 0)) ) { _q -= 1; }
|
|
8825
8994
|
(#3) = _q;
|
|
@@ -8845,11 +9014,21 @@ MkKernel.binop :quo_i,
|
|
|
8845
9014
|
object: '(#3) = rb_funcall((#1), rb_intern("quo"), 1, (#2));',
|
|
8846
9015
|
}
|
|
8847
9016
|
|
|
9017
|
+
# rcp_mul: `a.rcp_mul(b)` is `b / a`, and divides as `/` does.
|
|
8848
9018
|
MkKernel.binop :rcp_mul,
|
|
8849
9019
|
op: "rcp_mul",
|
|
8850
9020
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
8851
9021
|
expr: {
|
|
8852
|
-
|
|
9022
|
+
MkKernel::UINT_DTYPES => "if ((#1)==0) {ca_zerodiv();}; (#3) = (#2) / (#1);",
|
|
9023
|
+
int: %{
|
|
9024
|
+
if ((#1)==0) {ca_zerodiv();};
|
|
9025
|
+
if ((#1)==-1) { (#3) = (<type>) (0 - (uint64_t) (#2)); }
|
|
9026
|
+
else {
|
|
9027
|
+
<type> _q = (#2) / (#1);
|
|
9028
|
+
if ( (#2) % (#1) != 0 && (((#2) < 0) != ((#1) < 0)) ) { _q -= 1; }
|
|
9029
|
+
(#3) = _q;
|
|
9030
|
+
}
|
|
9031
|
+
},
|
|
8853
9032
|
float: "(#3) = (#2) / (#1);",
|
|
8854
9033
|
[:cmplx64] => "(#3) = op_cdiv_cmplx64((#2), (#1));",
|
|
8855
9034
|
[:cmplx128] => "(#3) = (#2) / (#1);",
|
|
@@ -8871,7 +9050,8 @@ MkKernel.binop :mod,
|
|
|
8871
9050
|
MkKernel::UINT_DTYPES => "if ((#2)==0) {ca_zerodiv();}; (#3) = (#1) % (#2);",
|
|
8872
9051
|
int: %{
|
|
8873
9052
|
if ((#2)==0) {ca_zerodiv();};
|
|
8874
|
-
{
|
|
9053
|
+
if ((#2)==-1) { (#3) = 0; }
|
|
9054
|
+
else {
|
|
8875
9055
|
<type> _r = (#1) % (#2);
|
|
8876
9056
|
if ( _r != 0 && ((_r < 0) != ((#2) < 0)) ) { _r += (#2); }
|
|
8877
9057
|
(#3) = _r;
|
|
@@ -8899,7 +9079,8 @@ MkKernel.binop :mod,
|
|
|
8899
9079
|
MkKernel.binop :fmod,
|
|
8900
9080
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8901
9081
|
expr: {
|
|
8902
|
-
|
|
9082
|
+
MkKernel::UINT_DTYPES => "if ((#2)==0) {ca_zerodiv();}; (#3) = (#1) % (#2);",
|
|
9083
|
+
int: "if ((#2)==0) {ca_zerodiv();}; (#3) = ((#2)==-1) ? 0 : (#1) % (#2);",
|
|
8903
9084
|
object: '(#3) = rb_funcall((#1), rb_intern("remainder"), 1, (#2));',
|
|
8904
9085
|
}.merge(MkKernel.float_widths("(#3) = fmod<f>((#1), (#2));"))
|
|
8905
9086
|
|
|
@@ -8932,11 +9113,29 @@ MkKernel.binop :bit_xor_i,
|
|
|
8932
9113
|
object: '(#3) = rb_funcall((#1), rb_intern("^"), 1, (#2));',
|
|
8933
9114
|
}
|
|
8934
9115
|
|
|
9116
|
+
# Shifts take the meaning Integer#<< and #>> give them: a negative count
|
|
9117
|
+
# shifts the other way, and a count of the width or more shifts every bit
|
|
9118
|
+
# out (0, or -1 for a negative value shifted right). C leaves both, and a
|
|
9119
|
+
# left shift of a negative value, undefined. The value is widened to 64
|
|
9120
|
+
# bits and shifted there -- unsigned for a left shift -- and the result
|
|
9121
|
+
# narrowed back, which keeps the low bits a shift within the width gives.
|
|
9122
|
+
# The bodies are self-contained, since carray-jit compiles them as they are.
|
|
8935
9123
|
MkKernel.binop :bit_lshift,
|
|
8936
9124
|
op: "<<",
|
|
8937
9125
|
source: MkKernel::INT_DTYPES + [:object],
|
|
8938
9126
|
expr: {
|
|
8939
|
-
|
|
9127
|
+
MkKernel::UINT_DTYPES => "(#3) = ((uint64_t) (#2) >= 8 * sizeof(<type>)) ? 0 : (<type>) ((uint64_t) (#1) << (#2));",
|
|
9128
|
+
int: %{
|
|
9129
|
+
{
|
|
9130
|
+
int64_t _x = (int64_t) (#1), _n = (int64_t) (#2), _r;
|
|
9131
|
+
const int64_t _w = (int64_t) (8 * sizeof(<type>));
|
|
9132
|
+
if ( _n >= _w ) _r = 0;
|
|
9133
|
+
else if ( _n <= -_w ) _r = _x < 0 ? -1 : 0;
|
|
9134
|
+
else if ( _n < 0 ) _r = _x >> -_n;
|
|
9135
|
+
else _r = (int64_t) ((uint64_t) _x << _n);
|
|
9136
|
+
(#3) = (<type>) _r;
|
|
9137
|
+
}
|
|
9138
|
+
},
|
|
8940
9139
|
object: '(#3) = rb_funcall((#1), rb_intern("<<"), 1, (#2));',
|
|
8941
9140
|
}
|
|
8942
9141
|
|
|
@@ -8944,7 +9143,18 @@ MkKernel.binop :bit_rshift,
|
|
|
8944
9143
|
op: ">>",
|
|
8945
9144
|
source: MkKernel::INT_DTYPES + [:object],
|
|
8946
9145
|
expr: {
|
|
8947
|
-
|
|
9146
|
+
MkKernel::UINT_DTYPES => "(#3) = ((uint64_t) (#2) >= 8 * sizeof(<type>)) ? 0 : (<type>) ((uint64_t) (#1) >> (#2));",
|
|
9147
|
+
int: %{
|
|
9148
|
+
{
|
|
9149
|
+
int64_t _x = (int64_t) (#1), _n = (int64_t) (#2), _r;
|
|
9150
|
+
const int64_t _w = (int64_t) (8 * sizeof(<type>));
|
|
9151
|
+
if ( _n >= _w ) _r = _x < 0 ? -1 : 0;
|
|
9152
|
+
else if ( _n <= -_w ) _r = 0;
|
|
9153
|
+
else if ( _n < 0 ) _r = (int64_t) ((uint64_t) _x << -_n);
|
|
9154
|
+
else _r = _x >> _n;
|
|
9155
|
+
(#3) = (<type>) _r;
|
|
9156
|
+
}
|
|
9157
|
+
},
|
|
8948
9158
|
object: '(#3) = rb_funcall((#1), rb_intern(">>"), 1, (#2));',
|
|
8949
9159
|
}
|
|
8950
9160
|
|
|
@@ -9014,16 +9224,16 @@ C
|
|
|
9014
9224
|
# fma: fused multiply-add — `self * op2 + op3` in a single rounding step
|
|
9015
9225
|
# (= C99 `fma(x, y, z)` and HW FMA3/FMA4 / ARM FMA instructions). More
|
|
9016
9226
|
# accurate than the equivalent two-op `a*b + c` expression which rounds
|
|
9017
|
-
# twice.
|
|
9227
|
+
# twice. float32 calls fmaf: fma in double followed by a narrowing is a
|
|
9228
|
+
# second rounding, and can land 1 ulp off. Used in Horner evaluation, linear interpolation, dot product,
|
|
9018
9229
|
# Newton iteration.
|
|
9019
9230
|
MkKernel.triop :fma,
|
|
9020
9231
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
9021
9232
|
expr: {
|
|
9022
9233
|
int: "(#4) = (#1) * (#2) + (#3);",
|
|
9023
|
-
float: "(#4) = fma((#1), (#2), (#3));",
|
|
9024
9234
|
complex: "(#4) = (#1) * (#2) + (#3);",
|
|
9025
9235
|
object: '(#4) = rb_funcall(rb_funcall((#1), rb_intern("*"), 1, (#2)), rb_intern("+"), 1, (#3));',
|
|
9026
|
-
}
|
|
9236
|
+
}.merge(MkKernel.float_widths("(#4) = fma<f>((#1), (#2), (#3));"))
|
|
9027
9237
|
|
|
9028
9238
|
# fms: fused multiply-subtract — `self * op2 - op3` in a single rounding.
|
|
9029
9239
|
# Useful in cross products and complex multiplication primitives.
|
|
@@ -9031,11 +9241,10 @@ MkKernel.triop :fms,
|
|
|
9031
9241
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
9032
9242
|
expr: {
|
|
9033
9243
|
int: "(#4) = (#1) * (#2) - (#3);",
|
|
9034
|
-
# fma(a, b, -c) = a*b - c with single rounding (= C99 / IEEE 754 hint).
|
|
9035
|
-
float: "(#4) = fma((#1), (#2), -(#3));",
|
|
9036
9244
|
complex: "(#4) = (#1) * (#2) - (#3);",
|
|
9037
9245
|
object: '(#4) = rb_funcall(rb_funcall((#1), rb_intern("*"), 1, (#2)), rb_intern("-"), 1, (#3));',
|
|
9038
|
-
|
|
9246
|
+
# fma(a, b, -c) = a*b - c with single rounding (= C99 / IEEE 754 hint).
|
|
9247
|
+
}.merge(MkKernel.float_widths("(#4) = fma<f>((#1), (#2), -(#3));"))
|
|
9039
9248
|
|
|
9040
9249
|
# clip: per-element clamp into [lo, hi]. 3.0 migration: was a hand-
|
|
9041
9250
|
# written eager-only method in ext/carray_generate.c that only accepted
|
|
@@ -9074,6 +9283,20 @@ MkKernel.triop :clip,
|
|
|
9074
9283
|
'(#4) = _v; }',
|
|
9075
9284
|
}
|
|
9076
9285
|
|
|
9286
|
+
# select: the per-cell choice a lazy `then_else` makes (#1 is the
|
|
9287
|
+
# condition, converted to the branches' common type; #2 / #3 are the
|
|
9288
|
+
# branches). No Ruby method: CArray#then_else builds the node.
|
|
9289
|
+
MkKernel.triop :select,
|
|
9290
|
+
bind: false,
|
|
9291
|
+
bang: false,
|
|
9292
|
+
source: MkKernel::MATH_NUMERIC + [:bool],
|
|
9293
|
+
expr: {
|
|
9294
|
+
int: "(#4) = (#1) ? (#2) : (#3);",
|
|
9295
|
+
float: "(#4) = (#1) ? (#2) : (#3);",
|
|
9296
|
+
complex: "(#4) = (#1) ? (#2) : (#3);",
|
|
9297
|
+
bool: "(#4) = (#1) ? (#2) : (#3);",
|
|
9298
|
+
}
|
|
9299
|
+
|
|
9077
9300
|
MkKernel.binop :power,
|
|
9078
9301
|
op: :power,
|
|
9079
9302
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
@@ -9151,7 +9374,11 @@ MkKernel.moncmp :is_nan,
|
|
|
9151
9374
|
int: "(#2) = 0;",
|
|
9152
9375
|
float: "(#2) = isnan(#1);",
|
|
9153
9376
|
complex: "(#2) = (isnan(creal(#1)) || isnan(cimag(#1)));",
|
|
9154
|
-
|
|
9377
|
+
# A Float answers by its bits; an Integer or a Rational is never NaN;
|
|
9378
|
+
# anything else answers through its own #nan?.
|
|
9379
|
+
object: '(#2) = RB_FLOAT_TYPE_P(#1) ? isnan(RFLOAT_VALUE(#1)) : '\
|
|
9380
|
+
'(RB_INTEGER_TYPE_P(#1) || RB_TYPE_P((#1), T_RATIONAL)) ? 0 : '\
|
|
9381
|
+
'RTEST(rb_funcall((#1), rb_intern("nan?"), 0));',
|
|
9155
9382
|
}
|
|
9156
9383
|
|
|
9157
9384
|
MkKernel.moncmp :is_inf,
|
|
@@ -9160,7 +9387,7 @@ MkKernel.moncmp :is_inf,
|
|
|
9160
9387
|
int: "(#2) = 0;",
|
|
9161
9388
|
float: "(#2) = isinf(#1);",
|
|
9162
9389
|
complex: "(#2) = (isinf(creal(#1)) || isinf(cimag(#1)));",
|
|
9163
|
-
object: '(#2) = rb_funcall((#1), rb_intern("infinite?"), 0);',
|
|
9390
|
+
object: '(#2) = RTEST(rb_funcall((#1), rb_intern("infinite?"), 0));',
|
|
9164
9391
|
}
|
|
9165
9392
|
|
|
9166
9393
|
MkKernel.moncmp :is_finite,
|
|
@@ -9169,7 +9396,7 @@ MkKernel.moncmp :is_finite,
|
|
|
9169
9396
|
int: "(#2) = 1;",
|
|
9170
9397
|
float: "(#2) = isfinite(#1);",
|
|
9171
9398
|
complex: "(#2) = (isfinite(creal(#1)) && isfinite(cimag(#1)));",
|
|
9172
|
-
object: '(#2) = rb_funcall((#1), rb_intern("finite?"), 0);',
|
|
9399
|
+
object: '(#2) = RTEST(rb_funcall((#1), rb_intern("finite?"), 0));',
|
|
9173
9400
|
}
|
|
9174
9401
|
|
|
9175
9402
|
# is_invalid = !is_finite (MS.1 mask SET family redesign): NaN or Inf
|
|
@@ -9199,7 +9426,10 @@ MkKernel.moncmp :signbit,
|
|
|
9199
9426
|
MkKernel::SINT_DTYPES => "(#2) = ((#1) < 0);",
|
|
9200
9427
|
MkKernel::UINT_DTYPES => "(#2) = 0;",
|
|
9201
9428
|
float: "(#2) = signbit(#1);",
|
|
9202
|
-
|
|
9429
|
+
# A Float answers by its sign bit (-0.0 and -NaN are negative), as
|
|
9430
|
+
# the float branch does; anything else through #negative?.
|
|
9431
|
+
object: '(#2) = RB_FLOAT_TYPE_P(#1) ? (signbit(RFLOAT_VALUE(#1)) != 0) : '\
|
|
9432
|
+
'(RTEST(rb_funcall((#1), rb_intern("negative?"), 0)) ? 1 : 0);',
|
|
9203
9433
|
}
|
|
9204
9434
|
|
|
9205
9435
|
# ---- P.5b.4: bincmp family (predicates returning bool) ----------------
|