carray 3.0.2 → 3.0.4
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 +568 -0
- data/README.md +6 -6
- data/carray.gemspec +1 -1
- data/ext/ca_axis_descriptor.h +27 -24
- data/ext/ca_axis_dispatch.c +220 -0
- data/ext/ca_axis_group.c +201 -123
- data/ext/ca_binop_dispatch.c +46 -52
- data/ext/ca_binop_dispatch.h +1 -1
- data/ext/ca_categorical_iterator.c +249 -183
- data/ext/ca_compare.c +7 -12
- data/ext/ca_for_buffer.h +21 -68
- data/ext/ca_group_iter.c +38 -23
- data/ext/ca_kernel_iterator.c +1765 -2076
- data/ext/ca_kernel_iterator.h +584 -566
- data/ext/ca_monop_dispatch.c +86 -63
- data/ext/ca_monop_dispatch.h +5 -0
- data/ext/ca_obj_array.c +223 -56
- data/ext/ca_obj_bincmp.c +172 -138
- data/ext/ca_obj_binop.c +116 -230
- data/ext/ca_obj_bitarray.c +40 -26
- data/ext/ca_obj_bitfield.c +100 -68
- data/ext/ca_obj_block.c +19 -9
- data/ext/ca_obj_byte_swap.c +45 -46
- data/ext/ca_obj_const_string.c +269 -44
- data/ext/ca_obj_face.c +612 -89
- data/ext/ca_obj_face.h +111 -47
- data/ext/ca_obj_fake.c +36 -79
- data/ext/ca_obj_farray.c +7 -3
- data/ext/ca_obj_field.c +33 -8
- data/ext/ca_obj_fixlen_string.c +23 -24
- data/ext/ca_obj_grid.c +541 -210
- data/ext/ca_obj_meld.c +331 -111
- data/ext/ca_obj_moncmp.c +80 -65
- data/ext/ca_obj_monop.c +177 -149
- data/ext/ca_obj_object.c +664 -248
- data/ext/ca_obj_record.c +63 -23
- data/ext/ca_obj_reduce.c +18 -16
- data/ext/ca_obj_refer.c +140 -58
- data/ext/ca_obj_remap.c +85 -24
- data/ext/ca_obj_repeat.c +24 -9
- data/ext/ca_obj_roll.c +121 -16
- data/ext/ca_obj_select.c +61 -58
- data/ext/ca_obj_select_axis.c +73 -107
- data/ext/ca_obj_shift.c +32 -102
- data/ext/ca_obj_stack.c +372 -122
- data/ext/ca_obj_stride.c +348 -140
- data/ext/ca_obj_string.c +88 -22
- data/ext/ca_obj_tile.c +38 -21
- data/ext/ca_obj_time.c +45 -31
- data/ext/ca_obj_timedelta.c +43 -29
- data/ext/ca_obj_transpose.c +17 -6
- data/ext/ca_obj_triop.c +125 -141
- data/ext/ca_obj_window.c +330 -44
- 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 +104 -81
- 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 +542 -0
- data/ext/carray.h +241 -92
- data/ext/carray_access.c +608 -276
- data/ext/carray_address_basis.c +494 -97
- data/ext/carray_attribute.c +129 -4
- data/ext/carray_bincount.c +39 -7
- data/ext/carray_broadcast.c +56 -19
- data/ext/carray_build_flags.h +1 -1
- data/ext/carray_call_cfunc.c +108 -28
- data/ext/carray_cast.c +1576 -223
- data/ext/carray_cast_func.rb +77 -24
- data/ext/carray_class.c +4 -3
- data/ext/carray_conversion.c +208 -75
- data/ext/carray_copy.c +82 -24
- data/ext/carray_core.c +646 -162
- data/ext/carray_count.c +171 -88
- data/ext/carray_element.c +94 -60
- data/ext/carray_factorize.c +670 -324
- data/ext/carray_generate.c +260 -83
- data/ext/carray_histogram.c +209 -106
- data/ext/carray_hold.c +14 -16
- data/ext/carray_index_classifier.c +422 -243
- data/ext/carray_internal.h +692 -3
- data/ext/carray_kernels_bincmp.c +7 -0
- data/ext/carray_kernels_binop.c +487 -120
- data/ext/carray_kernels_init.c +653 -50
- data/ext/carray_kernels_moncmp.c +15 -8
- data/ext/carray_kernels_monop.c +99 -43
- data/ext/carray_kernels_reduce_aggregate.c +3925 -6112
- data/ext/carray_kernels_reduce_boolean.c +112 -36
- data/ext/carray_kernels_reduce_cumulative.c +2127 -6903
- data/ext/carray_kernels_reduce_extreme.c +3032 -2892
- data/ext/carray_kernels_reduce_variance.c +439 -500
- data/ext/carray_kernels_scan.c +1097 -488
- data/ext/carray_kernels_search.c +3787 -2176
- data/ext/carray_kernels_sort.c +1200 -920
- data/ext/carray_kernels_triop.c +479 -7
- data/ext/carray_key_windows.c +216 -0
- data/ext/carray_lazy.c +967 -153
- data/ext/carray_loop.c +81 -65
- data/ext/carray_mask.c +910 -324
- data/ext/carray_math_kernel.h +1 -0
- data/ext/carray_median_percentile.c +299 -100
- data/ext/carray_memory_view.c +872 -758
- data/ext/carray_operator.c +1171 -364
- data/ext/carray_order.c +505 -133
- data/ext/carray_partition.c +112 -65
- data/ext/carray_random.c +327 -239
- data/ext/carray_round.c +273 -0
- data/ext/carray_scatter.c +346 -78
- data/ext/carray_segment.c +257 -0
- data/ext/carray_slab.c +249 -86
- data/ext/carray_sort.c +224 -227
- data/ext/carray_sort_kernel.c +8 -1
- data/ext/carray_struct.c +118 -39
- data/ext/carray_test.c +193 -69
- data/ext/carray_time_text.c +1182 -0
- data/ext/carray_undef.c +4 -0
- data/ext/carray_utils.c +579 -113
- data/ext/extconf.rb +27 -2
- data/ext/mk_call_cfunc.rb +19 -4
- data/ext/mk_pow5_table.rb +35 -0
- data/ext/mkkernel.rb +1202 -1014
- data/ext/ruby_carray.c +17 -5
- data/ext/version.h +4 -4
- data/ext/version.rb +4 -2
- data/lib/carray/attribute.rb +160 -17
- data/lib/carray/autoload_carray.rb +23 -27
- data/lib/carray/autoload_method_extension.rb +20 -5
- data/lib/carray/axis_group.rb +360 -63
- data/lib/carray/basics.rb +162 -28
- data/lib/carray/bincount_nd.rb +18 -2
- data/lib/carray/block_iterator.rb +231 -58
- data/lib/carray/boolean_reduce.rb +34 -42
- data/lib/carray/categorical.rb +242 -52
- data/lib/carray/categorical_iterator.rb +437 -827
- data/lib/carray/complex.rb +41 -34
- data/lib/carray/conditional.rb +150 -14
- data/lib/carray/const_string.rb +59 -4
- data/lib/carray/construct.rb +15 -11
- data/lib/carray/data_type_extension.rb +1 -0
- data/lib/carray/distance_limit.rb +77 -0
- data/lib/carray/fixlen_string.rb +7 -1
- data/lib/carray/frame/column_verbs.rb +70 -0
- data/lib/carray/frame/concat.rb +157 -6
- data/lib/carray/frame/convert.rb +82 -7
- data/lib/carray/frame/csv_parser.rb +688 -87
- data/lib/carray/frame/describe.rb +80 -0
- data/lib/carray/frame/frame.rb +369 -45
- data/lib/carray/frame/group.rb +631 -90
- data/lib/carray/frame/io.rb +506 -79
- data/lib/carray/frame/join.rb +411 -62
- data/lib/carray/frame/records.rb +116 -19
- data/lib/carray/frame/reshape.rb +455 -0
- data/lib/carray/frame/sort.rb +30 -18
- data/lib/carray/frame/verbs.rb +868 -61
- data/lib/carray/frame/windows.rb +117 -0
- data/lib/carray/frame.rb +4 -0
- data/lib/carray/fuse_source.rb +118 -2
- data/lib/carray/fusion.rb +257 -42
- data/lib/carray/histogram.rb +6 -1
- data/lib/carray/inspect.rb +54 -13
- data/lib/carray/iterator.rb +99 -9
- data/lib/carray/key_windows.rb +620 -0
- data/lib/carray/lazy.rb +216 -193
- data/lib/carray/mask_gap_fill.rb +112 -92
- data/lib/carray/math.rb +32 -19
- data/lib/carray/meld_reduce.rb +52 -21
- data/lib/carray/methods/align_addr.rb +8 -4
- data/lib/carray/methods/bin.rb +26 -18
- data/lib/carray/methods/bincount.rb +18 -2
- data/lib/carray/methods/bit_string.rb +15 -4
- data/lib/carray/methods/choose.rb +16 -3
- data/lib/carray/methods/composition.rb +18 -10
- data/lib/carray/methods/factorize.rb +2 -2
- data/lib/carray/methods/gather_nd.rb +25 -5
- data/lib/carray/methods/insert_block.rb +9 -14
- data/lib/carray/methods/is_between.rb +59 -0
- data/lib/carray/methods/is_in.rb +140 -10
- data/lib/carray/methods/join.rb +2 -7
- data/lib/carray/methods/lag.rb +49 -0
- data/lib/carray/methods/locate_addr.rb +182 -45
- data/lib/carray/methods/mode.rb +18 -1
- data/lib/carray/methods/nunique.rb +11 -6
- 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/methods/value_counts.rb +4 -11
- data/lib/carray/record_order.rb +208 -0
- data/lib/carray/rng.rb +3 -0
- data/lib/carray/runtime.rb +65 -17
- data/lib/carray/segment_iterator.rb +1221 -0
- data/lib/carray/serialize.rb +214 -30
- data/lib/carray/slab_iterator.rb +101 -27
- data/lib/carray/stack.rb +10 -4
- data/lib/carray/string.rb +105 -5
- data/lib/carray/string_operation_extension.rb +12 -4
- data/lib/carray/struct.rb +137 -58
- data/lib/carray/struct_builder.rb +124 -17
- data/lib/carray/time.rb +1222 -186
- data/lib/carray/window_iterator.rb +242 -81
- data/lib/carray.rb +9 -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 +5 -1
- data/yard-stubs/ca_obj_grid.rb +21 -10
- data/yard-stubs/ca_obj_refer.rb +5 -4
- data/yard-stubs/ca_obj_roll.rb +2 -3
- data/yard-stubs/ca_obj_shift.rb +5 -1
- data/yard-stubs/ca_obj_stride.rb +10 -7
- data/yard-stubs/ca_obj_tile.rb +1 -1
- data/yard-stubs/ca_obj_transpose.rb +1 -1
- data/yard-stubs/ca_obj_window.rb +9 -5
- data/yard-stubs/carray_access.rb +11 -5
- data/yard-stubs/carray_attribute.rb +0 -5
- data/yard-stubs/carray_broadcast.rb +1 -1
- data/yard-stubs/carray_cast.rb +88 -43
- data/yard-stubs/carray_conversion.rb +15 -2
- data/yard-stubs/carray_copy.rb +12 -5
- data/yard-stubs/carray_core.rb +3 -3
- data/yard-stubs/carray_count.rb +9 -0
- data/yard-stubs/carray_element.rb +10 -4
- data/yard-stubs/carray_generate.rb +21 -1
- data/yard-stubs/carray_lazy.rb +5 -1
- data/yard-stubs/carray_loop.rb +4 -1
- data/yard-stubs/carray_mask.rb +60 -12
- data/yard-stubs/carray_math.rb +52 -14
- data/yard-stubs/carray_median_percentile.rb +15 -8
- data/yard-stubs/carray_memory_view.rb +40 -23
- data/yard-stubs/carray_order.rb +61 -29
- data/yard-stubs/carray_random.rb +13 -4
- data/yard-stubs/carray_scatter.rb +21 -9
- data/yard-stubs/carray_segment.rb +72 -0
- data/yard-stubs/carray_slab.rb +10 -2
- data/yard-stubs/carray_sort.rb +49 -8
- data/yard-stubs/carray_test.rb +15 -3
- data/yard-stubs/carray_utils.rb +0 -26
- metadata +19 -5
- data/ext/ca_for_each_element.h +0 -239
- data/ext/carray_kernels_map.c +0 -3467
- data/lib/carray/arrow_tensor.rb +0 -401
- data/lib/carray/table.rb +0 -108
data/ext/mkkernel.rb
CHANGED
|
@@ -177,10 +177,8 @@ module MkKernel
|
|
|
177
177
|
# expr: { object: "..." }.merge(float_widths("(#2) = sin<f>(#1);"))
|
|
178
178
|
#
|
|
179
179
|
# Not every double-taking call wants this. `fabs`, `fmin` / `fmax`,
|
|
180
|
-
# `ceil` / `floor` / `trunc` and the `isnan` family are exact
|
|
181
|
-
# float either way
|
|
182
|
-
# double -- narrowing it would round `x + 0.5f` first and step the
|
|
183
|
-
# answer at the boundary.
|
|
180
|
+
# `ceil` / `floor` / `trunc` / `round` and the `isnan` family are exact
|
|
181
|
+
# on a float either way.
|
|
184
182
|
def self.float_widths(body)
|
|
185
183
|
{
|
|
186
184
|
[:f32] => at_width(body, "f", "float"),
|
|
@@ -406,8 +404,8 @@ module MkKernel
|
|
|
406
404
|
# only, output (an axis-local index) needs no re-lift.
|
|
407
405
|
# :relift -- value-returning kernels (min/max): descend, then re-lift the
|
|
408
406
|
# output back into the Face -- a full-reduction scalar via the
|
|
409
|
-
# registered
|
|
410
|
-
#
|
|
407
|
+
# registered storage_to_element hook, a per-axis CArray via
|
|
408
|
+
# ca_face_relift_entity (carries the subclass tail, e.g. unit).
|
|
411
409
|
raise "reduce: unknown face_gate #{face_gate} (expected nil, :strip, or :relift)" \
|
|
412
410
|
unless [nil, :strip, :relift].include?(face_gate)
|
|
413
411
|
# object_escape: a Symbol naming a Ruby instance method that handles the
|
|
@@ -659,41 +657,6 @@ module MkKernel
|
|
|
659
657
|
}
|
|
660
658
|
end
|
|
661
659
|
|
|
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
660
|
# Register a scan kernel (cumulative / prefix-scan: input shape ==
|
|
698
661
|
# output shape, running accumulator along one axis). The generated
|
|
699
662
|
# method takes exactly one axis argument (e.g., `a.cumsum_ki(0)`).
|
|
@@ -704,7 +667,11 @@ module MkKernel
|
|
|
704
667
|
# output: :f64 | :preserve,
|
|
705
668
|
# init: "0", # initial acc value (per fiber)
|
|
706
669
|
# step: "acc += v; r = acc", # binds v (T_LOAD), r (T_OUT), acc
|
|
707
|
-
# fallback: :wrap_to_f64 | :raise
|
|
670
|
+
# fallback: :wrap_to_f64 | :raise | :mask_count
|
|
671
|
+
#
|
|
672
|
+
# :mask_count is for a scan that reads only the mask (cumcount): a data
|
|
673
|
+
# type outside source: is answered as is_not_masked.cumsum(axis:) in
|
|
674
|
+
# int64, so the values are never converted.
|
|
708
675
|
#
|
|
709
676
|
# Init supports the same special tokens as reduce: T_LIMIT_HI /
|
|
710
677
|
# T_LIMIT_LO / T_ACC / T_OUT. Each scan slab (= fiber along the
|
|
@@ -737,14 +704,19 @@ module MkKernel
|
|
|
737
704
|
# the input is masked). Aligns core cummax / cummin with
|
|
738
705
|
# the reduction contract (empty max / min = UNDEF, no
|
|
739
706
|
# identity) and the axis-group scan family.
|
|
707
|
+
# private:
|
|
708
|
+
# false (default) -> bound as the public `<name>_ki`
|
|
709
|
+
# true -> bound only as the private method `<name>`, a part
|
|
710
|
+
# that lib reaches with `send` (not user surface)
|
|
740
711
|
def self.scan(name, source:, output:, init:, step:, fallback: :raise,
|
|
741
|
-
acc_type: nil, axis_default: nil, empty: nil
|
|
712
|
+
acc_type: nil, axis_default: nil, empty: nil, face_gate: nil,
|
|
713
|
+
private: false)
|
|
742
714
|
raise "duplicate kernel #{name}" if KERNELS.any? { |k| k[:name] == name }
|
|
743
715
|
source.each do |s|
|
|
744
716
|
raise "unknown source data_type #{s}" unless DTYPES.key?(s)
|
|
745
717
|
end
|
|
746
718
|
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)
|
|
719
|
+
raise "unknown fallback #{fallback}" unless %i[raise wrap_to_f64 mask_count].include?(fallback)
|
|
748
720
|
if output == :preserve && fallback == :wrap_to_f64
|
|
749
721
|
raise "#{name}: :preserve + :wrap_to_f64 is a semantic conflict"
|
|
750
722
|
end
|
|
@@ -760,6 +732,13 @@ module MkKernel
|
|
|
760
732
|
if empty == :undef && acc_type == :load_type
|
|
761
733
|
raise "#{name}: empty: :undef is not supported with acc_type: :load_type"
|
|
762
734
|
end
|
|
735
|
+
# face_gate: :relift descends an ORDERABLE Face to its storage, scans
|
|
736
|
+
# the storage and puts the Face back on the result (the reduce family's
|
|
737
|
+
# policy). Only a scan whose output is the source's own values may take
|
|
738
|
+
# it: a running sum of instants is not an instant.
|
|
739
|
+
unless face_gate.nil? || face_gate == :relift
|
|
740
|
+
raise "#{name}: unknown face_gate #{face_gate.inspect} (nil | :relift)"
|
|
741
|
+
end
|
|
763
742
|
|
|
764
743
|
KERNELS << {
|
|
765
744
|
kind: :scan,
|
|
@@ -772,6 +751,8 @@ module MkKernel
|
|
|
772
751
|
acc_type: acc_type,
|
|
773
752
|
axis_default: axis_default,
|
|
774
753
|
empty: empty,
|
|
754
|
+
face_gate: face_gate,
|
|
755
|
+
private: private,
|
|
775
756
|
}
|
|
776
757
|
end
|
|
777
758
|
|
|
@@ -967,7 +948,7 @@ module MkKernel
|
|
|
967
948
|
# is up to the body (= value-returning search variants).
|
|
968
949
|
def self.search(name, source:, output:, body:,
|
|
969
950
|
no_match: "-1", no_match_check: nil, undef_no_match: false,
|
|
970
|
-
mask_self: :raise,
|
|
951
|
+
mask_self: :raise, runtime_args: nil,
|
|
971
952
|
fallback: :raise, semantics: :fiber_local)
|
|
972
953
|
raise "search: unknown semantics #{semantics} (expected :fiber_local or :view_flat)" \
|
|
973
954
|
unless %i[fiber_local view_flat].include?(semantics)
|
|
@@ -979,12 +960,10 @@ module MkKernel
|
|
|
979
960
|
raise "search: no_match must be a String" unless no_match.is_a?(String)
|
|
980
961
|
raise "search: unknown mask_self #{mask_self} (expected :raise | :skip | :ignore)" \
|
|
981
962
|
unless %i[raise skip ignore].include?(mask_self)
|
|
982
|
-
#
|
|
983
|
-
#
|
|
984
|
-
#
|
|
985
|
-
#
|
|
986
|
-
raise "search: unknown mask_query #{mask_query} (expected :ignore | :undef)" \
|
|
987
|
-
unless %i[ignore undef].include?(mask_query)
|
|
963
|
+
# A masked query cell (or a scalar UNDEF query) has no answer: its output
|
|
964
|
+
# cell is UNDEF, so an undetermined query cannot become a determined
|
|
965
|
+
# result from the value that happens to sit under the mask. There is no
|
|
966
|
+
# option to read under the mask.
|
|
988
967
|
raise "search: unknown fallback #{fallback} (expected :raise)" \
|
|
989
968
|
unless fallback == :raise
|
|
990
969
|
|
|
@@ -1049,7 +1028,6 @@ module MkKernel
|
|
|
1049
1028
|
no_match_check: no_match_check,
|
|
1050
1029
|
undef_no_match: undef_no_match,
|
|
1051
1030
|
mask_self: mask_self,
|
|
1052
|
-
mask_query: mask_query,
|
|
1053
1031
|
runtime_args: runtime_args || [],
|
|
1054
1032
|
fallback: fallback,
|
|
1055
1033
|
semantics: semantics,
|
|
@@ -1446,6 +1424,12 @@ module MkKernel
|
|
|
1446
1424
|
true
|
|
1447
1425
|
end
|
|
1448
1426
|
|
|
1427
|
+
# The CArray names of the data types a kernel loads, for its refusal
|
|
1428
|
+
# message ("int8, uint8, ..."), not the generator's own short keys.
|
|
1429
|
+
def self.type_names(sources)
|
|
1430
|
+
sources.map { |s| s == :fixlen ? "fixlen" : DTYPES[s][:ca].sub(/\ACA_/, "").downcase }.join(", ")
|
|
1431
|
+
end
|
|
1432
|
+
|
|
1449
1433
|
def self.reduce_macro_suffix(k, src = nil)
|
|
1450
1434
|
# CA_OBJECT cannot ride the SIMD-licensed macros (= _PLUS / _MIN / _MAX
|
|
1451
1435
|
# / _STAR), which assume C operators (= acc is a VALUE, so
|
|
@@ -1624,9 +1608,11 @@ module MkKernel
|
|
|
1624
1608
|
require "fileutils"
|
|
1625
1609
|
FileUtils.mkdir_p(outdir)
|
|
1626
1610
|
tags = file_tags
|
|
1611
|
+
written = ["carray_kernels_init.c"]
|
|
1627
1612
|
tags.each do |kind, subgroup|
|
|
1628
1613
|
suffix = file_suffix(kind, subgroup)
|
|
1629
1614
|
path = File.join(outdir, "carray_kernels_#{suffix}.c")
|
|
1615
|
+
written << File.basename(path)
|
|
1630
1616
|
File.open(path, "w") do |io|
|
|
1631
1617
|
io.puts header
|
|
1632
1618
|
KERNELS.each do |k|
|
|
@@ -1640,6 +1626,11 @@ module MkKernel
|
|
|
1640
1626
|
File.open(File.join(outdir, "carray_kernels_init.c"), "w") do |io|
|
|
1641
1627
|
emit_aggregator_init(io, tags)
|
|
1642
1628
|
end
|
|
1629
|
+
# extconf compiles every carray_kernels_*.c it finds, so a file left
|
|
1630
|
+
# by a kind or sub-group that no longer exists has to go.
|
|
1631
|
+
Dir.glob(File.join(outdir, "carray_kernels_*.c")).each do |f|
|
|
1632
|
+
File.delete(f) unless written.include?(File.basename(f))
|
|
1633
|
+
end
|
|
1643
1634
|
end
|
|
1644
1635
|
|
|
1645
1636
|
# Which file a kernel belongs to. The three alias kinds live with the kind
|
|
@@ -1658,7 +1649,6 @@ module MkKernel
|
|
|
1658
1649
|
def self.emit_kernel_dispatch(io, k)
|
|
1659
1650
|
case k[:kind]
|
|
1660
1651
|
when :reduce then emit_reduce(io, k)
|
|
1661
|
-
when :map then emit_map(io, k)
|
|
1662
1652
|
when :scan then emit_scan(io, k)
|
|
1663
1653
|
when :sort then emit_sort(io, k)
|
|
1664
1654
|
when :search then emit_search(io, k)
|
|
@@ -1675,7 +1665,7 @@ module MkKernel
|
|
|
1675
1665
|
end
|
|
1676
1666
|
end
|
|
1677
1667
|
|
|
1678
|
-
KINDS = [:reduce, :
|
|
1668
|
+
KINDS = [:reduce, :scan, :sort, :search,
|
|
1679
1669
|
:monop, :binop, :triop, :moncmp, :bincmp].freeze
|
|
1680
1670
|
|
|
1681
1671
|
# Sub-split map for the reduce kind. As one file, reduce accounted for 71%
|
|
@@ -1686,17 +1676,16 @@ module MkKernel
|
|
|
1686
1676
|
# rather than the guess that monop and binop would dominate. A kernel added
|
|
1687
1677
|
# later needs an entry here; emit asserts, so a missing one is loud.
|
|
1688
1678
|
REDUCE_SUB_GROUPS = {
|
|
1689
|
-
# aggregate: arithmetic folds (sum / prod / mean and their
|
|
1690
|
-
#
|
|
1679
|
+
# aggregate: arithmetic folds (sum / prod / mean and their weighted
|
|
1680
|
+
# variants)
|
|
1691
1681
|
sum: :aggregate, prod: :aggregate, mean: :aggregate,
|
|
1692
|
-
sum_strict: :aggregate, mean_safe: :aggregate,
|
|
1693
1682
|
wsum: :aggregate, wmean: :aggregate,
|
|
1694
1683
|
# extreme: order-based (min / max, argmin / argmax, minmax)
|
|
1695
1684
|
min: :extreme, max: :extreme, minmax: :extreme,
|
|
1696
1685
|
argmin: :extreme, argmax: :extreme,
|
|
1697
1686
|
argmin_addr: :extreme, argmax_addr: :extreme,
|
|
1698
1687
|
# cumulative: counter accumulation (the count family and accumulate)
|
|
1699
|
-
|
|
1688
|
+
accumulate: :cumulative,
|
|
1700
1689
|
count_true: :cumulative, count_false: :cumulative,
|
|
1701
1690
|
count_equal: :cumulative,
|
|
1702
1691
|
# variance family: multi-state (Chan / Welford), and the one that
|
|
@@ -1784,11 +1773,116 @@ module MkKernel
|
|
|
1784
1773
|
base + HEADER_BLOCKS.join("\n")
|
|
1785
1774
|
end
|
|
1786
1775
|
|
|
1776
|
+
# Emits the native function of one source data type. The object lane
|
|
1777
|
+
# calls Ruby for every cell, so any cell can raise in the middle of a walk;
|
|
1778
|
+
# its function is rebuilt around ca_iter_ensure (see guard_object_native).
|
|
1779
|
+
# Every other lane is emitted as the emitter writes it.
|
|
1780
|
+
def self.emit_native_for(io, k, src)
|
|
1781
|
+
if src == :object
|
|
1782
|
+
buf = StringIO.new
|
|
1783
|
+
yield buf
|
|
1784
|
+
io.print guard_object_native(buf.string, "#{k[:name]}_ki_native_object")
|
|
1785
|
+
else
|
|
1786
|
+
yield io
|
|
1787
|
+
end
|
|
1788
|
+
end
|
|
1789
|
+
|
|
1790
|
+
# Index of the brace that closes the one at text[open], skipping string
|
|
1791
|
+
# and character literals and comments.
|
|
1792
|
+
def self.matching_brace(text, open)
|
|
1793
|
+
depth = 0
|
|
1794
|
+
i = open
|
|
1795
|
+
while i < text.length
|
|
1796
|
+
c = text[i]
|
|
1797
|
+
case c
|
|
1798
|
+
when '"', "'"
|
|
1799
|
+
i += 1
|
|
1800
|
+
i += (text[i] == "\\") ? 2 : 1 while i < text.length && text[i] != c
|
|
1801
|
+
when "/"
|
|
1802
|
+
if text[i + 1] == "/"
|
|
1803
|
+
i = text.index("\n", i) || text.length
|
|
1804
|
+
elsif text[i + 1] == "*"
|
|
1805
|
+
i = (text.index("*/", i + 2) || text.length) + 1
|
|
1806
|
+
end
|
|
1807
|
+
when "{"
|
|
1808
|
+
depth += 1
|
|
1809
|
+
when "}"
|
|
1810
|
+
depth -= 1
|
|
1811
|
+
return i if depth == 0
|
|
1812
|
+
end
|
|
1813
|
+
i += 1
|
|
1814
|
+
end
|
|
1815
|
+
raise "mkkernel: unbalanced braces"
|
|
1816
|
+
end
|
|
1817
|
+
|
|
1818
|
+
# Rebuilds the native function +fn+ found in +text+ so that its walks are
|
|
1819
|
+
# finished however it leaves. The body moves into a function of its own
|
|
1820
|
+
# that ca_iter_ensure runs; each `ca_iter_state` it declared becomes a
|
|
1821
|
+
# slot of a frame kept by the caller, since a state in the body's own
|
|
1822
|
+
# frame is gone once the body raises. The body text is kept as written:
|
|
1823
|
+
# the parameters are unpacked from the frame under their own names, and
|
|
1824
|
+
# each state name is defined to its slot for the length of the body.
|
|
1825
|
+
def self.guard_object_native(text, fn)
|
|
1826
|
+
header = /^static VALUE\n#{fn} \(([^)]*)\)\n\{\n/
|
|
1827
|
+
m = header.match(text) or raise "mkkernel: #{fn} not found"
|
|
1828
|
+
close = matching_brace(text, m.end(0) - 2)
|
|
1829
|
+
body = text[m.end(0)...close]
|
|
1830
|
+
rest = text[(close + 1)..]
|
|
1831
|
+
|
|
1832
|
+
states = []
|
|
1833
|
+
body = body.gsub(/^[ \t]*ca_iter_state[ \t]+(\w+(?:[ \t]*,[ \t]*\w+)*)[ \t]*;[ \t]*\n/) do
|
|
1834
|
+
states.concat($1.split(/[ \t]*,[ \t]*/))
|
|
1835
|
+
""
|
|
1836
|
+
end
|
|
1837
|
+
states.uniq!
|
|
1838
|
+
return text if states.empty?
|
|
1839
|
+
|
|
1840
|
+
params = m[1].split(",").map do |p|
|
|
1841
|
+
pm = p.strip.match(/\A(.*?)(\w+)\z/) or raise "mkkernel: #{fn} parameter #{p}"
|
|
1842
|
+
[pm[1].strip, pm[2]]
|
|
1843
|
+
end
|
|
1844
|
+
frame = "#{fn}_frame"
|
|
1845
|
+
|
|
1846
|
+
out = StringIO.new
|
|
1847
|
+
out.print m.pre_match
|
|
1848
|
+
out.puts "/* The object lane calls Ruby for every cell, so a walk can raise part"
|
|
1849
|
+
out.puts " way. Its states live in the caller's frame, and ca_iter_ensure"
|
|
1850
|
+
out.puts " finishes them however the body leaves. */"
|
|
1851
|
+
out.puts "typedef struct {"
|
|
1852
|
+
out.puts " ca_iter_state states[#{states.size}];"
|
|
1853
|
+
params.each { |type, name| out.puts " #{type} #{name};".sub(/\* /, "*") }
|
|
1854
|
+
out.puts "} #{frame};"
|
|
1855
|
+
out.puts
|
|
1856
|
+
out.puts "static VALUE"
|
|
1857
|
+
out.puts "#{fn}_body (VALUE arg)"
|
|
1858
|
+
out.puts "{"
|
|
1859
|
+
out.puts " #{frame} *fr = (#{frame} *) arg;"
|
|
1860
|
+
params.each do |type, name|
|
|
1861
|
+
out.puts " #{type} #{name} = fr->#{name};".sub(/\* /, "*")
|
|
1862
|
+
out.puts " (void) #{name};"
|
|
1863
|
+
end
|
|
1864
|
+
states.each_with_index { |s, i| out.puts "#define #{s} (fr->states[#{i}])" }
|
|
1865
|
+
out.print body.sub(/[ \t]*\z/, "")
|
|
1866
|
+
out.puts unless body.match?(/\n[ \t]*\z/)
|
|
1867
|
+
states.each { |s| out.puts "#undef #{s}" }
|
|
1868
|
+
out.puts "}"
|
|
1869
|
+
out.puts
|
|
1870
|
+
out.puts "static VALUE"
|
|
1871
|
+
out.puts "#{fn} (#{m[1]})"
|
|
1872
|
+
out.puts "{"
|
|
1873
|
+
out.puts " #{frame} fr;"
|
|
1874
|
+
params.each { |_, name| out.puts " fr.#{name} = #{name};" }
|
|
1875
|
+
out.puts " return ca_iter_ensure(#{states.size}, fr.states, #{fn}_body, (VALUE) &fr);"
|
|
1876
|
+
out.puts "}"
|
|
1877
|
+
out.print rest
|
|
1878
|
+
out.string
|
|
1879
|
+
end
|
|
1880
|
+
|
|
1787
1881
|
def self.emit_reduce(io, k)
|
|
1788
1882
|
io.puts
|
|
1789
1883
|
io.puts "/* ===== #{k[:name]}_ki ============================================ */"
|
|
1790
1884
|
k[:source].each do |src|
|
|
1791
|
-
|
|
1885
|
+
emit_native_for(io, k, src) { |o| emit_reduce_native(o, k, src) }
|
|
1792
1886
|
end
|
|
1793
1887
|
emit_reduce_dispatch(io, k)
|
|
1794
1888
|
end
|
|
@@ -1860,6 +1954,11 @@ module MkKernel
|
|
|
1860
1954
|
|
|
1861
1955
|
view_flat = (k[:semantics] == :view_flat)
|
|
1862
1956
|
|
|
1957
|
+
if streamable
|
|
1958
|
+
emit_reduce_streaming(io, k, si, oi, ruby_wrap, acc_var, acc_init,
|
|
1959
|
+
decls, reduce_stmt, finish_expr, src)
|
|
1960
|
+
end
|
|
1961
|
+
|
|
1863
1962
|
io.puts
|
|
1864
1963
|
io.puts <<~C
|
|
1865
1964
|
static VALUE
|
|
@@ -1868,8 +1967,7 @@ module MkKernel
|
|
|
1868
1967
|
C
|
|
1869
1968
|
|
|
1870
1969
|
if streamable
|
|
1871
|
-
|
|
1872
|
-
decls, reduce_stmt, finish_expr, extra_args, src)
|
|
1970
|
+
emit_reduce_streaming_call(io, k, src)
|
|
1873
1971
|
end
|
|
1874
1972
|
|
|
1875
1973
|
# L.1 / L.3 / L.4 (PROPOSAL_REDUCTION_LOOP_INTERCHANGE):
|
|
@@ -1917,12 +2015,22 @@ module MkKernel
|
|
|
1917
2015
|
else
|
|
1918
2016
|
mixed_buf = false
|
|
1919
2017
|
end
|
|
1920
|
-
|
|
2018
|
+
# The object lane is left out: its cell is a Ruby call, so the
|
|
2019
|
+
# interchange's contiguous SIMD loads buy nothing, and each output cell
|
|
2020
|
+
# accumulates in the same order on the generic walk.
|
|
2021
|
+
li_eligible = src != :object &&
|
|
2022
|
+
!k[:array_arg] && !k[:value_arg] &&
|
|
1921
2023
|
buf_count >= 1 && induction_count <= 1 && other_count == 0 &&
|
|
1922
2024
|
!mixed_buf
|
|
1923
2025
|
if li_eligible
|
|
2026
|
+
# The interchange folds every cell of an unmasked source, so it can
|
|
2027
|
+
# answer only when the reduced axis holds at least min_count cells;
|
|
2028
|
+
# otherwise the general walk writes the UNDEF (or fill_value) cells.
|
|
2029
|
+
li_gate = k[:mask_policy] == :min_count
|
|
2030
|
+
io.puts " if ( naxes != 1 || ! ca_too_few_present(ca->dim[slab_axes[0]], min_count, 0) ) {" if li_gate
|
|
1924
2031
|
emit_reduce_loop_interchange(io, k, si, oi, ruby_wrap, src,
|
|
1925
2032
|
acc_var, acc_init, reduce_stmt, finish_expr)
|
|
2033
|
+
io.puts " }" if li_gate
|
|
1926
2034
|
end
|
|
1927
2035
|
|
|
1928
2036
|
if view_flat
|
|
@@ -1932,7 +2040,7 @@ module MkKernel
|
|
|
1932
2040
|
# (slab walks the whole array in view-flat row-major); other
|
|
1933
2041
|
# naxes are rejected at entry.
|
|
1934
2042
|
io.puts " if ( naxes != 1 && naxes != ca->ndim ) {"
|
|
1935
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
2043
|
+
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
2044
|
io.puts " }"
|
|
1937
2045
|
end
|
|
1938
2046
|
|
|
@@ -1946,7 +2054,7 @@ module MkKernel
|
|
|
1946
2054
|
slab_axes, naxes, 0);
|
|
1947
2055
|
if ( rc != CA_ITER_OK ) {
|
|
1948
2056
|
rb_raise(rb_eRuntimeError,
|
|
1949
|
-
"
|
|
2057
|
+
"%s: kernel_iterator init failed rc=%d", ca_calling_method_name(), rc);
|
|
1950
2058
|
}
|
|
1951
2059
|
char *p;
|
|
1952
2060
|
boolean8_t *m;
|
|
@@ -1984,7 +2092,7 @@ module MkKernel
|
|
|
1984
2092
|
io.puts " slab_axes, naxes, 0);"
|
|
1985
2093
|
io.puts " if ( rcw != CA_ITER_OK ) {"
|
|
1986
2094
|
io.puts " ca_iter_state_finish(&st);"
|
|
1987
|
-
io.puts %Q[ rb_raise(rb_eRuntimeError, "
|
|
2095
|
+
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: weights kernel_iterator init failed rc=%d", ca_calling_method_name(), rcw);]
|
|
1988
2096
|
io.puts " }"
|
|
1989
2097
|
io.puts " char *p_w;"
|
|
1990
2098
|
end
|
|
@@ -1999,7 +2107,7 @@ module MkKernel
|
|
|
1999
2107
|
io.puts " if ( ! ca_iter_state_next_slab_axes(&st_w, &p_w, NULL) ) {"
|
|
2000
2108
|
io.puts " ca_iter_state_finish(&st);"
|
|
2001
2109
|
io.puts " ca_iter_state_finish(&st_w);"
|
|
2002
|
-
io.puts %Q[ rb_raise(rb_eRuntimeError, "
|
|
2110
|
+
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: weights iter exhausted early (shape invariant violated)", ca_calling_method_name());]
|
|
2003
2111
|
io.puts " }"
|
|
2004
2112
|
else
|
|
2005
2113
|
io.puts " while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {"
|
|
@@ -2035,6 +2143,11 @@ module MkKernel
|
|
|
2035
2143
|
if k[:all_nan_result] == :undef && FLOAT_DTYPES.include?(src)
|
|
2036
2144
|
all_nan_flag = "__anf_all_nan"
|
|
2037
2145
|
io.puts " int #{all_nan_flag} = 0;"
|
|
2146
|
+
elsif k[:all_nan_result] == :undef && src == :object
|
|
2147
|
+
# The object body takes no NaN cell, so a slab of present NaN
|
|
2148
|
+
# cells leaves the position unset.
|
|
2149
|
+
all_nan_flag = "__anf_all_nan"
|
|
2150
|
+
io.puts " int #{all_nan_flag} = ( masked_cnt < st.slab_elements && (#{finish_expr}) < 0 );"
|
|
2038
2151
|
end
|
|
2039
2152
|
emit_all_nan_fixup(io, k, src, si, pairs,
|
|
2040
2153
|
"masked_cnt < st.slab_elements",
|
|
@@ -2042,24 +2155,12 @@ module MkKernel
|
|
|
2042
2155
|
end
|
|
2043
2156
|
trigger = case k[:mask_policy]
|
|
2044
2157
|
when :strict then "masked_cnt > 0"
|
|
2045
|
-
when :all_masked then "masked_cnt
|
|
2158
|
+
when :all_masked then "ca_too_few_present(st.slab_elements - masked_cnt, -1, 1)"
|
|
2046
2159
|
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
|
|
2160
|
+
# An identity_on_empty kernel needs no cell (the else
|
|
2161
|
+
# branch writes finish = acc = identity for an empty slab).
|
|
2162
|
+
"ca_too_few_present(st.slab_elements - masked_cnt, min_count, " \
|
|
2163
|
+
"#{k[:identity_on_empty] ? 0 : 1})"
|
|
2063
2164
|
end
|
|
2064
2165
|
trigger = "(#{trigger}) || #{all_nan_flag}" if all_nan_flag
|
|
2065
2166
|
finish_emit = view_flat \
|
|
@@ -2078,20 +2179,22 @@ module MkKernel
|
|
|
2078
2179
|
else
|
|
2079
2180
|
if k[:array_arg]
|
|
2080
2181
|
wc = (k[:array_arg][:data_type] == :promote) ? "double" : si[:c]
|
|
2081
|
-
io.puts "
|
|
2182
|
+
io.puts " ca_size_t __sra_throwaway_mc = 0;"
|
|
2183
|
+
io.puts " CA_SLAB_REDUCE_ARRAY_T_EX(#{si[:c]}, #{wc}, st, p, m, st_w, p_w, #{acc_var}, #{acc_init}, #{reduce_stmt}, __sra_throwaway_mc);"
|
|
2184
|
+
io.puts " (void) __sra_throwaway_mc;"
|
|
2082
2185
|
else
|
|
2083
2186
|
suffix = MkKernel.reduce_macro_suffix(k, src)
|
|
2084
|
-
# Non-mask_policy path:
|
|
2085
|
-
#
|
|
2086
|
-
#
|
|
2087
|
-
if
|
|
2187
|
+
# Non-mask_policy path: the slab macros always take a masked_cnt,
|
|
2188
|
+
# so hand them a throwaway. The generic form (suffix "", the
|
|
2189
|
+
# :object lane) has a wrapper that hides it.
|
|
2190
|
+
if suffix == ""
|
|
2191
|
+
io.puts " CA_SLAB_REDUCE_T(#{si[:c]}, st, p, m, #{acc_var}, #{acc_init}, #{reduce_stmt});"
|
|
2192
|
+
else
|
|
2088
2193
|
io.puts " ca_size_t __sr_throwaway_mc = 0;"
|
|
2089
2194
|
emit_reduce_slab_call(io, k, src, si, oi, suffix, acc_var, acc_init,
|
|
2090
2195
|
reduce_stmt, "__sr_throwaway_mc",
|
|
2091
2196
|
indent: " ")
|
|
2092
2197
|
io.puts " (void) __sr_throwaway_mc;"
|
|
2093
|
-
else
|
|
2094
|
-
io.puts " CA_SLAB_REDUCE_T#{suffix}(#{si[:c]}, st, p, m, #{acc_var}, #{acc_init}, #{reduce_stmt});"
|
|
2095
2198
|
end
|
|
2096
2199
|
end
|
|
2097
2200
|
finish_emit = view_flat \
|
|
@@ -2166,7 +2269,7 @@ module MkKernel
|
|
|
2166
2269
|
io.puts " ca_iter_state st;"
|
|
2167
2270
|
io.puts " int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES, slab_axes, naxes, 0);"
|
|
2168
2271
|
io.puts " if ( rc != CA_ITER_OK ) {"
|
|
2169
|
-
io.puts %Q[ rb_raise(rb_eRuntimeError, "
|
|
2272
|
+
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: kernel_iterator init failed rc=%d", ca_calling_method_name(), rc);]
|
|
2170
2273
|
io.puts " }"
|
|
2171
2274
|
io.puts " char *p;"
|
|
2172
2275
|
io.puts " boolean8_t *m;"
|
|
@@ -2215,16 +2318,9 @@ module MkKernel
|
|
|
2215
2318
|
io.puts " sidx[sk] = 0;"
|
|
2216
2319
|
io.puts " }"
|
|
2217
2320
|
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
|
|
2321
|
+
# A count over nothing is 0, not UNDEF: it needs no cell.
|
|
2322
|
+
io.puts " if ( ca_too_few_present(st.slab_elements - masked_cnt, min_count, " \
|
|
2323
|
+
"#{counting ? 0 : 1}) ) {"
|
|
2228
2324
|
io.puts " if ( ! op_mask ) {"
|
|
2229
2325
|
io.puts " ca_create_mask(co);"
|
|
2230
2326
|
io.puts " op_mask = (boolean8_t *) co->mask->ptr;"
|
|
@@ -2277,10 +2373,8 @@ module MkKernel
|
|
|
2277
2373
|
# Layout per slab:
|
|
2278
2374
|
# Pass 1: sum = Σ x_i via CA_SLAB_REDUCE_T_PLUS_EX (SIMD :plus)
|
|
2279
2375
|
# 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.
|
|
2376
|
+
# mask trigger: ca_too_few_present(n_valid, min_count, 1 or 2), 2 for
|
|
2377
|
+
# the :n_minus_1 divisor.
|
|
2284
2378
|
# Pass 2: M2 = Σ (x_i - mean)² via the same macro with a centred
|
|
2285
2379
|
# REDUCE stmt.
|
|
2286
2380
|
# Finish: variance = M2 / DIVISOR (n or n-1); stddev variant applies
|
|
@@ -2387,9 +2481,9 @@ module MkKernel
|
|
|
2387
2481
|
finish_expr = if k[:output_transform] == :sqrt_clamp
|
|
2388
2482
|
if src == :object
|
|
2389
2483
|
# Object stddev outputs f64 (BigMath.sqrt is user's job).
|
|
2390
|
-
"
|
|
2484
|
+
"ca_sqrt_clamp(NUM2DBL(#{inner_var}))"
|
|
2391
2485
|
else
|
|
2392
|
-
"
|
|
2486
|
+
"ca_sqrt_clamp(#{inner_var})"
|
|
2393
2487
|
end
|
|
2394
2488
|
else
|
|
2395
2489
|
inner_var
|
|
@@ -2417,13 +2511,9 @@ module MkKernel
|
|
|
2417
2511
|
# min_count sentinel arg is always present (all four kernels use it).
|
|
2418
2512
|
extra_args = ", ca_size_t min_count"
|
|
2419
2513
|
|
|
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"
|
|
2514
|
+
# Fewer than required_min valid cells (2 for the n-1 divisor) leave the
|
|
2515
|
+
# statistic undefined, so the cell is UNDEF: the sample variance of one
|
|
2516
|
+
# value is 0/0.
|
|
2427
2517
|
|
|
2428
2518
|
# Reduce-call plumbing. :object keeps the scalar rb_funcall loop
|
|
2429
2519
|
# (CA_SLAB_REDUCE_T_EX). Every other src uses the 8-way accumulator
|
|
@@ -2464,7 +2554,7 @@ module MkKernel
|
|
|
2464
2554
|
slab_axes, naxes, 0);
|
|
2465
2555
|
if ( rc != CA_ITER_OK ) {
|
|
2466
2556
|
rb_raise(rb_eRuntimeError,
|
|
2467
|
-
"
|
|
2557
|
+
"%s: kernel_iterator init failed rc=%d", ca_calling_method_name(), rc);
|
|
2468
2558
|
}
|
|
2469
2559
|
char *p;
|
|
2470
2560
|
boolean8_t *m;
|
|
@@ -2476,7 +2566,7 @@ module MkKernel
|
|
|
2476
2566
|
ca_size_t masked_cnt = 0;
|
|
2477
2567
|
#{p1_reduce_call}
|
|
2478
2568
|
ca_size_t n_valid = st.slab_elements - masked_cnt;
|
|
2479
|
-
int __trigger = (min_count
|
|
2569
|
+
int __trigger = ca_too_few_present(n_valid, min_count, #{required_min});
|
|
2480
2570
|
if ( __trigger ) {
|
|
2481
2571
|
if ( ! op_mask ) {
|
|
2482
2572
|
ca_create_mask(co);
|
|
@@ -2484,11 +2574,6 @@ module MkKernel
|
|
|
2484
2574
|
}
|
|
2485
2575
|
op_mask[out_i] = 1;
|
|
2486
2576
|
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
2577
|
} else {
|
|
2493
2578
|
#{mean_setup}
|
|
2494
2579
|
/* Pass 2: M2 = Σ (x - mean)² */
|
|
@@ -2660,8 +2745,9 @@ module MkKernel
|
|
|
2660
2745
|
exposes ca->ptr as that buffer at O(1) cost (no materialise). */
|
|
2661
2746
|
if ( naxes == 1
|
|
2662
2747
|
&& slab_axes[0] != (int8_t)(ca->ndim - 1)
|
|
2663
|
-
&& ca
|
|
2664
|
-
&& ca_attach_is_alias(ca)
|
|
2748
|
+
&& ! ca_has_mask(ca)
|
|
2749
|
+
&& ca_attach_is_alias(ca)
|
|
2750
|
+
&& ca_fast_paths_enabled() ) {
|
|
2665
2751
|
int8_t __li_ax = slab_axes[0];
|
|
2666
2752
|
ca_size_t __li_M = ca->dim[__li_ax];
|
|
2667
2753
|
ca_size_t __li_INNER = 1;
|
|
@@ -2675,7 +2761,7 @@ module MkKernel
|
|
|
2675
2761
|
CArray *co;
|
|
2676
2762
|
GetCArray(vout, co);
|
|
2677
2763
|
#{oi[:c]} *op = (#{oi[:c]} *) co->ptr;
|
|
2678
|
-
ca_attach(ca);
|
|
2764
|
+
ca_attach(ca); /* window: numeric lanes only, nothing raises inside; O(1) alias attach */
|
|
2679
2765
|
const #{si[:c]} *p_root = (const #{si[:c]} *) ca->ptr;
|
|
2680
2766
|
|
|
2681
2767
|
/* Per-buffer stack accumulators (one per :plus state, sized
|
|
@@ -2709,9 +2795,10 @@ module MkKernel
|
|
|
2709
2795
|
if ( naxes == 1
|
|
2710
2796
|
&& slab_axes[0] >= 1
|
|
2711
2797
|
&& slab_axes[0] != (int8_t)(ca->ndim - 1)
|
|
2712
|
-
&& ca
|
|
2798
|
+
&& ! ca_has_mask(ca)
|
|
2713
2799
|
&& ca_func[ca->obj_type].attach == ca_stack_func.attach
|
|
2714
|
-
&& ((CAStack *) ca)->k_axis == 0
|
|
2800
|
+
&& ((CAStack *) ca)->k_axis == 0
|
|
2801
|
+
&& ca_fast_paths_enabled() ) {
|
|
2715
2802
|
CAStack *__li_st = (CAStack *) ca;
|
|
2716
2803
|
int8_t __li_ax = slab_axes[0];
|
|
2717
2804
|
ca_size_t __li_M = ca->dim[__li_ax];
|
|
@@ -2724,7 +2811,7 @@ module MkKernel
|
|
|
2724
2811
|
core reads parent->ptr without mask awareness). */
|
|
2725
2812
|
int __li_ok = 1;
|
|
2726
2813
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2727
|
-
if ( __li_st->parents[__kk]
|
|
2814
|
+
if ( ca_has_mask(__li_st->parents[__kk]) ) { __li_ok = 0; break; }
|
|
2728
2815
|
}
|
|
2729
2816
|
|
|
2730
2817
|
if ( __li_ok && __li_INNER >= 64 && __li_M * __li_INNER >= 1024 ) {
|
|
@@ -2739,7 +2826,7 @@ module MkKernel
|
|
|
2739
2826
|
ca_size_t __li_parent_out = __li_OUTER * __li_INNER;
|
|
2740
2827
|
const ca_size_t __li_TILE = 512;
|
|
2741
2828
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2742
|
-
ca_attach(__li_st->parents[__kk]);
|
|
2829
|
+
ca_attach(__li_st->parents[__kk]); /* window: numeric lanes only, nothing raises inside */
|
|
2743
2830
|
const #{si[:c]} *p_root = (const #{si[:c]} *) __li_st->parents[__kk]->ptr;
|
|
2744
2831
|
#{oi[:c]} *__li_op_k = op + (ca_size_t) __kk * __li_parent_out;
|
|
2745
2832
|
#{core.call("p_root", "__li_op_k", "__li_OUTER")}
|
|
@@ -2772,11 +2859,12 @@ module MkKernel
|
|
|
2772
2859
|
if ( naxes == 1
|
|
2773
2860
|
&& slab_axes[0] >= 1
|
|
2774
2861
|
&& slab_axes[0] != (int8_t)(ca->ndim - 1)
|
|
2775
|
-
&& ca
|
|
2862
|
+
&& ! ca_has_mask(ca)
|
|
2776
2863
|
&& ca_func[ca->obj_type].attach == ca_stack_func.attach
|
|
2777
2864
|
&& ((CAStack *) ca)->k_axis > 0
|
|
2778
2865
|
&& ( slab_axes[0] > ((CAStack *) ca)->k_axis
|
|
2779
|
-
|| slab_axes[0] + 1 == ((CAStack *) ca)->k_axis )
|
|
2866
|
+
|| slab_axes[0] + 1 == ((CAStack *) ca)->k_axis )
|
|
2867
|
+
&& ca_fast_paths_enabled() ) {
|
|
2780
2868
|
CAStack *__li_st = (CAStack *) ca;
|
|
2781
2869
|
int8_t __li_ax = slab_axes[0];
|
|
2782
2870
|
int8_t __li_kax = (int8_t) __li_st->k_axis;
|
|
@@ -2792,7 +2880,7 @@ module MkKernel
|
|
|
2792
2880
|
/* Bail to the generic path if any parent carries a mask. */
|
|
2793
2881
|
int __li_ok = 1;
|
|
2794
2882
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2795
|
-
if ( __li_st->parents[__kk]
|
|
2883
|
+
if ( ca_has_mask(__li_st->parents[__kk]) ) { __li_ok = 0; break; }
|
|
2796
2884
|
}
|
|
2797
2885
|
|
|
2798
2886
|
if ( __li_ok && __li_INNER >= 64 && __li_M * __li_INNER >= 1024 ) {
|
|
@@ -2833,7 +2921,7 @@ module MkKernel
|
|
|
2833
2921
|
ca_size_t __li_plane_elems = __li_M * __li_INNER;
|
|
2834
2922
|
const ca_size_t __li_TILE = 512;
|
|
2835
2923
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2836
|
-
ca_attach(__li_st->parents[__kk]);
|
|
2924
|
+
ca_attach(__li_st->parents[__kk]); /* window: numeric lanes only, nothing raises inside */
|
|
2837
2925
|
const #{si[:c]} *p_root = (const #{si[:c]} *) __li_st->parents[__kk]->ptr;
|
|
2838
2926
|
ca_size_t __li_k_base = (ca_size_t) __kk * __li_k_stride;
|
|
2839
2927
|
|
|
@@ -2915,10 +3003,11 @@ module MkKernel
|
|
|
2915
3003
|
if ( naxes == 1
|
|
2916
3004
|
&& slab_axes[0] >= 1
|
|
2917
3005
|
&& slab_axes[0] != (int8_t)(ca->ndim - 1)
|
|
2918
|
-
&& ca
|
|
3006
|
+
&& ! ca_has_mask(ca)
|
|
2919
3007
|
&& ca_func[ca->obj_type].attach == ca_stack_func.attach
|
|
2920
3008
|
&& ((CAStack *) ca)->k_axis > 0
|
|
2921
|
-
&& slab_axes[0] + 1 < ((CAStack *) ca)->k_axis
|
|
3009
|
+
&& slab_axes[0] + 1 < ((CAStack *) ca)->k_axis
|
|
3010
|
+
&& ca_fast_paths_enabled() ) {
|
|
2922
3011
|
CAStack *__li_st = (CAStack *) ca;
|
|
2923
3012
|
int8_t __li_ax = slab_axes[0];
|
|
2924
3013
|
int8_t __li_kax = (int8_t) __li_st->k_axis;
|
|
@@ -2936,7 +3025,7 @@ module MkKernel
|
|
|
2936
3025
|
/* Bail to the generic path if any parent carries a mask. */
|
|
2937
3026
|
int __li_ok = 1;
|
|
2938
3027
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2939
|
-
if ( __li_st->parents[__kk]
|
|
3028
|
+
if ( ca_has_mask(__li_st->parents[__kk]) ) { __li_ok = 0; break; }
|
|
2940
3029
|
}
|
|
2941
3030
|
|
|
2942
3031
|
if ( __li_ok && __li_eff_INNER >= 32 && __li_M * __li_eff_INNER >= 1024 ) {
|
|
@@ -2990,7 +3079,7 @@ module MkKernel
|
|
|
2990
3079
|
|
|
2991
3080
|
const ca_size_t __li_TILE = 512;
|
|
2992
3081
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2993
|
-
ca_attach(__li_st->parents[__kk]);
|
|
3082
|
+
ca_attach(__li_st->parents[__kk]); /* window: numeric lanes only, nothing raises inside */
|
|
2994
3083
|
const #{si[:c]} *p_root = (const #{si[:c]} *) __li_st->parents[__kk]->ptr;
|
|
2995
3084
|
ca_size_t __li_k_base = (ca_size_t) __kk * __li_k_stride;
|
|
2996
3085
|
|
|
@@ -3097,7 +3186,7 @@ module MkKernel
|
|
|
3097
3186
|
slab_axes, naxes, 0);
|
|
3098
3187
|
if ( rc != CA_ITER_OK ) {
|
|
3099
3188
|
rb_raise(rb_eRuntimeError,
|
|
3100
|
-
"
|
|
3189
|
+
"%s: kernel_iterator init failed rc=%d", ca_calling_method_name(), rc);
|
|
3101
3190
|
}
|
|
3102
3191
|
char *p;
|
|
3103
3192
|
boolean8_t *m;
|
|
@@ -3116,10 +3205,7 @@ module MkKernel
|
|
|
3116
3205
|
emit_all_nan_fixup(io, k, src, si, limit_init_state_vars(k, si, oi, src),
|
|
3117
3206
|
"masked_cnt < st.slab_elements", indent: " ")
|
|
3118
3207
|
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)"
|
|
3208
|
+
trigger = "ca_too_few_present(st.slab_elements - masked_cnt, min_count, 1)"
|
|
3123
3209
|
io.puts " if ( #{trigger} ) {"
|
|
3124
3210
|
io.puts " if ( ! op_mask_a ) {"
|
|
3125
3211
|
io.puts " ca_create_mask(co_a);"
|
|
@@ -3153,10 +3239,11 @@ module MkKernel
|
|
|
3153
3239
|
# pair of [1,...,1] entities instead.
|
|
3154
3240
|
io.puts " if ( naxes == ca->ndim && !keep_axis ) {"
|
|
3155
3241
|
if min_count
|
|
3156
|
-
# Full reduction: if the sole output cell is masked,
|
|
3157
|
-
#
|
|
3158
|
-
#
|
|
3159
|
-
|
|
3242
|
+
# Full reduction: if the sole output cell is masked, both answers are
|
|
3243
|
+
# UNDEF, still as a pair -- the shape of the answer does not depend on
|
|
3244
|
+
# the data. (Both op_mask_a and op_mask_b share the same bit since
|
|
3245
|
+
# both reductions consume the same slab, so checking _a is sufficient.)
|
|
3246
|
+
io.puts " if ( op_mask_a && op_mask_a[0] ) return rb_assoc_new(CA_UNDEF, CA_UNDEF);"
|
|
3160
3247
|
end
|
|
3161
3248
|
io.puts " return rb_assoc_new(#{ruby_wrap}(op_a[0]), #{ruby_wrap}(op_b[0]));"
|
|
3162
3249
|
io.puts " }"
|
|
@@ -3164,42 +3251,40 @@ module MkKernel
|
|
|
3164
3251
|
io.puts " }"
|
|
3165
3252
|
end
|
|
3166
3253
|
|
|
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)
|
|
3254
|
+
# The walk of the streaming reduce (see emit_reduce_streaming_call).
|
|
3192
3255
|
def self.emit_reduce_streaming(io, k, si, oi, ruby_wrap, acc_var,
|
|
3193
3256
|
acc_init, decls, reduce_stmt,
|
|
3194
|
-
finish_expr,
|
|
3257
|
+
finish_expr, src)
|
|
3195
3258
|
name = k[:name]
|
|
3196
|
-
min_count = (k[:mask_policy] == :min_count)
|
|
3197
3259
|
has_mp = !k[:mask_policy].nil?
|
|
3260
|
+
fn = "#{name}_ki_stream_#{src}"
|
|
3198
3261
|
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3262
|
+
# The walk runs under ca_lazy_arena_protect: the transfer from the lazy
|
|
3263
|
+
# source and the object lane's fold can both raise, and the arena must
|
|
3264
|
+
# be left however the walk ends. So it is a function of its own, with
|
|
3265
|
+
# the native's arguments carried in a context whose first member is
|
|
3266
|
+
# the chunk (released by ca_lazy_arena_release_held).
|
|
3267
|
+
io.puts
|
|
3268
|
+
io.puts "typedef struct {"
|
|
3269
|
+
io.puts " void *chunk;"
|
|
3270
|
+
io.puts " CArray *ca;"
|
|
3271
|
+
io.puts " #{si[:c]} value_arg;" if k[:value_arg]
|
|
3272
|
+
io.puts " ca_size_t min_count;" if k[:mask_policy] == :min_count
|
|
3273
|
+
io.puts "} #{fn}_t;"
|
|
3274
|
+
io.puts
|
|
3275
|
+
io.puts "static VALUE"
|
|
3276
|
+
io.puts "#{fn} (VALUE arg)"
|
|
3277
|
+
io.puts "{"
|
|
3278
|
+
io.puts " #{fn}_t *__ctx = (#{fn}_t *) arg;"
|
|
3279
|
+
io.puts " CArray *ca = __ctx->ca;"
|
|
3280
|
+
if k[:value_arg]
|
|
3281
|
+
io.puts " #{si[:c]} value_arg = __ctx->value_arg;"
|
|
3282
|
+
io.puts " (void) value_arg;"
|
|
3283
|
+
end
|
|
3284
|
+
if k[:mask_policy] == :min_count
|
|
3285
|
+
io.puts " ca_size_t min_count = __ctx->min_count;"
|
|
3286
|
+
io.puts " (void) min_count;"
|
|
3287
|
+
end
|
|
3203
3288
|
io.puts " ca_size_t __inner = 1;"
|
|
3204
3289
|
io.puts " int __k;"
|
|
3205
3290
|
io.puts " for ( __k = 1; __k < ca->ndim; __k++ ) __inner *= ca->dim[__k];"
|
|
@@ -3237,7 +3322,6 @@ module MkKernel
|
|
|
3237
3322
|
io.puts " ca_size_t masked_cnt = 0; /* unused on streaming (no mask) */"
|
|
3238
3323
|
io.puts " (void) masked_cnt;"
|
|
3239
3324
|
end
|
|
3240
|
-
io.puts " ca_lazy_arena_enter();"
|
|
3241
3325
|
if si[:c] == "VALUE"
|
|
3242
3326
|
# Object lane: the chunk holds VALUEs pulled from the source, and
|
|
3243
3327
|
# the fold below calls rb_funcall per cell. A collection there
|
|
@@ -3247,6 +3331,7 @@ module MkKernel
|
|
|
3247
3331
|
else
|
|
3248
3332
|
io.puts " #{si[:c]} *__chunk = (#{si[:c]} *) ca_lazy_arena_acquire(__chunk_elems * sizeof(#{si[:c]}));"
|
|
3249
3333
|
end
|
|
3334
|
+
io.puts " __ctx->chunk = __chunk;"
|
|
3250
3335
|
io.puts " while ( __outer_off < __outer ) {"
|
|
3251
3336
|
io.puts " ca_size_t __r = (__outer - __outer_off < __rows) ? (__outer - __outer_off) : __rows;"
|
|
3252
3337
|
io.puts " ca_size_t __n = __r * __inner;"
|
|
@@ -3261,21 +3346,84 @@ module MkKernel
|
|
|
3261
3346
|
io.puts " }"
|
|
3262
3347
|
io.puts " __outer_off += __r;"
|
|
3263
3348
|
io.puts " }"
|
|
3264
|
-
io.puts " ca_lazy_arena_release(__chunk);"
|
|
3265
|
-
io.puts " ca_lazy_arena_exit();"
|
|
3266
3349
|
if all_nan_stream
|
|
3267
3350
|
io.puts " if ( ! __anf_seen && ca->elements > 0 ) #{acc_var} = (#{si[:c]}) NAN;"
|
|
3268
3351
|
end
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
#
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3352
|
+
# Streaming has no mask source, so masked_cnt is 0 and none of the
|
|
3353
|
+
# mask-policy triggers fire: the answer is the finish expression.
|
|
3354
|
+
io.puts " return #{ruby_wrap}((#{oi[:c]}) (#{finish_expr}));"
|
|
3355
|
+
io.puts "}"
|
|
3356
|
+
end
|
|
3357
|
+
|
|
3358
|
+
# The streaming branch at the top of name_ki_native_<src>: a flat
|
|
3359
|
+
# reduction of an unmasked lazy view pulls the source in fixed-size
|
|
3360
|
+
# chunks via ca_xfer_stride instead of materialising it whole. Falls
|
|
3361
|
+
# through to the existing path when the conditions are not met.
|
|
3362
|
+
#
|
|
3363
|
+
# Strategy: chunk along the outermost axis. inner = prod(dim[1..]);
|
|
3364
|
+
# rows_per_chunk = max(1, target_elems / inner). 1-D is the natural
|
|
3365
|
+
# sub-case (inner == 1, rows = target_elems).
|
|
3366
|
+
#
|
|
3367
|
+
# Memory peak: O(max(target_elems, inner) * sizeof T) instead of O(N).
|
|
3368
|
+
# For N-D, this drops the outermost dim from the materialised buffer,
|
|
3369
|
+
# which is typically the dominant factor (e.g. 1000x10000 f64 reduction:
|
|
3370
|
+
# 80MB -> 80KB). Wall-clock perf at large N matches or beats
|
|
3371
|
+
# SRC_ATTACH because each chunk fits in L1d (or L2 for wide last dims).
|
|
3372
|
+
#
|
|
3373
|
+
# Restrictions handled by short-circuit checks (= falls through to
|
|
3374
|
+
# SRC_ATTACH path):
|
|
3375
|
+
# - axis kwarg (= partial reduction): naxes != ca->ndim
|
|
3376
|
+
# (structural blocker: CABinOp/CABinCmp xfer_stride rebuilds
|
|
3377
|
+
# right_strides from counts, so non-rectangular slab pulls
|
|
3378
|
+
# produce wrong values for the right operand.)
|
|
3379
|
+
# - non-lazy source: !ca_is_lazy_view(ca)
|
|
3380
|
+
# - mask present: ca_has_mask(ca)
|
|
3381
|
+
# - nothing to fold, or fewer cells than min_count asks for: the walk
|
|
3382
|
+
# has no mask, so it answers the finish expression, which is right
|
|
3383
|
+
# only when the mask policy cannot fire. An empty source and a
|
|
3384
|
+
# min_count above the cell count are the two ways it can without a
|
|
3385
|
+
# mask, and the full path answers them.
|
|
3386
|
+
def self.emit_reduce_streaming_call(io, k, src)
|
|
3387
|
+
fn = "#{k[:name]}_ki_stream_#{src}"
|
|
3388
|
+
io.puts " if ( naxes == ca->ndim && ca->ndim >= 1 && !keep_axis &&"
|
|
3389
|
+
io.puts " ca->elements > 0 &&"
|
|
3390
|
+
io.puts " ! ca_too_few_present(ca->elements, min_count, 0) &&" if k[:mask_policy] == :min_count
|
|
3391
|
+
io.puts " ca_is_lazy_view(ca) && ! ca_has_mask(ca) &&"
|
|
3392
|
+
io.puts " ca_fast_paths_enabled() ) {"
|
|
3393
|
+
io.puts " #{fn}_t __ctx;"
|
|
3394
|
+
io.puts " __ctx.chunk = NULL;"
|
|
3395
|
+
io.puts " __ctx.ca = ca;"
|
|
3396
|
+
io.puts " __ctx.value_arg = value_arg;" if k[:value_arg]
|
|
3397
|
+
io.puts " __ctx.min_count = min_count;" if k[:mask_policy] == :min_count
|
|
3398
|
+
io.puts " return ca_lazy_arena_protect(#{fn}, ca_lazy_arena_release_held,"
|
|
3399
|
+
io.puts " (VALUE) &__ctx);"
|
|
3400
|
+
io.puts " }"
|
|
3401
|
+
end
|
|
3402
|
+
|
|
3403
|
+
# Where the source is a lazy expression the kernel is about to compute
|
|
3404
|
+
# whole, a registered expression evaluator may compute it instead; the
|
|
3405
|
+
# dispatcher then goes on with that array. `unless_streaming` is the C
|
|
3406
|
+
# condition under which the reduction streams the expression in chunks
|
|
3407
|
+
# rather than making it whole -- there it is left to stream, since asking
|
|
3408
|
+
# would make it whole and take that much more memory.
|
|
3409
|
+
def self.emit_lazy_evaluated(io, unless_streaming: nil)
|
|
3410
|
+
cond = "ca_is_lazy_view(src)"
|
|
3411
|
+
cond += " && ! ( #{unless_streaming} )" if unless_streaming
|
|
3412
|
+
io.puts " if ( #{cond} ) {"
|
|
3413
|
+
io.puts " volatile VALUE evaluated = ca_lazy_evaluated(self);"
|
|
3414
|
+
io.puts " if ( ! NIL_P(evaluated) ) {"
|
|
3415
|
+
io.puts " self = evaluated;"
|
|
3416
|
+
io.puts " GetCArray(self, src);"
|
|
3278
3417
|
io.puts " }"
|
|
3418
|
+
io.puts " }"
|
|
3419
|
+
end
|
|
3420
|
+
|
|
3421
|
+
# Whether a reduction's native walk has the streaming branch
|
|
3422
|
+
# (emit_reduce_streaming_call): the conditions emit_reduce_native checks
|
|
3423
|
+
# before it emits one.
|
|
3424
|
+
def self.reduce_streams?(k)
|
|
3425
|
+
k[:outputs] != 2 && k[:algorithm] != :two_pass_centred &&
|
|
3426
|
+
!k[:array_arg] && !k[:reduce].to_s.include?("idx")
|
|
3279
3427
|
end
|
|
3280
3428
|
|
|
3281
3429
|
def self.emit_reduce_dispatch(io, k)
|
|
@@ -3303,12 +3451,13 @@ module MkKernel
|
|
|
3303
3451
|
io.puts " GetCArray(self, src);"
|
|
3304
3452
|
|
|
3305
3453
|
if k[:object_escape]
|
|
3306
|
-
# CA_OBJECT escape:
|
|
3307
|
-
#
|
|
3308
|
-
#
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
io.puts "
|
|
3454
|
+
# CA_OBJECT escape: the Ruby composition helper gets the original
|
|
3455
|
+
# argc/argv (weights + trailing options hash, untouched). The escape
|
|
3456
|
+
# itself comes after the options and axes are checked below, so a
|
|
3457
|
+
# wrong keyword or axis is reported here, in this method's name, as
|
|
3458
|
+
# on the numeric path.
|
|
3459
|
+
io.puts " int escape_argc = argc;"
|
|
3460
|
+
io.puts " VALUE *escape_argv = argv;"
|
|
3312
3461
|
end
|
|
3313
3462
|
|
|
3314
3463
|
if k[:face_gate]
|
|
@@ -3342,7 +3491,18 @@ module MkKernel
|
|
|
3342
3491
|
io.puts " int keep_axis = RTEST(rkeep_axis);"
|
|
3343
3492
|
io.puts " int8_t slab_axes[CA_RANK_MAX];"
|
|
3344
3493
|
io.puts " int8_t naxes = rb_ca_parse_reduce_axes_kw(raxis, src, slab_axes);"
|
|
3494
|
+
if k[:object_escape]
|
|
3495
|
+
io.puts " if ( src->data_type == CA_OBJECT ) {"
|
|
3496
|
+
if min_count
|
|
3497
|
+
io.puts " ca_min_count_arg(rmin_count, NULL);"
|
|
3498
|
+
end
|
|
3499
|
+
io.puts " return rb_funcallv(self, rb_intern(\"#{k[:object_escape]}\"), escape_argc, escape_argv);"
|
|
3500
|
+
io.puts " }"
|
|
3501
|
+
end
|
|
3502
|
+
emit_lazy_evaluated(io, unless_streaming:
|
|
3503
|
+
reduce_streams?(k) ? "naxes == src->ndim && src->ndim >= 1 && ! keep_axis && ! ca_has_mask(src)" : nil)
|
|
3345
3504
|
|
|
3505
|
+
weights_io = nil
|
|
3346
3506
|
if has_aarg
|
|
3347
3507
|
# Weight storage type: :promote materializes weights at the f64
|
|
3348
3508
|
# computation type (so a float weight against an int source is not
|
|
@@ -3357,7 +3517,7 @@ module MkKernel
|
|
|
3357
3517
|
# at dispatch (= simple, uniform kernel path). rev5 strict acceptance
|
|
3358
3518
|
# mirrors PROPOSAL_LINEAR_INTERP_PER_FIBER_MATCHED rev5 in spirit.
|
|
3359
3519
|
io.puts " if ( argc < 1 ) {"
|
|
3360
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
3520
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: missing weights argument (1+ required, got 0)", ca_calling_method_name());]
|
|
3361
3521
|
io.puts " }"
|
|
3362
3522
|
io.puts " volatile VALUE rweights = argv[0];"
|
|
3363
3523
|
io.puts " argc--; argv++;"
|
|
@@ -3370,98 +3530,106 @@ module MkKernel
|
|
|
3370
3530
|
io.puts " rweights = rb_funcall(rweights, rb_intern(\"[]\"), 1, INT2NUM(0));"
|
|
3371
3531
|
io.puts " }"
|
|
3372
3532
|
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
|
-
|
|
3533
|
+
# The weights are attached from here to the end of the call, so they
|
|
3534
|
+
# are made only once every check that can raise has passed; see the
|
|
3535
|
+
# data type check below.
|
|
3536
|
+
wio = StringIO.new
|
|
3537
|
+
wio.puts " volatile VALUE vcw;"
|
|
3538
|
+
wio.puts " CArray *cw;"
|
|
3539
|
+
wio.puts " if ( ! rb_obj_is_carray(rweights) ) {"
|
|
3540
|
+
wio.puts " /* W-A1: Ruby Numeric scalar -> full-shape constant weight CArray */"
|
|
3541
|
+
wio.puts " vcw = rb_carray_new(#{waca}, src->ndim, src->dim, 0, NULL);"
|
|
3542
|
+
wio.puts " GetCArray(vcw, cw); /* a new entity: its ptr needs no attach */"
|
|
3543
|
+
wio.puts " rb_ca_obj2ptr(vcw, rweights, cw->ptr); /* fill first cell */"
|
|
3544
|
+
wio.puts " /* broadcast first cell to all elements (= constant fill) */"
|
|
3545
|
+
wio.puts " {"
|
|
3546
|
+
wio.puts " char *p0 = cw->ptr;"
|
|
3547
|
+
wio.puts " for ( ca_size_t i = 1; i < cw->elements; i++ ) {"
|
|
3548
|
+
wio.puts " memcpy(cw->ptr + i * cw->bytes, p0, cw->bytes);"
|
|
3549
|
+
wio.puts " }"
|
|
3550
|
+
wio.puts " }"
|
|
3551
|
+
wio.puts " }"
|
|
3552
|
+
wio.puts " else {"
|
|
3553
|
+
wio.puts " /* CArray val: coerce to the weight storage type (f64 for :promote, so a"
|
|
3554
|
+
wio.puts " float weight against an int source is not truncated) */"
|
|
3555
|
+
wio.puts " /* vcv holds the view while cv is read: W-A2 below moves vcw on to"
|
|
3556
|
+
wio.puts " the materialised copy before it is done with cv. */"
|
|
3557
|
+
wio.puts " volatile VALUE vcv = rb_ca_wrap_readonly(rweights, INT2NUM(#{waca}));"
|
|
3558
|
+
wio.puts " CArray *cv;"
|
|
3559
|
+
wio.puts " GetCArray(vcv, cv);"
|
|
3560
|
+
wio.puts " vcw = vcv;"
|
|
3561
|
+
wio.puts ""
|
|
3562
|
+
wio.puts " /* rev5 form detection: A3 (same ndim) -> A2 (1-D axis-broadcast) -> raise */"
|
|
3563
|
+
wio.puts " if ( cv->ndim == src->ndim ) {"
|
|
3564
|
+
wio.puts " /* W-A3 commit-or-raise: shape strict match */"
|
|
3565
|
+
wio.puts " for ( int8_t i = 0; i < src->ndim; i++ ) {"
|
|
3566
|
+
wio.puts " if ( cv->dim[i] != src->dim[i] ) {"
|
|
3567
|
+
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]);]
|
|
3568
|
+
wio.puts " }"
|
|
3569
|
+
wio.puts " }"
|
|
3570
|
+
wio.puts " /* W-A3 path: use cv directly (= existing) */"
|
|
3571
|
+
wio.puts " cw = cv;"
|
|
3572
|
+
wio.puts " }"
|
|
3573
|
+
wio.puts " else if ( naxes == 1 && cv->ndim == 1 && cv->dim[0] == src->dim[slab_axes[0]] ) {"
|
|
3574
|
+
wio.puts " /* W-A2: 1-D axis-broadcast -> materialize to full shape */"
|
|
3575
|
+
wio.puts " ca_attach(cv); /* window: nothing raises inside */"
|
|
3576
|
+
wio.puts " vcw = rb_carray_new(#{waca}, src->ndim, src->dim, 0, NULL);"
|
|
3577
|
+
wio.puts " GetCArray(vcw, cw);"
|
|
3578
|
+
wio.puts " /* broadcast cv (1-D, length src->dim[slab_axes[0]]) along reduce axis */"
|
|
3579
|
+
wio.puts " {"
|
|
3580
|
+
wio.puts " int8_t baxis = slab_axes[0];"
|
|
3581
|
+
wio.puts " ca_size_t inner_stride = 1;"
|
|
3582
|
+
wio.puts " for ( int8_t ii = baxis + 1; ii < src->ndim; ii++ ) inner_stride *= src->dim[ii];"
|
|
3583
|
+
wio.puts " ca_size_t axis_n = src->dim[baxis];"
|
|
3584
|
+
wio.puts " ca_size_t outer = src->elements / (axis_n * inner_stride);"
|
|
3585
|
+
wio.puts " for ( ca_size_t o = 0; o < outer; o++ ) {"
|
|
3586
|
+
wio.puts " for ( ca_size_t a = 0; a < axis_n; a++ ) {"
|
|
3587
|
+
wio.puts " for ( ca_size_t in = 0; in < inner_stride; in++ ) {"
|
|
3588
|
+
wio.puts " ca_size_t flat = (o * axis_n + a) * inner_stride + in;"
|
|
3589
|
+
wio.puts " memcpy(cw->ptr + flat * cw->bytes, cv->ptr + a * cv->bytes, cw->bytes);"
|
|
3590
|
+
wio.puts " }"
|
|
3591
|
+
wio.puts " }"
|
|
3592
|
+
wio.puts " }"
|
|
3593
|
+
wio.puts " }"
|
|
3594
|
+
wio.puts " /* Propagate cv mask via W-A3 overlay path below (= mask materialize) */"
|
|
3595
|
+
wio.puts " if ( ca_has_mask(cv) ) {"
|
|
3596
|
+
wio.puts " /* Allocate cw mask + broadcast cv mask along axis */"
|
|
3597
|
+
wio.puts " ca_create_mask(cw);"
|
|
3598
|
+
wio.puts " boolean8_t *cw_m = (boolean8_t *)cw->mask->ptr;"
|
|
3599
|
+
wio.puts " boolean8_t *cv_m = (boolean8_t *)cv->mask->ptr;"
|
|
3600
|
+
wio.puts " int8_t baxis = slab_axes[0];"
|
|
3601
|
+
wio.puts " ca_size_t inner_stride = 1;"
|
|
3602
|
+
wio.puts " for ( int8_t ii = baxis + 1; ii < src->ndim; ii++ ) inner_stride *= src->dim[ii];"
|
|
3603
|
+
wio.puts " ca_size_t axis_n = src->dim[baxis];"
|
|
3604
|
+
wio.puts " ca_size_t outer = src->elements / (axis_n * inner_stride);"
|
|
3605
|
+
wio.puts " for ( ca_size_t o = 0; o < outer; o++ ) {"
|
|
3606
|
+
wio.puts " for ( ca_size_t a = 0; a < axis_n; a++ ) {"
|
|
3607
|
+
wio.puts " for ( ca_size_t in = 0; in < inner_stride; in++ ) {"
|
|
3608
|
+
wio.puts " ca_size_t flat = (o * axis_n + a) * inner_stride + in;"
|
|
3609
|
+
wio.puts " cw_m[flat] = cv_m[a];"
|
|
3610
|
+
wio.puts " }"
|
|
3611
|
+
wio.puts " }"
|
|
3612
|
+
wio.puts " }"
|
|
3613
|
+
wio.puts " }"
|
|
3614
|
+
wio.puts " ca_detach(cv);"
|
|
3615
|
+
wio.puts " }"
|
|
3616
|
+
wio.puts " else {"
|
|
3617
|
+
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));]
|
|
3618
|
+
wio.puts " }"
|
|
3619
|
+
wio.puts " }"
|
|
3456
3620
|
# Mask overlay (= legacy W-A3 path; if the materialized cw carries a mask, overlay it)
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3621
|
+
wio.puts " volatile VALUE __vsrc_keep = self; /* GC guard */ (void) __vsrc_keep;"
|
|
3622
|
+
wio.puts " if ( ca_has_mask(cw) ) {"
|
|
3623
|
+
wio.puts " CArray *src_copy = ca_copy(src);"
|
|
3624
|
+
wio.puts " VALUE vsrc_copy = ca_wrap_struct(src_copy);"
|
|
3625
|
+
wio.puts " ca_copy_mask_overlay(src_copy, src_copy->elements, 1, cw);"
|
|
3626
|
+
wio.puts " src = src_copy;"
|
|
3627
|
+
wio.puts " self = vsrc_copy;"
|
|
3628
|
+
wio.puts " }"
|
|
3629
|
+
# Attached last, after the overlay's copy of the source (which can
|
|
3630
|
+
# raise), and detached right after the switch.
|
|
3631
|
+
wio.puts " ca_attach(cw); /* window: nothing raises inside; the kernels are numeric */"
|
|
3632
|
+
weights_io = wio
|
|
3465
3633
|
end
|
|
3466
3634
|
|
|
3467
3635
|
if has_varg
|
|
@@ -3469,7 +3637,7 @@ module MkKernel
|
|
|
3469
3637
|
# runtime value_arg (e.g. `a.count_equal(5, 0)` -- 5 is value_arg,
|
|
3470
3638
|
# 0 is axis). Per-src cast happens inside the switch case below.
|
|
3471
3639
|
io.puts " if ( argc < 1 ) {"
|
|
3472
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
3640
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: missing value argument (1+ required, got 0)", ca_calling_method_name());]
|
|
3473
3641
|
io.puts " }"
|
|
3474
3642
|
io.puts " volatile VALUE rval = argv[0];"
|
|
3475
3643
|
io.puts " argc--; argv++;"
|
|
@@ -3477,16 +3645,16 @@ module MkKernel
|
|
|
3477
3645
|
|
|
3478
3646
|
if min_count
|
|
3479
3647
|
# 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 "
|
|
3648
|
+
# only interpret them; min_count is -1 when not given.
|
|
3649
|
+
io.puts " ca_size_t min_count = ca_min_count_arg(rmin_count, NULL);"
|
|
3650
|
+
# fill_value: UNDEF leaves the result undefined, as no fill_value does.
|
|
3651
|
+
# A full reduction with a fill keeps its axes so the fill goes into a
|
|
3652
|
+
# typed cell, as it does per axis; ca_reduce_fill takes the cell out.
|
|
3653
|
+
io.puts " if ( rfval == CA_UNDEF ) rfval = Qnil;"
|
|
3654
|
+
io.puts " int fill_whole = 0;"
|
|
3655
|
+
io.puts " if ( ! NIL_P(rfval) && naxes == src->ndim && ! keep_axis ) {"
|
|
3656
|
+
io.puts " keep_axis = 1;"
|
|
3657
|
+
io.puts " fill_whole = 1;"
|
|
3490
3658
|
io.puts " }"
|
|
3491
3659
|
io.puts " VALUE result;"
|
|
3492
3660
|
elsif has_aarg
|
|
@@ -3502,9 +3670,23 @@ module MkKernel
|
|
|
3502
3670
|
# axis is allowed. Variadic `a.sum(0, 1)` raises here with a migration
|
|
3503
3671
|
# hint pointing at the kwarg form.
|
|
3504
3672
|
io.puts " if ( argc > 0 ) {"
|
|
3505
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
3673
|
+
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
3674
|
io.puts " }"
|
|
3507
3675
|
|
|
3676
|
+
if weights_io
|
|
3677
|
+
# Refuse an unsupported source before the weights are attached: the
|
|
3678
|
+
# switch below would raise with them held.
|
|
3679
|
+
if k[:fallback] == :raise
|
|
3680
|
+
io.puts " switch ( src->data_type ) {"
|
|
3681
|
+
io.puts " " + k[:source].map { |s| "case #{DTYPES[s][:ca]}:" }.join(" ") + " break;"
|
|
3682
|
+
io.puts " default:"
|
|
3683
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
3684
|
+
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]);]
|
|
3685
|
+
io.puts " }"
|
|
3686
|
+
end
|
|
3687
|
+
io.print weights_io.string
|
|
3688
|
+
end
|
|
3689
|
+
|
|
3508
3690
|
io.puts " switch ( src->data_type ) {"
|
|
3509
3691
|
k[:source].each do |s|
|
|
3510
3692
|
si = DTYPES[s]
|
|
@@ -3515,8 +3697,14 @@ module MkKernel
|
|
|
3515
3697
|
# A fixlen query is a runtime-width byte blob with no
|
|
3516
3698
|
# scalar cast; pack it the way the search family does,
|
|
3517
3699
|
# which NUL-pads a short String to the cell width.
|
|
3518
|
-
|
|
3700
|
+
# ALLOCV, not the stack: a cell can be wide. The holder
|
|
3701
|
+
# keeps the buffer until the GC takes it.
|
|
3702
|
+
" volatile VALUE value_arg_holder = 0;\n" \
|
|
3703
|
+
" char *value_arg = ALLOCV_N(char, value_arg_holder, src->bytes);\n" \
|
|
3519
3704
|
" rb_ca_obj2ptr(self, rval, value_arg);\n"
|
|
3705
|
+
elsif INT_DTYPES.include?(s)
|
|
3706
|
+
" ca_check_integer_fits(rval, #{si[:ca]});\n" \
|
|
3707
|
+
" #{si[:c]} value_arg = (#{si[:c]}) #{si[:num2c]}(rval);\n"
|
|
3520
3708
|
else
|
|
3521
3709
|
" #{si[:c]} value_arg = (#{si[:c]}) #{si[:num2c]}(rval);\n"
|
|
3522
3710
|
end
|
|
@@ -3525,13 +3713,14 @@ module MkKernel
|
|
|
3525
3713
|
io.print varg_decl unless varg_decl.empty?
|
|
3526
3714
|
if use_result_var
|
|
3527
3715
|
io.puts " result = #{name}_ki_native_#{s}(self, src, slab_axes, naxes, keep_axis#{extra_call});"
|
|
3716
|
+
io.puts " RB_GC_GUARD(self);"
|
|
3528
3717
|
io.puts " break;"
|
|
3529
3718
|
else
|
|
3530
|
-
io.puts "
|
|
3719
|
+
io.puts " CA_RETURN_KEEPING(self, #{name}_ki_native_#{s}(self, src, slab_axes, naxes, keep_axis#{extra_call}));"
|
|
3531
3720
|
end
|
|
3532
3721
|
io.puts " }"
|
|
3533
3722
|
else
|
|
3534
|
-
io.puts " case #{si[:ca]}:
|
|
3723
|
+
io.puts " case #{si[:ca]}: CA_RETURN_KEEPING(self, #{name}_ki_native_#{s}(self, src, slab_axes, naxes, keep_axis));"
|
|
3535
3724
|
end
|
|
3536
3725
|
end
|
|
3537
3726
|
case k[:fallback]
|
|
@@ -3544,7 +3733,8 @@ module MkKernel
|
|
|
3544
3733
|
io.puts " switch ( src->data_type ) {"
|
|
3545
3734
|
io.puts " case CA_BOOLEAN: break; /* wrap: count-of-trues semantic */"
|
|
3546
3735
|
io.puts " default:"
|
|
3547
|
-
io.puts %Q[
|
|
3736
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
3737
|
+
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
3738
|
io.puts " }"
|
|
3549
3739
|
io.puts " VALUE vsrc = rb_ca_wrap_readonly(self, INT2NUM(CA_FLOAT64));"
|
|
3550
3740
|
io.puts " CArray *casted;"
|
|
@@ -3556,42 +3746,44 @@ module MkKernel
|
|
|
3556
3746
|
end
|
|
3557
3747
|
if use_result_var
|
|
3558
3748
|
io.puts " result = #{name}_ki_native_f64(vsrc, casted, slab_axes, naxes, keep_axis#{extra_call});"
|
|
3749
|
+
io.puts " RB_GC_GUARD(vsrc);"
|
|
3559
3750
|
io.puts " break;"
|
|
3560
3751
|
else
|
|
3561
|
-
io.puts "
|
|
3752
|
+
io.puts " CA_RETURN_KEEPING(vsrc, #{name}_ki_native_f64(vsrc, casted, slab_axes, naxes, keep_axis#{extra_call}));"
|
|
3562
3753
|
end
|
|
3563
3754
|
io.puts " }"
|
|
3564
3755
|
when :raise
|
|
3565
3756
|
io.puts " default:"
|
|
3566
|
-
io.puts %Q[
|
|
3757
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
3758
|
+
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
3759
|
end
|
|
3568
3760
|
io.puts " }"
|
|
3569
3761
|
|
|
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).
|
|
3762
|
+
# array_arg cleanup: detach weights (paired with ca_attach above),
|
|
3763
|
+
# ahead of the fill_value substitution, which can raise.
|
|
3587
3764
|
if has_aarg
|
|
3588
3765
|
io.puts " ca_detach(cw);"
|
|
3589
3766
|
end
|
|
3590
3767
|
|
|
3591
|
-
|
|
3768
|
+
relift = (k[:face_gate] == :relift)
|
|
3769
|
+
if min_count
|
|
3770
|
+
# Apply fill_value through the one rule the per-axis result uses. A
|
|
3771
|
+
# Face's fill is a value of the Face, so it goes in after the re-lift
|
|
3772
|
+
# below, as an assignment to the Face would put it.
|
|
3773
|
+
io.puts relift ? " if ( ! NIL_P(rfval) && _reduce_face == Qnil ) {" : " if ( ! NIL_P(rfval) ) {"
|
|
3774
|
+
io.puts " result = ca_reduce_fill(result, rfval, fill_whole);"
|
|
3775
|
+
io.puts " }"
|
|
3776
|
+
end
|
|
3777
|
+
|
|
3778
|
+
if relift
|
|
3592
3779
|
# Descended through emit_reduce_face_gate: re-lift the storage result
|
|
3593
3780
|
# back into the Face before returning.
|
|
3594
3781
|
emit_reduce_face_relift(io)
|
|
3782
|
+
if min_count
|
|
3783
|
+
io.puts " if ( ! NIL_P(rfval) && _reduce_face != Qnil ) {"
|
|
3784
|
+
io.puts " result = ca_reduce_fill(result, rfval, fill_whole);"
|
|
3785
|
+
io.puts " }"
|
|
3786
|
+
end
|
|
3595
3787
|
end
|
|
3596
3788
|
|
|
3597
3789
|
if use_result_var
|
|
@@ -3602,117 +3794,13 @@ module MkKernel
|
|
|
3602
3794
|
io.puts "}"
|
|
3603
3795
|
end
|
|
3604
3796
|
|
|
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
3797
|
# ---- scan emitter -----------------------------------------------------
|
|
3710
3798
|
|
|
3711
3799
|
def self.emit_scan(io, k)
|
|
3712
3800
|
io.puts
|
|
3713
3801
|
io.puts "/* ===== #{k[:name]}_ki ============================================ */"
|
|
3714
3802
|
k[:source].each do |src|
|
|
3715
|
-
|
|
3803
|
+
emit_native_for(io, k, src) { |o| emit_scan_native(o, k, src) }
|
|
3716
3804
|
end
|
|
3717
3805
|
emit_scan_dispatch(io, k)
|
|
3718
3806
|
end
|
|
@@ -3731,14 +3819,16 @@ module MkKernel
|
|
|
3731
3819
|
|
|
3732
3820
|
# empty: :undef -> allocate the output mask up front when the input
|
|
3733
3821
|
# carries one (a fiber can only go UNDEF on its leading masked cells,
|
|
3734
|
-
# which exist only when the input is masked).
|
|
3735
|
-
#
|
|
3736
|
-
# the
|
|
3822
|
+
# which exist only when the input is masked). The output is a fresh
|
|
3823
|
+
# row-major entity, and the walk cuts its slabs from a buffer laid out
|
|
3824
|
+
# the same way (the output's memory, or the walk's scratch when it
|
|
3825
|
+
# delivers by transfer), so a slab's offset from st_out.alias_ptr is
|
|
3826
|
+
# its offset in the output, and op_mask + that offset / cell size is the
|
|
3827
|
+
# fiber's mask base.
|
|
3737
3828
|
mask_setup =
|
|
3738
3829
|
if gated
|
|
3739
3830
|
"\n" \
|
|
3740
3831
|
" boolean8_t *op_mask = NULL;\n" \
|
|
3741
|
-
" char *co_root = (char *) co->ptr;\n" \
|
|
3742
3832
|
" if ( ca_has_mask(ca) ) {\n" \
|
|
3743
3833
|
" ca_create_mask(co);\n" \
|
|
3744
3834
|
" op_mask = (boolean8_t *) co->mask->ptr;\n" \
|
|
@@ -3753,7 +3843,7 @@ module MkKernel
|
|
|
3753
3843
|
" st_out, po, #{init_expr}, #{step_stmt});"
|
|
3754
3844
|
elsif gated
|
|
3755
3845
|
"boolean8_t *mo_fiber = op_mask\n" \
|
|
3756
|
-
" ? op_mask + (ca_size_t)(po -
|
|
3846
|
+
" ? op_mask + (ca_size_t)(po - st_out.alias_ptr) / (ca_size_t) co->bytes\n" \
|
|
3757
3847
|
" : NULL;\n" \
|
|
3758
3848
|
" CA_SLAB_SCAN_T_GATED(#{si[:c]}, #{oi[:c]}, st_in, pi, mi,\n" \
|
|
3759
3849
|
" st_out, po, mo_fiber, #{init_expr}, #{step_stmt});"
|
|
@@ -3783,14 +3873,14 @@ module MkKernel
|
|
|
3783
3873
|
slab_axes, 1, 0);
|
|
3784
3874
|
if ( rc != CA_ITER_OK ) {
|
|
3785
3875
|
rb_raise(rb_eRuntimeError,
|
|
3786
|
-
"
|
|
3876
|
+
"%s: input init failed rc=%d", ca_calling_method_name(), rc);
|
|
3787
3877
|
}
|
|
3788
|
-
rc =
|
|
3878
|
+
rc = ca_iter_state_init_l2_paired(&st_out, &st_in, co, CA_SLAB_AXES,
|
|
3789
3879
|
slab_axes, 1, CA_KERNEL_WRITE);
|
|
3790
3880
|
if ( rc != CA_ITER_OK ) {
|
|
3791
3881
|
ca_iter_state_finish(&st_in);
|
|
3792
3882
|
rb_raise(rb_eRuntimeError,
|
|
3793
|
-
"
|
|
3883
|
+
"%s: output init failed rc=%d", ca_calling_method_name(), rc);
|
|
3794
3884
|
}
|
|
3795
3885
|
#{mask_setup}
|
|
3796
3886
|
char *pi, *po;
|
|
@@ -3815,16 +3905,38 @@ module MkKernel
|
|
|
3815
3905
|
# multi-axis scan is semantically ambiguous (flatten-and-cumsum vs
|
|
3816
3906
|
# 2D prefix sum); users must chain explicitly. Arity flips from 1
|
|
3817
3907
|
# (positional Integer axis) to -1 (kwarg dispatcher).
|
|
3818
|
-
|
|
3819
|
-
|
|
3908
|
+
if k[:face_gate]
|
|
3909
|
+
# The scan proper runs on the storage; the bound entry below gates the
|
|
3910
|
+
# Face and re-lifts the result.
|
|
3911
|
+
io.puts "static VALUE rb_ca_#{name}_ki_storage (int argc, VALUE *argv, VALUE self);"
|
|
3912
|
+
io.puts
|
|
3913
|
+
io.puts "static VALUE"
|
|
3914
|
+
io.puts "rb_ca_#{name}_ki (int argc, VALUE *argv, VALUE self)"
|
|
3915
|
+
io.puts "{"
|
|
3916
|
+
io.puts " CArray *src;"
|
|
3917
|
+
io.puts " VALUE result;"
|
|
3918
|
+
io.puts " GetCArray(self, src);"
|
|
3919
|
+
emit_reduce_face_gate(io, name, true)
|
|
3920
|
+
io.puts " result = rb_ca_#{name}_ki_storage(argc, argv, self);"
|
|
3921
|
+
emit_reduce_face_relift(io)
|
|
3922
|
+
io.puts " return result;"
|
|
3923
|
+
io.puts "}"
|
|
3924
|
+
io.puts
|
|
3925
|
+
io.puts "static VALUE"
|
|
3926
|
+
io.puts "rb_ca_#{name}_ki_storage (int argc, VALUE *argv, VALUE self)"
|
|
3927
|
+
else
|
|
3928
|
+
io.puts "static VALUE"
|
|
3929
|
+
io.puts "rb_ca_#{name}_ki (int argc, VALUE *argv, VALUE self)"
|
|
3930
|
+
end
|
|
3820
3931
|
io.puts "{"
|
|
3821
3932
|
io.puts " CArray *src;"
|
|
3822
3933
|
io.puts " GetCArray(self, src);"
|
|
3934
|
+
emit_lazy_evaluated(io)
|
|
3823
3935
|
io.puts " volatile VALUE ropt = rb_pop_options(&argc, &argv);"
|
|
3824
3936
|
io.puts " volatile VALUE raxis = Qnil;"
|
|
3825
3937
|
io.puts " rb_scan_options(ropt, \"axis\", &raxis);"
|
|
3826
3938
|
io.puts " if ( argc > 0 ) {"
|
|
3827
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
3939
|
+
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
3940
|
io.puts " }"
|
|
3829
3941
|
if k[:axis_default] == :flatten
|
|
3830
3942
|
# Legacy compat: nil/omitted axis: flatten source and scan axis 0.
|
|
@@ -3836,7 +3948,7 @@ module MkKernel
|
|
|
3836
3948
|
io.puts " switch ( fsrc->data_type ) {"
|
|
3837
3949
|
k[:source].each do |s|
|
|
3838
3950
|
si = DTYPES[s]
|
|
3839
|
-
io.puts " case #{si[:ca]}:
|
|
3951
|
+
io.puts " case #{si[:ca]}: CA_RETURN_KEEPING(vflat, #{name}_ki_native_#{s}(vflat, fsrc, 0));"
|
|
3840
3952
|
end
|
|
3841
3953
|
case k[:fallback]
|
|
3842
3954
|
when :wrap_to_f64
|
|
@@ -3844,34 +3956,31 @@ module MkKernel
|
|
|
3844
3956
|
io.puts " VALUE vsrc = rb_ca_wrap_readonly(vflat, INT2NUM(CA_FLOAT64));"
|
|
3845
3957
|
io.puts " CArray *casted;"
|
|
3846
3958
|
io.puts " GetCArray(vsrc, casted);"
|
|
3847
|
-
io.puts "
|
|
3959
|
+
io.puts " CA_RETURN_KEEPING(vsrc, #{name}_ki_native_f64(vsrc, casted, 0));"
|
|
3848
3960
|
io.puts " }"
|
|
3849
3961
|
when :raise
|
|
3850
3962
|
io.puts " default:"
|
|
3851
|
-
io.puts %Q[
|
|
3963
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
3964
|
+
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]);]
|
|
3965
|
+
when :mask_count
|
|
3966
|
+
io.puts " default:"
|
|
3967
|
+
io.puts " return rb_ca_scan_mask_count(self, Qnil);"
|
|
3852
3968
|
end
|
|
3853
3969
|
io.puts " }"
|
|
3854
3970
|
io.puts " }"
|
|
3855
3971
|
else
|
|
3856
3972
|
io.puts " if ( NIL_P(raxis) ) {"
|
|
3857
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
3973
|
+
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
3974
|
io.puts " }"
|
|
3859
3975
|
end
|
|
3860
3976
|
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));]
|
|
3865
|
-
io.puts " }"
|
|
3866
|
-
io.puts " int axis = NUM2INT(raxis);"
|
|
3867
|
-
io.puts " if ( axis < 0 ) axis += src->ndim;"
|
|
3868
|
-
io.puts " if ( axis < 0 || axis >= src->ndim ) {"
|
|
3869
|
-
io.puts %Q[ rb_raise(rb_eArgError, "#{name}_ki: axis %d out of range for ndim %d", NUM2INT(raxis), src->ndim);]
|
|
3977
|
+
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());]
|
|
3870
3978
|
io.puts " }"
|
|
3979
|
+
io.puts " int axis = ca_normalize_axis_arg(raxis, src->ndim, NULL);"
|
|
3871
3980
|
io.puts " switch ( src->data_type ) {"
|
|
3872
3981
|
k[:source].each do |s|
|
|
3873
3982
|
si = DTYPES[s]
|
|
3874
|
-
io.puts " case #{si[:ca]}:
|
|
3983
|
+
io.puts " case #{si[:ca]}: CA_RETURN_KEEPING(self, #{name}_ki_native_#{s}(self, src, axis));"
|
|
3875
3984
|
end
|
|
3876
3985
|
case k[:fallback]
|
|
3877
3986
|
when :wrap_to_f64
|
|
@@ -3879,11 +3988,15 @@ module MkKernel
|
|
|
3879
3988
|
io.puts " VALUE vsrc = rb_ca_wrap_readonly(self, INT2NUM(CA_FLOAT64));"
|
|
3880
3989
|
io.puts " CArray *casted;"
|
|
3881
3990
|
io.puts " GetCArray(vsrc, casted);"
|
|
3882
|
-
io.puts "
|
|
3991
|
+
io.puts " CA_RETURN_KEEPING(vsrc, #{name}_ki_native_f64(vsrc, casted, axis));"
|
|
3883
3992
|
io.puts " }"
|
|
3884
3993
|
when :raise
|
|
3885
3994
|
io.puts " default:"
|
|
3886
|
-
io.puts %Q[
|
|
3995
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
3996
|
+
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]);]
|
|
3997
|
+
when :mask_count
|
|
3998
|
+
io.puts " default:"
|
|
3999
|
+
io.puts " return rb_ca_scan_mask_count(self, INT2NUM(axis));"
|
|
3887
4000
|
end
|
|
3888
4001
|
io.puts " }"
|
|
3889
4002
|
io.puts " return Qnil; /* unreachable */"
|
|
@@ -3898,7 +4011,7 @@ module MkKernel
|
|
|
3898
4011
|
k[:source].each do |src|
|
|
3899
4012
|
emit_sort_cmp(io, k, src)
|
|
3900
4013
|
emit_sort_quickselect(io, k, src) if k[:algorithm] == :partition
|
|
3901
|
-
|
|
4014
|
+
emit_native_for(io, k, src) { |o| emit_sort_native(o, k, src) }
|
|
3902
4015
|
end
|
|
3903
4016
|
emit_sort_dispatch(io, k)
|
|
3904
4017
|
end
|
|
@@ -4025,28 +4138,15 @@ module MkKernel
|
|
|
4025
4138
|
io.puts " const #{name}_pair_#{src} *pa = (const #{name}_pair_#{src} *) a;"
|
|
4026
4139
|
io.puts " const #{name}_pair_#{src} *pb = (const #{name}_pair_#{src} *) b;"
|
|
4027
4140
|
if is_object
|
|
4028
|
-
#
|
|
4029
|
-
#
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
io.puts "
|
|
4035
|
-
io.puts
|
|
4036
|
-
io.puts " rb_obj_classname(pa->v), rb_obj_classname(pb->v));"
|
|
4037
|
-
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;"
|
|
4141
|
+
# The object order: `<=>`, a Float NaN after every other value
|
|
4142
|
+
# (ca_obj_order_cmp); two NaNs fall to the index tie-break.
|
|
4143
|
+
io.puts " int c = ca_obj_order_cmp(pa->v, pb->v);"
|
|
4144
|
+
io.puts " if ( c != 0 ) return c;"
|
|
4145
|
+
elsif is_fp
|
|
4146
|
+
# The float order: NaN after every number (CA_FLOAT_ORDER_CMP).
|
|
4147
|
+
io.puts " int c = CA_FLOAT_ORDER_CMP(pa->v, pb->v);"
|
|
4148
|
+
io.puts " if ( c != 0 ) return c;"
|
|
4041
4149
|
else
|
|
4042
|
-
if is_fp
|
|
4043
|
-
# NaN policy :end -- NaN sorts to the end (matches qcmp_f_type in
|
|
4044
|
-
# carray_sort.c).
|
|
4045
|
-
io.puts " int nan_a = isnan(pa->v);"
|
|
4046
|
-
io.puts " int nan_b = isnan(pb->v);"
|
|
4047
|
-
io.puts " if ( nan_a && !nan_b ) return 1;"
|
|
4048
|
-
io.puts " if ( nan_b && !nan_a ) return -1;"
|
|
4049
|
-
end
|
|
4050
4150
|
io.puts " if ( pa->v > pb->v ) return 1;"
|
|
4051
4151
|
io.puts " if ( pa->v < pb->v ) return -1;"
|
|
4052
4152
|
end
|
|
@@ -4168,26 +4268,43 @@ module MkKernel
|
|
|
4168
4268
|
io.puts " rc = ca_iter_state_init_l2(&st_in, ca, CA_SLAB_AXES,"
|
|
4169
4269
|
io.puts " slab_axes, 1, 0);"
|
|
4170
4270
|
io.puts " if ( rc != CA_ITER_OK ) {"
|
|
4171
|
-
io.puts %Q[ rb_raise(rb_eRuntimeError, "
|
|
4271
|
+
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: input init failed rc=%d", ca_calling_method_name(), rc);]
|
|
4172
4272
|
io.puts " }"
|
|
4173
|
-
io.puts " rc =
|
|
4273
|
+
io.puts " rc = ca_iter_state_init_l2_paired(&st_out, &st_in, co, CA_SLAB_AXES,"
|
|
4174
4274
|
io.puts " slab_axes, 1, CA_KERNEL_WRITE);"
|
|
4175
4275
|
io.puts " if ( rc != CA_ITER_OK ) {"
|
|
4176
4276
|
io.puts " ca_iter_state_finish(&st_in);"
|
|
4177
|
-
io.puts %Q[ rb_raise(rb_eRuntimeError, "
|
|
4277
|
+
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: output init failed rc=%d", ca_calling_method_name(), rc);]
|
|
4178
4278
|
io.puts " }"
|
|
4179
4279
|
io.puts
|
|
4180
4280
|
io.puts " ca_size_t fiber_n = st_in.slab_elements;"
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
#
|
|
4185
|
-
|
|
4186
|
-
#
|
|
4281
|
+
if src == :object
|
|
4282
|
+
# The object comparator calls <=>, so the sort can raise part way.
|
|
4283
|
+
# ALLOCV frees these buffers whether the function returns or unwinds.
|
|
4284
|
+
# A full sort also needs aux: ca_sort_merge_pair_object is a mergesort.
|
|
4285
|
+
io.puts " volatile VALUE hbuf = 0, haux = 0;"
|
|
4286
|
+
io.puts " #{name}_pair_#{src} *buf = ALLOCV_N(#{name}_pair_#{src}, hbuf, fiber_n);"
|
|
4287
|
+
if partition
|
|
4288
|
+
io.puts " (void) haux;"
|
|
4289
|
+
else
|
|
4290
|
+
io.puts " #{name}_pair_#{src} *aux = ALLOCV_N(#{name}_pair_#{src}, haux, fiber_n);"
|
|
4291
|
+
end
|
|
4292
|
+
else
|
|
4293
|
+
# ALLOCV as well: the walk that fills buf is the only thing that
|
|
4294
|
+
# could raise, and it does not today, but the buffers need not rely
|
|
4295
|
+
# on that.
|
|
4296
|
+
io.puts " volatile VALUE hbuf = 0, haux = 0;"
|
|
4297
|
+
io.puts " #{name}_pair_#{src} *buf = ALLOCV_N(#{name}_pair_#{src}, hbuf, fiber_n);"
|
|
4298
|
+
end
|
|
4299
|
+
if has_kind && src != :object
|
|
4300
|
+
# The aux pair buffer for ca_sort_merge_pair_<src> (a ping-pong
|
|
4301
|
+
# bottom-up mergesort), taken once outside the fiber loop.
|
|
4187
4302
|
io.puts " #{name}_pair_#{src} *aux = NULL;"
|
|
4188
4303
|
io.puts " if ( do_stable ) {"
|
|
4189
|
-
io.puts " aux =
|
|
4304
|
+
io.puts " aux = ALLOCV_N(#{name}_pair_#{src}, haux, fiber_n);"
|
|
4190
4305
|
io.puts " }"
|
|
4306
|
+
elsif src != :object
|
|
4307
|
+
io.puts " (void) haux;"
|
|
4191
4308
|
end
|
|
4192
4309
|
|
|
4193
4310
|
if view_flat
|
|
@@ -4325,16 +4442,15 @@ module MkKernel
|
|
|
4325
4442
|
io.puts " */"
|
|
4326
4443
|
end
|
|
4327
4444
|
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});"
|
|
4445
|
+
# CA_OBJECT sorts with the emitted _cmp_object (rb_funcall(<=>)
|
|
4446
|
+
# based) through ca_sort_merge_pair_object rather than libc qsort: the
|
|
4447
|
+
# comparator raises on a pair that does not compare, and the raise
|
|
4448
|
+
# must not unwind through libc's frames. The cmp tie-breaks on the
|
|
4449
|
+
# original index, so the order is the same for both kinds, and
|
|
4450
|
+
# do_stable is ignored. NaN partition is skipped (Float#<=> answers
|
|
4451
|
+
# nil for NaN, which the comparator reports as ArgumentError).
|
|
4452
|
+
io.puts " (void) do_stable; /* CA_OBJECT: one stable sort serves both kinds */"
|
|
4453
|
+
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
4454
|
elsif is_fixlen
|
|
4339
4455
|
io.puts " /* CA_FIXLEN: libc qsort with the memcmp comparator. qsort isn't"
|
|
4340
4456
|
io.puts " stable, but #{name}_cmp_#{src} tie-breaks on the original index,"
|
|
@@ -4374,16 +4490,21 @@ module MkKernel
|
|
|
4374
4490
|
io.puts " gaps ordinal would have spent on the tie are never used, so a"
|
|
4375
4491
|
io.puts " later out_r stays <= the ordinal r at that position, preserving"
|
|
4376
4492
|
io.puts " both the ascending order and (via the order() method's"
|
|
4377
|
-
io.puts "
|
|
4378
|
-
io.puts "
|
|
4493
|
+
io.puts " descending transform) the descending order. NaN cells tie"
|
|
4494
|
+
io.puts " with each other, as sort_addr and the value-hash family treat"
|
|
4495
|
+
io.puts " every NaN as one value. */"
|
|
4379
4496
|
io.puts " ca_size_t out_r = 0;"
|
|
4380
4497
|
io.puts " for ( ca_size_t r = 0; r < #{rank_n}; r++ ) {"
|
|
4381
4498
|
io.puts " if ( r > 0 ) {"
|
|
4382
4499
|
io.puts " if ( dense ) {"
|
|
4383
4500
|
if is_object
|
|
4384
|
-
io.puts " if ( !
|
|
4501
|
+
io.puts " if ( ! ( ( CA_OBJ_ISNAN(buf[r].v) && CA_OBJ_ISNAN(buf[r - 1].v) )"
|
|
4502
|
+
io.puts " || ca_obj_equal(buf[r].v, buf[r - 1].v) ) ) out_r++;"
|
|
4385
4503
|
elsif is_fixlen
|
|
4386
4504
|
io.puts " if ( memcmp(buf[r].vp, buf[r - 1].vp, (size_t) buf[r].nb) != 0 ) out_r++;"
|
|
4505
|
+
elsif FLOAT_DTYPES.include?(src)
|
|
4506
|
+
io.puts " if ( buf[r].v != buf[r - 1].v"
|
|
4507
|
+
io.puts " && ! ( isnan(buf[r].v) && isnan(buf[r - 1].v) ) ) out_r++;"
|
|
4387
4508
|
else
|
|
4388
4509
|
io.puts " if ( buf[r].v != buf[r - 1].v ) out_r++;"
|
|
4389
4510
|
end
|
|
@@ -4393,15 +4514,32 @@ module MkKernel
|
|
|
4393
4514
|
io.puts " }"
|
|
4394
4515
|
io.puts " *(#{oi[:c]} *) (po + buf[r].i * ostride) = (#{oi[:c]}) out_r;"
|
|
4395
4516
|
io.puts " }"
|
|
4517
|
+
# dense == 2 (from CArray#order only): descending dense ranks, the
|
|
4518
|
+
# largest value 0, the NaN cells (sorted to the end) after the numbers.
|
|
4519
|
+
nan_test = if is_object then "CA_OBJ_ISNAN(buf[nan_start - 1].v)"
|
|
4520
|
+
elsif FLOAT_DTYPES.include?(src) then "isnan(buf[nan_start - 1].v)"
|
|
4521
|
+
end
|
|
4522
|
+
io.puts " if ( dense == 2 && #{rank_n} > 0 ) {"
|
|
4523
|
+
io.puts " ca_size_t nan_start = #{rank_n};"
|
|
4524
|
+
io.puts " while ( nan_start > 0 && #{nan_test} ) nan_start--;" if nan_test
|
|
4525
|
+
io.puts " #{oi[:c]} top = nan_start > 0"
|
|
4526
|
+
io.puts " ? *(#{oi[:c]} *) (po + buf[nan_start - 1].i * ostride) : (#{oi[:c]}) -1;"
|
|
4527
|
+
io.puts " for ( ca_size_t r = 0; r < #{rank_n}; r++ ) {"
|
|
4528
|
+
io.puts " #{oi[:c]} *o = (#{oi[:c]} *) (po + buf[r].i * ostride);"
|
|
4529
|
+
io.puts " *o = ( r < nan_start ) ? top - *o : top + 1;"
|
|
4530
|
+
io.puts " }"
|
|
4531
|
+
io.puts " }"
|
|
4396
4532
|
if mask_skip
|
|
4397
4533
|
io.puts " /* Mirror input mask to output (= UNDEF rank for masked"
|
|
4398
|
-
io.puts " cells). Write via co->mask->ptr directly
|
|
4399
|
-
io.puts "
|
|
4400
|
-
io.puts "
|
|
4401
|
-
io.puts "
|
|
4534
|
+
io.puts " cells). Write via co->mask->ptr directly, because a"
|
|
4535
|
+
io.puts " mask written through a WRITE walk's cursor does not"
|
|
4536
|
+
io.puts " reach the array. co is a fresh row-major entity and"
|
|
4537
|
+
io.puts " the walk cuts its slabs from a buffer laid out the"
|
|
4538
|
+
io.puts " same way, so the slab's offset from st_out.alias_ptr"
|
|
4539
|
+
io.puts " is its offset in co. */"
|
|
4402
4540
|
io.puts " if ( mi && co->mask ) {"
|
|
4403
4541
|
io.puts " boolean8_t *omask_base = (boolean8_t *) co->mask->ptr"
|
|
4404
|
-
io.puts " + ((po -
|
|
4542
|
+
io.puts " + ((po - st_out.alias_ptr) / (ca_size_t) co->bytes);"
|
|
4405
4543
|
io.puts " ca_size_t mostride = st_out.slab_mask_strides[0];"
|
|
4406
4544
|
io.puts " for ( ca_size_t k = 0; k < fiber_n; k++ ) {"
|
|
4407
4545
|
io.puts " if ( mi[k * mstride] ) omask_base[k * mostride] = 1;"
|
|
@@ -4427,43 +4565,33 @@ module MkKernel
|
|
|
4427
4565
|
end
|
|
4428
4566
|
|
|
4429
4567
|
io.puts " }"
|
|
4430
|
-
io.puts "
|
|
4431
|
-
|
|
4432
|
-
io.puts " if ( aux ) xfree(aux);"
|
|
4433
|
-
end
|
|
4568
|
+
io.puts " ALLOCV_END(hbuf);"
|
|
4569
|
+
io.puts " ALLOCV_END(haux);"
|
|
4434
4570
|
io.puts " ca_iter_state_finish(&st_in);"
|
|
4435
4571
|
io.puts " ca_iter_state_finish(&st_out);"
|
|
4436
4572
|
io.puts " return vout;"
|
|
4437
4573
|
io.puts "}"
|
|
4438
4574
|
end
|
|
4439
4575
|
|
|
4440
|
-
# Shared sort-family Face gate.
|
|
4441
|
-
#
|
|
4442
|
-
#
|
|
4443
|
-
#
|
|
4444
|
-
#
|
|
4445
|
-
#
|
|
4576
|
+
# Shared sort-family Face gate. Every kernel of the family answers
|
|
4577
|
+
# positions, so it runs on the array the Face's order is read from
|
|
4578
|
+
# (ca_order_key): the storage of an ORDERABLE Face, the Face's own dense
|
|
4579
|
+
# rank for a fixlen storage that is not ORDERABLE (CAConstString's offsets,
|
|
4580
|
+
# a record's bytes), and a refusal otherwise. `src`/`self` are rebound to
|
|
4581
|
+
# that array.
|
|
4446
4582
|
def self.emit_sort_face_gate(io, name)
|
|
4447
4583
|
io.puts " if ( ca_is_face(src) ) {"
|
|
4448
|
-
io.puts "
|
|
4449
|
-
io.puts "
|
|
4450
|
-
io.puts " VALUE _st = rb_ca_strip_face_value(self);"
|
|
4451
|
-
io.puts " CArray *_sc;"
|
|
4452
|
-
io.puts " GetCArray(_st, _sc);"
|
|
4453
|
-
io.puts " if ( _sc->data_type != CA_FIXLEN && ! _orderable ) {"
|
|
4454
|
-
io.puts %Q[ rb_raise(rb_eArgError,]
|
|
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));]
|
|
4458
|
-
io.puts " }"
|
|
4459
|
-
io.puts " self = _st;"
|
|
4460
|
-
io.puts " src = _sc;"
|
|
4584
|
+
io.puts " self = ca_order_key(self, ca_calling_method_name());"
|
|
4585
|
+
io.puts " GetCArray(self, src);"
|
|
4461
4586
|
io.puts " }"
|
|
4462
4587
|
end
|
|
4463
4588
|
|
|
4464
|
-
# Reduce-family Face gate.
|
|
4465
|
-
#
|
|
4466
|
-
#
|
|
4589
|
+
# Reduce-family Face gate. Descends an ORDERABLE Face to its storage and
|
|
4590
|
+
# refuses any other (ca_face_require_orderable): a fixlen storage is
|
|
4591
|
+
# reduced by memcmp only when the Face declares that its bytes are its
|
|
4592
|
+
# order (CAFixlenString). CAConstString and CARecord, whose bytes are no
|
|
4593
|
+
# order, answer min / max / minmax in Ruby and do not reach this gate.
|
|
4594
|
+
# self/src are rebound to storage. When
|
|
4467
4595
|
# `relift` is set (value-returning kernels), the original Face is saved to
|
|
4468
4596
|
# the function-scope `_reduce_face` so the dispatcher can re-lift the output.
|
|
4469
4597
|
def self.emit_reduce_face_gate(io, name, relift)
|
|
@@ -4472,16 +4600,11 @@ module MkKernel
|
|
|
4472
4600
|
end
|
|
4473
4601
|
io.puts " if ( ca_is_face(src) ) {"
|
|
4474
4602
|
io.puts " VALUE _face = self;"
|
|
4475
|
-
io.puts "
|
|
4476
|
-
io.puts " VALUE _st = rb_ca_strip_face_value(self);"
|
|
4603
|
+
io.puts " VALUE _st;"
|
|
4477
4604
|
io.puts " CArray *_sc;"
|
|
4605
|
+
io.puts %Q[ ca_face_require_orderable(_face, ca_calling_method_name(), "order");]
|
|
4606
|
+
io.puts " _st = rb_ca_strip_face_value(self);"
|
|
4478
4607
|
io.puts " GetCArray(_st, _sc);"
|
|
4479
|
-
io.puts " if ( _sc->data_type != CA_FIXLEN && ! _orderable ) {"
|
|
4480
|
-
io.puts %Q[ rb_raise(rb_eArgError,]
|
|
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));]
|
|
4484
|
-
io.puts " }"
|
|
4485
4608
|
if relift
|
|
4486
4609
|
io.puts " _reduce_face = _face;"
|
|
4487
4610
|
end
|
|
@@ -4491,32 +4614,37 @@ module MkKernel
|
|
|
4491
4614
|
end
|
|
4492
4615
|
|
|
4493
4616
|
# 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 ->
|
|
4617
|
+
# emit_reduce_face_gate. Full-reduction scalar -> storage_to_element hook;
|
|
4618
|
+
# per-axis CArray -> ca_face_relift_entity (tail, mask and copy_state).
|
|
4496
4619
|
# All-masked full reductions leave CA_UNDEF untouched.
|
|
4497
4620
|
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 " if ( ca_has_mask(_rca) ) {"
|
|
4507
|
-
io.puts " /* rb_ca_face_template resets mask to NULL; carry the reduced"
|
|
4508
|
-
io.puts " mask bits (all-masked slabs) onto the lifted Face. */"
|
|
4509
|
-
io.puts " CArray *_lca;"
|
|
4510
|
-
io.puts " GetCArray(_lifted, _lca);"
|
|
4511
|
-
io.puts " ca_copy_mask(_lca, _rca);"
|
|
4621
|
+
io.puts " if ( _reduce_face != Qnil && RB_TYPE_P(result, T_ARRAY) ) {"
|
|
4622
|
+
io.puts " /* a multi-output reduction (minmax): re-lift each output. */"
|
|
4623
|
+
io.puts " long _i;"
|
|
4624
|
+
io.puts " for ( _i = 0; _i < RARRAY_LEN(result); _i++ ) {"
|
|
4625
|
+
io.puts " VALUE _r = rb_ary_entry(result, _i);"
|
|
4626
|
+
io.puts " if ( _r == CA_UNDEF || _r == Qnil ) continue;"
|
|
4627
|
+
io.puts " if ( rb_obj_is_kind_of(_r, rb_cCArray) ) {"
|
|
4628
|
+
io.puts " _r = ca_face_relift_entity(_reduce_face, _r);"
|
|
4512
4629
|
io.puts " }"
|
|
4513
|
-
io.puts "
|
|
4630
|
+
io.puts " else {"
|
|
4631
|
+
io.puts " CArray *_fca;"
|
|
4632
|
+
io.puts " GetCArray(_reduce_face, _fca);"
|
|
4633
|
+
io.puts " CA_FACE_STORAGE_TO_ELEMENT_IF_FACE(_r, _reduce_face, _fca);"
|
|
4634
|
+
io.puts " }"
|
|
4635
|
+
io.puts " rb_ary_store(result, _i, _r);"
|
|
4636
|
+
io.puts " }"
|
|
4637
|
+
io.puts " }"
|
|
4638
|
+
io.puts " else if ( _reduce_face != Qnil && result != CA_UNDEF && result != Qnil ) {"
|
|
4639
|
+
io.puts " if ( rb_obj_is_kind_of(result, rb_cCArray) ) {"
|
|
4640
|
+
io.puts " /* per-axis: re-lift the storage result as a Face of the same class. */"
|
|
4641
|
+
io.puts " result = ca_face_relift_entity(_reduce_face, result);"
|
|
4514
4642
|
io.puts " }"
|
|
4515
4643
|
io.puts " else {"
|
|
4516
4644
|
io.puts " /* full reduction: decode the scalar via the registered hook. */"
|
|
4517
4645
|
io.puts " CArray *_fca;"
|
|
4518
4646
|
io.puts " GetCArray(_reduce_face, _fca);"
|
|
4519
|
-
io.puts "
|
|
4647
|
+
io.puts " CA_FACE_STORAGE_TO_ELEMENT_IF_FACE(result, _reduce_face, _fca);"
|
|
4520
4648
|
io.puts " }"
|
|
4521
4649
|
io.puts " }"
|
|
4522
4650
|
end
|
|
@@ -4528,7 +4656,7 @@ module MkKernel
|
|
|
4528
4656
|
has_kind = !partition # :full and :rank gain kind: dispatch (Option γ)
|
|
4529
4657
|
mask_sentinel = (k[:mask_self] == :sentinel)
|
|
4530
4658
|
# bind_ruby: false kernels are consumed by other .c files at the C
|
|
4531
|
-
# level (
|
|
4659
|
+
# level (carray_sort.c / carray_partition.c call the _mp twins).
|
|
4532
4660
|
# Emit extern linkage so the symbol is reachable across translation
|
|
4533
4661
|
# units. bind_ruby: true kernels stay static (only referenced by
|
|
4534
4662
|
# Init_carray_kernels in this file).
|
|
@@ -4540,6 +4668,10 @@ module MkKernel
|
|
|
4540
4668
|
# partition_index / rank_index, which are consumed at the C level
|
|
4541
4669
|
# by carray_order.c (= ordering helpers like nlargest, order).
|
|
4542
4670
|
storage = (k[:bind_ruby] == false || k[:c_callable]) ? "" : "static "
|
|
4671
|
+
# bind_ruby: false + mask_self: :sentinel kernels are reached only
|
|
4672
|
+
# through their _mp twins, so the fixed-masked_last entries below
|
|
4673
|
+
# would have no caller.
|
|
4674
|
+
mp_only = (k[:mask_self] == :sentinel && k[:bind_ruby] == false)
|
|
4543
4675
|
# PROPOSAL_PORTABLE_TEXTBOOK_SORT §9.5.2 Option γ:
|
|
4544
4676
|
# For algorithm :full / :rank, dispatch emits THREE C-level entries:
|
|
4545
4677
|
# rb_ca_<name>_ki_quick (self, vaxis) -> do_stable=0
|
|
@@ -4576,32 +4708,30 @@ module MkKernel
|
|
|
4576
4708
|
io.puts "{"
|
|
4577
4709
|
io.puts " CArray *src;"
|
|
4578
4710
|
io.puts " GetCArray(self, src);"
|
|
4579
|
-
io
|
|
4580
|
-
io.puts "
|
|
4581
|
-
io.puts " switch below and every index/addr
|
|
4582
|
-
io.puts "
|
|
4711
|
+
emit_lazy_evaluated(io)
|
|
4712
|
+
io.puts " /* Face gate: run on the array the Face's order is read from."
|
|
4713
|
+
io.puts " It is never a Face, so the switch below and every index/addr"
|
|
4714
|
+
io.puts " template output build on a plain array, not a face-lifted"
|
|
4715
|
+
io.puts " (NULL-ptr) view. */"
|
|
4583
4716
|
emit_sort_face_gate(io, name)
|
|
4584
4717
|
if k[:mask_self] == :raise
|
|
4585
4718
|
io.puts " /* sort family rejects masked sources globally (R3 / Q3): "
|
|
4586
4719
|
io.puts " masked elements would break the ordering invariant. */"
|
|
4587
4720
|
io.puts " if ( ca_has_mask(src) ) {"
|
|
4588
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
4721
|
+
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
4722
|
io.puts " }"
|
|
4590
4723
|
end
|
|
4591
|
-
io.puts " int axis =
|
|
4592
|
-
io.puts " if ( axis < 0 ) axis += src->ndim;"
|
|
4593
|
-
io.puts " if ( axis < 0 || axis >= src->ndim ) {"
|
|
4594
|
-
io.puts %Q[ rb_raise(rb_eArgError, "#{name}_ki: axis %d out of range for ndim %d", NUM2INT(vaxis), src->ndim);]
|
|
4595
|
-
io.puts " }"
|
|
4724
|
+
io.puts " int axis = ca_normalize_axis_arg(vaxis, src->ndim, NULL);"
|
|
4596
4725
|
io.puts " switch ( src->data_type ) {"
|
|
4597
4726
|
k[:source].each do |s|
|
|
4598
4727
|
si = DTYPES[s]
|
|
4599
4728
|
native_extra = mask_sentinel ? ", masked_last" : ""
|
|
4600
4729
|
native_extra += rank ? ", dense" : ""
|
|
4601
|
-
io.puts " case #{si[:ca]}:
|
|
4730
|
+
io.puts " case #{si[:ca]}: CA_RETURN_KEEPING(self, #{name}_ki_native_#{s}(self, src, axis, do_stable#{native_extra}));"
|
|
4602
4731
|
end
|
|
4603
4732
|
io.puts " default:"
|
|
4604
|
-
io.puts %Q[
|
|
4733
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
4734
|
+
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
4735
|
io.puts " }"
|
|
4606
4736
|
io.puts " return Qnil; /* unreachable */"
|
|
4607
4737
|
io.puts "}"
|
|
@@ -4614,30 +4744,33 @@ module MkKernel
|
|
|
4614
4744
|
dispatch_call_last = mask_sentinel ? ", 1" : ""
|
|
4615
4745
|
dispatch_call_last += rank ? ", 0" : ""
|
|
4616
4746
|
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4747
|
+
unless mp_only
|
|
4748
|
+
# _ki_quick: arity 2 (self, vaxis). Linkage = storage.
|
|
4749
|
+
io.puts
|
|
4750
|
+
io.puts "#{storage}VALUE"
|
|
4751
|
+
io.puts "rb_ca_#{name}_ki_quick (VALUE self, VALUE vaxis)"
|
|
4752
|
+
io.puts "{"
|
|
4753
|
+
io.puts " return #{name}_ki_dispatch(self, vaxis, 0#{dispatch_call_last});"
|
|
4754
|
+
io.puts "}"
|
|
4624
4755
|
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4756
|
+
# _ki_stable: arity 2 (self, vaxis). Linkage = storage.
|
|
4757
|
+
io.puts
|
|
4758
|
+
io.puts "#{storage}VALUE"
|
|
4759
|
+
io.puts "rb_ca_#{name}_ki_stable (VALUE self, VALUE vaxis)"
|
|
4760
|
+
io.puts "{"
|
|
4761
|
+
io.puts " return #{name}_ki_dispatch(self, vaxis, 1#{dispatch_call_last});"
|
|
4762
|
+
io.puts "}"
|
|
4632
4763
|
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4764
|
+
# _ki: arity 2 alias of _ki_quick (= preserves existing arity-1
|
|
4765
|
+
# Ruby binding and existing C extern callers).
|
|
4766
|
+
io.puts
|
|
4767
|
+
io.puts "#{storage}VALUE"
|
|
4768
|
+
io.puts "rb_ca_#{name}_ki (VALUE self, VALUE vaxis)"
|
|
4769
|
+
io.puts "{"
|
|
4770
|
+
io.puts " return rb_ca_#{name}_ki_quick(self, vaxis);"
|
|
4771
|
+
io.puts "}"
|
|
4772
|
+
|
|
4773
|
+
end
|
|
4641
4774
|
|
|
4642
4775
|
if mask_sentinel && k[:bind_ruby] == false
|
|
4643
4776
|
# _mp twins: explicit masked_last, non-Ruby (plain C int, no
|
|
@@ -4659,16 +4792,16 @@ module MkKernel
|
|
|
4659
4792
|
end
|
|
4660
4793
|
|
|
4661
4794
|
if rank
|
|
4662
|
-
#
|
|
4663
|
-
# carray_order.c's `order`
|
|
4664
|
-
# sugar over rank_index, defined in a different file, so it needs
|
|
4795
|
+
# _kind_dense: explicit kind (0 :quick / 1 :stable) and dense
|
|
4796
|
+
# (0 :ordinal / 1 :dense), extern for carray_order.c's `order`
|
|
4797
|
+
# (sugar over rank_index, defined in a different file, so it needs
|
|
4665
4798
|
# a non-static entry point the same way the mask_self: :sentinel
|
|
4666
4799
|
# kernels' _mp twins do).
|
|
4667
4800
|
io.puts
|
|
4668
4801
|
io.puts "VALUE"
|
|
4669
|
-
io.puts "rb_ca_#{name}
|
|
4802
|
+
io.puts "rb_ca_#{name}_ki_kind_dense (VALUE self, VALUE vaxis, int stable, int dense)"
|
|
4670
4803
|
io.puts "{"
|
|
4671
|
-
io.puts " return #{name}_ki_dispatch(self, vaxis,
|
|
4804
|
+
io.puts " return #{name}_ki_dispatch(self, vaxis, stable, dense);"
|
|
4672
4805
|
io.puts "}"
|
|
4673
4806
|
end
|
|
4674
4807
|
else
|
|
@@ -4683,45 +4816,45 @@ module MkKernel
|
|
|
4683
4816
|
io.puts "{"
|
|
4684
4817
|
io.puts " CArray *src;"
|
|
4685
4818
|
io.puts " GetCArray(self, src);"
|
|
4686
|
-
io
|
|
4687
|
-
io.puts "
|
|
4819
|
+
emit_lazy_evaluated(io)
|
|
4820
|
+
io.puts " /* Face gate (partition): same as the sort dispatcher. */"
|
|
4688
4821
|
emit_sort_face_gate(io, name)
|
|
4689
4822
|
if k[:mask_self] == :raise
|
|
4690
4823
|
io.puts " /* sort family rejects masked sources globally (R3 / Q3): "
|
|
4691
4824
|
io.puts " masked elements would break the ordering invariant. */"
|
|
4692
4825
|
io.puts " if ( ca_has_mask(src) ) {"
|
|
4693
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
4826
|
+
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
4827
|
io.puts " }"
|
|
4695
4828
|
end
|
|
4696
|
-
io.puts " int axis =
|
|
4697
|
-
io.puts "
|
|
4698
|
-
io.puts "
|
|
4699
|
-
io.puts %Q[ rb_raise(rb_eArgError, "#{name}_ki: axis %d out of range for ndim %d", NUM2INT(vaxis), src->ndim);]
|
|
4700
|
-
io.puts " }"
|
|
4701
|
-
io.puts " ca_size_t kth = NUM2SIZE(vkth);"
|
|
4829
|
+
io.puts " int axis = ca_normalize_axis_arg(vaxis, src->ndim, NULL);"
|
|
4830
|
+
io.puts " ca_size_t kth_raw = (ca_size_t) ca_integer_arg(vkth, \"kth\", NULL);"
|
|
4831
|
+
io.puts " ca_size_t kth = kth_raw;"
|
|
4702
4832
|
io.puts " ca_size_t axis_n = src->dim[axis];"
|
|
4703
4833
|
io.puts " if ( kth < 0 ) kth += axis_n;"
|
|
4704
4834
|
io.puts " if ( kth < 0 || kth >= axis_n ) {"
|
|
4705
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
4835
|
+
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
4836
|
io.puts " }"
|
|
4707
4837
|
io.puts " switch ( src->data_type ) {"
|
|
4708
4838
|
k[:source].each do |s|
|
|
4709
4839
|
si = DTYPES[s]
|
|
4710
4840
|
native_extra = mask_sentinel ? ", masked_last" : ""
|
|
4711
|
-
io.puts " case #{si[:ca]}:
|
|
4841
|
+
io.puts " case #{si[:ca]}: CA_RETURN_KEEPING(self, #{name}_ki_native_#{s}(self, src, axis, kth#{native_extra}));"
|
|
4712
4842
|
end
|
|
4713
4843
|
io.puts " default:"
|
|
4714
|
-
io.puts %Q[
|
|
4844
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
4845
|
+
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
4846
|
io.puts " }"
|
|
4716
4847
|
io.puts " return Qnil; /* unreachable */"
|
|
4717
4848
|
io.puts "}"
|
|
4718
4849
|
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4850
|
+
unless mp_only
|
|
4851
|
+
io.puts
|
|
4852
|
+
io.puts "#{storage}VALUE"
|
|
4853
|
+
io.puts "rb_ca_#{name}_ki (VALUE self, VALUE vaxis, VALUE vkth)"
|
|
4854
|
+
io.puts "{"
|
|
4855
|
+
io.puts " return #{name}_ki_dispatch(self, vaxis, vkth#{mask_sentinel ? ", 1" : ""});"
|
|
4856
|
+
io.puts "}"
|
|
4857
|
+
end
|
|
4725
4858
|
|
|
4726
4859
|
if mask_sentinel && k[:bind_ruby] == false
|
|
4727
4860
|
# _mp twin: explicit masked_last, extern for carray_partition.c's
|
|
@@ -4739,10 +4872,10 @@ module MkKernel
|
|
|
4739
4872
|
# wrapper). When public_method: is set, emit a -1-argc
|
|
4740
4873
|
# entry that parses `axis:` (and `kind:` for has_kind, and
|
|
4741
4874
|
# `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.
|
|
4875
|
+
# :rank) kwarg with default nil (= whole array) / :quick / :last /
|
|
4876
|
+
# :ordinal and dispatches to the appropriate positional entry.
|
|
4877
|
+
# For :partition algorithm, kth is accepted as a single positional
|
|
4878
|
+
# argument before the kwarg.
|
|
4746
4879
|
if k[:public_method]
|
|
4747
4880
|
legacy_name = k[:public_method].to_s
|
|
4748
4881
|
io.puts
|
|
@@ -4769,7 +4902,21 @@ module MkKernel
|
|
|
4769
4902
|
scan_targets << "&rmasked_position" if mask_sentinel
|
|
4770
4903
|
scan_targets << "&rmethod" if rank
|
|
4771
4904
|
io.puts " rb_scan_options(ropt, \"#{scan_keys.join(",")}\", #{scan_targets.join(", ")});"
|
|
4772
|
-
|
|
4905
|
+
# No axis means the whole array, as for sort: flatten and walk the
|
|
4906
|
+
# single fiber. rank_index keeps self's shape (a rank belongs to a
|
|
4907
|
+
# cell, as for order); the position lists keep the flat shape.
|
|
4908
|
+
io.puts " volatile VALUE whole_src = Qnil;" if rank
|
|
4909
|
+
io.puts " if ( NIL_P(raxis) ) { /* no axis: the whole array */"
|
|
4910
|
+
io.puts " whole_src = self;" if rank
|
|
4911
|
+
# The flattened view of a lazy expression is not one itself, so the
|
|
4912
|
+
# dispatcher would not see an expression to hand over: ask here.
|
|
4913
|
+
io.puts " {"
|
|
4914
|
+
io.puts " volatile VALUE evaluated = ca_lazy_evaluated(self);"
|
|
4915
|
+
io.puts " if ( ! NIL_P(evaluated) ) self = evaluated;"
|
|
4916
|
+
io.puts " }"
|
|
4917
|
+
io.puts " self = rb_ca_flatten(self);"
|
|
4918
|
+
io.puts " raxis = INT2NUM(0);"
|
|
4919
|
+
io.puts " }"
|
|
4773
4920
|
if rank
|
|
4774
4921
|
# method: :ordinal (default) / :dense -> dense int. :ordinal
|
|
4775
4922
|
# assigns every cell a distinct rank (ties broken by original
|
|
@@ -4777,37 +4924,15 @@ module MkKernel
|
|
|
4777
4924
|
# the standard "dense rank" used to compose rank as a sort_addr
|
|
4778
4925
|
# priority key without silently dropping lower-priority keys on
|
|
4779
4926
|
# 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 " }"
|
|
4927
|
+
io.puts " int dense = NIL_P(rmethod) ? 0 : ca_symbol_choice(rmethod, \"method\", \"ordinal\", \"dense\", NULL);"
|
|
4792
4928
|
end
|
|
4793
4929
|
if mask_sentinel
|
|
4794
4930
|
# masked_position: :last (default) / :first -> masked_last int.
|
|
4795
4931
|
# 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 " }"
|
|
4932
|
+
io.puts " int masked_last = NIL_P(rmasked_position) ? 1 : ca_symbol_choice(rmasked_position, \"masked_position\", \"first\", \"last\", NULL);"
|
|
4808
4933
|
end
|
|
4809
4934
|
if partition
|
|
4810
|
-
# partition_index(kth, axis:
|
|
4935
|
+
# partition_index(kth, axis: nil, masked_position: :last) -- kth
|
|
4811
4936
|
# positional, axis/masked_position kwargs.
|
|
4812
4937
|
io.puts " if ( argc != 1 ) {"
|
|
4813
4938
|
io.puts %Q[ rb_raise(rb_eArgError, "#{legacy_name}: wrong number of positional arguments (given %d, expected 1: kth)", argc);]
|
|
@@ -4818,28 +4943,27 @@ module MkKernel
|
|
|
4818
4943
|
io.puts " return rb_ca_#{name}_ki(self, raxis, argv[0]);"
|
|
4819
4944
|
end
|
|
4820
4945
|
else
|
|
4821
|
-
# sort_index(axis:
|
|
4946
|
+
# sort_index(axis: nil, kind: :quick, masked_position: :last) --
|
|
4822
4947
|
# no positional, kwargs only.
|
|
4823
4948
|
io.puts " if ( argc != 0 ) {"
|
|
4824
4949
|
io.puts %Q[ rb_raise(rb_eArgError, "#{legacy_name}: positional args no longer accepted (given %d args); use axis:/kind: kwargs", argc);]
|
|
4825
4950
|
io.puts " }"
|
|
4826
4951
|
# 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 " }"
|
|
4952
|
+
io.puts " int do_stable = NIL_P(rkind) ? 0 : ca_symbol_choice(rkind, \"kind\", \"quick\", \"stable\", NULL);"
|
|
4839
4953
|
if mask_sentinel
|
|
4840
4954
|
io.puts " return #{name}_ki_dispatch(self, raxis, do_stable, masked_last);"
|
|
4841
4955
|
elsif rank
|
|
4842
|
-
io.puts "
|
|
4956
|
+
io.puts " VALUE out = #{name}_ki_dispatch(self, raxis, do_stable, dense);"
|
|
4957
|
+
io.puts " if ( ! NIL_P(whole_src) ) {"
|
|
4958
|
+
io.puts " CArray *src;"
|
|
4959
|
+
io.puts " VALUE shape_argv[CA_RANK_MAX];"
|
|
4960
|
+
io.puts " GetCArray(whole_src, src);"
|
|
4961
|
+
io.puts " for ( int k = 0; k < src->ndim; k++ ) {"
|
|
4962
|
+
io.puts " shape_argv[k] = SIZE2NUM(src->dim[k]);"
|
|
4963
|
+
io.puts " }"
|
|
4964
|
+
io.puts " out = rb_ca_reshape((int) src->ndim, shape_argv, out);"
|
|
4965
|
+
io.puts " }"
|
|
4966
|
+
io.puts " return out;"
|
|
4843
4967
|
else
|
|
4844
4968
|
io.puts " return do_stable ? rb_ca_#{name}_ki_stable(self, raxis)"
|
|
4845
4969
|
io.puts " : rb_ca_#{name}_ki_quick(self, raxis);"
|
|
@@ -4880,7 +5004,7 @@ module MkKernel
|
|
|
4880
5004
|
io.puts
|
|
4881
5005
|
io.puts "/* ===== #{k[:name]}_ki ============================================ */"
|
|
4882
5006
|
k[:source].each do |src|
|
|
4883
|
-
|
|
5007
|
+
emit_native_for(io, k, src) { |o| emit_search_native(o, k, src) }
|
|
4884
5008
|
end
|
|
4885
5009
|
emit_search_dispatch(io, k)
|
|
4886
5010
|
end
|
|
@@ -4903,7 +5027,7 @@ module MkKernel
|
|
|
4903
5027
|
# body: Hash form picks per-data_type-kind branch (= :int / :float /
|
|
4904
5028
|
# :object / :fixlen); String form is uniform.
|
|
4905
5029
|
# PROPOSAL_MKKERNEL_OBJECT_DTYPE_BRANCH Phase 4: :object branch added for
|
|
4906
|
-
# CA_OBJECT (equality via
|
|
5030
|
+
# CA_OBJECT (equality via ca_obj_equal, ordering via rb_funcall(<=>)).
|
|
4907
5031
|
# PROPOSAL_SEARCH_SEMANTICS_UNIFY S2: :fixlen branch added for CA_FIXLEN
|
|
4908
5032
|
# (the cell is a runtime-width byte blob; comparison is memcmp over
|
|
4909
5033
|
# ca->bytes, the same total order fixlen bincmp uses).
|
|
@@ -4937,7 +5061,7 @@ module MkKernel
|
|
|
4937
5061
|
<<~C.chomp
|
|
4938
5062
|
int eps_is_default = NIL_P(reps);
|
|
4939
5063
|
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;
|
|
5064
|
+
double query_eps = eps_is_default ? (isfinite((double)query_val) ? (double)(#{eps_def}) * fabs((double)query_val) : 0.0) : user_eps;
|
|
4941
5065
|
C
|
|
4942
5066
|
elsif has_eps && no_eps
|
|
4943
5067
|
"(void) reps; /* #{is_fixlen ? "CA_FIXLEN" : "CA_OBJECT"}: eps ignored, exact match only */\n double query_eps = 0.0; (void) query_eps;"
|
|
@@ -4952,7 +5076,7 @@ module MkKernel
|
|
|
4952
5076
|
""
|
|
4953
5077
|
end
|
|
4954
5078
|
eps_percell = if has_eps && !no_eps
|
|
4955
|
-
"double query_eps = eps_is_default ? (double)(#{eps_def}) * fabs((double)query_val) : user_eps;"
|
|
5079
|
+
"double query_eps = eps_is_default ? (isfinite((double)query_val) ? (double)(#{eps_def}) * fabs((double)query_val) : 0.0) : user_eps;"
|
|
4956
5080
|
else
|
|
4957
5081
|
"double query_eps = 0.0; (void) query_eps;"
|
|
4958
5082
|
end
|
|
@@ -4964,14 +5088,36 @@ module MkKernel
|
|
|
4964
5088
|
# and let rb_ca_obj2ptr pack it; `query_val` is then a char* the memcmp
|
|
4965
5089
|
# body reads (mirrors the legacy flat bsearch ALLOCA_N path).
|
|
4966
5090
|
query_setup_a = if is_fixlen
|
|
4967
|
-
"
|
|
5091
|
+
"volatile VALUE query_val_holder = 0;\n" \
|
|
5092
|
+
" char *query_val = ALLOCV_N(char, query_val_holder, ca->bytes);\n" \
|
|
4968
5093
|
" rb_ca_obj2ptr(self, rval, query_val);"
|
|
4969
5094
|
else
|
|
5095
|
+
(INT_DTYPES.include?(src) ? "ca_check_integer_fits(rval, #{si[:ca]});\n " : "") +
|
|
4970
5096
|
"#{si[:c]} query_val_buf;\n" \
|
|
4971
5097
|
" rb_ca_obj2ptr(self, rval, &query_val_buf);\n" \
|
|
4972
5098
|
" #{si[:c]} query_val = query_val_buf;"
|
|
4973
5099
|
end
|
|
4974
5100
|
|
|
5101
|
+
# The object lane calls Ruby for every cell of the case B/C walk, which
|
|
5102
|
+
# runs between a direct attach and detach of self and val; a raise there
|
|
5103
|
+
# would leave both attached. An entity holds no attach, so the lane
|
|
5104
|
+
# reads an entity copy of whichever is a view.
|
|
5105
|
+
entity_bc = if is_object
|
|
5106
|
+
<<~C.chomp
|
|
5107
|
+
volatile VALUE vca_entity = self;
|
|
5108
|
+
if ( ! ca_is_entity(ca) ) {
|
|
5109
|
+
vca_entity = rb_ca_copy(self);
|
|
5110
|
+
GetCArray(vca_entity, ca);
|
|
5111
|
+
}
|
|
5112
|
+
if ( ! ca_is_entity(cv) ) {
|
|
5113
|
+
vsrc_val = rb_ca_copy(vsrc_val);
|
|
5114
|
+
GetCArray(vsrc_val, cv);
|
|
5115
|
+
}
|
|
5116
|
+
C
|
|
5117
|
+
else
|
|
5118
|
+
""
|
|
5119
|
+
end
|
|
5120
|
+
|
|
4975
5121
|
# case B/C query coercion. numeric/object: wrap-readonly to self's
|
|
4976
5122
|
# data_type when they differ. fixlen: require a CA_FIXLEN query of the
|
|
4977
5123
|
# same byte width (no scalar coercion exists for blobs), so the per-cell
|
|
@@ -4981,7 +5127,7 @@ module MkKernel
|
|
|
4981
5127
|
(void) vsrc_val; /* fixlen: no wrap-readonly coercion */
|
|
4982
5128
|
if ( cv->data_type != CA_FIXLEN || cv->bytes != ca->bytes ) {
|
|
4983
5129
|
rb_raise(rb_eArgError,
|
|
4984
|
-
"
|
|
5130
|
+
"%s: CA_FIXLEN query must be CA_FIXLEN of the same byte width (= %ld)", ca_calling_method_name(), (long) ca->bytes);
|
|
4985
5131
|
}
|
|
4986
5132
|
C
|
|
4987
5133
|
else
|
|
@@ -5016,56 +5162,41 @@ module MkKernel
|
|
|
5016
5162
|
# flat array path. op_mask is lazily allocated on the first no-match.
|
|
5017
5163
|
undef_nm = k[:undef_no_match]
|
|
5018
5164
|
nm_cond = "result == (#{oi[:c]})(#{no_match})"
|
|
5019
|
-
#
|
|
5020
|
-
#
|
|
5021
|
-
#
|
|
5022
|
-
|
|
5023
|
-
# op_mask declaration (only when an output cell can be masked);
|
|
5024
|
-
# content-only (the heredoc carries indentation).
|
|
5025
|
-
op_mask_decl = (undef_nm || undef_q) \
|
|
5026
|
-
? "boolean8_t *op_mask = NULL; /* lazily allocated on the first masked output cell */" \
|
|
5027
|
-
: "/* (no masked output cell) */"
|
|
5165
|
+
# A masked query cell yields a masked output cell, so an undetermined
|
|
5166
|
+
# query cannot be answered from the value that happens to sit under the
|
|
5167
|
+
# mask. Shares op_mask with undef_no_match.
|
|
5168
|
+
op_mask_decl = "boolean8_t *op_mask = NULL; /* lazily allocated on the first masked output cell */"
|
|
5028
5169
|
|
|
5029
5170
|
# Case A scalar UNDEF query (also reached by the A1 single-element
|
|
5030
5171
|
# reduction when that element is masked): every output cell is
|
|
5031
5172
|
# undetermined, so short-circuit before the query is coerced to a number.
|
|
5032
|
-
undef_query_a =
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
C
|
|
5046
|
-
else
|
|
5047
|
-
"/* (mask_query: :ignore) */"
|
|
5048
|
-
end
|
|
5173
|
+
undef_query_a = <<~C.chomp
|
|
5174
|
+
if ( rval == CA_UNDEF ) {
|
|
5175
|
+
int8_t undef_axes[1] = { (int8_t) axis };
|
|
5176
|
+
VALUE voutu = rb_ca_new_reduced(self, undef_axes, 1, #{oi[:ca]}, 0);
|
|
5177
|
+
CArray *cou;
|
|
5178
|
+
GetCArray(voutu, cou);
|
|
5179
|
+
MEMZERO((#{oi[:c]} *) cou->ptr, #{oi[:c]}, cou->elements);
|
|
5180
|
+
ca_create_mask(cou);
|
|
5181
|
+
memset(cou->mask->ptr, 1, (size_t) cou->elements);
|
|
5182
|
+
if ( ca->ndim == 1 ) { return Qnil; }
|
|
5183
|
+
return voutu;
|
|
5184
|
+
}
|
|
5185
|
+
C
|
|
5049
5186
|
|
|
5050
5187
|
# Case B/C query mask base. NULL when the query carries no mask.
|
|
5051
|
-
cv_mask_decl_bc =
|
|
5052
|
-
? "boolean8_t *cv_mask_base = ca_has_mask(cv) ? (boolean8_t *) cv->mask->ptr : NULL;" \
|
|
5053
|
-
: "/* (mask_query: :ignore) */"
|
|
5188
|
+
cv_mask_decl_bc = "boolean8_t *cv_mask_base = ca_has_mask(cv) ? (boolean8_t *) cv->mask->ptr : NULL;"
|
|
5054
5189
|
|
|
5055
5190
|
# Per-cell guard wrapping the body + write in case B/C. A masked query
|
|
5056
5191
|
# cell skips the body entirely and marks the output cell instead.
|
|
5057
|
-
undef_query_open_bc =
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
else
|
|
5066
|
-
"/* (mask_query: :ignore) */"
|
|
5067
|
-
end
|
|
5068
|
-
undef_query_close_bc = undef_q ? "}" : "/* (mask_query: :ignore) */"
|
|
5192
|
+
undef_query_open_bc = <<~C.chomp
|
|
5193
|
+
if ( cv_mask_base && cv_mask_base[val_offset / cv->bytes] ) {
|
|
5194
|
+
if ( ! op_mask ) { ca_create_mask(co); op_mask = (boolean8_t *) co->mask->ptr; }
|
|
5195
|
+
op_mask[flat] = 1;
|
|
5196
|
+
op[flat] = (#{oi[:c]}) 0; /* undetermined query; the mask bit is what counts */
|
|
5197
|
+
} else {
|
|
5198
|
+
C
|
|
5199
|
+
undef_query_close_bc = "}"
|
|
5069
5200
|
|
|
5070
5201
|
# Per-cell write block for case A (scalar query). `out_i` is the output
|
|
5071
5202
|
# cursor, `co` the output CArray. When undef_no_match: a no-match masks
|
|
@@ -5140,7 +5271,7 @@ module MkKernel
|
|
|
5140
5271
|
slab_axes, 1, 0);
|
|
5141
5272
|
if ( rc != CA_ITER_OK ) {
|
|
5142
5273
|
rb_raise(rb_eRuntimeError,
|
|
5143
|
-
"
|
|
5274
|
+
"%s: kernel_iterator init failed rc=%d", ca_calling_method_name(), rc);
|
|
5144
5275
|
}
|
|
5145
5276
|
C
|
|
5146
5277
|
|
|
@@ -5222,7 +5353,7 @@ module MkKernel
|
|
|
5222
5353
|
# transformation in two more inner loops.
|
|
5223
5354
|
io.puts <<~C
|
|
5224
5355
|
/* view_flat semantics + CArray val: NotImpError (deferred). */
|
|
5225
|
-
rb_raise(rb_eNotImpError, "
|
|
5356
|
+
rb_raise(rb_eNotImpError, "%s: view_flat semantics with CArray val (broadcast) not yet supported", ca_calling_method_name());
|
|
5226
5357
|
C
|
|
5227
5358
|
end
|
|
5228
5359
|
|
|
@@ -5245,7 +5376,9 @@ module MkKernel
|
|
|
5245
5376
|
*/
|
|
5246
5377
|
CArray *cv;
|
|
5247
5378
|
GetCArray(rval, cv);
|
|
5248
|
-
|
|
5379
|
+
/* volatile: once the query is wrapped below, only cv is used, and
|
|
5380
|
+
the wrapper must stay reachable while its cells are read. */
|
|
5381
|
+
volatile VALUE vsrc_val = rval;
|
|
5249
5382
|
#{coerce_bc}
|
|
5250
5383
|
|
|
5251
5384
|
/* base_shape = self.shape with axis removed. base_to_self[bk] =
|
|
@@ -5280,13 +5413,13 @@ module MkKernel
|
|
|
5280
5413
|
if ( i == axis ) continue;
|
|
5281
5414
|
if ( cv->dim[i] != ca->dim[i] ) {
|
|
5282
5415
|
rb_raise(rb_eArgError,
|
|
5283
|
-
"
|
|
5416
|
+
"%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
5417
|
axis, (int) i, (long) cv->dim[i], (int) i, (long) ca->dim[i]);
|
|
5285
5418
|
}
|
|
5286
5419
|
}
|
|
5287
5420
|
if ( cv->dim[axis] < 1 ) {
|
|
5288
5421
|
rb_raise(rb_eArgError,
|
|
5289
|
-
"
|
|
5422
|
+
"%s: empty query (val.dim[%d] = 0)", ca_calling_method_name(), axis);
|
|
5290
5423
|
}
|
|
5291
5424
|
cv_form = CV_FORM_A3;
|
|
5292
5425
|
cv_form_set = 1;
|
|
@@ -5296,7 +5429,7 @@ module MkKernel
|
|
|
5296
5429
|
/* A2: 1-D shared M-query (M >= 1); placed above A2.5 in rev5 */
|
|
5297
5430
|
if ( cv->dim[0] < 1 ) {
|
|
5298
5431
|
rb_raise(rb_eArgError,
|
|
5299
|
-
"
|
|
5432
|
+
"%s: empty query (val.dim[0] = 0)", ca_calling_method_name());
|
|
5300
5433
|
}
|
|
5301
5434
|
cv_form = CV_FORM_A2;
|
|
5302
5435
|
cv_form_set = 1;
|
|
@@ -5317,7 +5450,7 @@ module MkKernel
|
|
|
5317
5450
|
|
|
5318
5451
|
if ( ! cv_form_set ) {
|
|
5319
5452
|
rb_raise(rb_eArgError,
|
|
5320
|
-
"
|
|
5453
|
+
"%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
5454
|
(int) cv->ndim, (int) base_ndim, (int) ca->ndim);
|
|
5322
5455
|
}
|
|
5323
5456
|
|
|
@@ -5352,9 +5485,9 @@ module MkKernel
|
|
|
5352
5485
|
#{oi[:c]} *op = (#{oi[:c]} *) co->ptr;
|
|
5353
5486
|
#{op_mask_decl}
|
|
5354
5487
|
|
|
5488
|
+
#{entity_bc}
|
|
5355
5489
|
/* Attach self + val; rev4 uses raw row-major byte addressing. */
|
|
5356
|
-
|
|
5357
|
-
ca_attach(cv);
|
|
5490
|
+
ca_attach_n(2, ca, cv); /* window: numeric bodies do not raise; the object lane reads entities */
|
|
5358
5491
|
|
|
5359
5492
|
/* self_byte_stride[j] = bytes to advance along self axis j (row-major). */
|
|
5360
5493
|
ca_size_t self_byte_stride[CA_RANK_MAX];
|
|
@@ -5481,28 +5614,15 @@ module MkKernel
|
|
|
5481
5614
|
end
|
|
5482
5615
|
io.puts "{"
|
|
5483
5616
|
io.puts " CArray *src;"
|
|
5484
|
-
io.puts " VALUE self_ref = self; /* pre-strip Face, for to_comparable */"
|
|
5485
|
-
io.puts " int self_face_comparable = 0;"
|
|
5486
5617
|
io.puts " int self_was_face = 0;"
|
|
5487
5618
|
io.puts " GetCArray(self, src);"
|
|
5488
|
-
io
|
|
5489
|
-
io.puts "
|
|
5490
|
-
io.puts "
|
|
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. */"
|
|
5619
|
+
emit_lazy_evaluated(io)
|
|
5620
|
+
io.puts " /* Face gate (self): a Face reference is searched on its numeric"
|
|
5621
|
+
io.puts " storage, which ORDERABLE licenses; the query gate below brings"
|
|
5622
|
+
io.puts " the reference and the query into one space and descends. */"
|
|
5495
5623
|
io.puts " if ( ca_is_face(src) ) {"
|
|
5496
|
-
io.puts
|
|
5497
|
-
io.puts %Q[ rb_raise(rb_eArgError,]
|
|
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));]
|
|
5501
|
-
io.puts " }"
|
|
5624
|
+
io.puts %Q[ ca_face_require_orderable(self, ca_calling_method_name(), "order");]
|
|
5502
5625
|
io.puts " self_was_face = 1;"
|
|
5503
|
-
io.puts " self_face_comparable = ca_test_flag(src, CA_FLAG_FACE_COMPARABLE_STORAGE);"
|
|
5504
|
-
io.puts " self = rb_ca_strip_face_value(self);"
|
|
5505
|
-
io.puts " GetCArray(self, src);"
|
|
5506
5626
|
io.puts " }"
|
|
5507
5627
|
|
|
5508
5628
|
if has_eps
|
|
@@ -5514,39 +5634,26 @@ module MkKernel
|
|
|
5514
5634
|
io.puts " VALUE raxis = argv[1];"
|
|
5515
5635
|
io.puts " VALUE reps = (argc >= 3) ? argv[2] : Qnil;"
|
|
5516
5636
|
end
|
|
5517
|
-
io.puts " /* Query gate
|
|
5518
|
-
io.puts "
|
|
5519
|
-
io.puts "
|
|
5520
|
-
io.puts "
|
|
5521
|
-
io.puts "
|
|
5522
|
-
io.puts "
|
|
5523
|
-
io.puts "
|
|
5524
|
-
io.puts "
|
|
5525
|
-
io.puts "
|
|
5526
|
-
io.puts "
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
io.puts "
|
|
5533
|
-
io.puts "
|
|
5534
|
-
io.puts "
|
|
5535
|
-
io.puts "
|
|
5536
|
-
io.puts "
|
|
5537
|
-
io.puts %Q[ rval = ca_face_operand_descend(rval, "#{name}_ki");]
|
|
5538
|
-
io.puts " }"
|
|
5539
|
-
io.puts " } else if ( self_was_face ) {"
|
|
5540
|
-
io.puts " if ( rb_respond_to(self_ref, rb_intern(\"to_comparable\")) ) {"
|
|
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));]
|
|
5549
|
-
io.puts " }"
|
|
5637
|
+
io.puts " /* Query gate: the reference Face and the query are brought into one"
|
|
5638
|
+
io.puts " space (ca_face_reconcile_pair), then the reference descends to"
|
|
5639
|
+
io.puts " its storage. */"
|
|
5640
|
+
io.puts " if ( self_was_face ) {"
|
|
5641
|
+
io.puts " volatile VALUE ref = self, query = rval;"
|
|
5642
|
+
io.puts " ca_face_reconcile_pair(&ref, &query, ca_calling_method_name());"
|
|
5643
|
+
io.puts " rval = query;"
|
|
5644
|
+
io.puts " self = rb_ca_strip_face_value(ref);"
|
|
5645
|
+
io.puts " GetCArray(self, src);"
|
|
5646
|
+
io.puts " }"
|
|
5647
|
+
|
|
5648
|
+
# The query and the reference are compared in the type they match in,
|
|
5649
|
+
# as `a.eq(q)` compares (ca_query_match_type): the reference is read
|
|
5650
|
+
# through a cast view of that type, so the query is never cut to the
|
|
5651
|
+
# reference's type.
|
|
5652
|
+
io.puts " if ( ca_query_match_applies(src, rval) ) {"
|
|
5653
|
+
io.puts " int8_t qt = ca_query_match_type(src->data_type, rval);"
|
|
5654
|
+
io.puts " if ( qt != src->data_type ) {"
|
|
5655
|
+
io.puts " self = rb_ca_wrap_readonly(self, INT2NUM(qt));"
|
|
5656
|
+
io.puts " GetCArray(self, src);"
|
|
5550
5657
|
io.puts " }"
|
|
5551
5658
|
io.puts " }"
|
|
5552
5659
|
|
|
@@ -5554,24 +5661,21 @@ module MkKernel
|
|
|
5554
5661
|
when :raise
|
|
5555
5662
|
io.puts " /* mask_self: :raise -- global reject if self has any masked element. */"
|
|
5556
5663
|
io.puts " if ( ca_is_any_masked(src) ) {"
|
|
5557
|
-
io.puts %Q[ rb_raise(
|
|
5664
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: self should not have any masked elements", ca_calling_method_name());]
|
|
5558
5665
|
io.puts " }"
|
|
5559
5666
|
when :skip, :ignore
|
|
5560
5667
|
# body handles mask_in or ignores it
|
|
5561
5668
|
end
|
|
5562
5669
|
|
|
5563
|
-
io.puts " int axis =
|
|
5564
|
-
io.puts " if ( axis < 0 ) axis += src->ndim;"
|
|
5565
|
-
io.puts " if ( axis < 0 || axis >= src->ndim ) {"
|
|
5566
|
-
io.puts %Q[ rb_raise(rb_eArgError, "#{name}_ki: axis %d out of range for ndim %d", NUM2INT(raxis), src->ndim);]
|
|
5567
|
-
io.puts " }"
|
|
5670
|
+
io.puts " int axis = ca_normalize_axis_arg(raxis, src->ndim, NULL);"
|
|
5568
5671
|
io.puts " switch ( src->data_type ) {"
|
|
5569
5672
|
k[:source].each do |s|
|
|
5570
5673
|
si = DTYPES[s]
|
|
5571
|
-
io.puts " case #{si[:ca]}:
|
|
5674
|
+
io.puts " case #{si[:ca]}: CA_RETURN_KEEPING(self, #{name}_ki_native_#{s}(self, src, rval, axis#{extra_arg}));"
|
|
5572
5675
|
end
|
|
5573
5676
|
io.puts " default:"
|
|
5574
|
-
io.puts %Q[
|
|
5677
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
5678
|
+
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
5679
|
io.puts " }"
|
|
5576
5680
|
io.puts " return Qnil; /* unreachable */"
|
|
5577
5681
|
io.puts "}"
|
|
@@ -5821,11 +5925,15 @@ module MkKernel
|
|
|
5821
5925
|
int8_t in_dt;
|
|
5822
5926
|
TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
|
|
5823
5927
|
in_dt = ca->data_type;
|
|
5928
|
+
if ( ca_monop_#{name}_out_data_type[in_dt] <= CA_NONE ) {
|
|
5929
|
+
rb_raise(rb_eCADataTypeError,
|
|
5930
|
+
"#{name}!: source data_type :%s not supported", ca_type_name[in_dt]);
|
|
5931
|
+
}
|
|
5824
5932
|
if ( ca_monop_#{name}_out_data_type[in_dt] != in_dt ) {
|
|
5825
|
-
rb_raise(
|
|
5826
|
-
"#{name}!:
|
|
5827
|
-
"
|
|
5828
|
-
|
|
5933
|
+
rb_raise(rb_eCADataTypeError,
|
|
5934
|
+
"#{name}!: #{name} of :%s is :%s, which cannot be written back in place; "
|
|
5935
|
+
"use #{name}", ca_type_name[in_dt],
|
|
5936
|
+
ca_type_name[ca_monop_#{name}_out_data_type[in_dt]]);
|
|
5829
5937
|
}
|
|
5830
5938
|
return rb_ca_call_monop_bang(self, ca_monop_#{name});
|
|
5831
5939
|
}
|
|
@@ -6079,6 +6187,10 @@ module MkKernel
|
|
|
6079
6187
|
io.print <<~C
|
|
6080
6188
|
VALUE rb_ca_#{name} (VALUE self, VALUE other2, VALUE other3)
|
|
6081
6189
|
{ return rb_ca_call_triop(self, other2, other3, ca_triop_#{name}); }
|
|
6190
|
+
C
|
|
6191
|
+
# bang: false kernels get no bang method, so no bang entry either.
|
|
6192
|
+
return if k[:bang] == false
|
|
6193
|
+
io.print <<~C
|
|
6082
6194
|
|
|
6083
6195
|
VALUE rb_ca_#{name}_bang (VALUE self, VALUE other2, VALUE other3)
|
|
6084
6196
|
{ return rb_ca_call_triop_bang(self, other2, other3, ca_triop_#{name}); }
|
|
@@ -6350,15 +6462,58 @@ module MkKernel
|
|
|
6350
6462
|
# caller substitutes its own operands. Object-lane bodies are left out,
|
|
6351
6463
|
# since they call back into the interpreter and cannot be compiled apart
|
|
6352
6464
|
# from it.
|
|
6465
|
+
#
|
|
6466
|
+
# A comparison is keyed by the data type it compares, not by what it
|
|
6467
|
+
# answers, which is always boolean.
|
|
6353
6468
|
# ---------------------------------------------------------------------
|
|
6354
6469
|
|
|
6355
6470
|
BODY_TABLE_DTYPES = %i[i8 u8 i16 u16 i32 u32 i64 u64 f32 f64
|
|
6356
6471
|
bool cmplx64 cmplx128].freeze
|
|
6357
6472
|
|
|
6473
|
+
# Kernels written outside this generator that a lazy expression can still
|
|
6474
|
+
# hold. A Float or Complex array to an Integer power is CABinOp's ipow
|
|
6475
|
+
# (ext/ca_op_ipower.c, which applies op_powi_<type> cell by cell); its
|
|
6476
|
+
# body is that call.
|
|
6477
|
+
EXTRA_BODY_ROWS = %w[float32 float64 cmplx64 cmplx128].map { |t|
|
|
6478
|
+
["binop", "ipow", t, "(#3) = op_powi_<type>((#1), (#2));"]
|
|
6479
|
+
}.freeze
|
|
6480
|
+
|
|
6481
|
+
# The helpers the bodies call that are not in the C standard library:
|
|
6482
|
+
# the part of ca_op_powi.h between its markers, as it stands.
|
|
6483
|
+
def self.kernel_helpers
|
|
6484
|
+
text = File.read(File.join(__dir__, "ca_op_powi.h"))
|
|
6485
|
+
text[%r{/\* BEGIN kernel helpers \*/\n(.*)/\* END kernel helpers \*/}m, 1] or
|
|
6486
|
+
raise "ca_op_powi.h: kernel helper markers not found"
|
|
6487
|
+
end
|
|
6488
|
+
|
|
6489
|
+
# A lazy cast node (CAMonOp with op_id CAST_BASE + target) converts cell
|
|
6490
|
+
# by cell through ca_cast_block, which for a numeric target is a plain C
|
|
6491
|
+
# conversion (ext/carray_cast_func.rb). It is keyed as cast_<target> at
|
|
6492
|
+
# the source's data type. Left out: a boolean target, which checks its
|
|
6493
|
+
# input and raises, and a float or complex source to an integer target,
|
|
6494
|
+
# where an out-of-range value or NaN has no defined C result for a
|
|
6495
|
+
# second compilation to agree on. An int64 or uint64 source to float32
|
|
6496
|
+
# or cmplx64 goes through double, as ca_cast_block does.
|
|
6497
|
+
CAST_SOURCES = %i[bool i8 u8 i16 u16 i32 u32 i64 u64 f32 f64
|
|
6498
|
+
cmplx64 cmplx128].freeze
|
|
6499
|
+
CAST_TARGETS = %i[i8 u8 i16 u16 i32 u32 i64 u64 f32 f64
|
|
6500
|
+
cmplx64 cmplx128].freeze
|
|
6501
|
+
CAST_INEXACT = %i[f32 f64 cmplx64 cmplx128].freeze
|
|
6502
|
+
|
|
6503
|
+
def self.cast_body_rows
|
|
6504
|
+
CAST_SOURCES.product(CAST_TARGETS).filter_map { |src, dst|
|
|
6505
|
+
next if CAST_INEXACT.include?(src) && !CAST_INEXACT.include?(dst)
|
|
6506
|
+
name = DTYPES[dst][:ca].sub(/\ACA_/, "").downcase
|
|
6507
|
+
via = ( %i[i64 u64].include?(src) && %i[f32 cmplx64].include?(dst) ) ? "(double)" : ""
|
|
6508
|
+
["monop", "cast_#{name}", DTYPES[src][:ca].sub(/\ACA_/, "").downcase,
|
|
6509
|
+
"(#2) = (#{DTYPES[dst][:c]})#{via}(#1);"]
|
|
6510
|
+
}
|
|
6511
|
+
end
|
|
6512
|
+
|
|
6358
6513
|
def self.body_table_rows
|
|
6359
|
-
rows =
|
|
6514
|
+
rows = EXTRA_BODY_ROWS.map(&:dup) + cast_body_rows
|
|
6360
6515
|
KERNELS.each do |k|
|
|
6361
|
-
next unless %i[monop binop triop].include?(k[:kind])
|
|
6516
|
+
next unless %i[monop binop triop moncmp bincmp].include?(k[:kind])
|
|
6362
6517
|
BODY_TABLE_DTYPES.each do |src|
|
|
6363
6518
|
body = monop_expr_for(k, src)
|
|
6364
6519
|
next unless body.is_a?(String)
|
|
@@ -6370,8 +6525,10 @@ module MkKernel
|
|
|
6370
6525
|
rows
|
|
6371
6526
|
end
|
|
6372
6527
|
|
|
6528
|
+
# The block forms keep gsub from reading the backslashes in the
|
|
6529
|
+
# replacement as references to the match.
|
|
6373
6530
|
def self.c_string_literal(text)
|
|
6374
|
-
'"' + text.gsub("\\"
|
|
6531
|
+
'"' + text.gsub("\\") { "\\\\" }.gsub('"') { '\\"' }.gsub("\n") { '\\n' } + '"'
|
|
6375
6532
|
end
|
|
6376
6533
|
|
|
6377
6534
|
def self.emit_kernel_bodies(io)
|
|
@@ -6420,6 +6577,17 @@ module MkKernel
|
|
|
6420
6577
|
}
|
|
6421
6578
|
return Qnil;
|
|
6422
6579
|
}
|
|
6580
|
+
|
|
6581
|
+
/* CArray.__kernel_helpers__ -> String: the C a body above may call
|
|
6582
|
+
beyond the standard library, for the caller to put ahead of it. */
|
|
6583
|
+
static const char ca_kernel_helpers[] =
|
|
6584
|
+
#{c_string_literal(kernel_helpers)};
|
|
6585
|
+
|
|
6586
|
+
static VALUE
|
|
6587
|
+
rb_ca_s_kernel_helpers (VALUE klass)
|
|
6588
|
+
{
|
|
6589
|
+
return rb_str_new_cstr(ca_kernel_helpers);
|
|
6590
|
+
}
|
|
6423
6591
|
C
|
|
6424
6592
|
end
|
|
6425
6593
|
|
|
@@ -6441,6 +6609,8 @@ module MkKernel
|
|
|
6441
6609
|
end
|
|
6442
6610
|
io.puts " rb_define_singleton_method(rb_cCArray, \"__kernel_body__\","
|
|
6443
6611
|
io.puts " rb_ca_s_kernel_body, 3);"
|
|
6612
|
+
io.puts " rb_define_singleton_method(rb_cCArray, \"__kernel_helpers__\","
|
|
6613
|
+
io.puts " rb_ca_s_kernel_helpers, 0);"
|
|
6444
6614
|
io.puts "}"
|
|
6445
6615
|
end
|
|
6446
6616
|
|
|
@@ -6455,6 +6625,8 @@ module MkKernel
|
|
|
6455
6625
|
KERNELS.each { |k| emit_init_line(io, k) }
|
|
6456
6626
|
io.puts " rb_define_singleton_method(rb_cCArray, \"__kernel_body__\","
|
|
6457
6627
|
io.puts " rb_ca_s_kernel_body, 3);"
|
|
6628
|
+
io.puts " rb_define_singleton_method(rb_cCArray, \"__kernel_helpers__\","
|
|
6629
|
+
io.puts " rb_ca_s_kernel_helpers, 0);"
|
|
6458
6630
|
io.puts "}"
|
|
6459
6631
|
end
|
|
6460
6632
|
|
|
@@ -6515,7 +6687,6 @@ module MkKernel
|
|
|
6515
6687
|
end
|
|
6516
6688
|
arity = case k[:kind]
|
|
6517
6689
|
when :reduce then -1 # axis: kwarg + optional weights/value positional
|
|
6518
|
-
when :map then 0 # no args
|
|
6519
6690
|
when :scan then -1 # axis: kwarg (Integer required)
|
|
6520
6691
|
when :sort
|
|
6521
6692
|
# SO.3+ (rev8): :partition algorithm takes (axis, kth) = arity 2
|
|
@@ -6540,12 +6711,16 @@ module MkKernel
|
|
|
6540
6711
|
# reduce: default false (E.8 retire), opt-in via bind_ruby: true
|
|
6541
6712
|
# (used by CF.1 count_equal smoke + future kernel testing)
|
|
6542
6713
|
# sort: default true, opt-out via bind_ruby: false
|
|
6543
|
-
#
|
|
6714
|
+
# scan: bound as <name>_ki, or only privately as <name> (private:)
|
|
6544
6715
|
bind = case k[:kind]
|
|
6545
6716
|
when :reduce then k[:bind_ruby] == true
|
|
6546
6717
|
when :sort then k[:bind_ruby] != false
|
|
6718
|
+
when :scan then ! k[:private]
|
|
6547
6719
|
else true
|
|
6548
6720
|
end
|
|
6721
|
+
if k[:kind] == :scan && k[:private]
|
|
6722
|
+
io.puts %Q[ rb_define_private_method(rb_cCArray, "#{k[:name]}", rb_ca_#{k[:name]}_ki, #{arity});]
|
|
6723
|
+
end
|
|
6549
6724
|
# Smoke kernels (e.g. count_equal) expose their _ki binding only
|
|
6550
6725
|
# in dev builds, gated by CARRAY_DEV_BUILD. See
|
|
6551
6726
|
# devel/PROPOSAL_SMOKE_DEV_BUILD_GATE.md.
|
|
@@ -6558,7 +6733,7 @@ module MkKernel
|
|
|
6558
6733
|
# directly without a Ruby wrapper. This is paired with
|
|
6559
6734
|
# axis_default: :flatten because both express the legacy-compat
|
|
6560
6735
|
# convenience of these specific scan kernels.
|
|
6561
|
-
if k[:kind] == :scan && k[:axis_default] == :flatten
|
|
6736
|
+
if k[:kind] == :scan && k[:axis_default] == :flatten && ! k[:private]
|
|
6562
6737
|
io.puts %Q[ rb_define_method(rb_cCArray, "#{k[:name]}", rb_ca_#{k[:name]}_ki, #{arity});]
|
|
6563
6738
|
end
|
|
6564
6739
|
# Phase E rewire: when this kernel replaces a legacy CArray method,
|
|
@@ -6642,7 +6817,7 @@ MkKernel.reduce :min,
|
|
|
6642
6817
|
# bool acc is u64 (numeric output); cast the boolean8_t
|
|
6643
6818
|
# load to match and avoid a signed/unsigned compare.
|
|
6644
6819
|
bool: "acc = ((uint64_t) v < acc) ? (uint64_t) v : acc",
|
|
6645
|
-
object: 'if (acc == Qundef
|
|
6820
|
+
object: 'if (acc == Qundef || ca_obj_extremum_replaces(v, acc, rb_intern("<"))) acc = v;' },
|
|
6646
6821
|
reduction_kind: :min, # SL.1.2
|
|
6647
6822
|
all_nan_result: :nan,
|
|
6648
6823
|
# CA_FIXLEN: memcmp lexicographic min (byte order == the fixlen sort
|
|
@@ -6655,8 +6830,8 @@ MkKernel.reduce :min,
|
|
|
6655
6830
|
fallback: :raise,
|
|
6656
6831
|
mask_policy: :min_count, # Phase E
|
|
6657
6832
|
# An ORDERABLE Face descends to numeric storage, then the result is
|
|
6658
|
-
# re-lifted into the Face (scalar via
|
|
6659
|
-
#
|
|
6833
|
+
# re-lifted into the Face (scalar via storage_to_element, per-axis via
|
|
6834
|
+
# ca_face_relift_entity). This replaces per-Face lib overrides.
|
|
6660
6835
|
face_gate: :relift,
|
|
6661
6836
|
public_method: true # Phase E: rebind "min" -> rb_ca_min_ki
|
|
6662
6837
|
|
|
@@ -6667,7 +6842,7 @@ MkKernel.reduce :max,
|
|
|
6667
6842
|
# 0/1); the boolean-returning twin is `any` (= bool max).
|
|
6668
6843
|
reduce: { numeric: "acc = (v > acc) ? v : acc",
|
|
6669
6844
|
bool: "acc = ((uint64_t) v > acc) ? (uint64_t) v : acc",
|
|
6670
|
-
object: 'if (acc == Qundef
|
|
6845
|
+
object: 'if (acc == Qundef || ca_obj_extremum_replaces(v, acc, rb_intern(">"))) acc = v;' },
|
|
6671
6846
|
reduction_kind: :max, # SL.1.2
|
|
6672
6847
|
all_nan_result: :nan,
|
|
6673
6848
|
# CA_FIXLEN: memcmp lexicographic max (byte order == the fixlen sort order).
|
|
@@ -6680,20 +6855,6 @@ MkKernel.reduce :max,
|
|
|
6680
6855
|
face_gate: :relift,
|
|
6681
6856
|
public_method: true # Phase E: rebind "max" -> rb_ca_max_ki
|
|
6682
6857
|
|
|
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
6858
|
# all / any reduce: bool input -> bool output, axis-aware. Replaces the
|
|
6698
6859
|
# flat-only hand-written rb_ca_all_equal_p / rb_ca_any_equal_p /
|
|
6699
6860
|
# all_close? / any_close? / all_equiv? / any_equiv? in carray_stat.c
|
|
@@ -6710,6 +6871,15 @@ MkKernel.reduce :count,
|
|
|
6710
6871
|
# modern CPUs. For dramatically lopsided inputs (= mostly-false `all`
|
|
6711
6872
|
# or mostly-true `any`) a profile-driven early-break variant can be
|
|
6712
6873
|
# added later as a separate kernel.
|
|
6874
|
+
MkKernel.header_block <<~C
|
|
6875
|
+
/* The square root of a variance: an ε-negative from rounding becomes 0,
|
|
6876
|
+
a NaN stays NaN (fmax(NaN, 0.0) is 0.0). */
|
|
6877
|
+
#ifndef CARRAY_SQRT_CLAMP_DEFINED
|
|
6878
|
+
#define CARRAY_SQRT_CLAMP_DEFINED
|
|
6879
|
+
static inline double ca_sqrt_clamp (double v) { return sqrt(v < 0.0 ? 0.0 : v); }
|
|
6880
|
+
#endif
|
|
6881
|
+
C
|
|
6882
|
+
|
|
6713
6883
|
MkKernel.header_block <<~C
|
|
6714
6884
|
/* BOOL2VAL: bool -> Ruby (Qtrue/Qfalse). Used as ruby_scalar wrapper
|
|
6715
6885
|
for the all/any flat-reduction Ruby surface so `a.all` / `a.any`
|
|
@@ -6893,7 +7063,7 @@ MkKernel.reduce :minmax,
|
|
|
6893
7063
|
# CA_OBJECT minmax via Qundef sentinel + first-cell-init.
|
|
6894
7064
|
# One sentinel guards both lo + hi (they go Qundef
|
|
6895
7065
|
# together on init, get set together on first reduce).
|
|
6896
|
-
object: 'if (lo == Qundef) { lo = v; hi = v; } else { if (
|
|
7066
|
+
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
7067
|
outputs: 2,
|
|
6898
7068
|
all_nan_result: :nan,
|
|
6899
7069
|
finish: { min: "lo", max: "hi" },
|
|
@@ -6904,6 +7074,7 @@ MkKernel.reduce :minmax,
|
|
|
6904
7074
|
ruby_scalar: :auto,
|
|
6905
7075
|
fallback: :raise,
|
|
6906
7076
|
mask_policy: :min_count, # FM.1.5: match existing min / max behaviour
|
|
7077
|
+
face_gate: :relift,
|
|
6907
7078
|
public_method: :minmax # bind public name `minmax` -> rb_ca_minmax_ki
|
|
6908
7079
|
# (= surface-name override, no actual legacy)
|
|
6909
7080
|
|
|
@@ -6925,14 +7096,19 @@ MkKernel.reduce :minmax,
|
|
|
6925
7096
|
# replace the legacy `min_addr` / `max_addr` retired in E.7 stat_proc
|
|
6926
7097
|
# retire (commit f5c7ecd). 3.0 breaking: name change from `*_addr` to
|
|
6927
7098
|
# `*_index` is intentional.
|
|
7099
|
+
#
|
|
7100
|
+
# best_i starts at -1 (no cell taken yet), so the first unmasked non-NaN
|
|
7101
|
+
# cell is taken even when it equals the starting limit (an all-+Infinity
|
|
7102
|
+
# or all-INT_MAX fiber). `v == v` keeps a NaN from being taken first; a
|
|
7103
|
+
# fiber of NaN only is left to the all-NaN fixup (UNDEF).
|
|
6928
7104
|
|
|
6929
7105
|
MkKernel.reduce :argmin,
|
|
6930
7106
|
state: { best_v: :load_type, best_i: :int64_t },
|
|
6931
7107
|
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 (
|
|
7108
|
+
best_i: "-1" },
|
|
7109
|
+
reduce: { numeric: "if (v < best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7110
|
+
bool: "if (v < best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7111
|
+
object: 'if (!CA_OBJ_ISNAN(v) && (best_v == Qundef || ca_obj_cmp(v, best_v) < 0)) { best_v = v; best_i = idx; }' },
|
|
6936
7112
|
finish: "best_i",
|
|
6937
7113
|
# CA_FIXLEN: position of the memcmp-lexicographic min (first-wins on ties).
|
|
6938
7114
|
fixlen: :argmin,
|
|
@@ -6950,10 +7126,10 @@ MkKernel.reduce :argmin,
|
|
|
6950
7126
|
MkKernel.reduce :argmax,
|
|
6951
7127
|
state: { best_v: :load_type, best_i: :int64_t },
|
|
6952
7128
|
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 (
|
|
7129
|
+
best_i: "-1" },
|
|
7130
|
+
reduce: { numeric: "if (v > best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7131
|
+
bool: "if (v > best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7132
|
+
object: 'if (!CA_OBJ_ISNAN(v) && (best_v == Qundef || ca_obj_cmp(v, best_v) > 0)) { best_v = v; best_i = idx; }' },
|
|
6957
7133
|
finish: "best_i",
|
|
6958
7134
|
# CA_FIXLEN: position of the memcmp-lexicographic max (first-wins on ties).
|
|
6959
7135
|
fixlen: :argmax,
|
|
@@ -6995,10 +7171,10 @@ MkKernel.reduce :argmax,
|
|
|
6995
7171
|
MkKernel.reduce :argmin_addr,
|
|
6996
7172
|
state: { best_v: :load_type, best_i: :int64_t },
|
|
6997
7173
|
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 (
|
|
7174
|
+
best_i: "-1" },
|
|
7175
|
+
reduce: { numeric: "if (v < best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7176
|
+
bool: "if (v < best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7177
|
+
object: 'if (!CA_OBJ_ISNAN(v) && (best_v == Qundef || ca_obj_cmp(v, best_v) < 0)) { best_v = v; best_i = idx; }' },
|
|
7002
7178
|
finish: "best_i",
|
|
7003
7179
|
source: MkKernel::ALL_NUMERIC + [:bool, :object],
|
|
7004
7180
|
output: :i64,
|
|
@@ -7013,10 +7189,10 @@ MkKernel.reduce :argmin_addr,
|
|
|
7013
7189
|
MkKernel.reduce :argmax_addr,
|
|
7014
7190
|
state: { best_v: :load_type, best_i: :int64_t },
|
|
7015
7191
|
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 (
|
|
7192
|
+
best_i: "-1" },
|
|
7193
|
+
reduce: { numeric: "if (v > best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7194
|
+
bool: "if (v > best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7195
|
+
object: 'if (!CA_OBJ_ISNAN(v) && (best_v == Qundef || ca_obj_cmp(v, best_v) > 0)) { best_v = v; best_i = idx; }' },
|
|
7020
7196
|
finish: "best_i",
|
|
7021
7197
|
source: MkKernel::ALL_NUMERIC + [:bool, :object],
|
|
7022
7198
|
output: :i64,
|
|
@@ -7028,36 +7204,6 @@ MkKernel.reduce :argmax_addr,
|
|
|
7028
7204
|
all_nan_result: :undef,
|
|
7029
7205
|
public_method: :max_addr
|
|
7030
7206
|
|
|
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
7207
|
# accumulate -- sum that preserves input data_type (legacy rb_ca_accum,
|
|
7062
7208
|
# retired in E.7). Same machinery as sum_ki but output: :preserve so
|
|
7063
7209
|
# int8 stays int8 (will overflow on long arrays = legacy behaviour).
|
|
@@ -7153,10 +7299,11 @@ MkKernel.reduce :count_false,
|
|
|
7153
7299
|
|
|
7154
7300
|
MkKernel.reduce :count_equal,
|
|
7155
7301
|
init: "0",
|
|
7156
|
-
# The object lane compares with
|
|
7157
|
-
# on two VALUEs would ask whether they are
|
|
7302
|
+
# The object lane compares with ca_obj_equal (= Ruby ==, a NaN equal to
|
|
7303
|
+
# nothing), not the C ==, which on two VALUEs would ask whether they are
|
|
7304
|
+
# the same object.
|
|
7158
7305
|
reduce: { numeric: "if (v == value_arg) acc += 1",
|
|
7159
|
-
object: "if (
|
|
7306
|
+
object: "if (ca_obj_equal(v, value_arg)) acc += 1" },
|
|
7160
7307
|
reduction_kind: :plus, # SL.1.4 (conditional predication; clang predicates safely under reduction(+:acc))
|
|
7161
7308
|
source: MkKernel::ALL_NUMERIC + [:object, :fixlen],
|
|
7162
7309
|
fixlen: :count_equal, # bespoke memcmp walk (a blob has no scalar C type)
|
|
@@ -7210,7 +7357,7 @@ MkKernel.reduce :wsum,
|
|
|
7210
7357
|
shape: :rev5_strict, mask: :overlay },
|
|
7211
7358
|
mask_policy: :min_count,
|
|
7212
7359
|
identity_on_empty: true, # ERI.2: empty / all-masked -> 0.0 (weighted sum identity)
|
|
7213
|
-
object_escape: :
|
|
7360
|
+
object_escape: :wsum_object, # CA_OBJECT -> (w*self).sum (runtime.rb)
|
|
7214
7361
|
bind_ruby: true,
|
|
7215
7362
|
public_method: true
|
|
7216
7363
|
|
|
@@ -7227,7 +7374,7 @@ MkKernel.reduce :wsum,
|
|
|
7227
7374
|
# - min_count semantic "max masked" -> "min valid required" (Phase E)
|
|
7228
7375
|
# - complex (CMPLX64/128) dropped (= ALL_NUMERIC + :raise)
|
|
7229
7376
|
# - per-axis support gained
|
|
7230
|
-
# CA_OBJECT is handled via object_escape (runtime.rb
|
|
7377
|
+
# CA_OBJECT is handled via object_escape (runtime.rb wmean_object), not
|
|
7231
7378
|
# the numeric kernel: weight-coerce vs object arithmetic is cleaner as a
|
|
7232
7379
|
# (w*self).sum / Σw composition than a dedicated :object kernel branch.
|
|
7233
7380
|
|
|
@@ -7244,73 +7391,10 @@ MkKernel.reduce :wmean,
|
|
|
7244
7391
|
array_arg: { name: :weights, data_type: :promote,
|
|
7245
7392
|
shape: :rev5_strict, mask: :overlay },
|
|
7246
7393
|
mask_policy: :min_count,
|
|
7247
|
-
object_escape: :
|
|
7394
|
+
object_escape: :wmean_object, # CA_OBJECT -> weighted mean via runtime.rb
|
|
7248
7395
|
bind_ruby: true,
|
|
7249
7396
|
public_method: true
|
|
7250
7397
|
|
|
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
7398
|
# ---- scan kernels (cumulative / prefix scan along one axis) ----------
|
|
7315
7399
|
|
|
7316
7400
|
# cumsum / cumprod: data_type-conditional output (numeric -> f64 widening,
|
|
@@ -7332,6 +7416,18 @@ MkKernel.scan :cumsum,
|
|
|
7332
7416
|
fallback: :raise,
|
|
7333
7417
|
axis_default: :flatten
|
|
7334
7418
|
|
|
7419
|
+
# The running sum in the source's own integer type, added without a range
|
|
7420
|
+
# check (int64 wraps, as accumulate does). CATimedelta#cumsum reads it
|
|
7421
|
+
# for an exact running sum of durations.
|
|
7422
|
+
MkKernel.scan :cumsum_in_own_type,
|
|
7423
|
+
source: MkKernel::INT_DTYPES,
|
|
7424
|
+
output: :preserve,
|
|
7425
|
+
init: "0",
|
|
7426
|
+
step: "acc += v; r = acc",
|
|
7427
|
+
fallback: :raise,
|
|
7428
|
+
axis_default: :flatten,
|
|
7429
|
+
private: true
|
|
7430
|
+
|
|
7335
7431
|
MkKernel.scan :cumprod,
|
|
7336
7432
|
source: MkKernel::ALL_NUMERIC + MkKernel::CMPLX_DTYPES + [:bool, :object],
|
|
7337
7433
|
output: { numeric: :f64, complex: :cmplx128, bool: :u64, object: :object },
|
|
@@ -7360,13 +7456,14 @@ MkKernel.scan :cumprod,
|
|
|
7360
7456
|
# so the f32 lane needs no narrowing. Integer and boolean have no NaN
|
|
7361
7457
|
# and keep the limit init and the plain compare.
|
|
7362
7458
|
MkKernel.scan :cummax,
|
|
7459
|
+
face_gate: :relift,
|
|
7363
7460
|
source: MkKernel::ALL_NUMERIC + [:bool, :object],
|
|
7364
7461
|
output: { bool: :u64, default: :preserve },
|
|
7365
7462
|
init: { float: "NAN", numeric: "T_LIMIT_LO", bool: "T_LIMIT_LO", object: "Qnil" },
|
|
7366
7463
|
step: { float: "acc = fmax(acc, v); r = acc",
|
|
7367
7464
|
numeric: "if (v > acc) acc = v; r = acc",
|
|
7368
7465
|
bool: "if ((uint64_t) v > acc) acc = v; r = acc",
|
|
7369
|
-
object: 'if (acc == Qnil
|
|
7466
|
+
object: 'if (acc == Qnil || ca_obj_extremum_replaces(v, acc, rb_intern(">"))) acc = v; r = acc' },
|
|
7370
7467
|
fallback: :raise,
|
|
7371
7468
|
axis_default: :flatten,
|
|
7372
7469
|
empty: :undef
|
|
@@ -7379,26 +7476,30 @@ MkKernel.scan :cummax,
|
|
|
7379
7476
|
# so the f32 lane needs no narrowing. Integer and boolean have no NaN
|
|
7380
7477
|
# and keep the limit init and the plain compare.
|
|
7381
7478
|
MkKernel.scan :cummin,
|
|
7479
|
+
face_gate: :relift,
|
|
7382
7480
|
source: MkKernel::ALL_NUMERIC + [:bool, :object],
|
|
7383
7481
|
output: { bool: :u64, default: :preserve },
|
|
7384
7482
|
init: { float: "NAN", numeric: "T_LIMIT_HI", bool: "T_LIMIT_HI", object: "Qnil" },
|
|
7385
7483
|
step: { float: "acc = fmin(acc, v); r = acc",
|
|
7386
7484
|
numeric: "if (v < acc) acc = v; r = acc",
|
|
7387
7485
|
bool: "if ((uint64_t) v < acc) acc = v; r = acc",
|
|
7388
|
-
object: 'if (acc == Qnil
|
|
7486
|
+
object: 'if (acc == Qnil || ca_obj_extremum_replaces(v, acc, rb_intern("<"))) acc = v; r = acc' },
|
|
7389
7487
|
fallback: :raise,
|
|
7390
7488
|
axis_default: :flatten,
|
|
7391
7489
|
empty: :undef
|
|
7392
7490
|
|
|
7393
7491
|
# cumcount: running count of unmasked cells. Output data_type = int64.
|
|
7394
7492
|
# `(void) v` silences unused-variable warning for the unmasked path
|
|
7395
|
-
# (compiler DCEs the v load when STEP doesn't reference it).
|
|
7493
|
+
# (compiler DCEs the v load when STEP doesn't reference it). The count
|
|
7494
|
+
# reads only the mask, so a data type outside source: (object, complex,
|
|
7495
|
+
# boolean, fixlen, a Face) is counted off the mask without converting its
|
|
7496
|
+
# values (fallback: :mask_count).
|
|
7396
7497
|
MkKernel.scan :cumcount,
|
|
7397
7498
|
source: MkKernel::ALL_NUMERIC,
|
|
7398
7499
|
output: :i64,
|
|
7399
7500
|
init: "0",
|
|
7400
7501
|
step: "(void) v; r = ++acc",
|
|
7401
|
-
fallback: :
|
|
7502
|
+
fallback: :mask_count,
|
|
7402
7503
|
axis_default: :flatten
|
|
7403
7504
|
|
|
7404
7505
|
# uniq_scan: per-axis adjacent-compare scan returning a boolean
|
|
@@ -7446,7 +7547,7 @@ MkKernel.sort :sort_index,
|
|
|
7446
7547
|
nan_policy: :end,
|
|
7447
7548
|
fallback: :raise,
|
|
7448
7549
|
mask_self: :sentinel, # masked cells sort to masked_position: :first/:last (default :last)
|
|
7449
|
-
public_method: :sort_index, # binds public sort_index(axis:
|
|
7550
|
+
public_method: :sort_index, # binds public sort_index(axis: nil, masked_position: :last) via _kw trampoline
|
|
7450
7551
|
c_callable: true # extern linkage for carray_order.c (nlargest / order family)
|
|
7451
7552
|
|
|
7452
7553
|
# sort_addr: per-axis argsort returning view-flat addresses (=
|
|
@@ -7454,7 +7555,7 @@ MkKernel.sort :sort_index,
|
|
|
7454
7555
|
# sort_index but the kernel writes view-flat addresses instead of
|
|
7455
7556
|
# fiber-local indices, so the output can feed directly into
|
|
7456
7557
|
# ca_remap_new as a CARemap.idx. Internal-only (bind_ruby: false):
|
|
7457
|
-
# used by `a.sort(axis: k)` at the C level via
|
|
7558
|
+
# used by `a.sort(axis: k)` at the C level via rb_ca_sort_addr_ki_*_mp.
|
|
7458
7559
|
# rev3 of PROPOSAL_SORT_AXIS defers `sort_addr(axis: k)` as a public
|
|
7459
7560
|
# method to a future phase; here we just need the kernel machinery.
|
|
7460
7561
|
MkKernel.sort :sort_addr,
|
|
@@ -7481,7 +7582,7 @@ MkKernel.sort :partition_index,
|
|
|
7481
7582
|
fallback: :raise,
|
|
7482
7583
|
algorithm: :partition,
|
|
7483
7584
|
mask_self: :sentinel, # masked cells sort to masked_position: :first/:last (default :last)
|
|
7484
|
-
public_method: :partition_index, # binds partition_index(kth, axis:
|
|
7585
|
+
public_method: :partition_index, # binds partition_index(kth, axis: nil, masked_position: :last) via _kw trampoline
|
|
7485
7586
|
c_callable: true # extern linkage for carray_order.c (nlargest / nsmallest)
|
|
7486
7587
|
|
|
7487
7588
|
# partition_addr: view-flat-address variant of partition_index, parallel
|
|
@@ -7523,7 +7624,7 @@ MkKernel.sort :rank_index,
|
|
|
7523
7624
|
fallback: :raise,
|
|
7524
7625
|
algorithm: :rank,
|
|
7525
7626
|
mask_self: :skip,
|
|
7526
|
-
public_method: :rank_index, # binds public rank_index(axis:
|
|
7627
|
+
public_method: :rank_index, # binds public rank_index(axis: nil) via _kw trampoline
|
|
7527
7628
|
c_callable: true # extern linkage for carray_order.c (order method)
|
|
7528
7629
|
|
|
7529
7630
|
# ---- search family ------------------------------------------------------
|
|
@@ -7560,7 +7661,7 @@ MkKernel.search :find_value_index,
|
|
|
7560
7661
|
result = (ca_size_t) -1;
|
|
7561
7662
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7562
7663
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7563
|
-
if (
|
|
7664
|
+
if ( ca_obj_equal(v, query_val) ) { result = i; break; }
|
|
7564
7665
|
}
|
|
7565
7666
|
C
|
|
7566
7667
|
fixlen: <<~C,
|
|
@@ -7602,7 +7703,6 @@ MkKernel.search :bsearch,
|
|
|
7602
7703
|
while ( lo < hi ) {
|
|
7603
7704
|
ca_size_t mid = lo + (hi - lo) / 2;
|
|
7604
7705
|
T_LOAD v = *(T_LOAD *)(slab_ptr + mid * slab_stride);
|
|
7605
|
-
if ( isnan((double) v) ) { hi = mid; continue; }
|
|
7606
7706
|
if ( v < query_val ) lo = mid + 1;
|
|
7607
7707
|
else if ( v > query_val ) hi = mid;
|
|
7608
7708
|
else { result = mid; break; }
|
|
@@ -7611,36 +7711,30 @@ MkKernel.search :bsearch,
|
|
|
7611
7711
|
C
|
|
7612
7712
|
float: <<~C,
|
|
7613
7713
|
result = (ca_size_t) -1;
|
|
7614
|
-
if ( ! isnan((double) query_val) ) {
|
|
7714
|
+
if ( ! isnan((double) query_val) ) { /* a NaN query matches nothing */
|
|
7615
7715
|
ca_size_t lo = 0;
|
|
7616
7716
|
ca_size_t hi = slab_n;
|
|
7617
7717
|
while ( lo < hi ) {
|
|
7618
7718
|
ca_size_t mid = lo + (hi - lo) / 2;
|
|
7619
7719
|
T_LOAD v = *(T_LOAD *)(slab_ptr + mid * slab_stride);
|
|
7620
|
-
|
|
7621
|
-
if (
|
|
7622
|
-
else if (
|
|
7720
|
+
int c = CA_FLOAT_ORDER_CMP(v, query_val);
|
|
7721
|
+
if ( c < 0 ) lo = mid + 1;
|
|
7722
|
+
else if ( c > 0 ) hi = mid;
|
|
7623
7723
|
else { result = mid; break; }
|
|
7624
7724
|
}
|
|
7625
7725
|
}
|
|
7626
7726
|
C
|
|
7627
7727
|
object: <<~C,
|
|
7628
|
-
/* CA_OBJECT bsearch:
|
|
7629
|
-
slab is sorted by
|
|
7630
|
-
|
|
7631
|
-
a Float-cell NaN would raise from <=>). */
|
|
7728
|
+
/* CA_OBJECT bsearch: the object order (ca_obj_order_cmp), which the
|
|
7729
|
+
slab is assumed to be sorted by; a NaN query matches nothing, as in
|
|
7730
|
+
float. */
|
|
7632
7731
|
result = (ca_size_t) -1;
|
|
7633
7732
|
ca_size_t lo = 0;
|
|
7634
|
-
ca_size_t hi = slab_n;
|
|
7733
|
+
ca_size_t hi = CA_OBJ_ISNAN(query_val) ? 0 : slab_n; /* NaN matches nothing */
|
|
7635
7734
|
while ( lo < hi ) {
|
|
7636
7735
|
ca_size_t mid = lo + (hi - lo) / 2;
|
|
7637
7736
|
T_LOAD v = *(T_LOAD *)(slab_ptr + mid * slab_stride);
|
|
7638
|
-
|
|
7639
|
-
if ( NIL_P(r) ) {
|
|
7640
|
-
rb_raise(rb_eArgError, "bsearch: comparison of %s with %s failed",
|
|
7641
|
-
rb_obj_classname(v), rb_obj_classname(query_val));
|
|
7642
|
-
}
|
|
7643
|
-
int c = NUM2INT(r);
|
|
7737
|
+
int c = ca_obj_order_cmp(v, query_val);
|
|
7644
7738
|
if ( c < 0 ) lo = mid + 1;
|
|
7645
7739
|
else if ( c > 0 ) hi = mid;
|
|
7646
7740
|
else { result = mid; break; }
|
|
@@ -7675,7 +7769,7 @@ MkKernel.search :bsearch,
|
|
|
7675
7769
|
#
|
|
7676
7770
|
# Per-data_type body: int uses exact `v == query_val` (eps ignored); float
|
|
7677
7771
|
# uses `fabs(v - query_val) <= query_eps` (eps_default = FLT_EPSILON*|val|
|
|
7678
|
-
# or DBL_EPSILON*|val|); object uses
|
|
7772
|
+
# or DBL_EPSILON*|val|); object uses ca_obj_equal (eps ignored); fixlen uses
|
|
7679
7773
|
# memcmp exact match (eps ignored). Sole search implementation: the
|
|
7680
7774
|
# no-axis (flat) surface routes here via flatten + axis 0
|
|
7681
7775
|
# (PROPOSAL_SEARCH_SEMANTICS_UNIFY S2/S3; the legacy flat scan was removed).
|
|
@@ -7697,18 +7791,18 @@ MkKernel.search :search,
|
|
|
7697
7791
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7698
7792
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7699
7793
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7700
|
-
if ( fabs((double)(v - query_val)) <= query_eps ) { result = i; break; }
|
|
7794
|
+
if ( v == query_val || fabs((double)(v - query_val)) <= query_eps ) { result = i; break; }
|
|
7701
7795
|
}
|
|
7702
7796
|
C
|
|
7703
7797
|
object: <<~C,
|
|
7704
|
-
/* CA_OBJECT search: exact equality via
|
|
7798
|
+
/* CA_OBJECT search: exact equality via ca_obj_equal. eps is silently
|
|
7705
7799
|
ignored (= no meaningful tolerance on arbitrary Ruby objects). */
|
|
7706
7800
|
result = (ca_size_t) -1;
|
|
7707
7801
|
(void) query_eps;
|
|
7708
7802
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7709
7803
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7710
7804
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7711
|
-
if (
|
|
7805
|
+
if ( ca_obj_equal(v, query_val) ) { result = i; break; }
|
|
7712
7806
|
}
|
|
7713
7807
|
C
|
|
7714
7808
|
fixlen: <<~C,
|
|
@@ -7747,13 +7841,16 @@ MkKernel.search :search_nearest,
|
|
|
7747
7841
|
output: :ca_size,
|
|
7748
7842
|
body: {
|
|
7749
7843
|
int: <<~C,
|
|
7844
|
+
/* The distance is exact in uint64: a double loses the difference
|
|
7845
|
+
between neighbouring int64 values above 2**53. */
|
|
7750
7846
|
result = (ca_size_t) -1;
|
|
7751
|
-
|
|
7847
|
+
uint64_t best_dist = 0;
|
|
7752
7848
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7753
7849
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7754
7850
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7755
|
-
|
|
7756
|
-
|
|
7851
|
+
uint64_t dist = ( v >= query_val ) ? (uint64_t) v - (uint64_t) query_val
|
|
7852
|
+
: (uint64_t) query_val - (uint64_t) v;
|
|
7853
|
+
if ( result == (ca_size_t) -1 || dist < best_dist ) { best_dist = dist; result = i; }
|
|
7757
7854
|
}
|
|
7758
7855
|
(void) query_eps; /* unused: search_nearest has no eps semantics */
|
|
7759
7856
|
C
|
|
@@ -7763,8 +7860,11 @@ MkKernel.search :search_nearest,
|
|
|
7763
7860
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7764
7861
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7765
7862
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7766
|
-
|
|
7767
|
-
|
|
7863
|
+
/* An equal value is at distance 0 even when both are infinite
|
|
7864
|
+
(Inf - Inf is NaN); a NaN distance never wins. */
|
|
7865
|
+
double dist = ( v == query_val ) ? 0.0 : fabs((double)v - (double)query_val);
|
|
7866
|
+
if ( dist != dist ) continue;
|
|
7867
|
+
if ( result == (ca_size_t) -1 || dist < best_dist ) { best_dist = dist; result = i; }
|
|
7768
7868
|
}
|
|
7769
7869
|
(void) query_eps; /* unused: search_nearest has no eps semantics */
|
|
7770
7870
|
C
|
|
@@ -7783,20 +7883,22 @@ MkKernel.search :search_nearest,
|
|
|
7783
7883
|
int nearest_by_distance = rb_respond_to(query_val, nearest_id);
|
|
7784
7884
|
if ( ! nearest_by_distance && ! rb_obj_is_kind_of(query_val, rb_cNumeric) ) {
|
|
7785
7885
|
rb_raise(rb_eCADataTypeError,
|
|
7786
|
-
"
|
|
7886
|
+
"%s: nearest needs a distance, and %s is neither a "
|
|
7787
7887
|
"number nor answers #distance (define one on the stored "
|
|
7788
7888
|
"objects, or use search / bsearch for an exact match)",
|
|
7789
|
-
rb_obj_classname(query_val));
|
|
7889
|
+
ca_calling_method_name(), rb_obj_classname(query_val));
|
|
7790
7890
|
}
|
|
7791
7891
|
result = (ca_size_t) -1;
|
|
7792
7892
|
VALUE best = Qnil;
|
|
7793
7893
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7794
7894
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7795
7895
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7796
|
-
VALUE dist =
|
|
7896
|
+
VALUE dist = ca_obj_equal(v, query_val) ? INT2FIX(0)
|
|
7897
|
+
: nearest_by_distance
|
|
7797
7898
|
? rb_funcall(query_val, nearest_id, 1, v)
|
|
7798
7899
|
: rb_funcall(rb_funcall(query_val, '-', 1, v),
|
|
7799
7900
|
rb_intern("abs"), 0);
|
|
7901
|
+
if ( CA_OBJ_ISNAN(dist) ) continue; /* a NaN is no distance, as in float */
|
|
7800
7902
|
if ( NIL_P(best) || RTEST(rb_funcall(dist, rb_intern("<"), 1, best)) ) {
|
|
7801
7903
|
best = dist; result = i;
|
|
7802
7904
|
}
|
|
@@ -7834,7 +7936,6 @@ MkKernel.search :bsearch_addr,
|
|
|
7834
7936
|
while ( lo < hi ) {
|
|
7835
7937
|
ca_size_t mid = lo + (hi - lo) / 2;
|
|
7836
7938
|
T_LOAD v = *(T_LOAD *)(slab_ptr + mid * slab_stride);
|
|
7837
|
-
if ( isnan((double) v) ) { hi = mid; continue; }
|
|
7838
7939
|
if ( v < query_val ) lo = mid + 1;
|
|
7839
7940
|
else if ( v > query_val ) hi = mid;
|
|
7840
7941
|
else { result = mid; break; }
|
|
@@ -7843,15 +7944,15 @@ MkKernel.search :bsearch_addr,
|
|
|
7843
7944
|
C
|
|
7844
7945
|
float: <<~C,
|
|
7845
7946
|
result = (ca_size_t) -1;
|
|
7846
|
-
if ( ! isnan((double) query_val) ) {
|
|
7947
|
+
if ( ! isnan((double) query_val) ) { /* a NaN query matches nothing */
|
|
7847
7948
|
ca_size_t lo = 0;
|
|
7848
7949
|
ca_size_t hi = slab_n;
|
|
7849
7950
|
while ( lo < hi ) {
|
|
7850
7951
|
ca_size_t mid = lo + (hi - lo) / 2;
|
|
7851
7952
|
T_LOAD v = *(T_LOAD *)(slab_ptr + mid * slab_stride);
|
|
7852
|
-
|
|
7853
|
-
if (
|
|
7854
|
-
else if (
|
|
7953
|
+
int c = CA_FLOAT_ORDER_CMP(v, query_val);
|
|
7954
|
+
if ( c < 0 ) lo = mid + 1;
|
|
7955
|
+
else if ( c > 0 ) hi = mid;
|
|
7855
7956
|
else { result = mid; break; }
|
|
7856
7957
|
}
|
|
7857
7958
|
}
|
|
@@ -7859,16 +7960,11 @@ MkKernel.search :bsearch_addr,
|
|
|
7859
7960
|
object: <<~C,
|
|
7860
7961
|
result = (ca_size_t) -1;
|
|
7861
7962
|
ca_size_t lo = 0;
|
|
7862
|
-
ca_size_t hi = slab_n;
|
|
7963
|
+
ca_size_t hi = CA_OBJ_ISNAN(query_val) ? 0 : slab_n; /* NaN matches nothing */
|
|
7863
7964
|
while ( lo < hi ) {
|
|
7864
7965
|
ca_size_t mid = lo + (hi - lo) / 2;
|
|
7865
7966
|
T_LOAD v = *(T_LOAD *)(slab_ptr + mid * slab_stride);
|
|
7866
|
-
|
|
7867
|
-
if ( NIL_P(r) ) {
|
|
7868
|
-
rb_raise(rb_eArgError, "bsearch_addr: comparison of %s with %s failed",
|
|
7869
|
-
rb_obj_classname(v), rb_obj_classname(query_val));
|
|
7870
|
-
}
|
|
7871
|
-
int c = NUM2INT(r);
|
|
7967
|
+
int c = ca_obj_order_cmp(v, query_val);
|
|
7872
7968
|
if ( c < 0 ) lo = mid + 1;
|
|
7873
7969
|
else if ( c > 0 ) hi = mid;
|
|
7874
7970
|
else { result = mid; break; }
|
|
@@ -7913,7 +8009,7 @@ MkKernel.search :search_addr,
|
|
|
7913
8009
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7914
8010
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7915
8011
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7916
|
-
if ( fabs((double)v - (double)query_val) <= query_eps ) { result = i; break; }
|
|
8012
|
+
if ( v == query_val || fabs((double)v - (double)query_val) <= query_eps ) { result = i; break; }
|
|
7917
8013
|
}
|
|
7918
8014
|
C
|
|
7919
8015
|
object: <<~C,
|
|
@@ -7922,7 +8018,7 @@ MkKernel.search :search_addr,
|
|
|
7922
8018
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7923
8019
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7924
8020
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7925
|
-
if (
|
|
8021
|
+
if ( ca_obj_equal(v, query_val) ) { result = i; break; }
|
|
7926
8022
|
}
|
|
7927
8023
|
C
|
|
7928
8024
|
fixlen: <<~C,
|
|
@@ -7948,13 +8044,16 @@ MkKernel.search :search_nearest_addr,
|
|
|
7948
8044
|
output: :ca_size,
|
|
7949
8045
|
body: {
|
|
7950
8046
|
int: <<~C,
|
|
8047
|
+
/* The distance is exact in uint64: a double loses the difference
|
|
8048
|
+
between neighbouring int64 values above 2**53. */
|
|
7951
8049
|
result = (ca_size_t) -1;
|
|
7952
|
-
|
|
8050
|
+
uint64_t best_dist = 0;
|
|
7953
8051
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7954
8052
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7955
8053
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7956
|
-
|
|
7957
|
-
|
|
8054
|
+
uint64_t dist = ( v >= query_val ) ? (uint64_t) v - (uint64_t) query_val
|
|
8055
|
+
: (uint64_t) query_val - (uint64_t) v;
|
|
8056
|
+
if ( result == (ca_size_t) -1 || dist < best_dist ) { best_dist = dist; result = i; }
|
|
7958
8057
|
}
|
|
7959
8058
|
(void) query_eps;
|
|
7960
8059
|
C
|
|
@@ -7964,8 +8063,11 @@ MkKernel.search :search_nearest_addr,
|
|
|
7964
8063
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7965
8064
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7966
8065
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7967
|
-
|
|
7968
|
-
|
|
8066
|
+
/* An equal value is at distance 0 even when both are infinite
|
|
8067
|
+
(Inf - Inf is NaN); a NaN distance never wins. */
|
|
8068
|
+
double dist = ( v == query_val ) ? 0.0 : fabs((double)v - (double)query_val);
|
|
8069
|
+
if ( dist != dist ) continue;
|
|
8070
|
+
if ( result == (ca_size_t) -1 || dist < best_dist ) { best_dist = dist; result = i; }
|
|
7969
8071
|
}
|
|
7970
8072
|
(void) query_eps;
|
|
7971
8073
|
C
|
|
@@ -7984,20 +8086,22 @@ MkKernel.search :search_nearest_addr,
|
|
|
7984
8086
|
int nearest_by_distance = rb_respond_to(query_val, nearest_id);
|
|
7985
8087
|
if ( ! nearest_by_distance && ! rb_obj_is_kind_of(query_val, rb_cNumeric) ) {
|
|
7986
8088
|
rb_raise(rb_eCADataTypeError,
|
|
7987
|
-
"
|
|
8089
|
+
"%s: nearest needs a distance, and %s is neither a "
|
|
7988
8090
|
"number nor answers #distance (define one on the stored "
|
|
7989
8091
|
"objects, or use search / bsearch for an exact match)",
|
|
7990
|
-
rb_obj_classname(query_val));
|
|
8092
|
+
ca_calling_method_name(), rb_obj_classname(query_val));
|
|
7991
8093
|
}
|
|
7992
8094
|
result = (ca_size_t) -1;
|
|
7993
8095
|
VALUE best = Qnil;
|
|
7994
8096
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7995
8097
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7996
8098
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7997
|
-
VALUE dist =
|
|
8099
|
+
VALUE dist = ca_obj_equal(v, query_val) ? INT2FIX(0)
|
|
8100
|
+
: nearest_by_distance
|
|
7998
8101
|
? rb_funcall(query_val, nearest_id, 1, v)
|
|
7999
8102
|
: rb_funcall(rb_funcall(query_val, '-', 1, v),
|
|
8000
8103
|
rb_intern("abs"), 0);
|
|
8104
|
+
if ( CA_OBJ_ISNAN(dist) ) continue; /* a NaN is no distance, as in float */
|
|
8001
8105
|
if ( NIL_P(best) || RTEST(rb_funcall(dist, rb_intern("<"), 1, best)) ) {
|
|
8002
8106
|
best = dist; result = i;
|
|
8003
8107
|
}
|
|
@@ -8043,11 +8147,14 @@ MkKernel.search :linear_section_binary,
|
|
|
8043
8147
|
no_match: "NAN",
|
|
8044
8148
|
no_match_check: "isnan(op[0])",
|
|
8045
8149
|
mask_self: :raise,
|
|
8046
|
-
mask_query: :undef,
|
|
8047
8150
|
body: <<~C,
|
|
8048
8151
|
result = NAN;
|
|
8049
8152
|
if ( slab_n <= 1 ) {
|
|
8050
|
-
|
|
8153
|
+
/* A one-point axis has no segment: only the point itself is in
|
|
8154
|
+
* range, at 0. An empty one has nothing in range. */
|
|
8155
|
+
if ( slab_n == 1 && query_val == *(double *)slab_ptr ) {
|
|
8156
|
+
result = 0.0;
|
|
8157
|
+
}
|
|
8051
8158
|
}
|
|
8052
8159
|
else {
|
|
8053
8160
|
double y0 = *(double *)(slab_ptr + 0 * slab_stride);
|
|
@@ -8131,11 +8238,14 @@ MkKernel.search :linear_section_linear,
|
|
|
8131
8238
|
no_match: "NAN",
|
|
8132
8239
|
no_match_check: "isnan(op[0])",
|
|
8133
8240
|
mask_self: :raise,
|
|
8134
|
-
mask_query: :undef,
|
|
8135
8241
|
body: <<~C,
|
|
8136
8242
|
result = NAN;
|
|
8137
8243
|
if ( slab_n <= 1 ) {
|
|
8138
|
-
|
|
8244
|
+
/* A one-point axis has no segment: only the point itself is in
|
|
8245
|
+
* range, at 0. An empty one has nothing in range. */
|
|
8246
|
+
if ( slab_n == 1 && query_val == *(double *)slab_ptr ) {
|
|
8247
|
+
result = 0.0;
|
|
8248
|
+
}
|
|
8139
8249
|
}
|
|
8140
8250
|
else {
|
|
8141
8251
|
ca_size_t x1 = 0;
|
|
@@ -8170,7 +8280,6 @@ MkKernel.search :linear_fetch,
|
|
|
8170
8280
|
no_match: "NAN",
|
|
8171
8281
|
no_match_check: "isnan(op[0])",
|
|
8172
8282
|
mask_self: :raise,
|
|
8173
|
-
mask_query: :undef,
|
|
8174
8283
|
body: <<~C,
|
|
8175
8284
|
result = NAN;
|
|
8176
8285
|
if ( slab_n >= 1 && query_val >= 0.0 && query_val <= (double)(slab_n - 1) ) {
|
|
@@ -8265,9 +8374,10 @@ MkKernel.monop :abs_i,
|
|
|
8265
8374
|
# (= used by .real chain composition); abs is the user-facing op that
|
|
8266
8375
|
# returns the real-valued magnitude entity.
|
|
8267
8376
|
MkKernel.monop :abs,
|
|
8268
|
-
source: MkKernel::ALL_NUMERIC + MkKernel::CMPLX_DTYPES,
|
|
8269
|
-
output: { numeric: :preserve, complex: :real_of_source },
|
|
8377
|
+
source: MkKernel::ALL_NUMERIC + MkKernel::CMPLX_DTYPES + [:object],
|
|
8378
|
+
output: { numeric: :preserve, complex: :real_of_source, object: :preserve },
|
|
8270
8379
|
expr: {
|
|
8380
|
+
[:object] => '(#2) = rb_funcall((#1), rb_intern("abs"), 0);',
|
|
8271
8381
|
MkKernel::SINT_SMALL_DTYPES => "(#2) = abs(#1);",
|
|
8272
8382
|
MkKernel::SINT64_DTYPES => "(#2) = llabs(#1);",
|
|
8273
8383
|
MkKernel::UINT_DTYPES => "(#2) = (#1);",
|
|
@@ -8383,7 +8493,9 @@ MkKernel.monop :imag_i,
|
|
|
8383
8493
|
MkKernel.monop :arg_i,
|
|
8384
8494
|
source: MkKernel::FLOAT_DTYPES + MkKernel::CMPLX_DTYPES,
|
|
8385
8495
|
expr: {
|
|
8386
|
-
|
|
8496
|
+
# A real cell's argument is 0, pi or NaN. Taken in double and rounded,
|
|
8497
|
+
# as the eager arg does; cargf returns pi one ulp low.
|
|
8498
|
+
[:f32] => "(#2) = carg((cmplx128_t)(#1));",
|
|
8387
8499
|
[:f64] => "(#2) = carg((cmplx128_t)(#1));",
|
|
8388
8500
|
[:cmplx64] => "(#2) = cargf(#1);",
|
|
8389
8501
|
[:cmplx128] => "(#2) = carg(#1);",
|
|
@@ -8432,14 +8544,18 @@ MkKernel.monfunc :round,
|
|
|
8432
8544
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8433
8545
|
expr: {
|
|
8434
8546
|
int: "(#2) = (#1);",
|
|
8435
|
-
|
|
8547
|
+
# Half away from zero, as Float#round. floor(x + 0.5) would round in
|
|
8548
|
+
# the addition: 0.49999999999999994 + 0.5 is 1.0.
|
|
8549
|
+
float: "(#2) = round(#1);",
|
|
8436
8550
|
object: '(#2) = rb_funcall((#1), rb_intern("round"), 0);',
|
|
8437
8551
|
}
|
|
8438
8552
|
|
|
8439
8553
|
MkKernel.monfunc :rcp,
|
|
8440
8554
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
8441
8555
|
expr: {
|
|
8442
|
-
|
|
8556
|
+
MkKernel::UINT_DTYPES => "if ((#1)==0) {ca_zerodiv();}; (#2) = 1/(#1);",
|
|
8557
|
+
# 1 / x floored: 1 for 1, -1 for every negative x, 0 otherwise.
|
|
8558
|
+
int: "if ((#1)==0) {ca_zerodiv();}; (#2) = ((#1)==1) ? 1 : ((#1) < 0) ? -1 : 0;",
|
|
8443
8559
|
float: "(#2) = 1/(#1);",
|
|
8444
8560
|
[:cmplx64] => "(#2) = op_crcp_cmplx64(#1);",
|
|
8445
8561
|
[:cmplx128] => "(#2) = 1/(#1);",
|
|
@@ -8548,11 +8664,13 @@ end
|
|
|
8548
8664
|
|
|
8549
8665
|
# expm1, log1p: float + object only (C99 doesn't standardise complex
|
|
8550
8666
|
# variants). Widening monfunc — integer input auto-casts to f64.
|
|
8667
|
+
# CAMath.expm1 / log1p are defined in lib/carray/math.rb instead: Ruby's
|
|
8668
|
+
# Math has neither, so the generated front-end could not take a Numeric.
|
|
8551
8669
|
{
|
|
8552
8670
|
expm1: "expm1",
|
|
8553
8671
|
log1p: "log1p",
|
|
8554
8672
|
}.each do |op, c_fn|
|
|
8555
|
-
MkKernel.monfunc op,
|
|
8673
|
+
MkKernel.monfunc op, cmath: false,
|
|
8556
8674
|
source: MkKernel::FLOAT_DTYPES + [:object],
|
|
8557
8675
|
expr: {
|
|
8558
8676
|
object: MkKernel.obj_float_math("#{c_fn}(<v>)", c_fn),
|
|
@@ -8737,14 +8855,15 @@ MkKernel.monfunc :rad_pi,
|
|
|
8737
8855
|
# NaN the result is NaN. Distinct kernel — cannot be
|
|
8738
8856
|
# aliased onto pmax.
|
|
8739
8857
|
#
|
|
8740
|
-
# Integer
|
|
8741
|
-
#
|
|
8858
|
+
# Integer branches are identical across all three (no NaN). The float
|
|
8859
|
+
# and object branches differ: pmax / pmin let a NaN lose (an object cell's
|
|
8860
|
+
# Float NaN included), maximum / minimum let it win.
|
|
8742
8861
|
MkKernel.binop :pmax,
|
|
8743
8862
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8744
8863
|
expr: {
|
|
8745
8864
|
int: "(#3) = (#1) > (#2) ? (#1) : (#2);",
|
|
8746
8865
|
float: "(#3) = fmax(#1, #2);",
|
|
8747
|
-
object: '(#3) =
|
|
8866
|
+
object: '(#3) = ca_obj_extremum_replaces((#2), (#1), rb_intern(">")) ? (#2) : (#1);',
|
|
8748
8867
|
}
|
|
8749
8868
|
|
|
8750
8869
|
MkKernel.binop :pmin,
|
|
@@ -8752,26 +8871,28 @@ MkKernel.binop :pmin,
|
|
|
8752
8871
|
expr: {
|
|
8753
8872
|
int: "(#3) = (#1) < (#2) ? (#1) : (#2);",
|
|
8754
8873
|
float: "(#3) = fmin(#1, #2);",
|
|
8755
|
-
object: '(#3) =
|
|
8874
|
+
object: '(#3) = ca_obj_extremum_replaces((#2), (#1), rb_intern("<")) ? (#2) : (#1);',
|
|
8756
8875
|
}
|
|
8757
8876
|
|
|
8758
8877
|
# NaN-propagate variants. Float
|
|
8759
8878
|
# branch tests both operands for NaN and short-circuits to NaN; non-
|
|
8760
|
-
# float branches reuse the comparison form.
|
|
8879
|
+
# float branches reuse the comparison form. Of two zeros, maximum is +0
|
|
8880
|
+
# and minimum is -0 in either order: a bare comparison would answer with
|
|
8881
|
+
# whichever came second.
|
|
8761
8882
|
MkKernel.binop :maximum,
|
|
8762
8883
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8763
8884
|
expr: {
|
|
8764
8885
|
int: "(#3) = (#1) > (#2) ? (#1) : (#2);",
|
|
8765
|
-
float: "(#3) = isnan(#1) ? (#1) :
|
|
8766
|
-
object: '(#3) =
|
|
8886
|
+
float: "(#3) = isnan(#1) ? (#1) : isnan(#2) ? (#2) : ((#1) > (#2) || ((#1) == (#2) && !signbit(#1))) ? (#1) : (#2);",
|
|
8887
|
+
object: '(#3) = CA_OBJ_ISNAN(#1) ? (#1) : CA_OBJ_ISNAN(#2) ? (#2) : ca_obj_cmp((#2), (#1)) > 0 ? (#2) : (#1);',
|
|
8767
8888
|
}
|
|
8768
8889
|
|
|
8769
8890
|
MkKernel.binop :minimum,
|
|
8770
8891
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8771
8892
|
expr: {
|
|
8772
8893
|
int: "(#3) = (#1) < (#2) ? (#1) : (#2);",
|
|
8773
|
-
float: "(#3) = isnan(#1) ? (#1) :
|
|
8774
|
-
object: '(#3) =
|
|
8894
|
+
float: "(#3) = isnan(#1) ? (#1) : isnan(#2) ? (#2) : ((#1) < (#2) || ((#1) == (#2) && signbit(#1))) ? (#1) : (#2);",
|
|
8895
|
+
object: '(#3) = CA_OBJ_ISNAN(#1) ? (#1) : CA_OBJ_ISNAN(#2) ? (#2) : ca_obj_cmp((#2), (#1)) < 0 ? (#2) : (#1);',
|
|
8775
8896
|
}
|
|
8776
8897
|
|
|
8777
8898
|
# + and - work on the parts independently, so one generic expression
|
|
@@ -8812,6 +8933,11 @@ MkKernel.binop :mul,
|
|
|
8812
8933
|
# sign, so they keep the bare C form. Float `/` is true division and is
|
|
8813
8934
|
# unchanged (matching Ruby `Float#/`); the identity above therefore holds
|
|
8814
8935
|
# for integers only, exactly as in Ruby.
|
|
8936
|
+
#
|
|
8937
|
+
# A signed divisor of -1 is taken apart from C's `/`: MIN / -1 does not
|
|
8938
|
+
# fit, and the hardware's answer to it differs (x86 traps, ARM returns
|
|
8939
|
+
# MIN). The quotient is the negation wrapped like any other overflow, so
|
|
8940
|
+
# MIN / -1 is MIN and MIN % -1 is 0 on every machine.
|
|
8815
8941
|
MkKernel.binop :div,
|
|
8816
8942
|
op: "/",
|
|
8817
8943
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
@@ -8819,7 +8945,8 @@ MkKernel.binop :div,
|
|
|
8819
8945
|
MkKernel::UINT_DTYPES => "if ((#2)==0) {ca_zerodiv();}; (#3) = (#1) / (#2);",
|
|
8820
8946
|
int: %{
|
|
8821
8947
|
if ((#2)==0) {ca_zerodiv();};
|
|
8822
|
-
{
|
|
8948
|
+
if ((#2)==-1) { (#3) = (<type>) (0 - (uint64_t) (#1)); }
|
|
8949
|
+
else {
|
|
8823
8950
|
<type> _q = (#1) / (#2);
|
|
8824
8951
|
if ( (#1) % (#2) != 0 && (((#1) < 0) != ((#2) < 0)) ) { _q -= 1; }
|
|
8825
8952
|
(#3) = _q;
|
|
@@ -8845,11 +8972,21 @@ MkKernel.binop :quo_i,
|
|
|
8845
8972
|
object: '(#3) = rb_funcall((#1), rb_intern("quo"), 1, (#2));',
|
|
8846
8973
|
}
|
|
8847
8974
|
|
|
8975
|
+
# rcp_mul: `a.rcp_mul(b)` is `b / a`, and divides as `/` does.
|
|
8848
8976
|
MkKernel.binop :rcp_mul,
|
|
8849
8977
|
op: "rcp_mul",
|
|
8850
8978
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
8851
8979
|
expr: {
|
|
8852
|
-
|
|
8980
|
+
MkKernel::UINT_DTYPES => "if ((#1)==0) {ca_zerodiv();}; (#3) = (#2) / (#1);",
|
|
8981
|
+
int: %{
|
|
8982
|
+
if ((#1)==0) {ca_zerodiv();};
|
|
8983
|
+
if ((#1)==-1) { (#3) = (<type>) (0 - (uint64_t) (#2)); }
|
|
8984
|
+
else {
|
|
8985
|
+
<type> _q = (#2) / (#1);
|
|
8986
|
+
if ( (#2) % (#1) != 0 && (((#2) < 0) != ((#1) < 0)) ) { _q -= 1; }
|
|
8987
|
+
(#3) = _q;
|
|
8988
|
+
}
|
|
8989
|
+
},
|
|
8853
8990
|
float: "(#3) = (#2) / (#1);",
|
|
8854
8991
|
[:cmplx64] => "(#3) = op_cdiv_cmplx64((#2), (#1));",
|
|
8855
8992
|
[:cmplx128] => "(#3) = (#2) / (#1);",
|
|
@@ -8871,7 +9008,8 @@ MkKernel.binop :mod,
|
|
|
8871
9008
|
MkKernel::UINT_DTYPES => "if ((#2)==0) {ca_zerodiv();}; (#3) = (#1) % (#2);",
|
|
8872
9009
|
int: %{
|
|
8873
9010
|
if ((#2)==0) {ca_zerodiv();};
|
|
8874
|
-
{
|
|
9011
|
+
if ((#2)==-1) { (#3) = 0; }
|
|
9012
|
+
else {
|
|
8875
9013
|
<type> _r = (#1) % (#2);
|
|
8876
9014
|
if ( _r != 0 && ((_r < 0) != ((#2) < 0)) ) { _r += (#2); }
|
|
8877
9015
|
(#3) = _r;
|
|
@@ -8899,7 +9037,8 @@ MkKernel.binop :mod,
|
|
|
8899
9037
|
MkKernel.binop :fmod,
|
|
8900
9038
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8901
9039
|
expr: {
|
|
8902
|
-
|
|
9040
|
+
MkKernel::UINT_DTYPES => "if ((#2)==0) {ca_zerodiv();}; (#3) = (#1) % (#2);",
|
|
9041
|
+
int: "if ((#2)==0) {ca_zerodiv();}; (#3) = ((#2)==-1) ? 0 : (#1) % (#2);",
|
|
8903
9042
|
object: '(#3) = rb_funcall((#1), rb_intern("remainder"), 1, (#2));',
|
|
8904
9043
|
}.merge(MkKernel.float_widths("(#3) = fmod<f>((#1), (#2));"))
|
|
8905
9044
|
|
|
@@ -8932,11 +9071,29 @@ MkKernel.binop :bit_xor_i,
|
|
|
8932
9071
|
object: '(#3) = rb_funcall((#1), rb_intern("^"), 1, (#2));',
|
|
8933
9072
|
}
|
|
8934
9073
|
|
|
9074
|
+
# Shifts take the meaning Integer#<< and #>> give them: a negative count
|
|
9075
|
+
# shifts the other way, and a count of the width or more shifts every bit
|
|
9076
|
+
# out (0, or -1 for a negative value shifted right). C leaves both, and a
|
|
9077
|
+
# left shift of a negative value, undefined. The value is widened to 64
|
|
9078
|
+
# bits and shifted there -- unsigned for a left shift -- and the result
|
|
9079
|
+
# narrowed back, which keeps the low bits a shift within the width gives.
|
|
9080
|
+
# The bodies are self-contained, since carray-jit compiles them as they are.
|
|
8935
9081
|
MkKernel.binop :bit_lshift,
|
|
8936
9082
|
op: "<<",
|
|
8937
9083
|
source: MkKernel::INT_DTYPES + [:object],
|
|
8938
9084
|
expr: {
|
|
8939
|
-
|
|
9085
|
+
MkKernel::UINT_DTYPES => "(#3) = ((uint64_t) (#2) >= 8 * sizeof(<type>)) ? 0 : (<type>) ((uint64_t) (#1) << (#2));",
|
|
9086
|
+
int: %{
|
|
9087
|
+
{
|
|
9088
|
+
int64_t _x = (int64_t) (#1), _n = (int64_t) (#2), _r;
|
|
9089
|
+
const int64_t _w = (int64_t) (8 * sizeof(<type>));
|
|
9090
|
+
if ( _n >= _w ) _r = 0;
|
|
9091
|
+
else if ( _n <= -_w ) _r = _x < 0 ? -1 : 0;
|
|
9092
|
+
else if ( _n < 0 ) _r = _x >> -_n;
|
|
9093
|
+
else _r = (int64_t) ((uint64_t) _x << _n);
|
|
9094
|
+
(#3) = (<type>) _r;
|
|
9095
|
+
}
|
|
9096
|
+
},
|
|
8940
9097
|
object: '(#3) = rb_funcall((#1), rb_intern("<<"), 1, (#2));',
|
|
8941
9098
|
}
|
|
8942
9099
|
|
|
@@ -8944,7 +9101,18 @@ MkKernel.binop :bit_rshift,
|
|
|
8944
9101
|
op: ">>",
|
|
8945
9102
|
source: MkKernel::INT_DTYPES + [:object],
|
|
8946
9103
|
expr: {
|
|
8947
|
-
|
|
9104
|
+
MkKernel::UINT_DTYPES => "(#3) = ((uint64_t) (#2) >= 8 * sizeof(<type>)) ? 0 : (<type>) ((uint64_t) (#1) >> (#2));",
|
|
9105
|
+
int: %{
|
|
9106
|
+
{
|
|
9107
|
+
int64_t _x = (int64_t) (#1), _n = (int64_t) (#2), _r;
|
|
9108
|
+
const int64_t _w = (int64_t) (8 * sizeof(<type>));
|
|
9109
|
+
if ( _n >= _w ) _r = _x < 0 ? -1 : 0;
|
|
9110
|
+
else if ( _n <= -_w ) _r = 0;
|
|
9111
|
+
else if ( _n < 0 ) _r = (int64_t) ((uint64_t) _x << -_n);
|
|
9112
|
+
else _r = _x >> _n;
|
|
9113
|
+
(#3) = (<type>) _r;
|
|
9114
|
+
}
|
|
9115
|
+
},
|
|
8948
9116
|
object: '(#3) = rb_funcall((#1), rb_intern(">>"), 1, (#2));',
|
|
8949
9117
|
}
|
|
8950
9118
|
|
|
@@ -9014,16 +9182,16 @@ C
|
|
|
9014
9182
|
# fma: fused multiply-add — `self * op2 + op3` in a single rounding step
|
|
9015
9183
|
# (= C99 `fma(x, y, z)` and HW FMA3/FMA4 / ARM FMA instructions). More
|
|
9016
9184
|
# accurate than the equivalent two-op `a*b + c` expression which rounds
|
|
9017
|
-
# twice.
|
|
9185
|
+
# twice. float32 calls fmaf: fma in double followed by a narrowing is a
|
|
9186
|
+
# second rounding, and can land 1 ulp off. Used in Horner evaluation, linear interpolation, dot product,
|
|
9018
9187
|
# Newton iteration.
|
|
9019
9188
|
MkKernel.triop :fma,
|
|
9020
9189
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
9021
9190
|
expr: {
|
|
9022
9191
|
int: "(#4) = (#1) * (#2) + (#3);",
|
|
9023
|
-
float: "(#4) = fma((#1), (#2), (#3));",
|
|
9024
9192
|
complex: "(#4) = (#1) * (#2) + (#3);",
|
|
9025
9193
|
object: '(#4) = rb_funcall(rb_funcall((#1), rb_intern("*"), 1, (#2)), rb_intern("+"), 1, (#3));',
|
|
9026
|
-
}
|
|
9194
|
+
}.merge(MkKernel.float_widths("(#4) = fma<f>((#1), (#2), (#3));"))
|
|
9027
9195
|
|
|
9028
9196
|
# fms: fused multiply-subtract — `self * op2 - op3` in a single rounding.
|
|
9029
9197
|
# Useful in cross products and complex multiplication primitives.
|
|
@@ -9031,11 +9199,10 @@ MkKernel.triop :fms,
|
|
|
9031
9199
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
9032
9200
|
expr: {
|
|
9033
9201
|
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
9202
|
complex: "(#4) = (#1) * (#2) - (#3);",
|
|
9037
9203
|
object: '(#4) = rb_funcall(rb_funcall((#1), rb_intern("*"), 1, (#2)), rb_intern("-"), 1, (#3));',
|
|
9038
|
-
|
|
9204
|
+
# fma(a, b, -c) = a*b - c with single rounding (= C99 / IEEE 754 hint).
|
|
9205
|
+
}.merge(MkKernel.float_widths("(#4) = fma<f>((#1), (#2), -(#3));"))
|
|
9039
9206
|
|
|
9040
9207
|
# clip: per-element clamp into [lo, hi]. 3.0 migration: was a hand-
|
|
9041
9208
|
# written eager-only method in ext/carray_generate.c that only accepted
|
|
@@ -9050,13 +9217,13 @@ MkKernel.triop :fms,
|
|
|
9050
9217
|
# UNDEF-fill case can now be expressed as `out = a.clip(lo, hi);
|
|
9051
9218
|
# out[a.lt(lo) | a.gt(hi)] = UNDEF`). Pinned in test.
|
|
9052
9219
|
#
|
|
9053
|
-
# Bound at the C level as `
|
|
9220
|
+
# Bound at the C level as `clamp_between` (made private by lazy.rb). The
|
|
9054
9221
|
# public `CArray#clip` is a Ruby wrapper in lib/carray/math.rb that
|
|
9055
9222
|
# restores the rich pre-3.0 API surface (nil-bound one-sided clip +
|
|
9056
9223
|
# optional fill). `clip!` is retired in 3.0 (view-by-default
|
|
9057
9224
|
# convention) so the bang form is suppressed via `bang: false`.
|
|
9058
9225
|
MkKernel.triop :clip,
|
|
9059
|
-
op: "
|
|
9226
|
+
op: "clamp_between",
|
|
9060
9227
|
bang: false,
|
|
9061
9228
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
9062
9229
|
expr: {
|
|
@@ -9074,6 +9241,20 @@ MkKernel.triop :clip,
|
|
|
9074
9241
|
'(#4) = _v; }',
|
|
9075
9242
|
}
|
|
9076
9243
|
|
|
9244
|
+
# select: the per-cell choice a lazy `then_else` makes (#1 is the
|
|
9245
|
+
# condition, converted to the branches' common type; #2 / #3 are the
|
|
9246
|
+
# branches). No Ruby method: CArray#then_else builds the node.
|
|
9247
|
+
MkKernel.triop :select,
|
|
9248
|
+
bind: false,
|
|
9249
|
+
bang: false,
|
|
9250
|
+
source: MkKernel::MATH_NUMERIC + [:bool],
|
|
9251
|
+
expr: {
|
|
9252
|
+
int: "(#4) = (#1) ? (#2) : (#3);",
|
|
9253
|
+
float: "(#4) = (#1) ? (#2) : (#3);",
|
|
9254
|
+
complex: "(#4) = (#1) ? (#2) : (#3);",
|
|
9255
|
+
bool: "(#4) = (#1) ? (#2) : (#3);",
|
|
9256
|
+
}
|
|
9257
|
+
|
|
9077
9258
|
MkKernel.binop :power,
|
|
9078
9259
|
op: :power,
|
|
9079
9260
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
@@ -9151,7 +9332,11 @@ MkKernel.moncmp :is_nan,
|
|
|
9151
9332
|
int: "(#2) = 0;",
|
|
9152
9333
|
float: "(#2) = isnan(#1);",
|
|
9153
9334
|
complex: "(#2) = (isnan(creal(#1)) || isnan(cimag(#1)));",
|
|
9154
|
-
|
|
9335
|
+
# A Float answers by its bits; an Integer or a Rational is never NaN;
|
|
9336
|
+
# anything else answers through its own #nan?.
|
|
9337
|
+
object: '(#2) = RB_FLOAT_TYPE_P(#1) ? isnan(RFLOAT_VALUE(#1)) : '\
|
|
9338
|
+
'(RB_INTEGER_TYPE_P(#1) || RB_TYPE_P((#1), T_RATIONAL)) ? 0 : '\
|
|
9339
|
+
'RTEST(rb_funcall((#1), rb_intern("nan?"), 0));',
|
|
9155
9340
|
}
|
|
9156
9341
|
|
|
9157
9342
|
MkKernel.moncmp :is_inf,
|
|
@@ -9160,7 +9345,7 @@ MkKernel.moncmp :is_inf,
|
|
|
9160
9345
|
int: "(#2) = 0;",
|
|
9161
9346
|
float: "(#2) = isinf(#1);",
|
|
9162
9347
|
complex: "(#2) = (isinf(creal(#1)) || isinf(cimag(#1)));",
|
|
9163
|
-
object: '(#2) = rb_funcall((#1), rb_intern("infinite?"), 0);',
|
|
9348
|
+
object: '(#2) = RTEST(rb_funcall((#1), rb_intern("infinite?"), 0));',
|
|
9164
9349
|
}
|
|
9165
9350
|
|
|
9166
9351
|
MkKernel.moncmp :is_finite,
|
|
@@ -9169,7 +9354,7 @@ MkKernel.moncmp :is_finite,
|
|
|
9169
9354
|
int: "(#2) = 1;",
|
|
9170
9355
|
float: "(#2) = isfinite(#1);",
|
|
9171
9356
|
complex: "(#2) = (isfinite(creal(#1)) && isfinite(cimag(#1)));",
|
|
9172
|
-
object: '(#2) = rb_funcall((#1), rb_intern("finite?"), 0);',
|
|
9357
|
+
object: '(#2) = RTEST(rb_funcall((#1), rb_intern("finite?"), 0));',
|
|
9173
9358
|
}
|
|
9174
9359
|
|
|
9175
9360
|
# is_invalid = !is_finite (MS.1 mask SET family redesign): NaN or Inf
|
|
@@ -9199,7 +9384,10 @@ MkKernel.moncmp :signbit,
|
|
|
9199
9384
|
MkKernel::SINT_DTYPES => "(#2) = ((#1) < 0);",
|
|
9200
9385
|
MkKernel::UINT_DTYPES => "(#2) = 0;",
|
|
9201
9386
|
float: "(#2) = signbit(#1);",
|
|
9202
|
-
|
|
9387
|
+
# A Float answers by its sign bit (-0.0 and -NaN are negative), as
|
|
9388
|
+
# the float branch does; anything else through #negative?.
|
|
9389
|
+
object: '(#2) = RB_FLOAT_TYPE_P(#1) ? (signbit(RFLOAT_VALUE(#1)) != 0) : '\
|
|
9390
|
+
'(RTEST(rb_funcall((#1), rb_intern("negative?"), 0)) ? 1 : 0);',
|
|
9203
9391
|
}
|
|
9204
9392
|
|
|
9205
9393
|
# ---- P.5b.4: bincmp family (predicates returning bool) ----------------
|