carray 3.0.3 → 3.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +469 -0
- data/README.md +3 -2
- data/carray.gemspec +1 -1
- data/ext/ca_array_pool.c +25 -1
- data/ext/ca_axis_descriptor.h +27 -24
- data/ext/ca_axis_dispatch.c +14 -26
- data/ext/ca_axis_group.c +27 -27
- data/ext/ca_binop_dispatch.c +1 -29
- data/ext/ca_binop_dispatch.h +0 -1
- data/ext/ca_categorical_iterator.c +231 -153
- data/ext/ca_compare.c +9 -20
- data/ext/ca_composite_dispatch.c +9 -9
- data/ext/ca_group_iter.c +16 -17
- data/ext/ca_kernel_iterator.c +1240 -2000
- data/ext/ca_kernel_iterator.h +502 -519
- data/ext/ca_monop_dispatch.c +82 -59
- data/ext/ca_monop_dispatch.h +1 -0
- data/ext/ca_obj_array.c +152 -45
- data/ext/ca_obj_bincmp.c +25 -21
- data/ext/ca_obj_binop.c +16 -26
- data/ext/ca_obj_bitarray.c +8 -3
- data/ext/ca_obj_bitfield.c +63 -49
- data/ext/ca_obj_block.c +18 -9
- data/ext/ca_obj_byte_swap.c +7 -7
- data/ext/ca_obj_const_string.c +188 -22
- data/ext/ca_obj_face.c +280 -58
- data/ext/ca_obj_face.h +19 -0
- data/ext/ca_obj_fake.c +15 -29
- data/ext/ca_obj_farray.c +6 -3
- data/ext/ca_obj_field.c +30 -8
- data/ext/ca_obj_fixlen_string.c +2 -3
- data/ext/ca_obj_grid.c +455 -134
- data/ext/ca_obj_meld.c +172 -34
- data/ext/ca_obj_moncmp.c +23 -8
- data/ext/ca_obj_monop.c +113 -67
- data/ext/ca_obj_object.c +210 -52
- data/ext/ca_obj_record.c +15 -7
- data/ext/ca_obj_reduce.c +17 -8
- data/ext/ca_obj_refer.c +42 -35
- data/ext/ca_obj_remap.c +30 -9
- data/ext/ca_obj_repeat.c +9 -10
- data/ext/ca_obj_roll.c +31 -10
- data/ext/ca_obj_select.c +48 -14
- data/ext/ca_obj_select_axis.c +15 -47
- data/ext/ca_obj_shift.c +41 -7
- data/ext/ca_obj_stack.c +180 -34
- data/ext/ca_obj_stride.c +190 -134
- data/ext/ca_obj_string.c +80 -4
- data/ext/ca_obj_tile.c +32 -11
- data/ext/ca_obj_time.c +22 -6
- data/ext/ca_obj_timedelta.c +22 -6
- data/ext/ca_obj_transpose.c +13 -7
- data/ext/ca_obj_triop.c +26 -32
- data/ext/ca_obj_window.c +267 -74
- data/ext/ca_op_ipower.c +4 -4
- data/ext/ca_sort_kernels.h +6 -3
- data/ext/ca_sweep_engine.c +2 -2
- data/ext/caframe_csv_reader.c +80 -1
- data/ext/carray.h +214 -75
- data/ext/carray_access.c +200 -99
- data/ext/carray_address_basis.c +343 -87
- data/ext/carray_attribute.c +113 -6
- data/ext/carray_bincount.c +10 -8
- data/ext/carray_broadcast.c +13 -8
- data/ext/carray_build_flags.h +3 -0
- data/ext/carray_call_cfunc.c +108 -0
- data/ext/carray_cast.c +375 -96
- data/ext/carray_cast_func.rb +90 -20
- data/ext/carray_class.c +4 -3
- data/ext/carray_config.h +28 -0
- data/ext/carray_conversion.c +18 -9
- data/ext/carray_copy.c +51 -14
- data/ext/carray_core.c +150 -55
- data/ext/carray_count.c +56 -20
- data/ext/carray_element.c +81 -33
- data/ext/carray_factorize.c +138 -84
- data/ext/carray_generate.c +199 -52
- data/ext/carray_histogram.c +63 -18
- data/ext/carray_hold.c +12 -15
- data/ext/carray_index_classifier.c +388 -176
- data/ext/carray_internal.h +348 -29
- data/ext/carray_kernels_bincmp.c +4453 -0
- data/ext/carray_kernels_binop.c +11368 -0
- data/ext/carray_kernels_init.c +1734 -0
- data/ext/carray_kernels_moncmp.c +2104 -0
- data/ext/carray_kernels_monop.c +18369 -0
- data/ext/carray_kernels_reduce_aggregate.c +23818 -0
- data/ext/carray_kernels_reduce_boolean.c +406 -0
- data/ext/carray_kernels_reduce_cumulative.c +10086 -0
- data/ext/carray_kernels_reduce_extreme.c +17634 -0
- data/ext/carray_kernels_reduce_variance.c +3849 -0
- data/ext/carray_kernels_scan.c +4302 -0
- data/ext/carray_kernels_search.c +33829 -0
- data/ext/carray_kernels_sort.c +10897 -0
- data/ext/carray_kernels_triop.c +1864 -0
- data/ext/carray_key_windows.c +220 -0
- data/ext/carray_lazy.c +170 -111
- data/ext/carray_mask.c +337 -94
- data/ext/carray_median_percentile.c +82 -28
- data/ext/carray_memory_view.c +669 -561
- data/ext/carray_operator.c +129 -52
- data/ext/carray_order.c +604 -77
- data/ext/carray_partition.c +28 -27
- data/ext/carray_random.c +85 -31
- data/ext/carray_round.c +273 -0
- data/ext/carray_scatter.c +87 -45
- data/ext/carray_segment.c +3 -3
- data/ext/carray_slab.c +123 -37
- data/ext/carray_sort.c +259 -51
- data/ext/carray_sort_kernel.c +5 -7
- data/ext/carray_struct.c +54 -61
- data/ext/carray_test.c +26 -5
- data/ext/carray_time_text.c +4 -2
- data/ext/carray_undef.c +4 -0
- data/ext/carray_utils.c +309 -66
- data/ext/mk_call_cfunc.rb +19 -2
- data/ext/mkkernel.rb +403 -350
- data/ext/ruby_carray.c +10 -4
- data/ext/version.h +4 -4
- data/lib/carray/attribute.rb +139 -22
- data/lib/carray/autoload_carray.rb +8 -31
- data/lib/carray/autoload_method_extension.rb +14 -5
- data/lib/carray/axis_group.rb +255 -106
- data/lib/carray/basics.rb +105 -17
- data/lib/carray/bincount_nd.rb +12 -2
- data/lib/carray/block_iterator.rb +126 -23
- data/lib/carray/boolean_reduce.rb +17 -17
- data/lib/carray/categorical.rb +195 -59
- data/lib/carray/categorical_iterator.rb +132 -29
- data/lib/carray/complex.rb +5 -5
- data/lib/carray/conditional.rb +67 -18
- data/lib/carray/const_string.rb +24 -5
- data/lib/carray/construct.rb +15 -11
- data/lib/carray/data_type_extension.rb +9 -1
- data/lib/carray/distance_limit.rb +77 -0
- data/lib/carray/fixlen_string.rb +48 -3
- data/lib/carray/frame/column_verbs.rb +71 -0
- data/lib/carray/frame/concat.rb +17 -3
- data/lib/carray/frame/convert.rb +69 -0
- data/lib/carray/frame/csv_parser.rb +111 -17
- data/lib/carray/frame/describe.rb +3 -2
- data/lib/carray/frame/frame.rb +252 -28
- data/lib/carray/frame/group.rb +603 -101
- data/lib/carray/frame/io.rb +117 -28
- data/lib/carray/frame/join.rb +312 -51
- data/lib/carray/frame/records.rb +83 -7
- data/lib/carray/frame/reshape.rb +42 -35
- data/lib/carray/frame/sort.rb +7 -19
- data/lib/carray/frame/verbs.rb +188 -48
- data/lib/carray/frame/windows.rb +117 -0
- data/lib/carray/frame.rb +2 -0
- data/lib/carray/fuse_source.rb +42 -6
- data/lib/carray/fusion.rb +40 -16
- data/lib/carray/histogram.rb +6 -1
- data/lib/carray/inspect.rb +31 -8
- data/lib/carray/iterator.rb +85 -7
- data/lib/carray/key_windows.rb +620 -0
- data/lib/carray/lazy.rb +217 -183
- data/lib/carray/mask_gap_fill.rb +63 -57
- data/lib/carray/meld_reduce.rb +12 -14
- data/lib/carray/methods/bin.rb +26 -18
- data/lib/carray/methods/bincount.rb +16 -0
- data/lib/carray/methods/bit_string.rb +1 -1
- data/lib/carray/methods/choose.rb +16 -3
- data/lib/carray/methods/composition.rb +3 -1
- data/lib/carray/methods/gather_nd.rb +14 -5
- data/lib/carray/methods/insert_block.rb +2 -2
- data/lib/carray/methods/is_between.rb +59 -0
- data/lib/carray/methods/is_in.rb +61 -37
- data/lib/carray/methods/join.rb +1 -6
- data/lib/carray/methods/lag.rb +49 -0
- data/lib/carray/methods/locate_addr.rb +47 -51
- data/lib/carray/methods/mode.rb +1 -1
- data/lib/carray/methods/nunique.rb +11 -6
- data/lib/carray/methods/resize.rb +3 -3
- data/lib/carray/methods/value_counts.rb +4 -11
- data/lib/carray/record_order.rb +3 -3
- data/lib/carray/rng.rb +3 -0
- data/lib/carray/runtime.rb +53 -8
- data/lib/carray/segment_iterator.rb +323 -88
- data/lib/carray/serialize.rb +45 -14
- data/lib/carray/slab_iterator.rb +50 -0
- data/lib/carray/stack.rb +15 -12
- data/lib/carray/string.rb +113 -5
- data/lib/carray/string_operation_extension.rb +21 -6
- data/lib/carray/struct.rb +9 -21
- data/lib/carray/struct_builder.rb +24 -11
- data/lib/carray/time.rb +819 -158
- data/lib/carray/window_iterator.rb +122 -16
- data/lib/carray.rb +7 -0
- data/yard-stubs/ca_obj_bitfield.rb +1 -1
- data/yard-stubs/ca_obj_field.rb +4 -0
- data/yard-stubs/ca_obj_grid.rb +21 -10
- data/yard-stubs/ca_obj_refer.rb +2 -1
- data/yard-stubs/ca_obj_roll.rb +2 -2
- data/yard-stubs/ca_obj_shift.rb +5 -1
- data/yard-stubs/ca_obj_tile.rb +1 -1
- data/yard-stubs/ca_obj_transpose.rb +5 -4
- data/yard-stubs/ca_obj_window.rb +8 -3
- data/yard-stubs/carray_access.rb +6 -1
- data/yard-stubs/carray_attribute.rb +3 -6
- data/yard-stubs/carray_cast.rb +12 -0
- data/yard-stubs/carray_conversion.rb +7 -2
- data/yard-stubs/carray_copy.rb +4 -2
- data/yard-stubs/carray_count.rb +4 -4
- data/yard-stubs/carray_element.rb +17 -10
- data/yard-stubs/carray_generate.rb +21 -1
- data/yard-stubs/carray_mask.rb +32 -3
- data/yard-stubs/carray_math.rb +41 -5
- data/yard-stubs/carray_median_percentile.rb +5 -2
- data/yard-stubs/carray_memory_view.rb +40 -23
- data/yard-stubs/carray_order.rb +129 -22
- data/yard-stubs/carray_random.rb +13 -4
- data/yard-stubs/carray_scatter.rb +17 -10
- data/yard-stubs/carray_slab.rb +9 -2
- data/yard-stubs/carray_sort.rb +6 -5
- data/yard-stubs/carray_test.rb +5 -3
- metadata +25 -3
- data/lib/carray/arrow_tensor.rb +0 -401
- data/lib/carray/table.rb +0 -108
data/ext/mkkernel.rb
CHANGED
|
@@ -44,26 +44,29 @@ module MkKernel
|
|
|
44
44
|
# value_arg: dispatchers (CF.1) to cast a positional Ruby argument to
|
|
45
45
|
# the kernel's T_IN type. Signed integers use NUM2LL, unsigned
|
|
46
46
|
# NUM2ULL, floats NUM2DBL, with the dispatcher adding the per-type cast.
|
|
47
|
+
# `c` of a multi-byte type is its unaligned typedef from carray.h
|
|
48
|
+
# (ca_u_float64_t): a view's cells may sit at any byte, and a kernel
|
|
49
|
+
# reads them through `c *`.
|
|
47
50
|
DTYPES = {
|
|
48
51
|
i8: { c: "int8_t", ca: "CA_INT8", ruby: "LL2NUM", num2c: "NUM2LL",
|
|
49
52
|
limit_hi: "INT8_MAX", limit_lo: "INT8_MIN" },
|
|
50
53
|
u8: { c: "uint8_t", ca: "CA_UINT8", ruby: "ULL2NUM", num2c: "NUM2ULL",
|
|
51
54
|
limit_hi: "UINT8_MAX", limit_lo: "0" },
|
|
52
|
-
i16: { c: "
|
|
55
|
+
i16: { c: "ca_u_int16_t", ca: "CA_INT16", ruby: "LL2NUM", num2c: "NUM2LL",
|
|
53
56
|
limit_hi: "INT16_MAX", limit_lo: "INT16_MIN" },
|
|
54
|
-
u16: { c: "
|
|
57
|
+
u16: { c: "ca_u_uint16_t", ca: "CA_UINT16", ruby: "ULL2NUM", num2c: "NUM2ULL",
|
|
55
58
|
limit_hi: "UINT16_MAX", limit_lo: "0" },
|
|
56
|
-
i32: { c: "
|
|
59
|
+
i32: { c: "ca_u_int32_t", ca: "CA_INT32", ruby: "LL2NUM", num2c: "NUM2LL",
|
|
57
60
|
limit_hi: "INT32_MAX", limit_lo: "INT32_MIN" },
|
|
58
|
-
u32: { c: "
|
|
61
|
+
u32: { c: "ca_u_uint32_t", ca: "CA_UINT32", ruby: "ULL2NUM", num2c: "NUM2ULL",
|
|
59
62
|
limit_hi: "UINT32_MAX", limit_lo: "0" },
|
|
60
|
-
i64: { c: "
|
|
63
|
+
i64: { c: "ca_u_int64_t", ca: "CA_INT64", ruby: "LL2NUM", num2c: "NUM2LL",
|
|
61
64
|
limit_hi: "INT64_MAX", limit_lo: "INT64_MIN" },
|
|
62
|
-
u64: { c: "
|
|
65
|
+
u64: { c: "ca_u_uint64_t", ca: "CA_UINT64", ruby: "ULL2NUM", num2c: "NUM2ULL",
|
|
63
66
|
limit_hi: "UINT64_MAX", limit_lo: "0" },
|
|
64
|
-
f32: { c: "
|
|
67
|
+
f32: { c: "ca_u_float32_t", ca: "CA_FLOAT32", ruby: "rb_float_new", num2c: "NUM2DBL",
|
|
65
68
|
limit_hi: "INFINITY", limit_lo: "-INFINITY" },
|
|
66
|
-
f64: { c: "
|
|
69
|
+
f64: { c: "ca_u_float64_t", ca: "CA_FLOAT64", ruby: "rb_float_new", num2c: "NUM2DBL",
|
|
67
70
|
limit_hi: "INFINITY", limit_lo: "-INFINITY" },
|
|
68
71
|
# E.6a (2026-06-03): boolean as a first-class source DTYPE.
|
|
69
72
|
# Intentionally NOT included in ALL_NUMERIC -- kernels opt boolean in
|
|
@@ -90,11 +93,11 @@ module MkKernel
|
|
|
90
93
|
# CA_NTYPE coverage parity with mkmath, mkkernel needs to know the
|
|
91
94
|
# complex and object data_types too. These entries are NOT included in
|
|
92
95
|
# ALL_NUMERIC; pick up via MATH_TYPES / MATH_NUMERIC aliases.
|
|
93
|
-
cmplx64: { c: "
|
|
96
|
+
cmplx64: { c: "ca_u_cmplx64_t", ca: "CA_CMPLX64", ruby: "CC2NUM",
|
|
94
97
|
num2c: "NUM2CC", limit_hi: "0", limit_lo: "0" },
|
|
95
|
-
cmplx128: { c: "
|
|
98
|
+
cmplx128: { c: "ca_u_cmplx128_t", ca: "CA_CMPLX128", ruby: "CC2NUM",
|
|
96
99
|
num2c: "NUM2CC", limit_hi: "0", limit_lo: "0" },
|
|
97
|
-
object: { c: "VALUE",
|
|
100
|
+
object: { c: "VALUE", ca: "CA_OBJECT", ruby: "(VALUE)",
|
|
98
101
|
num2c: "(VALUE)", limit_hi: "0", limit_lo: "0" },
|
|
99
102
|
# P.5b.4: fixlen for bincmp (= match, eq/ne/gt/lt/ge/le against
|
|
100
103
|
# fixed-byte strings). The c: type is `char` because fixlen kernels
|
|
@@ -177,10 +180,8 @@ module MkKernel
|
|
|
177
180
|
# expr: { object: "..." }.merge(float_widths("(#2) = sin<f>(#1);"))
|
|
178
181
|
#
|
|
179
182
|
# 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.
|
|
183
|
+
# `ceil` / `floor` / `trunc` / `round` and the `isnan` family are exact
|
|
184
|
+
# on a float either way.
|
|
184
185
|
def self.float_widths(body)
|
|
185
186
|
{
|
|
186
187
|
[:f32] => at_width(body, "f", "float"),
|
|
@@ -706,8 +707,13 @@ module MkKernel
|
|
|
706
707
|
# the input is masked). Aligns core cummax / cummin with
|
|
707
708
|
# the reduction contract (empty max / min = UNDEF, no
|
|
708
709
|
# identity) and the axis-group scan family.
|
|
710
|
+
# private:
|
|
711
|
+
# false (default) -> bound as the public `<name>_ki`
|
|
712
|
+
# true -> bound only as the private method `<name>`, a part
|
|
713
|
+
# that lib reaches with `send` (not user surface)
|
|
709
714
|
def self.scan(name, source:, output:, init:, step:, fallback: :raise,
|
|
710
|
-
acc_type: nil, axis_default: nil, empty: nil, face_gate: nil
|
|
715
|
+
acc_type: nil, axis_default: nil, empty: nil, face_gate: nil,
|
|
716
|
+
private: false)
|
|
711
717
|
raise "duplicate kernel #{name}" if KERNELS.any? { |k| k[:name] == name }
|
|
712
718
|
source.each do |s|
|
|
713
719
|
raise "unknown source data_type #{s}" unless DTYPES.key?(s)
|
|
@@ -749,6 +755,7 @@ module MkKernel
|
|
|
749
755
|
axis_default: axis_default,
|
|
750
756
|
empty: empty,
|
|
751
757
|
face_gate: face_gate,
|
|
758
|
+
private: private,
|
|
752
759
|
}
|
|
753
760
|
end
|
|
754
761
|
|
|
@@ -944,7 +951,7 @@ module MkKernel
|
|
|
944
951
|
# is up to the body (= value-returning search variants).
|
|
945
952
|
def self.search(name, source:, output:, body:,
|
|
946
953
|
no_match: "-1", no_match_check: nil, undef_no_match: false,
|
|
947
|
-
mask_self: :raise,
|
|
954
|
+
mask_self: :raise, runtime_args: nil,
|
|
948
955
|
fallback: :raise, semantics: :fiber_local)
|
|
949
956
|
raise "search: unknown semantics #{semantics} (expected :fiber_local or :view_flat)" \
|
|
950
957
|
unless %i[fiber_local view_flat].include?(semantics)
|
|
@@ -956,12 +963,10 @@ module MkKernel
|
|
|
956
963
|
raise "search: no_match must be a String" unless no_match.is_a?(String)
|
|
957
964
|
raise "search: unknown mask_self #{mask_self} (expected :raise | :skip | :ignore)" \
|
|
958
965
|
unless %i[raise skip ignore].include?(mask_self)
|
|
959
|
-
#
|
|
960
|
-
#
|
|
961
|
-
#
|
|
962
|
-
#
|
|
963
|
-
raise "search: unknown mask_query #{mask_query} (expected :ignore | :undef)" \
|
|
964
|
-
unless %i[ignore undef].include?(mask_query)
|
|
966
|
+
# A masked query cell (or a scalar UNDEF query) has no answer: its output
|
|
967
|
+
# cell is UNDEF, so an undetermined query cannot become a determined
|
|
968
|
+
# result from the value that happens to sit under the mask. There is no
|
|
969
|
+
# option to read under the mask.
|
|
965
970
|
raise "search: unknown fallback #{fallback} (expected :raise)" \
|
|
966
971
|
unless fallback == :raise
|
|
967
972
|
|
|
@@ -1026,7 +1031,6 @@ module MkKernel
|
|
|
1026
1031
|
no_match_check: no_match_check,
|
|
1027
1032
|
undef_no_match: undef_no_match,
|
|
1028
1033
|
mask_self: mask_self,
|
|
1029
|
-
mask_query: mask_query,
|
|
1030
1034
|
runtime_args: runtime_args || [],
|
|
1031
1035
|
fallback: fallback,
|
|
1032
1036
|
semantics: semantics,
|
|
@@ -1774,13 +1778,15 @@ module MkKernel
|
|
|
1774
1778
|
|
|
1775
1779
|
# Emits the native function of one source data type. The object lane
|
|
1776
1780
|
# calls Ruby for every cell, so any cell can raise in the middle of a walk;
|
|
1777
|
-
# its function is rebuilt around ca_iter_ensure (see
|
|
1778
|
-
#
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
+
# its function is rebuilt around ca_iter_ensure (see guard_native). So is
|
|
1782
|
+
# every lane of a family that passes guard: true, whose walks allocate
|
|
1783
|
+
# (an output mask made on the first empty slab, a scratch buffer), since a
|
|
1784
|
+
# failed allocation raises too. Other lanes are emitted as written.
|
|
1785
|
+
def self.emit_native_for(io, k, src, guard: false)
|
|
1786
|
+
if src == :object || guard
|
|
1781
1787
|
buf = StringIO.new
|
|
1782
1788
|
yield buf
|
|
1783
|
-
io.print
|
|
1789
|
+
io.print guard_native(buf.string, "#{k[:name]}_ki_native_#{src}")
|
|
1784
1790
|
else
|
|
1785
1791
|
yield io
|
|
1786
1792
|
end
|
|
@@ -1821,7 +1827,7 @@ module MkKernel
|
|
|
1821
1827
|
# frame is gone once the body raises. The body text is kept as written:
|
|
1822
1828
|
# the parameters are unpacked from the frame under their own names, and
|
|
1823
1829
|
# each state name is defined to its slot for the length of the body.
|
|
1824
|
-
def self.
|
|
1830
|
+
def self.guard_native(text, fn)
|
|
1825
1831
|
header = /^static VALUE\n#{fn} \(([^)]*)\)\n\{\n/
|
|
1826
1832
|
m = header.match(text) or raise "mkkernel: #{fn} not found"
|
|
1827
1833
|
close = matching_brace(text, m.end(0) - 2)
|
|
@@ -1844,9 +1850,9 @@ module MkKernel
|
|
|
1844
1850
|
|
|
1845
1851
|
out = StringIO.new
|
|
1846
1852
|
out.print m.pre_match
|
|
1847
|
-
out.puts "/* The
|
|
1848
|
-
out.puts "
|
|
1849
|
-
out.puts " finishes them however the body leaves. */"
|
|
1853
|
+
out.puts "/* The walks can raise part way (a Ruby call per cell in the object"
|
|
1854
|
+
out.puts " lane, an allocation in any lane). Their states live in the caller's"
|
|
1855
|
+
out.puts " frame, and ca_iter_ensure finishes them however the body leaves. */"
|
|
1850
1856
|
out.puts "typedef struct {"
|
|
1851
1857
|
out.puts " ca_iter_state states[#{states.size}];"
|
|
1852
1858
|
params.each { |type, name| out.puts " #{type} #{name};".sub(/\* /, "*") }
|
|
@@ -1881,7 +1887,7 @@ module MkKernel
|
|
|
1881
1887
|
io.puts
|
|
1882
1888
|
io.puts "/* ===== #{k[:name]}_ki ============================================ */"
|
|
1883
1889
|
k[:source].each do |src|
|
|
1884
|
-
emit_native_for(io, k, src) { |o| emit_reduce_native(o, k, src) }
|
|
1890
|
+
emit_native_for(io, k, src, guard: true) { |o| emit_reduce_native(o, k, src) }
|
|
1885
1891
|
end
|
|
1886
1892
|
emit_reduce_dispatch(io, k)
|
|
1887
1893
|
end
|
|
@@ -2178,20 +2184,22 @@ module MkKernel
|
|
|
2178
2184
|
else
|
|
2179
2185
|
if k[:array_arg]
|
|
2180
2186
|
wc = (k[:array_arg][:data_type] == :promote) ? "double" : si[:c]
|
|
2181
|
-
io.puts "
|
|
2187
|
+
io.puts " ca_size_t __sra_throwaway_mc = 0;"
|
|
2188
|
+
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);"
|
|
2189
|
+
io.puts " (void) __sra_throwaway_mc;"
|
|
2182
2190
|
else
|
|
2183
2191
|
suffix = MkKernel.reduce_macro_suffix(k, src)
|
|
2184
|
-
# Non-mask_policy path:
|
|
2185
|
-
#
|
|
2186
|
-
#
|
|
2187
|
-
if
|
|
2192
|
+
# Non-mask_policy path: the slab macros always take a masked_cnt,
|
|
2193
|
+
# so hand them a throwaway. The generic form (suffix "", the
|
|
2194
|
+
# :object lane) has a wrapper that hides it.
|
|
2195
|
+
if suffix == ""
|
|
2196
|
+
io.puts " CA_SLAB_REDUCE_T(#{si[:c]}, st, p, m, #{acc_var}, #{acc_init}, #{reduce_stmt});"
|
|
2197
|
+
else
|
|
2188
2198
|
io.puts " ca_size_t __sr_throwaway_mc = 0;"
|
|
2189
2199
|
emit_reduce_slab_call(io, k, src, si, oi, suffix, acc_var, acc_init,
|
|
2190
2200
|
reduce_stmt, "__sr_throwaway_mc",
|
|
2191
2201
|
indent: " ")
|
|
2192
2202
|
io.puts " (void) __sr_throwaway_mc;"
|
|
2193
|
-
else
|
|
2194
|
-
io.puts " CA_SLAB_REDUCE_T#{suffix}(#{si[:c]}, st, p, m, #{acc_var}, #{acc_init}, #{reduce_stmt});"
|
|
2195
2203
|
end
|
|
2196
2204
|
end
|
|
2197
2205
|
finish_emit = view_flat \
|
|
@@ -3549,9 +3557,12 @@ module MkKernel
|
|
|
3549
3557
|
wio.puts " else {"
|
|
3550
3558
|
wio.puts " /* CArray val: coerce to the weight storage type (f64 for :promote, so a"
|
|
3551
3559
|
wio.puts " float weight against an int source is not truncated) */"
|
|
3552
|
-
wio.puts " vcw
|
|
3560
|
+
wio.puts " /* vcv holds the view while cv is read: W-A2 below moves vcw on to"
|
|
3561
|
+
wio.puts " the materialised copy before it is done with cv. */"
|
|
3562
|
+
wio.puts " volatile VALUE vcv = rb_ca_wrap_readonly(rweights, INT2NUM(#{waca}));"
|
|
3553
3563
|
wio.puts " CArray *cv;"
|
|
3554
|
-
wio.puts " GetCArray(
|
|
3564
|
+
wio.puts " GetCArray(vcv, cv);"
|
|
3565
|
+
wio.puts " vcw = vcv;"
|
|
3555
3566
|
wio.puts ""
|
|
3556
3567
|
wio.puts " /* rev5 form detection: A3 (same ndim) -> A2 (1-D axis-broadcast) -> raise */"
|
|
3557
3568
|
wio.puts " if ( cv->ndim == src->ndim ) {"
|
|
@@ -3565,17 +3576,21 @@ module MkKernel
|
|
|
3565
3576
|
wio.puts " cw = cv;"
|
|
3566
3577
|
wio.puts " }"
|
|
3567
3578
|
wio.puts " else if ( naxes == 1 && cv->ndim == 1 && cv->dim[0] == src->dim[slab_axes[0]] ) {"
|
|
3568
|
-
wio.puts " /* W-A2: 1-D axis-broadcast -> materialize to full shape
|
|
3569
|
-
wio.puts "
|
|
3579
|
+
wio.puts " /* W-A2: 1-D axis-broadcast -> materialize to full shape. cw and"
|
|
3580
|
+
wio.puts " its mask are made before cv is attached: making them allocates. */"
|
|
3570
3581
|
wio.puts " vcw = rb_carray_new(#{waca}, src->ndim, src->dim, 0, NULL);"
|
|
3571
3582
|
wio.puts " GetCArray(vcw, cw);"
|
|
3583
|
+
wio.puts " if ( ca_has_mask(cv) ) {"
|
|
3584
|
+
wio.puts " ca_create_mask(cw);"
|
|
3585
|
+
wio.puts " }"
|
|
3586
|
+
wio.puts " ca_attach(cv); /* window: nothing raises inside */"
|
|
3572
3587
|
wio.puts " /* broadcast cv (1-D, length src->dim[slab_axes[0]]) along reduce axis */"
|
|
3573
3588
|
wio.puts " {"
|
|
3574
3589
|
wio.puts " int8_t baxis = slab_axes[0];"
|
|
3575
3590
|
wio.puts " ca_size_t inner_stride = 1;"
|
|
3576
3591
|
wio.puts " for ( int8_t ii = baxis + 1; ii < src->ndim; ii++ ) inner_stride *= src->dim[ii];"
|
|
3577
3592
|
wio.puts " ca_size_t axis_n = src->dim[baxis];"
|
|
3578
|
-
wio.puts " ca_size_t outer = src->elements / (axis_n * inner_stride);"
|
|
3593
|
+
wio.puts " ca_size_t outer = ( axis_n && inner_stride ) ? src->elements / (axis_n * inner_stride) : 0; /* a zero-length axis leaves nothing to fill */"
|
|
3579
3594
|
wio.puts " for ( ca_size_t o = 0; o < outer; o++ ) {"
|
|
3580
3595
|
wio.puts " for ( ca_size_t a = 0; a < axis_n; a++ ) {"
|
|
3581
3596
|
wio.puts " for ( ca_size_t in = 0; in < inner_stride; in++ ) {"
|
|
@@ -3587,15 +3602,14 @@ module MkKernel
|
|
|
3587
3602
|
wio.puts " }"
|
|
3588
3603
|
wio.puts " /* Propagate cv mask via W-A3 overlay path below (= mask materialize) */"
|
|
3589
3604
|
wio.puts " if ( ca_has_mask(cv) ) {"
|
|
3590
|
-
wio.puts " /*
|
|
3591
|
-
wio.puts " ca_create_mask(cw);"
|
|
3605
|
+
wio.puts " /* broadcast cv mask along axis */"
|
|
3592
3606
|
wio.puts " boolean8_t *cw_m = (boolean8_t *)cw->mask->ptr;"
|
|
3593
3607
|
wio.puts " boolean8_t *cv_m = (boolean8_t *)cv->mask->ptr;"
|
|
3594
3608
|
wio.puts " int8_t baxis = slab_axes[0];"
|
|
3595
3609
|
wio.puts " ca_size_t inner_stride = 1;"
|
|
3596
3610
|
wio.puts " for ( int8_t ii = baxis + 1; ii < src->ndim; ii++ ) inner_stride *= src->dim[ii];"
|
|
3597
3611
|
wio.puts " ca_size_t axis_n = src->dim[baxis];"
|
|
3598
|
-
wio.puts " ca_size_t outer = src->elements / (axis_n * inner_stride);"
|
|
3612
|
+
wio.puts " ca_size_t outer = ( axis_n && inner_stride ) ? src->elements / (axis_n * inner_stride) : 0; /* a zero-length axis leaves nothing to fill */"
|
|
3599
3613
|
wio.puts " for ( ca_size_t o = 0; o < outer; o++ ) {"
|
|
3600
3614
|
wio.puts " for ( ca_size_t a = 0; a < axis_n; a++ ) {"
|
|
3601
3615
|
wio.puts " for ( ca_size_t in = 0; in < inner_stride; in++ ) {"
|
|
@@ -3813,14 +3827,18 @@ module MkKernel
|
|
|
3813
3827
|
|
|
3814
3828
|
# empty: :undef -> allocate the output mask up front when the input
|
|
3815
3829
|
# carries one (a fiber can only go UNDEF on its leading masked cells,
|
|
3816
|
-
# which exist only when the input is masked).
|
|
3817
|
-
#
|
|
3818
|
-
# the
|
|
3830
|
+
# which exist only when the input is masked). The output is a fresh
|
|
3831
|
+
# row-major entity, and the walk cuts its slabs from a buffer laid out
|
|
3832
|
+
# the same way (the output's memory, or the walk's scratch when it
|
|
3833
|
+
# delivers by transfer), so a slab's offset from st_out.alias_ptr is
|
|
3834
|
+
# its offset in the output, and op_mask + that offset / cell size is the
|
|
3835
|
+
# fiber's mask base.
|
|
3819
3836
|
mask_setup =
|
|
3820
3837
|
if gated
|
|
3838
|
+
# Before the walks open: creating the mask allocates, and a raise
|
|
3839
|
+
# inside them would jump past their finish.
|
|
3821
3840
|
"\n" \
|
|
3822
3841
|
" boolean8_t *op_mask = NULL;\n" \
|
|
3823
|
-
" char *co_root = (char *) co->ptr;\n" \
|
|
3824
3842
|
" if ( ca_has_mask(ca) ) {\n" \
|
|
3825
3843
|
" ca_create_mask(co);\n" \
|
|
3826
3844
|
" op_mask = (boolean8_t *) co->mask->ptr;\n" \
|
|
@@ -3835,7 +3853,7 @@ module MkKernel
|
|
|
3835
3853
|
" st_out, po, #{init_expr}, #{step_stmt});"
|
|
3836
3854
|
elsif gated
|
|
3837
3855
|
"boolean8_t *mo_fiber = op_mask\n" \
|
|
3838
|
-
" ? op_mask + (ca_size_t)(po -
|
|
3856
|
+
" ? op_mask + (ca_size_t)(po - st_out.alias_ptr) / (ca_size_t) co->bytes\n" \
|
|
3839
3857
|
" : NULL;\n" \
|
|
3840
3858
|
" CA_SLAB_SCAN_T_GATED(#{si[:c]}, #{oi[:c]}, st_in, pi, mi,\n" \
|
|
3841
3859
|
" st_out, po, mo_fiber, #{init_expr}, #{step_stmt});"
|
|
@@ -3859,6 +3877,7 @@ module MkKernel
|
|
|
3859
3877
|
INIT inside the macro. */
|
|
3860
3878
|
int8_t slab_axes[CA_RANK_MAX];
|
|
3861
3879
|
slab_axes[0] = (int8_t) axis;
|
|
3880
|
+
#{mask_setup}
|
|
3862
3881
|
ca_iter_state st_in, st_out;
|
|
3863
3882
|
int rc;
|
|
3864
3883
|
rc = ca_iter_state_init_l2(&st_in, ca, CA_SLAB_AXES,
|
|
@@ -3874,7 +3893,6 @@ module MkKernel
|
|
|
3874
3893
|
rb_raise(rb_eRuntimeError,
|
|
3875
3894
|
"%s: output init failed rc=%d", ca_calling_method_name(), rc);
|
|
3876
3895
|
}
|
|
3877
|
-
#{mask_setup}
|
|
3878
3896
|
char *pi, *po;
|
|
3879
3897
|
boolean8_t *mi, *mo;
|
|
3880
3898
|
while ( ca_iter_state_next_slab_axes(&st_in, &pi, &mi) &&
|
|
@@ -3968,11 +3986,7 @@ module MkKernel
|
|
|
3968
3986
|
io.puts " if ( TYPE(raxis) == T_ARRAY ) {"
|
|
3969
3987
|
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());]
|
|
3970
3988
|
io.puts " }"
|
|
3971
|
-
io.puts " int axis = (
|
|
3972
|
-
io.puts " if ( axis < 0 ) axis += src->ndim;"
|
|
3973
|
-
io.puts " if ( axis < 0 || axis >= src->ndim ) {"
|
|
3974
|
-
io.puts %Q[ rb_raise(rb_eArgError, "%s: axis %d out of range for ndim %d", ca_calling_method_name(), NUM2INT(raxis), src->ndim);]
|
|
3975
|
-
io.puts " }"
|
|
3989
|
+
io.puts " int axis = ca_normalize_axis_arg(raxis, src->ndim, NULL);"
|
|
3976
3990
|
io.puts " switch ( src->data_type ) {"
|
|
3977
3991
|
k[:source].each do |s|
|
|
3978
3992
|
si = DTYPES[s]
|
|
@@ -4134,35 +4148,15 @@ module MkKernel
|
|
|
4134
4148
|
io.puts " const #{name}_pair_#{src} *pa = (const #{name}_pair_#{src} *) a;"
|
|
4135
4149
|
io.puts " const #{name}_pair_#{src} *pb = (const #{name}_pair_#{src} *) b;"
|
|
4136
4150
|
if is_object
|
|
4137
|
-
#
|
|
4138
|
-
#
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
#
|
|
4143
|
-
|
|
4144
|
-
io.puts "
|
|
4145
|
-
io.puts " int nan_b = CA_OBJ_ISNAN(pb->v);"
|
|
4146
|
-
io.puts " if ( nan_a && !nan_b ) return 1;"
|
|
4147
|
-
io.puts " if ( nan_b && !nan_a ) return -1;"
|
|
4148
|
-
io.puts " if ( !nan_a ) {"
|
|
4149
|
-
io.puts %Q{ VALUE r = rb_funcall(pa->v, rb_intern("<=>"), 1, pb->v);}
|
|
4150
|
-
io.puts " if ( NIL_P(r) ) {"
|
|
4151
|
-
io.puts " ca_obj_compare_refused(pa->v, pb->v);"
|
|
4152
|
-
io.puts " }"
|
|
4153
|
-
io.puts " int c = NUM2INT(r);"
|
|
4154
|
-
io.puts " if ( c > 0 ) return 1;"
|
|
4155
|
-
io.puts " if ( c < 0 ) return -1;"
|
|
4156
|
-
io.puts " }"
|
|
4151
|
+
# The object order: `<=>`, a Float NaN after every other value
|
|
4152
|
+
# (ca_obj_order_cmp); two NaNs fall to the index tie-break.
|
|
4153
|
+
io.puts " int c = ca_obj_order_cmp(pa->v, pb->v);"
|
|
4154
|
+
io.puts " if ( c != 0 ) return c;"
|
|
4155
|
+
elsif is_fp
|
|
4156
|
+
# The float order: NaN after every number (CA_FLOAT_ORDER_CMP).
|
|
4157
|
+
io.puts " int c = CA_FLOAT_ORDER_CMP(pa->v, pb->v);"
|
|
4158
|
+
io.puts " if ( c != 0 ) return c;"
|
|
4157
4159
|
else
|
|
4158
|
-
if is_fp
|
|
4159
|
-
# NaN policy :end -- NaN sorts to the end (matches qcmp_f_type in
|
|
4160
|
-
# carray_sort.c).
|
|
4161
|
-
io.puts " int nan_a = isnan(pa->v);"
|
|
4162
|
-
io.puts " int nan_b = isnan(pb->v);"
|
|
4163
|
-
io.puts " if ( nan_a && !nan_b ) return 1;"
|
|
4164
|
-
io.puts " if ( nan_b && !nan_a ) return -1;"
|
|
4165
|
-
end
|
|
4166
4160
|
io.puts " if ( pa->v > pb->v ) return 1;"
|
|
4167
4161
|
io.puts " if ( pa->v < pb->v ) return -1;"
|
|
4168
4162
|
end
|
|
@@ -4183,7 +4177,7 @@ module MkKernel
|
|
|
4183
4177
|
if is_fixlen
|
|
4184
4178
|
io.puts " buf[#{dst}].vp = pi + #{kexpr} * istride; buf[#{dst}].nb = (ca_size_t) ca->bytes;"
|
|
4185
4179
|
else
|
|
4186
|
-
io.puts " buf[#{dst}].v
|
|
4180
|
+
io.puts " memcpy(&buf[#{dst}].v, pi + #{kexpr} * istride, sizeof(buf[#{dst}].v));"
|
|
4187
4181
|
end
|
|
4188
4182
|
end
|
|
4189
4183
|
|
|
@@ -4279,21 +4273,10 @@ module MkKernel
|
|
|
4279
4273
|
io.puts " when input has any mask. */"
|
|
4280
4274
|
io.puts " if ( ca_has_mask(ca) ) ca_create_mask(co);"
|
|
4281
4275
|
end
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
io.puts "
|
|
4286
|
-
io.puts " if ( rc != CA_ITER_OK ) {"
|
|
4287
|
-
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: input init failed rc=%d", ca_calling_method_name(), rc);]
|
|
4288
|
-
io.puts " }"
|
|
4289
|
-
io.puts " rc = ca_iter_state_init_l2_paired(&st_out, &st_in, co, CA_SLAB_AXES,"
|
|
4290
|
-
io.puts " slab_axes, 1, CA_KERNEL_WRITE);"
|
|
4291
|
-
io.puts " if ( rc != CA_ITER_OK ) {"
|
|
4292
|
-
io.puts " ca_iter_state_finish(&st_in);"
|
|
4293
|
-
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: output init failed rc=%d", ca_calling_method_name(), rc);]
|
|
4294
|
-
io.puts " }"
|
|
4295
|
-
io.puts
|
|
4296
|
-
io.puts " ca_size_t fiber_n = st_in.slab_elements;"
|
|
4276
|
+
# The buffers are taken before the walks open: an allocation that
|
|
4277
|
+
# raises inside them would jump past their finish. The slab is the
|
|
4278
|
+
# sort axis alone, so a fiber holds ca->dim[axis] cells.
|
|
4279
|
+
io.puts " ca_size_t fiber_n = ca->dim[axis];"
|
|
4297
4280
|
if src == :object
|
|
4298
4281
|
# The object comparator calls <=>, so the sort can raise part way.
|
|
4299
4282
|
# ALLOCV frees these buffers whether the function returns or unwinds.
|
|
@@ -4306,19 +4289,39 @@ module MkKernel
|
|
|
4306
4289
|
io.puts " #{name}_pair_#{src} *aux = ALLOCV_N(#{name}_pair_#{src}, haux, fiber_n);"
|
|
4307
4290
|
end
|
|
4308
4291
|
else
|
|
4309
|
-
io.puts "
|
|
4310
|
-
io.puts "
|
|
4292
|
+
io.puts " volatile VALUE hbuf = 0, haux = 0;"
|
|
4293
|
+
io.puts " #{name}_pair_#{src} *buf = ALLOCV_N(#{name}_pair_#{src}, hbuf, fiber_n);"
|
|
4311
4294
|
end
|
|
4312
|
-
if has_kind && src
|
|
4313
|
-
#
|
|
4314
|
-
#
|
|
4315
|
-
#
|
|
4295
|
+
if has_kind && %i[f32 f64].include?(src)
|
|
4296
|
+
# aux is the mergesort's ping-pong buffer and, before that, the
|
|
4297
|
+
# scratch of the NaN pre-partition, so a float kernel always takes it.
|
|
4298
|
+
io.puts " #{name}_pair_#{src} *aux = ALLOCV_N(#{name}_pair_#{src}, haux, fiber_n);"
|
|
4299
|
+
elsif has_kind && src != :object && src != :fixlen
|
|
4300
|
+
# The aux pair buffer for ca_sort_merge_pair_<src> (a ping-pong
|
|
4301
|
+
# bottom-up mergesort), taken once outside the fiber loop. The fixlen
|
|
4302
|
+
# lane sorts with qsort and needs none.
|
|
4316
4303
|
io.puts " #{name}_pair_#{src} *aux = NULL;"
|
|
4317
4304
|
io.puts " if ( do_stable ) {"
|
|
4318
|
-
io.puts " aux =
|
|
4305
|
+
io.puts " aux = ALLOCV_N(#{name}_pair_#{src}, haux, fiber_n);"
|
|
4319
4306
|
io.puts " }"
|
|
4307
|
+
elsif src != :object
|
|
4308
|
+
io.puts " (void) haux;"
|
|
4320
4309
|
end
|
|
4321
4310
|
|
|
4311
|
+
io.puts " ca_iter_state st_in, st_out;"
|
|
4312
|
+
io.puts " int rc;"
|
|
4313
|
+
io.puts " rc = ca_iter_state_init_l2(&st_in, ca, CA_SLAB_AXES,"
|
|
4314
|
+
io.puts " slab_axes, 1, 0);"
|
|
4315
|
+
io.puts " if ( rc != CA_ITER_OK ) {"
|
|
4316
|
+
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: input init failed rc=%d", ca_calling_method_name(), rc);]
|
|
4317
|
+
io.puts " }"
|
|
4318
|
+
io.puts " rc = ca_iter_state_init_l2_paired(&st_out, &st_in, co, CA_SLAB_AXES,"
|
|
4319
|
+
io.puts " slab_axes, 1, CA_KERNEL_WRITE);"
|
|
4320
|
+
io.puts " if ( rc != CA_ITER_OK ) {"
|
|
4321
|
+
io.puts " ca_iter_state_finish(&st_in);"
|
|
4322
|
+
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: output init failed rc=%d", ca_calling_method_name(), rc);]
|
|
4323
|
+
io.puts " }"
|
|
4324
|
+
|
|
4322
4325
|
if view_flat
|
|
4323
4326
|
io.puts
|
|
4324
4327
|
io.puts " /* :view_flat semantics: kernel computes view-flat address"
|
|
@@ -4370,7 +4373,7 @@ module MkKernel
|
|
|
4370
4373
|
if is_fixlen
|
|
4371
4374
|
io.puts " buf[k].vp = pi + k * istride; buf[k].nb = (ca_size_t) ca->bytes;"
|
|
4372
4375
|
else
|
|
4373
|
-
io.puts " buf[k].v
|
|
4376
|
+
io.puts " memcpy(&buf[k].v, pi + k * istride, sizeof(buf[k].v));"
|
|
4374
4377
|
end
|
|
4375
4378
|
io.puts " buf[k].i = #{payload.call("k")};"
|
|
4376
4379
|
io.puts " }"
|
|
@@ -4385,7 +4388,7 @@ module MkKernel
|
|
|
4385
4388
|
if is_fixlen
|
|
4386
4389
|
io.puts " buf[k_unmasked].vp = pi + k * istride; buf[k_unmasked].nb = (ca_size_t) ca->bytes;"
|
|
4387
4390
|
else
|
|
4388
|
-
io.puts " buf[k_unmasked].v
|
|
4391
|
+
io.puts " memcpy(&buf[k_unmasked].v, pi + k * istride, sizeof(buf[k_unmasked].v));"
|
|
4389
4392
|
end
|
|
4390
4393
|
io.puts " buf[k_unmasked].i = k;"
|
|
4391
4394
|
io.puts " k_unmasked++;"
|
|
@@ -4401,7 +4404,7 @@ module MkKernel
|
|
|
4401
4404
|
if is_fixlen
|
|
4402
4405
|
io.puts " buf[k].vp = pi + k * istride; buf[k].nb = (ca_size_t) ca->bytes;"
|
|
4403
4406
|
else
|
|
4404
|
-
io.puts " buf[k].v
|
|
4407
|
+
io.puts " memcpy(&buf[k].v, pi + k * istride, sizeof(buf[k].v));"
|
|
4405
4408
|
end
|
|
4406
4409
|
io.puts " buf[k].i = k;"
|
|
4407
4410
|
io.puts " }"
|
|
@@ -4474,7 +4477,7 @@ module MkKernel
|
|
|
4474
4477
|
io.puts " /* P.3/P.4: NaN pre-partition for float argsort -- NaN pairs are"
|
|
4475
4478
|
io.puts " pushed to the tail (= NaN-at-end policy, matches sort_copy"
|
|
4476
4479
|
io.puts " AC8). Only the finite slice is sorted. */"
|
|
4477
|
-
io.puts " ca_size_t finite_n = ca_partition_nan_pair_#{src}((ca_pair_#{src} *) #{buf_expr}, (ca_size_t) #{sort_n_expr});"
|
|
4480
|
+
io.puts " ca_size_t finite_n = ca_partition_nan_pair_#{src}((ca_pair_#{src} *) #{buf_expr}, (ca_size_t) #{sort_n_expr}, (ca_pair_#{src} *) aux);"
|
|
4478
4481
|
io.puts " if ( do_stable ) {"
|
|
4479
4482
|
io.puts " ca_sort_merge_pair_#{src}((ca_pair_#{src} *) #{buf_expr}, (ca_pair_#{src} *) aux, finite_n);"
|
|
4480
4483
|
io.puts " } else {"
|
|
@@ -4502,16 +4505,21 @@ module MkKernel
|
|
|
4502
4505
|
io.puts " gaps ordinal would have spent on the tie are never used, so a"
|
|
4503
4506
|
io.puts " later out_r stays <= the ordinal r at that position, preserving"
|
|
4504
4507
|
io.puts " both the ascending order and (via the order() method's"
|
|
4505
|
-
io.puts "
|
|
4506
|
-
io.puts "
|
|
4508
|
+
io.puts " descending transform) the descending order. NaN cells tie"
|
|
4509
|
+
io.puts " with each other, as sort_addr and the value-hash family treat"
|
|
4510
|
+
io.puts " every NaN as one value. */"
|
|
4507
4511
|
io.puts " ca_size_t out_r = 0;"
|
|
4508
4512
|
io.puts " for ( ca_size_t r = 0; r < #{rank_n}; r++ ) {"
|
|
4509
4513
|
io.puts " if ( r > 0 ) {"
|
|
4510
4514
|
io.puts " if ( dense ) {"
|
|
4511
4515
|
if is_object
|
|
4512
|
-
io.puts " if ( !
|
|
4516
|
+
io.puts " if ( ! ( ( CA_OBJ_ISNAN(buf[r].v) && CA_OBJ_ISNAN(buf[r - 1].v) )"
|
|
4517
|
+
io.puts " || ca_obj_equal(buf[r].v, buf[r - 1].v) ) ) out_r++;"
|
|
4513
4518
|
elsif is_fixlen
|
|
4514
4519
|
io.puts " if ( memcmp(buf[r].vp, buf[r - 1].vp, (size_t) buf[r].nb) != 0 ) out_r++;"
|
|
4520
|
+
elsif FLOAT_DTYPES.include?(src)
|
|
4521
|
+
io.puts " if ( buf[r].v != buf[r - 1].v"
|
|
4522
|
+
io.puts " && ! ( isnan(buf[r].v) && isnan(buf[r - 1].v) ) ) out_r++;"
|
|
4515
4523
|
else
|
|
4516
4524
|
io.puts " if ( buf[r].v != buf[r - 1].v ) out_r++;"
|
|
4517
4525
|
end
|
|
@@ -4521,15 +4529,32 @@ module MkKernel
|
|
|
4521
4529
|
io.puts " }"
|
|
4522
4530
|
io.puts " *(#{oi[:c]} *) (po + buf[r].i * ostride) = (#{oi[:c]}) out_r;"
|
|
4523
4531
|
io.puts " }"
|
|
4532
|
+
# dense == 2 (from CArray#order only): descending dense ranks, the
|
|
4533
|
+
# largest value 0, the NaN cells (sorted to the end) after the numbers.
|
|
4534
|
+
nan_test = if is_object then "CA_OBJ_ISNAN(buf[nan_start - 1].v)"
|
|
4535
|
+
elsif FLOAT_DTYPES.include?(src) then "isnan(buf[nan_start - 1].v)"
|
|
4536
|
+
end
|
|
4537
|
+
io.puts " if ( dense == 2 && #{rank_n} > 0 ) {"
|
|
4538
|
+
io.puts " ca_size_t nan_start = #{rank_n};"
|
|
4539
|
+
io.puts " while ( nan_start > 0 && #{nan_test} ) nan_start--;" if nan_test
|
|
4540
|
+
io.puts " #{oi[:c]} top = nan_start > 0"
|
|
4541
|
+
io.puts " ? *(#{oi[:c]} *) (po + buf[nan_start - 1].i * ostride) : (#{oi[:c]}) -1;"
|
|
4542
|
+
io.puts " for ( ca_size_t r = 0; r < #{rank_n}; r++ ) {"
|
|
4543
|
+
io.puts " #{oi[:c]} *o = (#{oi[:c]} *) (po + buf[r].i * ostride);"
|
|
4544
|
+
io.puts " *o = ( r < nan_start ) ? top - *o : top + 1;"
|
|
4545
|
+
io.puts " }"
|
|
4546
|
+
io.puts " }"
|
|
4524
4547
|
if mask_skip
|
|
4525
4548
|
io.puts " /* Mirror input mask to output (= UNDEF rank for masked"
|
|
4526
|
-
io.puts " cells). Write via co->mask->ptr directly
|
|
4527
|
-
io.puts "
|
|
4528
|
-
io.puts "
|
|
4529
|
-
io.puts "
|
|
4549
|
+
io.puts " cells). Write via co->mask->ptr directly, because a"
|
|
4550
|
+
io.puts " mask written through a WRITE walk's cursor does not"
|
|
4551
|
+
io.puts " reach the array. co is a fresh row-major entity and"
|
|
4552
|
+
io.puts " the walk cuts its slabs from a buffer laid out the"
|
|
4553
|
+
io.puts " same way, so the slab's offset from st_out.alias_ptr"
|
|
4554
|
+
io.puts " is its offset in co. */"
|
|
4530
4555
|
io.puts " if ( mi && co->mask ) {"
|
|
4531
4556
|
io.puts " boolean8_t *omask_base = (boolean8_t *) co->mask->ptr"
|
|
4532
|
-
io.puts " + ((po -
|
|
4557
|
+
io.puts " + ((po - st_out.alias_ptr) / (ca_size_t) co->bytes);"
|
|
4533
4558
|
io.puts " ca_size_t mostride = st_out.slab_mask_strides[0];"
|
|
4534
4559
|
io.puts " for ( ca_size_t k = 0; k < fiber_n; k++ ) {"
|
|
4535
4560
|
io.puts " if ( mi[k * mstride] ) omask_base[k * mostride] = 1;"
|
|
@@ -4555,45 +4580,33 @@ module MkKernel
|
|
|
4555
4580
|
end
|
|
4556
4581
|
|
|
4557
4582
|
io.puts " }"
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
io.puts " ALLOCV_END(haux);"
|
|
4561
|
-
else
|
|
4562
|
-
io.puts " xfree(buf);"
|
|
4563
|
-
if has_kind
|
|
4564
|
-
io.puts " if ( aux ) xfree(aux);"
|
|
4565
|
-
end
|
|
4566
|
-
end
|
|
4583
|
+
io.puts " ALLOCV_END(hbuf);"
|
|
4584
|
+
io.puts " ALLOCV_END(haux);"
|
|
4567
4585
|
io.puts " ca_iter_state_finish(&st_in);"
|
|
4568
4586
|
io.puts " ca_iter_state_finish(&st_out);"
|
|
4569
4587
|
io.puts " return vout;"
|
|
4570
4588
|
io.puts "}"
|
|
4571
4589
|
end
|
|
4572
4590
|
|
|
4573
|
-
# Shared sort-family Face gate.
|
|
4574
|
-
#
|
|
4575
|
-
#
|
|
4576
|
-
#
|
|
4577
|
-
#
|
|
4578
|
-
#
|
|
4591
|
+
# Shared sort-family Face gate. Every kernel of the family answers
|
|
4592
|
+
# positions, so it runs on the array the Face's order is read from
|
|
4593
|
+
# (ca_order_key): the storage of an ORDERABLE Face, the Face's own dense
|
|
4594
|
+
# rank for a fixlen storage that is not ORDERABLE (CAConstString's offsets,
|
|
4595
|
+
# a record's bytes), and a refusal otherwise. `src`/`self` are rebound to
|
|
4596
|
+
# that array.
|
|
4579
4597
|
def self.emit_sort_face_gate(io, name)
|
|
4580
4598
|
io.puts " if ( ca_is_face(src) ) {"
|
|
4581
|
-
io.puts "
|
|
4582
|
-
io.puts "
|
|
4583
|
-
io.puts " VALUE _st = rb_ca_strip_face_value(self);"
|
|
4584
|
-
io.puts " CArray *_sc;"
|
|
4585
|
-
io.puts " GetCArray(_st, _sc);"
|
|
4586
|
-
io.puts " if ( _sc->data_type != CA_FIXLEN && ! _orderable ) {"
|
|
4587
|
-
io.puts %Q[ ca_face_refuse_order(ca_calling_method_name(), _face, "order");]
|
|
4588
|
-
io.puts " }"
|
|
4589
|
-
io.puts " self = _st;"
|
|
4590
|
-
io.puts " src = _sc;"
|
|
4599
|
+
io.puts " self = ca_order_key(self, ca_calling_method_name());"
|
|
4600
|
+
io.puts " GetCArray(self, src);"
|
|
4591
4601
|
io.puts " }"
|
|
4592
4602
|
end
|
|
4593
4603
|
|
|
4594
|
-
# Reduce-family Face gate.
|
|
4595
|
-
#
|
|
4596
|
-
#
|
|
4604
|
+
# Reduce-family Face gate. Descends an ORDERABLE Face to its storage and
|
|
4605
|
+
# refuses any other (ca_face_require_orderable): a fixlen storage is
|
|
4606
|
+
# reduced by memcmp only when the Face declares that its bytes are its
|
|
4607
|
+
# order (CAFixlenString). CAConstString and CARecord, whose bytes are no
|
|
4608
|
+
# order, answer min / max / minmax in Ruby and do not reach this gate.
|
|
4609
|
+
# self/src are rebound to storage. When
|
|
4597
4610
|
# `relift` is set (value-returning kernels), the original Face is saved to
|
|
4598
4611
|
# the function-scope `_reduce_face` so the dispatcher can re-lift the output.
|
|
4599
4612
|
def self.emit_reduce_face_gate(io, name, relift)
|
|
@@ -4602,13 +4615,11 @@ module MkKernel
|
|
|
4602
4615
|
end
|
|
4603
4616
|
io.puts " if ( ca_is_face(src) ) {"
|
|
4604
4617
|
io.puts " VALUE _face = self;"
|
|
4605
|
-
io.puts "
|
|
4606
|
-
io.puts " VALUE _st = rb_ca_strip_face_value(self);"
|
|
4618
|
+
io.puts " VALUE _st;"
|
|
4607
4619
|
io.puts " CArray *_sc;"
|
|
4620
|
+
io.puts %Q[ ca_face_require_orderable(_face, ca_calling_method_name(), "order");]
|
|
4621
|
+
io.puts " _st = rb_ca_strip_face_value(self);"
|
|
4608
4622
|
io.puts " GetCArray(_st, _sc);"
|
|
4609
|
-
io.puts " if ( _sc->data_type != CA_FIXLEN && ! _orderable ) {"
|
|
4610
|
-
io.puts %Q[ ca_face_refuse_order(ca_calling_method_name(), _face, "order");]
|
|
4611
|
-
io.puts " }"
|
|
4612
4623
|
if relift
|
|
4613
4624
|
io.puts " _reduce_face = _face;"
|
|
4614
4625
|
end
|
|
@@ -4713,10 +4724,10 @@ module MkKernel
|
|
|
4713
4724
|
io.puts " CArray *src;"
|
|
4714
4725
|
io.puts " GetCArray(self, src);"
|
|
4715
4726
|
emit_lazy_evaluated(io)
|
|
4716
|
-
io.puts " /* Face gate:
|
|
4717
|
-
io.puts "
|
|
4718
|
-
io.puts "
|
|
4719
|
-
io.puts "
|
|
4727
|
+
io.puts " /* Face gate: run on the array the Face's order is read from."
|
|
4728
|
+
io.puts " It is never a Face, so the switch below and every index/addr"
|
|
4729
|
+
io.puts " template output build on a plain array, not a face-lifted"
|
|
4730
|
+
io.puts " (NULL-ptr) view. */"
|
|
4720
4731
|
emit_sort_face_gate(io, name)
|
|
4721
4732
|
if k[:mask_self] == :raise
|
|
4722
4733
|
io.puts " /* sort family rejects masked sources globally (R3 / Q3): "
|
|
@@ -4725,11 +4736,7 @@ module MkKernel
|
|
|
4725
4736
|
io.puts %Q[ rb_raise(rb_eArgError, "%s: masked input not supported (use ca.value or ca.strip_mask(fill))", ca_calling_method_name());]
|
|
4726
4737
|
io.puts " }"
|
|
4727
4738
|
end
|
|
4728
|
-
io.puts " int axis = (
|
|
4729
|
-
io.puts " if ( axis < 0 ) axis += src->ndim;"
|
|
4730
|
-
io.puts " if ( axis < 0 || axis >= src->ndim ) {"
|
|
4731
|
-
io.puts %Q[ rb_raise(rb_eArgError, "%s: axis %d out of range for ndim %d", ca_calling_method_name(), NUM2INT(vaxis), src->ndim);]
|
|
4732
|
-
io.puts " }"
|
|
4739
|
+
io.puts " int axis = ca_normalize_axis_arg(vaxis, src->ndim, NULL);"
|
|
4733
4740
|
io.puts " switch ( src->data_type ) {"
|
|
4734
4741
|
k[:source].each do |s|
|
|
4735
4742
|
si = DTYPES[s]
|
|
@@ -4825,8 +4832,7 @@ module MkKernel
|
|
|
4825
4832
|
io.puts " CArray *src;"
|
|
4826
4833
|
io.puts " GetCArray(self, src);"
|
|
4827
4834
|
emit_lazy_evaluated(io)
|
|
4828
|
-
io.puts " /* Face gate (partition): same as the sort dispatcher
|
|
4829
|
-
io.puts " fixlen -> memcmp default, numeric -> ORDERABLE required. */"
|
|
4835
|
+
io.puts " /* Face gate (partition): same as the sort dispatcher. */"
|
|
4830
4836
|
emit_sort_face_gate(io, name)
|
|
4831
4837
|
if k[:mask_self] == :raise
|
|
4832
4838
|
io.puts " /* sort family rejects masked sources globally (R3 / Q3): "
|
|
@@ -4835,11 +4841,7 @@ module MkKernel
|
|
|
4835
4841
|
io.puts %Q[ rb_raise(rb_eArgError, "%s: masked input not supported (use ca.value or ca.strip_mask(fill))", ca_calling_method_name());]
|
|
4836
4842
|
io.puts " }"
|
|
4837
4843
|
end
|
|
4838
|
-
io.puts " int axis = (
|
|
4839
|
-
io.puts " if ( axis < 0 ) axis += src->ndim;"
|
|
4840
|
-
io.puts " if ( axis < 0 || axis >= src->ndim ) {"
|
|
4841
|
-
io.puts %Q[ rb_raise(rb_eArgError, "%s: axis %d out of range for ndim %d", ca_calling_method_name(), NUM2INT(vaxis), src->ndim);]
|
|
4842
|
-
io.puts " }"
|
|
4844
|
+
io.puts " int axis = ca_normalize_axis_arg(vaxis, src->ndim, NULL);"
|
|
4843
4845
|
io.puts " ca_size_t kth_raw = (ca_size_t) ca_integer_arg(vkth, \"kth\", NULL);"
|
|
4844
4846
|
io.puts " ca_size_t kth = kth_raw;"
|
|
4845
4847
|
io.puts " ca_size_t axis_n = src->dim[axis];"
|
|
@@ -5017,7 +5019,7 @@ module MkKernel
|
|
|
5017
5019
|
io.puts
|
|
5018
5020
|
io.puts "/* ===== #{k[:name]}_ki ============================================ */"
|
|
5019
5021
|
k[:source].each do |src|
|
|
5020
|
-
emit_native_for(io, k, src) { |o| emit_search_native(o, k, src) }
|
|
5022
|
+
emit_native_for(io, k, src, guard: true) { |o| emit_search_native(o, k, src) }
|
|
5021
5023
|
end
|
|
5022
5024
|
emit_search_dispatch(io, k)
|
|
5023
5025
|
end
|
|
@@ -5175,56 +5177,41 @@ module MkKernel
|
|
|
5175
5177
|
# flat array path. op_mask is lazily allocated on the first no-match.
|
|
5176
5178
|
undef_nm = k[:undef_no_match]
|
|
5177
5179
|
nm_cond = "result == (#{oi[:c]})(#{no_match})"
|
|
5178
|
-
#
|
|
5179
|
-
#
|
|
5180
|
-
#
|
|
5181
|
-
|
|
5182
|
-
# op_mask declaration (only when an output cell can be masked);
|
|
5183
|
-
# content-only (the heredoc carries indentation).
|
|
5184
|
-
op_mask_decl = (undef_nm || undef_q) \
|
|
5185
|
-
? "boolean8_t *op_mask = NULL; /* lazily allocated on the first masked output cell */" \
|
|
5186
|
-
: "/* (no masked output cell) */"
|
|
5180
|
+
# A masked query cell yields a masked output cell, so an undetermined
|
|
5181
|
+
# query cannot be answered from the value that happens to sit under the
|
|
5182
|
+
# mask. Shares op_mask with undef_no_match.
|
|
5183
|
+
op_mask_decl = "boolean8_t *op_mask = NULL; /* lazily allocated on the first masked output cell */"
|
|
5187
5184
|
|
|
5188
5185
|
# Case A scalar UNDEF query (also reached by the A1 single-element
|
|
5189
5186
|
# reduction when that element is masked): every output cell is
|
|
5190
5187
|
# undetermined, so short-circuit before the query is coerced to a number.
|
|
5191
|
-
undef_query_a =
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
C
|
|
5205
|
-
else
|
|
5206
|
-
"/* (mask_query: :ignore) */"
|
|
5207
|
-
end
|
|
5188
|
+
undef_query_a = <<~C.chomp
|
|
5189
|
+
if ( rval == CA_UNDEF ) {
|
|
5190
|
+
int8_t undef_axes[1] = { (int8_t) axis };
|
|
5191
|
+
VALUE voutu = rb_ca_new_reduced(self, undef_axes, 1, #{oi[:ca]}, 0);
|
|
5192
|
+
CArray *cou;
|
|
5193
|
+
GetCArray(voutu, cou);
|
|
5194
|
+
MEMZERO((#{oi[:c]} *) cou->ptr, #{oi[:c]}, cou->elements);
|
|
5195
|
+
ca_create_mask(cou);
|
|
5196
|
+
memset(cou->mask->ptr, 1, (size_t) cou->elements);
|
|
5197
|
+
if ( ca->ndim == 1 ) { return Qnil; }
|
|
5198
|
+
return voutu;
|
|
5199
|
+
}
|
|
5200
|
+
C
|
|
5208
5201
|
|
|
5209
5202
|
# Case B/C query mask base. NULL when the query carries no mask.
|
|
5210
|
-
cv_mask_decl_bc =
|
|
5211
|
-
? "boolean8_t *cv_mask_base = ca_has_mask(cv) ? (boolean8_t *) cv->mask->ptr : NULL;" \
|
|
5212
|
-
: "/* (mask_query: :ignore) */"
|
|
5203
|
+
cv_mask_decl_bc = "boolean8_t *cv_mask_base = ca_has_mask(cv) ? (boolean8_t *) cv->mask->ptr : NULL;"
|
|
5213
5204
|
|
|
5214
5205
|
# Per-cell guard wrapping the body + write in case B/C. A masked query
|
|
5215
5206
|
# cell skips the body entirely and marks the output cell instead.
|
|
5216
|
-
undef_query_open_bc =
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
else
|
|
5225
|
-
"/* (mask_query: :ignore) */"
|
|
5226
|
-
end
|
|
5227
|
-
undef_query_close_bc = undef_q ? "}" : "/* (mask_query: :ignore) */"
|
|
5207
|
+
undef_query_open_bc = <<~C.chomp
|
|
5208
|
+
if ( cv_mask_base && cv_mask_base[val_offset / cv->bytes] ) {
|
|
5209
|
+
if ( ! op_mask ) { ca_create_mask(co); op_mask = (boolean8_t *) co->mask->ptr; }
|
|
5210
|
+
op_mask[flat] = 1;
|
|
5211
|
+
op[flat] = (#{oi[:c]}) 0; /* undetermined query; the mask bit is what counts */
|
|
5212
|
+
} else {
|
|
5213
|
+
C
|
|
5214
|
+
undef_query_close_bc = "}"
|
|
5228
5215
|
|
|
5229
5216
|
# Per-cell write block for case A (scalar query). `out_i` is the output
|
|
5230
5217
|
# cursor, `co` the output CArray. When undef_no_match: a no-match masks
|
|
@@ -5292,7 +5279,7 @@ module MkKernel
|
|
|
5292
5279
|
CArray *co;
|
|
5293
5280
|
GetCArray(vout, co);
|
|
5294
5281
|
#{oi[:c]} *op = (#{oi[:c]} *) co->ptr;
|
|
5295
|
-
#{op_mask_decl}
|
|
5282
|
+
#{undef_nm ? op_mask_decl : ""}
|
|
5296
5283
|
|
|
5297
5284
|
ca_iter_state st;
|
|
5298
5285
|
int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
|
|
@@ -5514,6 +5501,12 @@ module MkKernel
|
|
|
5514
5501
|
#{op_mask_decl}
|
|
5515
5502
|
|
|
5516
5503
|
#{entity_bc}
|
|
5504
|
+
/* The output mask is made before the window opens, since making it
|
|
5505
|
+
allocates; it comes off again below when no cell needed it. */
|
|
5506
|
+
if ( #{undef_nm ? "1" : "ca_has_mask(cv)"} ) {
|
|
5507
|
+
ca_create_mask(co);
|
|
5508
|
+
op_mask = (boolean8_t *) co->mask->ptr;
|
|
5509
|
+
}
|
|
5517
5510
|
/* Attach self + val; rev4 uses raw row-major byte addressing. */
|
|
5518
5511
|
ca_attach_n(2, ca, cv); /* window: numeric bodies do not raise; the object lane reads entities */
|
|
5519
5512
|
|
|
@@ -5620,6 +5613,11 @@ module MkKernel
|
|
|
5620
5613
|
|
|
5621
5614
|
ca_detach(ca);
|
|
5622
5615
|
ca_detach(cv);
|
|
5616
|
+
if ( op_mask && ! memchr(op_mask, 1, (size_t) co->elements) ) {
|
|
5617
|
+
CArray *unused = co->mask;
|
|
5618
|
+
co->mask = NULL;
|
|
5619
|
+
ca_free(unused);
|
|
5620
|
+
}
|
|
5623
5621
|
return vout;
|
|
5624
5622
|
}
|
|
5625
5623
|
C
|
|
@@ -5642,21 +5640,15 @@ module MkKernel
|
|
|
5642
5640
|
end
|
|
5643
5641
|
io.puts "{"
|
|
5644
5642
|
io.puts " CArray *src;"
|
|
5645
|
-
io.puts " VALUE self_ref = self; /* pre-strip Face, for to_comparable */"
|
|
5646
5643
|
io.puts " int self_was_face = 0;"
|
|
5647
5644
|
io.puts " GetCArray(self, src);"
|
|
5648
5645
|
emit_lazy_evaluated(io)
|
|
5649
|
-
io.puts " /* Face gate (self):
|
|
5650
|
-
io.puts "
|
|
5651
|
-
io.puts "
|
|
5652
|
-
io.puts " query into the same space. */"
|
|
5646
|
+
io.puts " /* Face gate (self): a Face reference is searched on its numeric"
|
|
5647
|
+
io.puts " storage, which ORDERABLE licenses; the query gate below brings"
|
|
5648
|
+
io.puts " the reference and the query into one space and descends. */"
|
|
5653
5649
|
io.puts " if ( ca_is_face(src) ) {"
|
|
5654
|
-
io.puts
|
|
5655
|
-
io.puts %Q[ ca_face_refuse_order(ca_calling_method_name(), self, "order");]
|
|
5656
|
-
io.puts " }"
|
|
5650
|
+
io.puts %Q[ ca_face_require_orderable(self, ca_calling_method_name(), "order");]
|
|
5657
5651
|
io.puts " self_was_face = 1;"
|
|
5658
|
-
io.puts " self = rb_ca_strip_face_value(self);"
|
|
5659
|
-
io.puts " GetCArray(self, src);"
|
|
5660
5652
|
io.puts " }"
|
|
5661
5653
|
|
|
5662
5654
|
if has_eps
|
|
@@ -5667,38 +5659,38 @@ module MkKernel
|
|
|
5667
5659
|
io.puts " VALUE rval = argv[0];"
|
|
5668
5660
|
io.puts " VALUE raxis = argv[1];"
|
|
5669
5661
|
io.puts " VALUE reps = (argc >= 3) ? argv[2] : Qnil;"
|
|
5662
|
+
io.puts " /* A tolerance is a distance: a negative one (or NaN) means nothing. */"
|
|
5663
|
+
io.puts " if ( ! NIL_P(reps) ) {"
|
|
5664
|
+
io.puts " double eps_given = NUM2DBL(reps);"
|
|
5665
|
+
io.puts " if ( ! ( eps_given >= 0.0 ) ) {"
|
|
5666
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: eps must be zero or positive (got %"PRIsVALUE")", ca_calling_method_name(), rb_inspect(reps));]
|
|
5667
|
+
io.puts " }"
|
|
5668
|
+
io.puts " }"
|
|
5670
5669
|
end
|
|
5671
|
-
io.puts " /* Query gate: the reference Face
|
|
5672
|
-
io.puts " space (
|
|
5670
|
+
io.puts " /* Query gate: the reference Face and the query are brought into one"
|
|
5671
|
+
io.puts " space (ca_face_reconcile_pair), then the reference descends to"
|
|
5672
|
+
io.puts " its storage. */"
|
|
5673
5673
|
io.puts " if ( self_was_face ) {"
|
|
5674
|
-
io.puts "
|
|
5674
|
+
io.puts " volatile VALUE ref = self, query = rval;"
|
|
5675
|
+
io.puts " ca_face_reconcile_pair(&ref, &query, ca_calling_method_name());"
|
|
5676
|
+
io.puts " rval = query;"
|
|
5677
|
+
io.puts " self = rb_ca_strip_face_value(ref);"
|
|
5678
|
+
io.puts " GetCArray(self, src);"
|
|
5675
5679
|
io.puts " }"
|
|
5676
5680
|
|
|
5677
|
-
# The query and the reference are compared in the type they
|
|
5678
|
-
# `a.eq(q)` compares
|
|
5679
|
-
#
|
|
5680
|
-
# reference
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
# a cast view of that type. An Integer scalar keeps the reference's type
|
|
5684
|
-
# and is refused when it does not fit.
|
|
5685
|
-
io.puts " if ( ca_is_integer_type(src) || ca_is_float_type(src) || src->data_type == CA_BOOLEAN ) {"
|
|
5686
|
-
io.puts " int8_t qt = src->data_type;"
|
|
5687
|
-
io.puts " if ( RB_FLOAT_TYPE_P(rval) ) {"
|
|
5688
|
-
io.puts " qt = ca_promote_scalar_type(src->data_type, rval, CA_FLOAT64);"
|
|
5689
|
-
io.puts " } else if ( rb_obj_is_kind_of(rval, rb_cCArray) ) {"
|
|
5690
|
-
io.puts " CArray *qv;"
|
|
5691
|
-
io.puts " TypedData_Get_Struct(rval, CArray, &carray_data_type, qv);"
|
|
5692
|
-
io.puts " if ( ca_is_integer_type(qv) || ca_is_float_type(qv) || qv->data_type == CA_BOOLEAN ) {"
|
|
5693
|
-
io.puts " qt = ca_value_match_type(ca_promote_type(src->data_type, qv->data_type),"
|
|
5694
|
-
io.puts " src->data_type, qv->data_type);"
|
|
5695
|
-
io.puts " }"
|
|
5696
|
-
io.puts " }"
|
|
5681
|
+
# The query and the reference are compared in the type they match in,
|
|
5682
|
+
# as `a.eq(q)` compares (ca_query_match_type): the reference is read
|
|
5683
|
+
# through a cast view of that type, so the query is never cut to the
|
|
5684
|
+
# reference's type.
|
|
5685
|
+
io.puts " if ( ca_query_match_applies(src, rval) ) {"
|
|
5686
|
+
io.puts " int8_t qt = ca_query_match_type(src->data_type, rval);"
|
|
5697
5687
|
io.puts " if ( qt != src->data_type ) {"
|
|
5698
5688
|
io.puts " self = rb_ca_wrap_readonly(self, INT2NUM(qt));"
|
|
5699
5689
|
io.puts " GetCArray(self, src);"
|
|
5700
5690
|
io.puts " }"
|
|
5701
5691
|
io.puts " }"
|
|
5692
|
+
io.puts " self = ca_fixlen_widen_for_query(self, rval);"
|
|
5693
|
+
io.puts " GetCArray(self, src);"
|
|
5702
5694
|
|
|
5703
5695
|
case k[:mask_self]
|
|
5704
5696
|
when :raise
|
|
@@ -5710,11 +5702,7 @@ module MkKernel
|
|
|
5710
5702
|
# body handles mask_in or ignores it
|
|
5711
5703
|
end
|
|
5712
5704
|
|
|
5713
|
-
io.puts " int axis = (
|
|
5714
|
-
io.puts " if ( axis < 0 ) axis += src->ndim;"
|
|
5715
|
-
io.puts " if ( axis < 0 || axis >= src->ndim ) {"
|
|
5716
|
-
io.puts %Q[ rb_raise(rb_eArgError, "%s: axis %d out of range for ndim %d", ca_calling_method_name(), NUM2INT(raxis), src->ndim);]
|
|
5717
|
-
io.puts " }"
|
|
5705
|
+
io.puts " int axis = ca_normalize_axis_arg(raxis, src->ndim, NULL);"
|
|
5718
5706
|
io.puts " switch ( src->data_type ) {"
|
|
5719
5707
|
k[:source].each do |s|
|
|
5720
5708
|
si = DTYPES[s]
|
|
@@ -5734,13 +5722,14 @@ module MkKernel
|
|
|
5734
5722
|
# (`ca_monop_<name>_<suffix>`). mkmath uses typedef names
|
|
5735
5723
|
# `float32_t` / `float64_t` here, not the raw C types `float` / `double`,
|
|
5736
5724
|
# so that `ca_monop_dispatch.c`'s extern references stay ABI-stable.
|
|
5737
|
-
# For all other data_types, the suffix
|
|
5725
|
+
# For all other data_types, the suffix is `DTYPES[src][:c]` without the
|
|
5726
|
+
# `ca_u_` of its unaligned cell typedef (ca_u_int32_t -> int32_t).
|
|
5738
5727
|
def self.math_suffix(src)
|
|
5739
5728
|
case src
|
|
5740
5729
|
when :f32 then "float32_t"
|
|
5741
5730
|
when :f64 then "float64_t"
|
|
5742
5731
|
when :fixlen then "fixlen" # symbol suffix differs from c-type "char"
|
|
5743
|
-
else DTYPES[src][:c]
|
|
5732
|
+
else DTYPES[src][:c].sub(/\Aca_u_/, "")
|
|
5744
5733
|
end
|
|
5745
5734
|
end
|
|
5746
5735
|
|
|
@@ -5819,7 +5808,8 @@ module MkKernel
|
|
|
5819
5808
|
uses_input = raw.include?("#1")
|
|
5820
5809
|
# SL.2.x phase 2 (monop emit substrate hardening, MEMO_MONOP_STRIDED_AUDIT.md):
|
|
5821
5810
|
# 3-way split (mask / contig / strided) symmetric with binop SL.2.x phase 1.
|
|
5822
|
-
# contig branch (i1==1 && i2==1) uses index form + `
|
|
5811
|
+
# contig branch (i1==1 && i2==1) uses index form + `omp simd` (`__restrict__`
|
|
5812
|
+
# only when input and output are apart).
|
|
5823
5813
|
# strided branch uses `p += i` increment form (per-iter multiply removed).
|
|
5824
5814
|
# mask branch unchanged (per-cell predicate, not SIMD-amenable).
|
|
5825
5815
|
if uses_input
|
|
@@ -5827,11 +5817,11 @@ module MkKernel
|
|
|
5827
5817
|
|
|
5828
5818
|
static void
|
|
5829
5819
|
ca_monop_#{k[:name]}_#{suffix} (ca_size_t n, boolean8_t *m,
|
|
5830
|
-
char *
|
|
5831
|
-
char *
|
|
5820
|
+
char *ptr1, ca_size_t i1,
|
|
5821
|
+
char *ptr2, ca_size_t i2)
|
|
5832
5822
|
{
|
|
5833
|
-
#{c_type_in} *
|
|
5834
|
-
#{c_type_out} *
|
|
5823
|
+
#{c_type_in} *q1 = (#{c_type_in} *) ptr1;
|
|
5824
|
+
#{c_type_out} *q2 = (#{c_type_out} *) ptr2;
|
|
5835
5825
|
ca_size_t k;
|
|
5836
5826
|
if ( m ) {
|
|
5837
5827
|
#{c_type_in} *p1 = q1;
|
|
@@ -5847,17 +5837,38 @@ module MkKernel
|
|
|
5847
5837
|
}
|
|
5848
5838
|
} else if ( i1 == 1 && i2 == 1 ) {
|
|
5849
5839
|
/* contig branch: SIMD-friendly form. Index-form `q[k]` exposes
|
|
5850
|
-
the unit-stride access to the vectorizer
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5840
|
+
the unit-stride access to the vectorizer. Element-wise monop
|
|
5841
|
+
has no reassoc, so ε-close numeric contract is unaffected.
|
|
5842
|
+
The input and output are either apart, the same cells (a
|
|
5843
|
+
bang, or the lazy scratch applied in place), or overlapping
|
|
5844
|
+
otherwise; `__restrict__` is promised only when apart, since
|
|
5845
|
+
the body may store part of a cell before it has read it. */
|
|
5854
5846
|
#{c_type_in} *p1;
|
|
5855
5847
|
#{c_type_out} *p2;
|
|
5856
5848
|
(void)p1; (void)p2;
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5849
|
+
uintptr_t a1 = (uintptr_t) q1, a2 = (uintptr_t) q2;
|
|
5850
|
+
if ( a2 + n * sizeof(*q2) <= a1 || a1 + n * sizeof(*q1) <= a2 ) {
|
|
5851
|
+
#{c_type_in} * __restrict__ r1 = q1;
|
|
5852
|
+
#{c_type_out} * __restrict__ r2 = q2;
|
|
5853
|
+
#pragma omp simd
|
|
5854
|
+
for (k=0; k<n; k++) {
|
|
5855
|
+
p1 = r1 + k; p2 = r2 + k;
|
|
5856
|
+
{ #{expr_body} }
|
|
5857
|
+
}
|
|
5858
|
+
}
|
|
5859
|
+
else if ( a1 == a2 && sizeof(*q1) == sizeof(*q2) ) {
|
|
5860
|
+
/* in place: an iteration reads and writes only its own cell */
|
|
5861
|
+
#pragma omp simd
|
|
5862
|
+
for (k=0; k<n; k++) {
|
|
5863
|
+
p1 = q1 + k; p2 = q2 + k;
|
|
5864
|
+
{ #{expr_body} }
|
|
5865
|
+
}
|
|
5866
|
+
}
|
|
5867
|
+
else {
|
|
5868
|
+
for (k=0; k<n; k++) {
|
|
5869
|
+
p1 = q1 + k; p2 = q2 + k;
|
|
5870
|
+
{ #{expr_body} }
|
|
5871
|
+
}
|
|
5861
5872
|
}
|
|
5862
5873
|
} else {
|
|
5863
5874
|
/* strided branch: numo-shape `p += s` increment form. */
|
|
@@ -6043,7 +6054,8 @@ module MkKernel
|
|
|
6043
6054
|
expr_body = raw.gsub(/#1/, "*p1").gsub(/#2/, "*p2").gsub(/#3/, "*p3").gsub(/<type>/, suffix)
|
|
6044
6055
|
# Option B+C pilot (binop emit substrate hardening, MEMO_BINOP_STRIDED_AUDIT.md):
|
|
6045
6056
|
# The no-mask body is split into a contig branch (i1==i2==i3==1) using the
|
|
6046
|
-
# `q[k]` index form with
|
|
6057
|
+
# `q[k]` index form with `#pragma omp simd` (`__restrict__`-qualified only
|
|
6058
|
+
# when the output is apart from both inputs)
|
|
6047
6059
|
# (SL.2.x-style insurance, element-wise has no reassoc -> ε-close unaffected),
|
|
6048
6060
|
# and a strided branch using `p += i` increment form (numo-shape inner loop).
|
|
6049
6061
|
# `i1`/`i2`/`i3` remain element units (NOT byte) -- byte-flip is Option A scope.
|
|
@@ -6053,13 +6065,13 @@ module MkKernel
|
|
|
6053
6065
|
|
|
6054
6066
|
static void
|
|
6055
6067
|
ca_binop_#{k[:name]}_#{suffix} (ca_size_t n, boolean8_t *m,
|
|
6056
|
-
char *
|
|
6057
|
-
char *
|
|
6058
|
-
char *
|
|
6068
|
+
char *ptr1, ca_size_t i1,
|
|
6069
|
+
char *ptr2, ca_size_t i2,
|
|
6070
|
+
char *ptr3, ca_size_t i3)
|
|
6059
6071
|
{
|
|
6060
|
-
#{c_type} *
|
|
6061
|
-
#{c_type} *
|
|
6062
|
-
#{c_type} *
|
|
6072
|
+
#{c_type} *q1 = (#{c_type} *) ptr1;
|
|
6073
|
+
#{c_type} *q2 = (#{c_type} *) ptr2;
|
|
6074
|
+
#{c_type} *q3 = (#{c_type} *) ptr3;
|
|
6063
6075
|
ca_size_t k;
|
|
6064
6076
|
if ( m ) {
|
|
6065
6077
|
#{c_type} *p1 = q1, *p2 = q2, *p3 = q3;
|
|
@@ -6075,16 +6087,41 @@ module MkKernel
|
|
|
6075
6087
|
}
|
|
6076
6088
|
} else if ( i1 == 1 && i2 == 1 && i3 == 1 ) {
|
|
6077
6089
|
/* contig branch: SIMD-friendly form. Index-form `q[k]` exposes
|
|
6078
|
-
the unit-stride access to the vectorizer
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6090
|
+
the unit-stride access to the vectorizer. Element-wise binop
|
|
6091
|
+
has no reassoc, so ε-close numeric contract is unaffected.
|
|
6092
|
+
The output is apart from each input, the same cells as it (a
|
|
6093
|
+
bang), or overlaps it otherwise; `__restrict__` is promised
|
|
6094
|
+
only when apart from both, since the body may store part of a
|
|
6095
|
+
cell before it has read it. */
|
|
6082
6096
|
#{c_type} *p1, *p2, *p3;
|
|
6097
|
+
uintptr_t a1 = (uintptr_t) q1, a2 = (uintptr_t) q2, a3 = (uintptr_t) q3;
|
|
6098
|
+
uintptr_t nb = (uintptr_t) n * sizeof(*q3);
|
|
6099
|
+
int apart1 = ( a3 + nb <= a1 || a1 + nb <= a3 );
|
|
6100
|
+
int apart2 = ( a3 + nb <= a2 || a2 + nb <= a3 );
|
|
6083
6101
|
(void)p1; (void)p2; (void)p3;
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6102
|
+
if ( apart1 && apart2 ) {
|
|
6103
|
+
#{c_type} * __restrict__ r1 = q1;
|
|
6104
|
+
#{c_type} * __restrict__ r2 = q2;
|
|
6105
|
+
#{c_type} * __restrict__ r3 = q3;
|
|
6106
|
+
#pragma omp simd
|
|
6107
|
+
for (k=0; k<n; k++) {
|
|
6108
|
+
p1 = r1 + k; p2 = r2 + k; p3 = r3 + k;
|
|
6109
|
+
{ #{expr_body} }
|
|
6110
|
+
}
|
|
6111
|
+
}
|
|
6112
|
+
else if ( ( apart1 || a1 == a3 ) && ( apart2 || a2 == a3 ) ) {
|
|
6113
|
+
/* in place: an iteration reads and writes only its own cell */
|
|
6114
|
+
#pragma omp simd
|
|
6115
|
+
for (k=0; k<n; k++) {
|
|
6116
|
+
p1 = q1 + k; p2 = q2 + k; p3 = q3 + k;
|
|
6117
|
+
{ #{expr_body} }
|
|
6118
|
+
}
|
|
6119
|
+
}
|
|
6120
|
+
else {
|
|
6121
|
+
for (k=0; k<n; k++) {
|
|
6122
|
+
p1 = q1 + k; p2 = q2 + k; p3 = q3 + k;
|
|
6123
|
+
{ #{expr_body} }
|
|
6124
|
+
}
|
|
6088
6125
|
}
|
|
6089
6126
|
} else {
|
|
6090
6127
|
/* strided branch: numo-shape `p += s` increment form. */
|
|
@@ -6539,19 +6576,29 @@ module MkKernel
|
|
|
6539
6576
|
# the source's data type. Left out: a boolean target, which checks its
|
|
6540
6577
|
# input and raises, and a float or complex source to an integer target,
|
|
6541
6578
|
# where an out-of-range value or NaN has no defined C result for a
|
|
6542
|
-
# second compilation to agree on.
|
|
6579
|
+
# second compilation to agree on. An int64 or uint64 source to float32
|
|
6580
|
+
# or cmplx64 goes through double, as ca_cast_block does.
|
|
6543
6581
|
CAST_SOURCES = %i[bool i8 u8 i16 u16 i32 u32 i64 u64 f32 f64
|
|
6544
6582
|
cmplx64 cmplx128].freeze
|
|
6545
6583
|
CAST_TARGETS = %i[i8 u8 i16 u16 i32 u32 i64 u64 f32 f64
|
|
6546
6584
|
cmplx64 cmplx128].freeze
|
|
6547
6585
|
CAST_INEXACT = %i[f32 f64 cmplx64 cmplx128].freeze
|
|
6548
6586
|
|
|
6587
|
+
# The plain C type of a data type, for a body that leaves this file
|
|
6588
|
+
# (CArray.__kernel_body__, a fusion plan): the unaligned typedef in :c is
|
|
6589
|
+
# about how a kernel reaches a cell, not about the value.
|
|
6590
|
+
def self.value_c(src)
|
|
6591
|
+
{ "ca_u_float32_t" => "float", "ca_u_float64_t" => "double" }
|
|
6592
|
+
.fetch(DTYPES[src][:c]) { |c| c.sub(/\Aca_u_/, "") }
|
|
6593
|
+
end
|
|
6594
|
+
|
|
6549
6595
|
def self.cast_body_rows
|
|
6550
6596
|
CAST_SOURCES.product(CAST_TARGETS).filter_map { |src, dst|
|
|
6551
6597
|
next if CAST_INEXACT.include?(src) && !CAST_INEXACT.include?(dst)
|
|
6552
6598
|
name = DTYPES[dst][:ca].sub(/\ACA_/, "").downcase
|
|
6599
|
+
via = ( %i[i64 u64].include?(src) && %i[f32 cmplx64].include?(dst) ) ? "(double)" : ""
|
|
6553
6600
|
["monop", "cast_#{name}", DTYPES[src][:ca].sub(/\ACA_/, "").downcase,
|
|
6554
|
-
"(#2) = (#{
|
|
6601
|
+
"(#2) = (#{value_c(dst)})#{via}(#1);"]
|
|
6555
6602
|
}
|
|
6556
6603
|
end
|
|
6557
6604
|
|
|
@@ -6756,12 +6803,16 @@ module MkKernel
|
|
|
6756
6803
|
# reduce: default false (E.8 retire), opt-in via bind_ruby: true
|
|
6757
6804
|
# (used by CF.1 count_equal smoke + future kernel testing)
|
|
6758
6805
|
# sort: default true, opt-out via bind_ruby: false
|
|
6759
|
-
# scan:
|
|
6806
|
+
# scan: bound as <name>_ki, or only privately as <name> (private:)
|
|
6760
6807
|
bind = case k[:kind]
|
|
6761
6808
|
when :reduce then k[:bind_ruby] == true
|
|
6762
6809
|
when :sort then k[:bind_ruby] != false
|
|
6810
|
+
when :scan then ! k[:private]
|
|
6763
6811
|
else true
|
|
6764
6812
|
end
|
|
6813
|
+
if k[:kind] == :scan && k[:private]
|
|
6814
|
+
io.puts %Q[ rb_define_private_method(rb_cCArray, "#{k[:name]}", rb_ca_#{k[:name]}_ki, #{arity});]
|
|
6815
|
+
end
|
|
6765
6816
|
# Smoke kernels (e.g. count_equal) expose their _ki binding only
|
|
6766
6817
|
# in dev builds, gated by CARRAY_DEV_BUILD. See
|
|
6767
6818
|
# devel/PROPOSAL_SMOKE_DEV_BUILD_GATE.md.
|
|
@@ -6774,7 +6825,7 @@ module MkKernel
|
|
|
6774
6825
|
# directly without a Ruby wrapper. This is paired with
|
|
6775
6826
|
# axis_default: :flatten because both express the legacy-compat
|
|
6776
6827
|
# convenience of these specific scan kernels.
|
|
6777
|
-
if k[:kind] == :scan && k[:axis_default] == :flatten
|
|
6828
|
+
if k[:kind] == :scan && k[:axis_default] == :flatten && ! k[:private]
|
|
6778
6829
|
io.puts %Q[ rb_define_method(rb_cCArray, "#{k[:name]}", rb_ca_#{k[:name]}_ki, #{arity});]
|
|
6779
6830
|
end
|
|
6780
6831
|
# Phase E rewire: when this kernel replaces a legacy CArray method,
|
|
@@ -7398,7 +7449,7 @@ MkKernel.reduce :wsum,
|
|
|
7398
7449
|
shape: :rev5_strict, mask: :overlay },
|
|
7399
7450
|
mask_policy: :min_count,
|
|
7400
7451
|
identity_on_empty: true, # ERI.2: empty / all-masked -> 0.0 (weighted sum identity)
|
|
7401
|
-
object_escape: :
|
|
7452
|
+
object_escape: :wsum_object, # CA_OBJECT -> (w*self).sum (runtime.rb)
|
|
7402
7453
|
bind_ruby: true,
|
|
7403
7454
|
public_method: true
|
|
7404
7455
|
|
|
@@ -7415,7 +7466,7 @@ MkKernel.reduce :wsum,
|
|
|
7415
7466
|
# - min_count semantic "max masked" -> "min valid required" (Phase E)
|
|
7416
7467
|
# - complex (CMPLX64/128) dropped (= ALL_NUMERIC + :raise)
|
|
7417
7468
|
# - per-axis support gained
|
|
7418
|
-
# CA_OBJECT is handled via object_escape (runtime.rb
|
|
7469
|
+
# CA_OBJECT is handled via object_escape (runtime.rb wmean_object), not
|
|
7419
7470
|
# the numeric kernel: weight-coerce vs object arithmetic is cleaner as a
|
|
7420
7471
|
# (w*self).sum / Σw composition than a dedicated :object kernel branch.
|
|
7421
7472
|
|
|
@@ -7432,7 +7483,7 @@ MkKernel.reduce :wmean,
|
|
|
7432
7483
|
array_arg: { name: :weights, data_type: :promote,
|
|
7433
7484
|
shape: :rev5_strict, mask: :overlay },
|
|
7434
7485
|
mask_policy: :min_count,
|
|
7435
|
-
object_escape: :
|
|
7486
|
+
object_escape: :wmean_object, # CA_OBJECT -> weighted mean via runtime.rb
|
|
7436
7487
|
bind_ruby: true,
|
|
7437
7488
|
public_method: true
|
|
7438
7489
|
|
|
@@ -7457,6 +7508,18 @@ MkKernel.scan :cumsum,
|
|
|
7457
7508
|
fallback: :raise,
|
|
7458
7509
|
axis_default: :flatten
|
|
7459
7510
|
|
|
7511
|
+
# The running sum in the source's own integer type, added without a range
|
|
7512
|
+
# check (int64 wraps, as accumulate does). CATimedelta#cumsum reads it
|
|
7513
|
+
# for an exact running sum of durations.
|
|
7514
|
+
MkKernel.scan :cumsum_in_own_type,
|
|
7515
|
+
source: MkKernel::INT_DTYPES,
|
|
7516
|
+
output: :preserve,
|
|
7517
|
+
init: "0",
|
|
7518
|
+
step: "acc += v; r = acc",
|
|
7519
|
+
fallback: :raise,
|
|
7520
|
+
axis_default: :flatten,
|
|
7521
|
+
private: true
|
|
7522
|
+
|
|
7460
7523
|
MkKernel.scan :cumprod,
|
|
7461
7524
|
source: MkKernel::ALL_NUMERIC + MkKernel::CMPLX_DTYPES + [:bool, :object],
|
|
7462
7525
|
output: { numeric: :f64, complex: :cmplx128, bool: :u64, object: :object },
|
|
@@ -7707,8 +7770,8 @@ MkKernel.search :find_value_index,
|
|
|
7707
7770
|
mask_self: :raise,
|
|
7708
7771
|
fallback: :raise
|
|
7709
7772
|
|
|
7710
|
-
# bsearch_ki: per-slice binary search returning the matching position
|
|
7711
|
-
# (assumes a sorted slab). NaN handling: NaN sorts to end; query == NaN
|
|
7773
|
+
# bsearch_ki: per-slice binary search returning the first matching position
|
|
7774
|
+
# (assumes a sorted slab; of equal cells, the first). NaN handling: NaN sorts to end; query == NaN
|
|
7712
7775
|
# never matches. Hand-rolled strided binary search (= libc bsearch()
|
|
7713
7776
|
# requires contig memory; slab_stride may be > sizeof(T_LOAD) for a
|
|
7714
7777
|
# non-innermost search axis). NaN guards are dead code for integer
|
|
@@ -7732,47 +7795,41 @@ MkKernel.search :bsearch,
|
|
|
7732
7795
|
while ( lo < hi ) {
|
|
7733
7796
|
ca_size_t mid = lo + (hi - lo) / 2;
|
|
7734
7797
|
T_LOAD v = *(T_LOAD *)(slab_ptr + mid * slab_stride);
|
|
7735
|
-
if ( isnan((double) v) ) { hi = mid; continue; }
|
|
7736
7798
|
if ( v < query_val ) lo = mid + 1;
|
|
7737
7799
|
else if ( v > query_val ) hi = mid;
|
|
7738
|
-
else { result = mid;
|
|
7800
|
+
else { result = mid; hi = mid; } /* keep looking left: the first equal cell */
|
|
7739
7801
|
}
|
|
7740
7802
|
}
|
|
7741
7803
|
C
|
|
7742
7804
|
float: <<~C,
|
|
7743
7805
|
result = (ca_size_t) -1;
|
|
7744
|
-
if ( ! isnan((double) query_val) ) {
|
|
7806
|
+
if ( ! isnan((double) query_val) ) { /* a NaN query matches nothing */
|
|
7745
7807
|
ca_size_t lo = 0;
|
|
7746
7808
|
ca_size_t hi = slab_n;
|
|
7747
7809
|
while ( lo < hi ) {
|
|
7748
7810
|
ca_size_t mid = lo + (hi - lo) / 2;
|
|
7749
7811
|
T_LOAD v = *(T_LOAD *)(slab_ptr + mid * slab_stride);
|
|
7750
|
-
|
|
7751
|
-
if (
|
|
7752
|
-
else if (
|
|
7753
|
-
else { result = mid;
|
|
7812
|
+
int c = CA_FLOAT_ORDER_CMP(v, query_val);
|
|
7813
|
+
if ( c < 0 ) lo = mid + 1;
|
|
7814
|
+
else if ( c > 0 ) hi = mid;
|
|
7815
|
+
else { result = mid; hi = mid; } /* keep looking left: the first equal cell */
|
|
7754
7816
|
}
|
|
7755
7817
|
}
|
|
7756
7818
|
C
|
|
7757
7819
|
object: <<~C,
|
|
7758
|
-
/* CA_OBJECT bsearch:
|
|
7759
|
-
slab is sorted by
|
|
7760
|
-
|
|
7820
|
+
/* CA_OBJECT bsearch: the object order (ca_obj_order_cmp), which the
|
|
7821
|
+
slab is assumed to be sorted by; a NaN query matches nothing, as in
|
|
7822
|
+
float. */
|
|
7761
7823
|
result = (ca_size_t) -1;
|
|
7762
7824
|
ca_size_t lo = 0;
|
|
7763
7825
|
ca_size_t hi = CA_OBJ_ISNAN(query_val) ? 0 : slab_n; /* NaN matches nothing */
|
|
7764
7826
|
while ( lo < hi ) {
|
|
7765
7827
|
ca_size_t mid = lo + (hi - lo) / 2;
|
|
7766
7828
|
T_LOAD v = *(T_LOAD *)(slab_ptr + mid * slab_stride);
|
|
7767
|
-
|
|
7768
|
-
VALUE r = rb_funcall(v, rb_intern("<=>"), 1, query_val);
|
|
7769
|
-
if ( NIL_P(r) ) {
|
|
7770
|
-
ca_obj_compare_refused(v, query_val);
|
|
7771
|
-
}
|
|
7772
|
-
int c = NUM2INT(r);
|
|
7829
|
+
int c = ca_obj_order_cmp(v, query_val);
|
|
7773
7830
|
if ( c < 0 ) lo = mid + 1;
|
|
7774
7831
|
else if ( c > 0 ) hi = mid;
|
|
7775
|
-
else { result = mid;
|
|
7832
|
+
else { result = mid; hi = mid; } /* keep looking left: the first equal cell */
|
|
7776
7833
|
}
|
|
7777
7834
|
C
|
|
7778
7835
|
fixlen: <<~C,
|
|
@@ -7788,7 +7845,7 @@ MkKernel.search :bsearch,
|
|
|
7788
7845
|
int c = memcmp(v, query_val, (size_t) ca->bytes);
|
|
7789
7846
|
if ( c < 0 ) lo = mid + 1;
|
|
7790
7847
|
else if ( c > 0 ) hi = mid;
|
|
7791
|
-
else { result = mid;
|
|
7848
|
+
else { result = mid; hi = mid; } /* keep looking left: the first equal cell */
|
|
7792
7849
|
}
|
|
7793
7850
|
C
|
|
7794
7851
|
},
|
|
@@ -7971,25 +8028,24 @@ MkKernel.search :bsearch_addr,
|
|
|
7971
8028
|
while ( lo < hi ) {
|
|
7972
8029
|
ca_size_t mid = lo + (hi - lo) / 2;
|
|
7973
8030
|
T_LOAD v = *(T_LOAD *)(slab_ptr + mid * slab_stride);
|
|
7974
|
-
if ( isnan((double) v) ) { hi = mid; continue; }
|
|
7975
8031
|
if ( v < query_val ) lo = mid + 1;
|
|
7976
8032
|
else if ( v > query_val ) hi = mid;
|
|
7977
|
-
else { result = mid;
|
|
8033
|
+
else { result = mid; hi = mid; } /* keep looking left: the first equal cell */
|
|
7978
8034
|
}
|
|
7979
8035
|
}
|
|
7980
8036
|
C
|
|
7981
8037
|
float: <<~C,
|
|
7982
8038
|
result = (ca_size_t) -1;
|
|
7983
|
-
if ( ! isnan((double) query_val) ) {
|
|
8039
|
+
if ( ! isnan((double) query_val) ) { /* a NaN query matches nothing */
|
|
7984
8040
|
ca_size_t lo = 0;
|
|
7985
8041
|
ca_size_t hi = slab_n;
|
|
7986
8042
|
while ( lo < hi ) {
|
|
7987
8043
|
ca_size_t mid = lo + (hi - lo) / 2;
|
|
7988
8044
|
T_LOAD v = *(T_LOAD *)(slab_ptr + mid * slab_stride);
|
|
7989
|
-
|
|
7990
|
-
if (
|
|
7991
|
-
else if (
|
|
7992
|
-
else { result = mid;
|
|
8045
|
+
int c = CA_FLOAT_ORDER_CMP(v, query_val);
|
|
8046
|
+
if ( c < 0 ) lo = mid + 1;
|
|
8047
|
+
else if ( c > 0 ) hi = mid;
|
|
8048
|
+
else { result = mid; hi = mid; } /* keep looking left: the first equal cell */
|
|
7993
8049
|
}
|
|
7994
8050
|
}
|
|
7995
8051
|
C
|
|
@@ -8000,15 +8056,10 @@ MkKernel.search :bsearch_addr,
|
|
|
8000
8056
|
while ( lo < hi ) {
|
|
8001
8057
|
ca_size_t mid = lo + (hi - lo) / 2;
|
|
8002
8058
|
T_LOAD v = *(T_LOAD *)(slab_ptr + mid * slab_stride);
|
|
8003
|
-
|
|
8004
|
-
VALUE r = rb_funcall(v, rb_intern("<=>"), 1, query_val);
|
|
8005
|
-
if ( NIL_P(r) ) {
|
|
8006
|
-
ca_obj_compare_refused(v, query_val);
|
|
8007
|
-
}
|
|
8008
|
-
int c = NUM2INT(r);
|
|
8059
|
+
int c = ca_obj_order_cmp(v, query_val);
|
|
8009
8060
|
if ( c < 0 ) lo = mid + 1;
|
|
8010
8061
|
else if ( c > 0 ) hi = mid;
|
|
8011
|
-
else { result = mid;
|
|
8062
|
+
else { result = mid; hi = mid; } /* keep looking left: the first equal cell */
|
|
8012
8063
|
}
|
|
8013
8064
|
C
|
|
8014
8065
|
fixlen: <<~C,
|
|
@@ -8022,7 +8073,7 @@ MkKernel.search :bsearch_addr,
|
|
|
8022
8073
|
int c = memcmp(v, query_val, (size_t) ca->bytes);
|
|
8023
8074
|
if ( c < 0 ) lo = mid + 1;
|
|
8024
8075
|
else if ( c > 0 ) hi = mid;
|
|
8025
|
-
else { result = mid;
|
|
8076
|
+
else { result = mid; hi = mid; } /* keep looking left: the first equal cell */
|
|
8026
8077
|
}
|
|
8027
8078
|
C
|
|
8028
8079
|
},
|
|
@@ -8188,19 +8239,18 @@ MkKernel.search :linear_section_binary,
|
|
|
8188
8239
|
no_match: "NAN",
|
|
8189
8240
|
no_match_check: "isnan(op[0])",
|
|
8190
8241
|
mask_self: :raise,
|
|
8191
|
-
mask_query: :undef,
|
|
8192
8242
|
body: <<~C,
|
|
8193
8243
|
result = NAN;
|
|
8194
8244
|
if ( slab_n <= 1 ) {
|
|
8195
8245
|
/* A one-point axis has no segment: only the point itself is in
|
|
8196
8246
|
* range, at 0. An empty one has nothing in range. */
|
|
8197
|
-
if ( slab_n == 1 && query_val == *(
|
|
8247
|
+
if ( slab_n == 1 && query_val == *(ca_u_float64_t *)slab_ptr ) {
|
|
8198
8248
|
result = 0.0;
|
|
8199
8249
|
}
|
|
8200
8250
|
}
|
|
8201
8251
|
else {
|
|
8202
|
-
double y0 = *(
|
|
8203
|
-
double yn = *(
|
|
8252
|
+
double y0 = *(ca_u_float64_t *)(slab_ptr + 0 * slab_stride);
|
|
8253
|
+
double yn = *(ca_u_float64_t *)(slab_ptr + (slab_n - 1) * slab_stride);
|
|
8204
8254
|
int ascending = ( yn >= y0 ); /* grid direction (monotonic assumed) */
|
|
8205
8255
|
ca_size_t x1 = 0;
|
|
8206
8256
|
int found = 0;
|
|
@@ -8223,8 +8273,8 @@ MkKernel.search :linear_section_binary,
|
|
|
8223
8273
|
double gd = (query_val - y0) / (yn - y0) * (double)(slab_n - 1);
|
|
8224
8274
|
if ( gd >= 0.0 && gd < (double)(slab_n - 1) ) {
|
|
8225
8275
|
ca_size_t ag = (ca_size_t) gd;
|
|
8226
|
-
double yga = *(
|
|
8227
|
-
double ygb = *(
|
|
8276
|
+
double yga = *(ca_u_float64_t *)(slab_ptr + ag * slab_stride);
|
|
8277
|
+
double ygb = *(ca_u_float64_t *)(slab_ptr + (ag + 1) * slab_stride);
|
|
8228
8278
|
if ( (yga - query_val) * (ygb - query_val) <= 0 ) {
|
|
8229
8279
|
x1 = ag; found = 1;
|
|
8230
8280
|
}
|
|
@@ -8239,7 +8289,7 @@ MkKernel.search :linear_section_binary,
|
|
|
8239
8289
|
double ya = y0;
|
|
8240
8290
|
while ( (b - a) > 1 ) {
|
|
8241
8291
|
ca_size_t c = (a + b) / 2;
|
|
8242
|
-
double yc = *(
|
|
8292
|
+
double yc = *(ca_u_float64_t *)(slab_ptr + c * slab_stride);
|
|
8243
8293
|
if ( (ya - query_val) * (yc - query_val) <= 0 ) { b = c; }
|
|
8244
8294
|
else { a = c; ya = yc; }
|
|
8245
8295
|
}
|
|
@@ -8248,8 +8298,8 @@ MkKernel.search :linear_section_binary,
|
|
|
8248
8298
|
}
|
|
8249
8299
|
|
|
8250
8300
|
if ( found ) {
|
|
8251
|
-
double y1 = *(
|
|
8252
|
-
double y2 = *(
|
|
8301
|
+
double y1 = *(ca_u_float64_t *)(slab_ptr + x1 * slab_stride);
|
|
8302
|
+
double y2 = *(ca_u_float64_t *)(slab_ptr + (x1 + 1) * slab_stride);
|
|
8253
8303
|
double rest = (query_val - y1) / (y2 - y1);
|
|
8254
8304
|
if ( y2 != 0.0 && fabs(y2 - query_val) / fabs(y2) < DBL_EPSILON * 100 ) {
|
|
8255
8305
|
result = (double)(x1 + 1);
|
|
@@ -8280,13 +8330,12 @@ MkKernel.search :linear_section_linear,
|
|
|
8280
8330
|
no_match: "NAN",
|
|
8281
8331
|
no_match_check: "isnan(op[0])",
|
|
8282
8332
|
mask_self: :raise,
|
|
8283
|
-
mask_query: :undef,
|
|
8284
8333
|
body: <<~C,
|
|
8285
8334
|
result = NAN;
|
|
8286
8335
|
if ( slab_n <= 1 ) {
|
|
8287
8336
|
/* A one-point axis has no segment: only the point itself is in
|
|
8288
8337
|
* range, at 0. An empty one has nothing in range. */
|
|
8289
|
-
if ( slab_n == 1 && query_val == *(
|
|
8338
|
+
if ( slab_n == 1 && query_val == *(ca_u_float64_t *)slab_ptr ) {
|
|
8290
8339
|
result = 0.0;
|
|
8291
8340
|
}
|
|
8292
8341
|
}
|
|
@@ -8294,15 +8343,15 @@ MkKernel.search :linear_section_linear,
|
|
|
8294
8343
|
ca_size_t x1 = 0;
|
|
8295
8344
|
int found = 0;
|
|
8296
8345
|
for ( ca_size_t k = 0; k < slab_n - 1; k++ ) {
|
|
8297
|
-
double yk = *(
|
|
8298
|
-
double yk1 = *(
|
|
8346
|
+
double yk = *(ca_u_float64_t *)(slab_ptr + k * slab_stride);
|
|
8347
|
+
double yk1 = *(ca_u_float64_t *)(slab_ptr + (k + 1) * slab_stride);
|
|
8299
8348
|
if ( (query_val - yk) * (query_val - yk1) <= 0 ) {
|
|
8300
8349
|
x1 = k; found = 1; break;
|
|
8301
8350
|
}
|
|
8302
8351
|
}
|
|
8303
8352
|
if ( found ) {
|
|
8304
|
-
double y1 = *(
|
|
8305
|
-
double y2 = *(
|
|
8353
|
+
double y1 = *(ca_u_float64_t *)(slab_ptr + x1 * slab_stride);
|
|
8354
|
+
double y2 = *(ca_u_float64_t *)(slab_ptr + (x1 + 1) * slab_stride);
|
|
8306
8355
|
result = (query_val - y1) / (y2 - y1) + (double) x1;
|
|
8307
8356
|
}
|
|
8308
8357
|
|
|
@@ -8323,15 +8372,14 @@ MkKernel.search :linear_fetch,
|
|
|
8323
8372
|
no_match: "NAN",
|
|
8324
8373
|
no_match_check: "isnan(op[0])",
|
|
8325
8374
|
mask_self: :raise,
|
|
8326
|
-
mask_query: :undef,
|
|
8327
8375
|
body: <<~C,
|
|
8328
8376
|
result = NAN;
|
|
8329
8377
|
if ( slab_n >= 1 && query_val >= 0.0 && query_val <= (double)(slab_n - 1) ) {
|
|
8330
8378
|
ca_size_t il = (ca_size_t) floor(query_val);
|
|
8331
8379
|
ca_size_t iu = (ca_size_t) ceil(query_val);
|
|
8332
8380
|
double w = query_val - floor(query_val);
|
|
8333
|
-
double yl = *(
|
|
8334
|
-
double yu = *(
|
|
8381
|
+
double yl = *(ca_u_float64_t *)(slab_ptr + il * slab_stride);
|
|
8382
|
+
double yu = *(ca_u_float64_t *)(slab_ptr + iu * slab_stride);
|
|
8335
8383
|
result = yu * w + yl * (1.0 - w);
|
|
8336
8384
|
}
|
|
8337
8385
|
C
|
|
@@ -8588,7 +8636,9 @@ MkKernel.monfunc :round,
|
|
|
8588
8636
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8589
8637
|
expr: {
|
|
8590
8638
|
int: "(#2) = (#1);",
|
|
8591
|
-
|
|
8639
|
+
# Half away from zero, as Float#round. floor(x + 0.5) would round in
|
|
8640
|
+
# the addition: 0.49999999999999994 + 0.5 is 1.0.
|
|
8641
|
+
float: "(#2) = round(#1);",
|
|
8592
8642
|
object: '(#2) = rb_funcall((#1), rb_intern("round"), 0);',
|
|
8593
8643
|
}
|
|
8594
8644
|
|
|
@@ -8889,7 +8939,7 @@ MkKernel.monfunc :rad_pi,
|
|
|
8889
8939
|
# Pair-wise max / min — three flavours:
|
|
8890
8940
|
#
|
|
8891
8941
|
# pmax / pmin CArray legacy names (kept for backwards compat),
|
|
8892
|
-
# NaN-skip semantics
|
|
8942
|
+
# NaN-skip semantics, as C99 fmax / fmin.
|
|
8893
8943
|
# fmax / fmin Ruby-level aliases of pmax / pmin (= same kernel,
|
|
8894
8944
|
# same NaN-skip behaviour). Matches the C99 library
|
|
8895
8945
|
# function of the same name.
|
|
@@ -8899,12 +8949,15 @@ MkKernel.monfunc :rad_pi,
|
|
|
8899
8949
|
#
|
|
8900
8950
|
# Integer branches are identical across all three (no NaN). The float
|
|
8901
8951
|
# and object branches differ: pmax / pmin let a NaN lose (an object cell's
|
|
8902
|
-
# Float NaN included), maximum / minimum let it win.
|
|
8952
|
+
# Float NaN included), maximum / minimum let it win. In both flavours, of
|
|
8953
|
+
# two zeros the max is +0 and the min is -0 in either order. C's fmax /
|
|
8954
|
+
# fmin leave that choice to the library (glibc and macOS answer
|
|
8955
|
+
# differently), so the float branch does not call them.
|
|
8903
8956
|
MkKernel.binop :pmax,
|
|
8904
8957
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8905
8958
|
expr: {
|
|
8906
8959
|
int: "(#3) = (#1) > (#2) ? (#1) : (#2);",
|
|
8907
|
-
float: "(#3) =
|
|
8960
|
+
float: "(#3) = isnan(#1) ? (#2) : isnan(#2) ? (#1) : ((#1) > (#2) || ((#1) == (#2) && !signbit(#1))) ? (#1) : (#2);",
|
|
8908
8961
|
object: '(#3) = ca_obj_extremum_replaces((#2), (#1), rb_intern(">")) ? (#2) : (#1);',
|
|
8909
8962
|
}
|
|
8910
8963
|
|
|
@@ -8912,7 +8965,7 @@ MkKernel.binop :pmin,
|
|
|
8912
8965
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8913
8966
|
expr: {
|
|
8914
8967
|
int: "(#3) = (#1) < (#2) ? (#1) : (#2);",
|
|
8915
|
-
float: "(#3) =
|
|
8968
|
+
float: "(#3) = isnan(#1) ? (#2) : isnan(#2) ? (#1) : ((#1) < (#2) || ((#1) == (#2) && signbit(#1))) ? (#1) : (#2);",
|
|
8916
8969
|
object: '(#3) = ca_obj_extremum_replaces((#2), (#1), rb_intern("<")) ? (#2) : (#1);',
|
|
8917
8970
|
}
|
|
8918
8971
|
|
|
@@ -9259,13 +9312,13 @@ MkKernel.triop :fms,
|
|
|
9259
9312
|
# UNDEF-fill case can now be expressed as `out = a.clip(lo, hi);
|
|
9260
9313
|
# out[a.lt(lo) | a.gt(hi)] = UNDEF`). Pinned in test.
|
|
9261
9314
|
#
|
|
9262
|
-
# Bound at the C level as `
|
|
9315
|
+
# Bound at the C level as `clamp_between` (made private by lazy.rb). The
|
|
9263
9316
|
# public `CArray#clip` is a Ruby wrapper in lib/carray/math.rb that
|
|
9264
9317
|
# restores the rich pre-3.0 API surface (nil-bound one-sided clip +
|
|
9265
9318
|
# optional fill). `clip!` is retired in 3.0 (view-by-default
|
|
9266
9319
|
# convention) so the bang form is suppressed via `bang: false`.
|
|
9267
9320
|
MkKernel.triop :clip,
|
|
9268
|
-
op: "
|
|
9321
|
+
op: "clamp_between",
|
|
9269
9322
|
bang: false,
|
|
9270
9323
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
9271
9324
|
expr: {
|