carray 3.0.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +2 -1
- data/CHANGELOG.md +845 -3
- data/{NEWS.md → CHANGELOG.v1.md} +1 -1
- data/README.md +33 -33
- data/carray.gemspec +12 -8
- data/ext/ca_axis_dispatch.c +33 -4
- data/ext/ca_axis_group.c +256 -114
- data/ext/ca_binop_dispatch.c +5 -6
- data/ext/ca_binop_dispatch.h +0 -7
- data/ext/ca_categorical_iterator.c +121 -67
- data/ext/ca_for_each_element.h +6 -8
- data/ext/ca_group_iter.c +1 -1
- data/ext/ca_kernel_iterator.c +386 -74
- data/ext/ca_kernel_iterator.h +145 -45
- data/ext/ca_obj_array.c +62 -20
- data/ext/ca_obj_bincmp.c +57 -27
- data/ext/ca_obj_binop.c +86 -30
- data/ext/ca_obj_block.c +4 -4
- data/ext/ca_obj_const_string.c +88 -27
- data/ext/ca_obj_face.c +104 -0
- data/ext/ca_obj_face.h +48 -1
- data/ext/ca_obj_fake.c +11 -0
- data/ext/ca_obj_fixlen_string.c +21 -6
- data/ext/ca_obj_grid.c +11 -3
- data/ext/ca_obj_meld.c +125 -22
- data/ext/ca_obj_moncmp.c +33 -14
- data/ext/ca_obj_monop.c +19 -7
- data/ext/ca_obj_object.c +107 -4
- data/ext/ca_obj_record.c +3 -1
- data/ext/ca_obj_refer.c +30 -6
- data/ext/ca_obj_roll.c +13 -5
- data/ext/ca_obj_select.c +49 -34
- data/ext/ca_obj_select_axis.c +16 -6
- data/ext/ca_obj_shift.c +3 -3
- data/ext/ca_obj_stack.c +3 -8
- data/ext/ca_obj_stride.c +178 -24
- data/ext/ca_obj_string.c +11 -5
- data/ext/ca_obj_tile.c +13 -5
- data/ext/ca_obj_time.c +3 -1
- data/ext/ca_obj_timedelta.c +3 -1
- data/ext/ca_obj_transpose.c +1 -1
- data/ext/ca_obj_triop.c +62 -23
- data/ext/ca_obj_window.c +94 -19
- data/ext/ca_op_cmplx64.h +123 -0
- data/ext/ca_op_ipower.c +1 -5
- data/ext/ca_rng_normal.h +42 -0
- data/ext/ca_rng_xoshiro256pp.h +105 -0
- data/ext/ca_sort_kernels.h +5 -5
- data/ext/ca_sweep_engine.c +362 -155
- data/ext/ca_sweep_engine.h +39 -9
- data/ext/ca_transform_common.c +7 -0
- data/ext/carray.h +148 -132
- data/ext/carray_access.c +83 -49
- data/ext/carray_address_basis.c +590 -0
- data/ext/carray_bincount.c +8 -8
- data/ext/carray_broadcast.c +102 -9
- data/ext/carray_build_flags.h +3 -0
- data/ext/carray_call_cfunc.c +2010 -335
- data/ext/carray_call_cfunc.h +153 -0
- data/ext/carray_cast.c +184 -72
- data/ext/carray_conversion.c +22 -22
- data/ext/carray_copy.c +67 -32
- data/ext/carray_core.c +140 -13
- data/ext/carray_count.c +10 -11
- data/ext/carray_element.c +7 -7
- data/ext/carray_factorize.c +72 -51
- data/ext/carray_hold.c +1 -1
- data/ext/carray_index_classifier.c +7 -21
- data/ext/carray_internal.h +36 -3
- data/ext/carray_kernels_bincmp.c +1 -0
- data/ext/carray_kernels_binop.c +320 -298
- data/ext/carray_kernels_init.c +1095 -0
- data/ext/carray_kernels_map.c +1 -0
- data/ext/carray_kernels_moncmp.c +1 -0
- data/ext/carray_kernels_monop.c +412 -411
- data/ext/carray_kernels_reduce_aggregate.c +256 -87
- data/ext/carray_kernels_reduce_boolean.c +1 -0
- data/ext/carray_kernels_reduce_cumulative.c +324 -54
- data/ext/carray_kernels_reduce_extreme.c +605 -58
- data/ext/carray_kernels_reduce_variance.c +1 -0
- data/ext/carray_kernels_scan.c +5 -4
- data/ext/carray_kernels_search.c +187 -106
- data/ext/carray_kernels_sort.c +1 -0
- data/ext/carray_kernels_triop.c +1 -0
- data/ext/carray_lazy.c +175 -5
- data/ext/carray_loop.c +7 -1
- data/ext/carray_mask.c +29 -13
- data/ext/carray_median_percentile.c +56 -1
- data/ext/carray_memory_view.c +46 -21
- data/ext/carray_operator.c +50 -53
- data/ext/carray_order.c +1 -1
- data/ext/carray_partition.c +4 -4
- data/ext/carray_random.c +391 -47
- data/ext/carray_scatter.c +1 -1
- data/ext/carray_slab.c +13 -0
- data/ext/carray_sort.c +22 -24
- data/ext/carray_sort_kernel.c +10 -10
- data/ext/carray_test.c +1 -1
- data/ext/extconf.rb +18 -0
- data/ext/mk_call_cfunc.rb +323 -145
- data/ext/mkkernel.rb +651 -129
- data/ext/ruby_carray.c +28 -17
- data/ext/version.h +4 -4
- data/lib/carray/autoload_carray.rb +24 -3
- data/lib/carray/autoload_method_extension.rb +14 -1
- data/lib/carray/axis_group.rb +84 -7
- data/lib/carray/basics.rb +63 -59
- data/lib/carray/bincount_nd.rb +12 -12
- data/lib/carray/block_iterator.rb +111 -25
- data/lib/carray/categorical.rb +154 -37
- data/lib/carray/categorical_iterator.rb +300 -151
- data/lib/carray/conditional.rb +14 -14
- data/lib/carray/const_string.rb +131 -27
- data/lib/carray/construct.rb +61 -0
- data/lib/carray/core_extensions.rb +62 -5
- data/lib/carray/data_type_extension.rb +21 -1
- data/lib/carray/data_type_limits.rb +91 -0
- data/lib/carray/fixlen_string.rb +1 -1
- data/lib/carray/frame/concat.rb +3 -3
- data/lib/carray/frame/convert.rb +1 -1
- data/lib/carray/frame/csv_parser.rb +11 -4
- data/lib/carray/frame/frame.rb +85 -21
- data/lib/carray/frame/group.rb +36 -3
- data/lib/carray/frame/io.rb +224 -15
- data/lib/carray/frame/records.rb +18 -4
- data/lib/carray/frame/sort.rb +1 -1
- data/lib/carray/frame/verbs.rb +32 -12
- data/lib/carray/fuse_source.rb +123 -0
- data/lib/carray/fusion.rb +218 -0
- data/lib/carray/histogram.rb +16 -16
- data/lib/carray/inspect.rb +43 -16
- data/lib/carray/iterator.rb +147 -3
- data/lib/carray/lazy.rb +88 -73
- data/lib/carray/mask_gap_fill.rb +3 -1
- data/lib/carray/meld_reduce.rb +2 -2
- data/lib/carray/methods/align_addr.rb +1 -1
- data/lib/carray/methods/composition.rb +1 -1
- data/lib/carray/methods/discovery_along.rb +74 -0
- data/lib/carray/methods/factorize.rb +50 -0
- data/lib/carray/methods/is_in.rb +25 -14
- data/lib/carray/methods/locate_addr.rb +81 -3
- data/lib/carray/methods/mask_duplicates.rb +36 -2
- data/lib/carray/methods/meshgrid.rb +4 -5
- data/lib/carray/methods/mode.rb +2 -2
- data/lib/carray/methods/nunique.rb +23 -2
- data/lib/carray/methods/repeat.rb +110 -0
- data/lib/carray/methods/snap.rb +7 -2
- data/lib/carray/methods/unique.rb +44 -4
- data/lib/carray/methods/value_counts.rb +2 -2
- data/lib/carray/rng.rb +86 -0
- data/lib/carray/runtime.rb +0 -19
- data/lib/carray/slab_iterator.rb +77 -19
- data/lib/carray/string_operation_extension.rb +10 -6
- data/lib/carray/time.rb +906 -491
- data/lib/carray/window_iterator.rb +421 -27
- data/lib/carray.rb +7 -5
- data/yard-stubs/ca_obj_array.rb +385 -0
- data/yard-stubs/ca_obj_bitarray.rb +38 -0
- data/yard-stubs/ca_obj_bitfield.rb +43 -0
- data/yard-stubs/ca_obj_block.rb +68 -0
- data/yard-stubs/ca_obj_byte_swap.rb +56 -0
- data/yard-stubs/ca_obj_fake.rb +31 -0
- data/yard-stubs/ca_obj_farray.rb +32 -0
- data/yard-stubs/ca_obj_field.rb +45 -0
- data/yard-stubs/ca_obj_grid.rb +35 -0
- data/yard-stubs/ca_obj_refer.rb +72 -0
- data/yard-stubs/ca_obj_roll.rb +45 -0
- data/yard-stubs/ca_obj_shift.rb +43 -0
- data/yard-stubs/ca_obj_stride.rb +181 -0
- data/yard-stubs/ca_obj_tile.rb +29 -0
- data/yard-stubs/ca_obj_transpose.rb +40 -0
- data/yard-stubs/ca_obj_window.rb +57 -0
- data/yard-stubs/carray_access.rb +131 -0
- data/yard-stubs/carray_attribute.rb +246 -0
- data/yard-stubs/carray_broadcast.rb +37 -0
- data/yard-stubs/carray_cast.rb +489 -0
- data/yard-stubs/carray_class.rb +65 -0
- data/yard-stubs/carray_conversion.rb +76 -0
- data/yard-stubs/carray_copy.rb +79 -0
- data/yard-stubs/carray_core.rb +34 -0
- data/yard-stubs/carray_count.rb +84 -0
- data/yard-stubs/carray_element.rb +108 -0
- data/yard-stubs/carray_generate.rb +66 -0
- data/yard-stubs/carray_lazy.rb +228 -0
- data/yard-stubs/carray_loop.rb +140 -0
- data/yard-stubs/carray_mask.rb +259 -0
- data/yard-stubs/carray_math.rb +1615 -0
- data/yard-stubs/carray_mathfunc.rb +45 -0
- data/yard-stubs/carray_median_percentile.rb +103 -0
- data/yard-stubs/carray_memory_view.rb +163 -0
- data/yard-stubs/carray_order.rb +252 -0
- data/yard-stubs/carray_random.rb +89 -0
- data/yard-stubs/carray_scatter.rb +106 -0
- data/yard-stubs/carray_slab.rb +59 -0
- data/yard-stubs/carray_sort.rb +165 -0
- data/yard-stubs/carray_test.rb +85 -0
- data/yard-stubs/carray_undef.rb +64 -0
- data/yard-stubs/carray_utils.rb +97 -0
- data/yard-stubs/ruby_carray.rb +193 -0
- metadata +67 -10
- data/ext/ca_obj_unbound_repeat.c +0 -496
data/ext/mkkernel.rb
CHANGED
|
@@ -144,6 +144,55 @@ module MkKernel
|
|
|
144
144
|
SNIPPET
|
|
145
145
|
end
|
|
146
146
|
|
|
147
|
+
# Complex libm comes in two widths: `csqrt` takes a `double _Complex`,
|
|
148
|
+
# `csqrtf` a `float _Complex`. A body written with only the double-
|
|
149
|
+
# taking name still compiles for a cmplx64 cell, because the cell
|
|
150
|
+
# widens on the way in and rounds on the way out -- so the kernel
|
|
151
|
+
# computes at cmplx128 whatever the array said it was, and the `+ - *`
|
|
152
|
+
# kernels beside it stay narrow. This builds the two expr entries that
|
|
153
|
+
# hold each complex data_type at its own width.
|
|
154
|
+
#
|
|
155
|
+
# In `body`, `<f>` marks each spot the `f` suffix belongs and `<t>` a
|
|
156
|
+
# real scalar of the matching width:
|
|
157
|
+
#
|
|
158
|
+
# cmplx_widths("(#2) = csqrt<f>(#1);")
|
|
159
|
+
# cmplx_widths("{ <t> _r = creal<f>(#1); ... }")
|
|
160
|
+
#
|
|
161
|
+
# Returns array-keyed entries, so merge it into an expr Hash that
|
|
162
|
+
# carries the other families:
|
|
163
|
+
#
|
|
164
|
+
# expr: { float: "...", object: "..." }.merge(cmplx_widths("..."))
|
|
165
|
+
def self.cmplx_widths(body)
|
|
166
|
+
{
|
|
167
|
+
[:cmplx64] => at_width(body, "f", "float"),
|
|
168
|
+
[:cmplx128] => at_width(body, "", "double"),
|
|
169
|
+
}
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# The same split for the real families, and for the same reason: `sin`
|
|
173
|
+
# takes a double, so an f32 cell widens on the way in and rounds on the
|
|
174
|
+
# way out. Use it in place of a `float:` entry whenever the body calls
|
|
175
|
+
# a libm function that has an `f` variant.
|
|
176
|
+
#
|
|
177
|
+
# expr: { object: "..." }.merge(float_widths("(#2) = sin<f>(#1);"))
|
|
178
|
+
#
|
|
179
|
+
# Not every double-taking call wants this. `fabs`, `fmin` / `fmax`,
|
|
180
|
+
# `ceil` / `floor` / `trunc` and the `isnan` family are exact on a
|
|
181
|
+
# float either way, and `round` (`floor(x + 0.5)`) is exact only in
|
|
182
|
+
# double -- narrowing it would round `x + 0.5f` first and step the
|
|
183
|
+
# answer at the boundary.
|
|
184
|
+
def self.float_widths(body)
|
|
185
|
+
{
|
|
186
|
+
[:f32] => at_width(body, "f", "float"),
|
|
187
|
+
[:f64] => at_width(body, "", "double"),
|
|
188
|
+
}
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Substitute the width markers in a kernel body.
|
|
192
|
+
def self.at_width(body, suffix, real_type)
|
|
193
|
+
body.gsub("<f>", suffix).gsub("<t>", real_type)
|
|
194
|
+
end
|
|
195
|
+
|
|
147
196
|
# CA_NTYPE order from ext/carray.h. Drives the per-data_type table layout
|
|
148
197
|
# for eager-style monop/binop tables `ca_<form>_<name>[CA_NTYPE]`.
|
|
149
198
|
# `:reserved` slots emit `ca_<form>_not_implement` (= retired holes
|
|
@@ -311,6 +360,7 @@ module MkKernel
|
|
|
311
360
|
face_gate: nil,
|
|
312
361
|
object_escape: nil,
|
|
313
362
|
identity_on_empty: false,
|
|
363
|
+
all_nan_result: nil,
|
|
314
364
|
outputs: 1,
|
|
315
365
|
# Two-pass centred algorithm (variance / stddev family).
|
|
316
366
|
# When algorithm: :two_pass_centred is set, `state / init /
|
|
@@ -416,6 +466,30 @@ module MkKernel
|
|
|
416
466
|
end
|
|
417
467
|
end
|
|
418
468
|
end
|
|
469
|
+
# all_nan_result: what an extreme-value reduction answers when every
|
|
470
|
+
# contributing cell was NaN. The `(v < acc) ? v : acc` reduce body is
|
|
471
|
+
# false for NaN, so acc is never updated and the init (+/-INFINITY)
|
|
472
|
+
# leaks out as the answer -- a value that is neither the minimum of
|
|
473
|
+
# anything nor a missing marker, and indistinguishable from data that
|
|
474
|
+
# genuinely held only +INFINITY.
|
|
475
|
+
#
|
|
476
|
+
# :nan -- answer NaN (C99 fmin folded: a lone non-NaN wins, two
|
|
477
|
+
# NaNs give NaN). For kernels whose output can hold it.
|
|
478
|
+
# :undef -- answer UNDEF, by folding into the mask_policy trigger.
|
|
479
|
+
# For kernels whose output is a position (argmin family):
|
|
480
|
+
# an integer output cannot hold NaN, and index 0 would be
|
|
481
|
+
# a lie about where the minimum is.
|
|
482
|
+
#
|
|
483
|
+
# Only the float srcs act on it; integers and boolean have no NaN and
|
|
484
|
+
# their init is a legitimate value.
|
|
485
|
+
unless all_nan_result.nil? || %i[nan undef].include?(all_nan_result)
|
|
486
|
+
raise "#{name}: all_nan_result #{all_nan_result.inspect} invalid " \
|
|
487
|
+
"(expected :nan, :undef, or nil)"
|
|
488
|
+
end
|
|
489
|
+
if all_nan_result == :undef && mask_policy.nil?
|
|
490
|
+
raise "#{name}: all_nan_result: :undef needs a mask_policy " \
|
|
491
|
+
"(UNDEF is written through the mask_policy trigger)"
|
|
492
|
+
end
|
|
419
493
|
if value_arg
|
|
420
494
|
raise "#{name}: value_arg must be a Hash" unless value_arg.is_a?(Hash)
|
|
421
495
|
raise "#{name}: value_arg: target must be :T_IN" unless value_arg[:target] == :T_IN
|
|
@@ -429,16 +503,29 @@ module MkKernel
|
|
|
429
503
|
# selects direction (:min / :max) and output kind:
|
|
430
504
|
# :min / :max -> extremum blob, output data_type = CA_FIXLEN
|
|
431
505
|
# :argmin / :argmax -> position of the extremum, output i64
|
|
506
|
+
# :count_equal -> how many cells memcmp-equal value_arg, output i64
|
|
432
507
|
# Author must also list :fixlen in source: (parallel to how :object
|
|
433
508
|
# opts in via source: + an :object body).
|
|
509
|
+
#
|
|
510
|
+
# :count_equal is the one mode that takes a value_arg, and takes it as a
|
|
511
|
+
# byte blob rather than a scalar: the dispatcher packs the query with
|
|
512
|
+
# rb_ca_obj2ptr into a ca->bytes buffer, the same way the search family
|
|
513
|
+
# already does for a fixlen query (which is what makes a short query
|
|
514
|
+
# NUL-pad to the cell width instead of never matching).
|
|
434
515
|
if fixlen
|
|
435
|
-
unless %i[min max argmin argmax].include?(fixlen)
|
|
436
|
-
raise "#{name}: fixlen: must be :min / :max / :argmin / :argmax (got #{fixlen.inspect})"
|
|
516
|
+
unless %i[min max argmin argmax count_equal].include?(fixlen)
|
|
517
|
+
raise "#{name}: fixlen: must be :min / :max / :argmin / :argmax / :count_equal (got #{fixlen.inspect})"
|
|
437
518
|
end
|
|
438
519
|
raise "#{name}: fixlen: requires :fixlen in source:" unless source.include?(:fixlen)
|
|
439
520
|
raise "#{name}: fixlen: requires mask_policy: :min_count" unless mask_policy == :min_count
|
|
440
521
|
raise "#{name}: fixlen: requires outputs: 1" unless outputs == 1
|
|
441
|
-
raise "#{name}: fixlen: does not support
|
|
522
|
+
raise "#{name}: fixlen: does not support array_arg" if array_arg
|
|
523
|
+
if value_arg && fixlen != :count_equal
|
|
524
|
+
raise "#{name}: fixlen: only :count_equal takes a value_arg"
|
|
525
|
+
end
|
|
526
|
+
if fixlen == :count_equal && !value_arg
|
|
527
|
+
raise "#{name}: fixlen: :count_equal requires value_arg"
|
|
528
|
+
end
|
|
442
529
|
elsif source.include?(:fixlen)
|
|
443
530
|
raise "#{name}: source includes :fixlen but no fixlen: spec given"
|
|
444
531
|
end
|
|
@@ -563,6 +650,7 @@ module MkKernel
|
|
|
563
650
|
face_gate: face_gate,
|
|
564
651
|
object_escape: object_escape,
|
|
565
652
|
identity_on_empty: identity_on_empty,
|
|
653
|
+
all_nan_result: all_nan_result,
|
|
566
654
|
outputs: outputs,
|
|
567
655
|
algorithm: algorithm,
|
|
568
656
|
divisor: divisor,
|
|
@@ -757,7 +845,7 @@ module MkKernel
|
|
|
757
845
|
# output cell is UNDEF).
|
|
758
846
|
# :sentinel -> masked cells are treated as an incomparable sentinel,
|
|
759
847
|
# the same role NaN plays for :end nan_policy but runtime-
|
|
760
|
-
# selectable and
|
|
848
|
+
# selectable and type-agnostic. Per fiber, unmasked
|
|
761
849
|
# cells are compacted into a contiguous sub-range and only
|
|
762
850
|
# that sub-range is sorted/quickselected; masked cells are
|
|
763
851
|
# compacted into the complementary sub-range at the head
|
|
@@ -1005,7 +1093,9 @@ module MkKernel
|
|
|
1005
1093
|
# demotes to real f64). Hash form requires the kernel author to
|
|
1006
1094
|
# write expr that produces output data_type values for each source family
|
|
1007
1095
|
# (e.g. cabs() for complex returning double).
|
|
1008
|
-
raise "#{name}: unknown output #{output}"
|
|
1096
|
+
raise "#{name}: unknown output #{output}" \
|
|
1097
|
+
unless output == :preserve || output == :real_of_source ||
|
|
1098
|
+
DTYPES.key?(output) || output.is_a?(Hash)
|
|
1009
1099
|
KERNELS << {
|
|
1010
1100
|
kind: :monop,
|
|
1011
1101
|
name: name,
|
|
@@ -1188,6 +1278,13 @@ module MkKernel
|
|
|
1188
1278
|
# fallback when no family matches. Uses the same family aliases
|
|
1189
1279
|
# as monop_expr_family_match? (:numeric / :int / :float / :complex
|
|
1190
1280
|
# / :bool / :object).
|
|
1281
|
+
#
|
|
1282
|
+
# A Hash value (or a bare output form) may also be :real_of_source,
|
|
1283
|
+
# meaning "the real component width of the source complex data_type"
|
|
1284
|
+
# (cmplx64 -> f32, cmplx128 -> f64). This is what an op like abs
|
|
1285
|
+
# wants for its complex family: the magnitude of a cmplx64 is a
|
|
1286
|
+
# float32, the same width `.real` and `.imag` already return. Naming
|
|
1287
|
+
# :f64 there would hand cmplx64 the real width of cmplx128.
|
|
1191
1288
|
# SL.1.1: Resolve the reduce macro suffix for a given kernel entry,
|
|
1192
1289
|
# driven by reduction_kind. Returns "" for :none (= legacy
|
|
1193
1290
|
# CA_SLAB_REDUCE_T_EX), or "_PLUS" / "_MIN" / "_MAX" / "_STAR" to
|
|
@@ -1208,7 +1305,7 @@ module MkKernel
|
|
|
1208
1305
|
#
|
|
1209
1306
|
# Ineligible:
|
|
1210
1307
|
# - suffix == "" (:object src / no_simd_src override — reduce body
|
|
1211
|
-
# is Ruby callback or
|
|
1308
|
+
# is Ruby callback or type-specific, single-accumulator required)
|
|
1212
1309
|
# - array_arg (weighted reductions use the ARRAY_T_EX macro family,
|
|
1213
1310
|
# which has its own emit path)
|
|
1214
1311
|
def self.reduce_8way_eligible?(k, src, suffix)
|
|
@@ -1293,6 +1390,62 @@ module MkKernel
|
|
|
1293
1390
|
io.puts "#undef #{expr_macro}"
|
|
1294
1391
|
end
|
|
1295
1392
|
|
|
1393
|
+
# State vars that start at the type's limit (T_LIMIT_HI / T_LIMIT_LO)
|
|
1394
|
+
# and so can leak their init as an answer. A position state (argmin's
|
|
1395
|
+
# best_i, init "0") is deliberately not one of them: index 0 is a value
|
|
1396
|
+
# a real minimum can legitimately produce.
|
|
1397
|
+
def self.limit_init_state_vars(k, si, oi, src)
|
|
1398
|
+
return [] unless k[:state]
|
|
1399
|
+
k[:state].keys.filter_map do |var|
|
|
1400
|
+
token = pick_family_string(k[:init][var], src, "init")
|
|
1401
|
+
next unless %w[T_LIMIT_HI T_LIMIT_LO].include?(token)
|
|
1402
|
+
[var, resolve_init_expr(k[:init][var], oi, si, src)]
|
|
1403
|
+
end
|
|
1404
|
+
end
|
|
1405
|
+
|
|
1406
|
+
# Emit the all-NaN fix-up for an extreme-value reduction (see the
|
|
1407
|
+
# all_nan_result: validation in MkKernel.reduce for why it exists).
|
|
1408
|
+
#
|
|
1409
|
+
# The per-cell reduce body is left alone: it stays the SIMD-licensed
|
|
1410
|
+
# ternary, so the 8-way horizontal split (reduce_8way_eligible?) and
|
|
1411
|
+
# the `reduction(min:acc)` clause are untouched, and ordinary data pays
|
|
1412
|
+
# one comparison per slab. Carrying a "saw a number" flag as a second
|
|
1413
|
+
# DSL state var was the alternative and costs more than it looks: it
|
|
1414
|
+
# takes the kernel over reduce_8way_eligible?'s single-state gate and
|
|
1415
|
+
# drops min / max back onto the legacy single-accumulator macro.
|
|
1416
|
+
#
|
|
1417
|
+
# acc still sitting at its init means either nothing updated it or the
|
|
1418
|
+
# data genuinely held only +/-INFINITY. Those are told apart by
|
|
1419
|
+
# walking the slab once more for a non-NaN cell -- O(n), and only for
|
|
1420
|
+
# a slab whose answer came out equal to the init.
|
|
1421
|
+
#
|
|
1422
|
+
# Returns true when it emitted anything.
|
|
1423
|
+
def self.emit_all_nan_fixup(io, k, src, si, pairs, valid_guard,
|
|
1424
|
+
indent: " ", flag_var: nil)
|
|
1425
|
+
mode = k[:all_nan_result]
|
|
1426
|
+
return false unless mode
|
|
1427
|
+
return false unless FLOAT_DTYPES.include?(src)
|
|
1428
|
+
cond = pairs.map { |var, init| "#{var} == (#{init})" }.join(" && ")
|
|
1429
|
+
cond = "(#{cond}) && #{valid_guard}" if valid_guard
|
|
1430
|
+
io.puts "#{indent}/* all-NaN fix-up (all_nan_result: #{mode.inspect}). */"
|
|
1431
|
+
io.puts "#{indent}if ( #{cond} ) {"
|
|
1432
|
+
io.puts "#{indent} int64_t __anf_seen = 0;"
|
|
1433
|
+
io.puts "#{indent} ca_size_t __anf_mc = 0;"
|
|
1434
|
+
io.puts "#{indent} CA_SLAB_REDUCE_T_EX(#{si[:c]}, st, p, m, __anf_seen, 0, " \
|
|
1435
|
+
"__anf_seen |= (v == v), __anf_mc);"
|
|
1436
|
+
io.puts "#{indent} (void) __anf_mc;"
|
|
1437
|
+
io.puts "#{indent} if ( ! __anf_seen ) {"
|
|
1438
|
+
if mode == :nan
|
|
1439
|
+
pairs.each { |var, _| io.puts "#{indent} #{var} = (#{si[:c]}) NAN;" }
|
|
1440
|
+
else
|
|
1441
|
+
raise "#{k[:name]}: all_nan_result: :undef needs flag_var" unless flag_var
|
|
1442
|
+
io.puts "#{indent} #{flag_var} = 1;"
|
|
1443
|
+
end
|
|
1444
|
+
io.puts "#{indent} }"
|
|
1445
|
+
io.puts "#{indent}}"
|
|
1446
|
+
true
|
|
1447
|
+
end
|
|
1448
|
+
|
|
1296
1449
|
def self.reduce_macro_suffix(k, src = nil)
|
|
1297
1450
|
# CA_OBJECT cannot ride the SIMD-licensed macros (= _PLUS / _MIN / _MAX
|
|
1298
1451
|
# / _STAR), which assume C operators (= acc is a VALUE, so
|
|
@@ -1325,6 +1478,24 @@ module MkKernel
|
|
|
1325
1478
|
end
|
|
1326
1479
|
end
|
|
1327
1480
|
|
|
1481
|
+
# Real component width of a complex data_type: cmplx64 -> f32,
|
|
1482
|
+
# cmplx128 -> f64. Raises for anything else, since :real_of_source
|
|
1483
|
+
# only has a meaning for a complex source.
|
|
1484
|
+
REAL_OF_CMPLX = { cmplx64: :f32, cmplx128: :f64 }.freeze
|
|
1485
|
+
|
|
1486
|
+
def self.resolve_output_dtype(kernel, src, dt)
|
|
1487
|
+
case dt
|
|
1488
|
+
when :preserve then DTYPES[src]
|
|
1489
|
+
when :real_of_source
|
|
1490
|
+
real = REAL_OF_CMPLX[src]
|
|
1491
|
+
raise "#{kernel[:name]}: output :real_of_source needs a complex src (got #{src})" \
|
|
1492
|
+
unless real
|
|
1493
|
+
DTYPES[real]
|
|
1494
|
+
else
|
|
1495
|
+
DTYPES[dt]
|
|
1496
|
+
end
|
|
1497
|
+
end
|
|
1498
|
+
|
|
1328
1499
|
def self.output_info(kernel, src)
|
|
1329
1500
|
out = kernel[:output]
|
|
1330
1501
|
case out
|
|
@@ -1334,16 +1505,15 @@ module MkKernel
|
|
|
1334
1505
|
next if family == :default
|
|
1335
1506
|
if monop_expr_family_match?(family, src)
|
|
1336
1507
|
# Hash value may itself be :preserve (= "same as source for this
|
|
1337
|
-
# family") or a data_type symbol like :f64.
|
|
1338
|
-
return (
|
|
1508
|
+
# family"), :real_of_source, or a data_type symbol like :f64.
|
|
1509
|
+
return resolve_output_dtype(kernel, src, dt)
|
|
1339
1510
|
end
|
|
1340
1511
|
end
|
|
1341
1512
|
raise "#{kernel[:name]}: output Hash has no match for src #{src} and no :default" \
|
|
1342
1513
|
unless out.key?(:default)
|
|
1343
|
-
|
|
1344
|
-
(dt == :preserve) ? DTYPES[src] : DTYPES[dt]
|
|
1514
|
+
resolve_output_dtype(kernel, src, out[:default])
|
|
1345
1515
|
else
|
|
1346
|
-
|
|
1516
|
+
resolve_output_dtype(kernel, src, out)
|
|
1347
1517
|
end
|
|
1348
1518
|
end
|
|
1349
1519
|
|
|
@@ -1699,7 +1869,7 @@ module MkKernel
|
|
|
1699
1869
|
|
|
1700
1870
|
if streamable
|
|
1701
1871
|
emit_reduce_streaming(io, k, si, oi, ruby_wrap, acc_var, acc_init,
|
|
1702
|
-
decls, reduce_stmt, finish_expr, extra_args)
|
|
1872
|
+
decls, reduce_stmt, finish_expr, extra_args, src)
|
|
1703
1873
|
end
|
|
1704
1874
|
|
|
1705
1875
|
# L.1 / L.3 / L.4 (PROPOSAL_REDUCTION_LOOP_INTERCHANGE):
|
|
@@ -1859,6 +2029,17 @@ module MkKernel
|
|
|
1859
2029
|
emit_reduce_slab_call(io, k, src, si, oi, suffix, acc_var, acc_init,
|
|
1860
2030
|
reduce_stmt, "masked_cnt", indent: " ")
|
|
1861
2031
|
end
|
|
2032
|
+
all_nan_flag = nil
|
|
2033
|
+
if k[:all_nan_result]
|
|
2034
|
+
pairs = limit_init_state_vars(k, si, oi, src)
|
|
2035
|
+
if k[:all_nan_result] == :undef && FLOAT_DTYPES.include?(src)
|
|
2036
|
+
all_nan_flag = "__anf_all_nan"
|
|
2037
|
+
io.puts " int #{all_nan_flag} = 0;"
|
|
2038
|
+
end
|
|
2039
|
+
emit_all_nan_fixup(io, k, src, si, pairs,
|
|
2040
|
+
"masked_cnt < st.slab_elements",
|
|
2041
|
+
indent: " ", flag_var: all_nan_flag)
|
|
2042
|
+
end
|
|
1862
2043
|
trigger = case k[:mask_policy]
|
|
1863
2044
|
when :strict then "masked_cnt > 0"
|
|
1864
2045
|
when :all_masked then "masked_cnt == st.slab_elements"
|
|
@@ -1880,6 +2061,7 @@ module MkKernel
|
|
|
1880
2061
|
": st.slab_elements - masked_cnt < min_count)"
|
|
1881
2062
|
end
|
|
1882
2063
|
end
|
|
2064
|
+
trigger = "(#{trigger}) || #{all_nan_flag}" if all_nan_flag
|
|
1883
2065
|
finish_emit = view_flat \
|
|
1884
2066
|
? "(transform_active ? (outer_off + ((ca_size_t)(#{finish_expr})) * axis_vstride) : ((ca_size_t)(#{finish_expr})))" \
|
|
1885
2067
|
: "(#{finish_expr})"
|
|
@@ -1962,13 +2144,15 @@ module MkKernel
|
|
|
1962
2144
|
# the numeric argmin's best_i. mask_policy is always :min_count here.
|
|
1963
2145
|
def self.emit_reduce_native_fixlen(io, k, src)
|
|
1964
2146
|
name = k[:name]
|
|
2147
|
+
counting = k[:fixlen] == :count_equal
|
|
1965
2148
|
want_max = %i[max argmax].include?(k[:fixlen])
|
|
1966
|
-
index_out = %i[argmin argmax].include?(k[:fixlen])
|
|
2149
|
+
index_out = %i[argmin argmax].include?(k[:fixlen]) || counting
|
|
1967
2150
|
cmp = want_max ? ">" : "<"
|
|
2151
|
+
varg = counting ? ", const char *value_arg" : ""
|
|
1968
2152
|
|
|
1969
2153
|
io.puts
|
|
1970
2154
|
io.puts "static VALUE"
|
|
1971
|
-
io.puts "#{name}_ki_native_fixlen (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)"
|
|
2155
|
+
io.puts "#{name}_ki_native_fixlen (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis#{varg}, ca_size_t min_count)"
|
|
1972
2156
|
io.puts "{"
|
|
1973
2157
|
io.puts " ca_size_t K = ca->bytes; /* uniform fixlen byte width */"
|
|
1974
2158
|
if index_out
|
|
@@ -1989,9 +2173,13 @@ module MkKernel
|
|
|
1989
2173
|
io.puts " ca_size_t out_i = 0;"
|
|
1990
2174
|
io.puts " boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */"
|
|
1991
2175
|
io.puts " while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {"
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
2176
|
+
if counting
|
|
2177
|
+
io.puts " int64_t cnt = 0;"
|
|
2178
|
+
else
|
|
2179
|
+
io.puts " const char *best = NULL;"
|
|
2180
|
+
io.puts " ca_size_t best_i = 0;"
|
|
2181
|
+
io.puts " (void) best_i;" unless index_out # value output ignores the index
|
|
2182
|
+
end
|
|
1995
2183
|
io.puts " ca_size_t masked_cnt = 0;"
|
|
1996
2184
|
io.puts " int8_t sndim = st.slab_ndim;"
|
|
1997
2185
|
io.puts " ca_size_t sidx[CA_RANK_MAX] = { 0 };"
|
|
@@ -2007,12 +2195,18 @@ module MkKernel
|
|
|
2007
2195
|
io.puts " }"
|
|
2008
2196
|
io.puts " else {"
|
|
2009
2197
|
io.puts " const char *q = (const char *) p + doff;"
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2198
|
+
if counting
|
|
2199
|
+
io.puts " if ( memcmp(q, value_arg, (size_t) K) == 0 ) {"
|
|
2200
|
+
io.puts " cnt++;"
|
|
2201
|
+
io.puts " }"
|
|
2202
|
+
else
|
|
2203
|
+
io.puts " if ( best == NULL ) {"
|
|
2204
|
+
io.puts " best = q; best_i = idx;"
|
|
2205
|
+
io.puts " }"
|
|
2206
|
+
io.puts " else if ( memcmp(q, best, (size_t) K) #{cmp} 0 ) {"
|
|
2207
|
+
io.puts " best = q; best_i = idx;"
|
|
2208
|
+
io.puts " }"
|
|
2209
|
+
end
|
|
2016
2210
|
io.puts " }"
|
|
2017
2211
|
io.puts " /* row-major odometer (innermost slab axis fastest) so idx"
|
|
2018
2212
|
io.puts " matches CA_SLAB_REDUCE_T's flat slab index. */"
|
|
@@ -2021,8 +2215,16 @@ module MkKernel
|
|
|
2021
2215
|
io.puts " sidx[sk] = 0;"
|
|
2022
2216
|
io.puts " }"
|
|
2023
2217
|
io.puts " }"
|
|
2024
|
-
|
|
2025
|
-
|
|
2218
|
+
# ERI.0: a count over nothing is 0, not UNDEF -- so the default
|
|
2219
|
+
# (min_count < 0) never fires the mask for :count_equal. An explicit
|
|
2220
|
+
# min_count: still does.
|
|
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
|
|
2026
2228
|
io.puts " if ( ! op_mask ) {"
|
|
2027
2229
|
io.puts " ca_create_mask(co);"
|
|
2028
2230
|
io.puts " op_mask = (boolean8_t *) co->mask->ptr;"
|
|
@@ -2036,7 +2238,9 @@ module MkKernel
|
|
|
2036
2238
|
io.puts " out_i++;"
|
|
2037
2239
|
io.puts " }"
|
|
2038
2240
|
io.puts " else {"
|
|
2039
|
-
if
|
|
2241
|
+
if counting
|
|
2242
|
+
io.puts " ((int64_t *) op)[out_i] = cnt;"
|
|
2243
|
+
elsif index_out
|
|
2040
2244
|
io.puts " ((int64_t *) op)[out_i] = (int64_t) best_i;"
|
|
2041
2245
|
else
|
|
2042
2246
|
io.puts " memcpy(op + out_i * K, best, (size_t) K);"
|
|
@@ -2364,6 +2568,49 @@ module MkKernel
|
|
|
2364
2568
|
buf_decls = plus_info.map do |pi|
|
|
2365
2569
|
" #{pi[:c_type]} #{pi[:buf]}[512];\n"
|
|
2366
2570
|
end.join
|
|
2571
|
+
|
|
2572
|
+
# all-NaN fix-up for the tiled core (see emit_all_nan_fixup for the
|
|
2573
|
+
# reduction counterpart). Each output cell of a tile has its own
|
|
2574
|
+
# accumulator, so the check is per column: a column whose accumulator
|
|
2575
|
+
# is still at the init either saw only NaN or only +/-INFINITY.
|
|
2576
|
+
#
|
|
2577
|
+
# The `_Pragma("omp simd")` j-loop is left alone -- carrying a
|
|
2578
|
+
# "saw a number" flag beside the accumulator would double its loads
|
|
2579
|
+
# and stores on every cell of every column. Instead the tile is
|
|
2580
|
+
# tested once after the M loop (tile_len comparisons against M *
|
|
2581
|
+
# tile_len already done), and only a tile that holds a suspicious
|
|
2582
|
+
# column re-reads its rows.
|
|
2583
|
+
li_nan_fixup =
|
|
2584
|
+
if k[:all_nan_result] == :nan && FLOAT_DTYPES.include?(src) &&
|
|
2585
|
+
plus_info.size == 1
|
|
2586
|
+
pi = plus_info.first
|
|
2587
|
+
seen_buf = "__li_buf_seen"
|
|
2588
|
+
lambda do |m_step|
|
|
2589
|
+
<<~C.rstrip
|
|
2590
|
+
{
|
|
2591
|
+
int __li_susp = 0;
|
|
2592
|
+
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {
|
|
2593
|
+
if ( #{pi[:buf]}[__j] == (#{pi[:c_type]}) (#{pi[:init]}) ) { __li_susp = 1; break; }
|
|
2594
|
+
}
|
|
2595
|
+
if ( __li_susp ) {
|
|
2596
|
+
int8_t #{seen_buf}[512];
|
|
2597
|
+
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) #{seen_buf}[__j] = 0;
|
|
2598
|
+
for ( ca_size_t __li_i = 0; __li_i < __li_M; __li_i++ ) {
|
|
2599
|
+
const #{si[:c]} *__li_row = __li_plane + __li_i * #{m_step} + __li_tile;
|
|
2600
|
+
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {
|
|
2601
|
+
#{seen_buf}[__j] |= (__li_row[__j] == __li_row[__j]);
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {
|
|
2605
|
+
if ( ! #{seen_buf}[__j] ) #{pi[:buf]}[__j] = (#{pi[:c_type]}) NAN;
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
C
|
|
2610
|
+
end
|
|
2611
|
+
else
|
|
2612
|
+
lambda { |_m_step| "" }
|
|
2613
|
+
end
|
|
2367
2614
|
init_loops = plus_info.map do |pi|
|
|
2368
2615
|
" for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {\n" \
|
|
2369
2616
|
" #{pi[:buf]}[__j] = (#{pi[:c_type]}) (#{pi[:init]});\n" \
|
|
@@ -2393,6 +2640,7 @@ module MkKernel
|
|
|
2393
2640
|
(void) v;
|
|
2394
2641
|
}
|
|
2395
2642
|
}
|
|
2643
|
+
#{li_nan_fixup.call("__li_INNER")}
|
|
2396
2644
|
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {
|
|
2397
2645
|
#{opv}[__li_o * __li_INNER + __li_tile + __j] = (#{oi[:c]}) (#{finish_li});
|
|
2398
2646
|
}
|
|
@@ -2613,6 +2861,7 @@ module MkKernel
|
|
|
2613
2861
|
(void) v;
|
|
2614
2862
|
}
|
|
2615
2863
|
}
|
|
2864
|
+
#{li_nan_fixup.call("__li_INNER")}
|
|
2616
2865
|
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {
|
|
2617
2866
|
__li_op_k[__li_tile + __j] = (#{oi[:c]}) (#{finish_li});
|
|
2618
2867
|
}
|
|
@@ -2643,8 +2892,8 @@ module MkKernel
|
|
|
2643
2892
|
write stride 1 (= contig output tail) both preserved -> SIMD
|
|
2644
2893
|
tile reduce + write maintained.
|
|
2645
2894
|
|
|
2646
|
-
Performance characteristic (deliver-via-view
|
|
2647
|
-
|
|
2895
|
+
Performance characteristic (deliver-via-view -- the surface
|
|
2896
|
+
prioritises delivering the cells over avoiding a copy): bench (b2 pattern, M=200, K=5, eff_INNER
|
|
2648
2897
|
=360, INNER_pre_K=16) yields 3674 us vs eager-entity 1545 us
|
|
2649
2898
|
= 2.38x slow. Root cause is the multi-parent data layout
|
|
2650
2899
|
(= 5 separate 9 MB regions instead of one contig 46 MB),
|
|
@@ -2771,6 +3020,7 @@ module MkKernel
|
|
|
2771
3020
|
(void) v;
|
|
2772
3021
|
}
|
|
2773
3022
|
}
|
|
3023
|
+
#{li_nan_fixup.call("__li_M_stride")}
|
|
2774
3024
|
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {
|
|
2775
3025
|
__li_op_k[__li_tile + __j] = (#{oi[:c]}) (#{finish_li});
|
|
2776
3026
|
}
|
|
@@ -2800,8 +3050,8 @@ module MkKernel
|
|
|
2800
3050
|
# reductions consume the same slab)
|
|
2801
3051
|
# - no streaming / view_flat / array_arg / value_arg
|
|
2802
3052
|
# - reduction_kind: :none only
|
|
2803
|
-
# The if-form discipline
|
|
2804
|
-
#
|
|
3053
|
+
# The if-form discipline for multi-reduction fused kernels (see
|
|
3054
|
+
# emit_min_max below) is enforced by author, not by the generator.
|
|
2805
3055
|
def self.emit_reduce_native_multi(io, k, src)
|
|
2806
3056
|
si = DTYPES[src]
|
|
2807
3057
|
oi = output_info(k, src)
|
|
@@ -2862,6 +3112,10 @@ module MkKernel
|
|
|
2862
3112
|
if min_count
|
|
2863
3113
|
io.puts " ca_size_t masked_cnt = 0;"
|
|
2864
3114
|
io.puts " CA_SLAB_REDUCE_T_EX(#{si[:c]}, st, p, m, #{acc_var}, #{acc_init}, #{reduce_stmt}, masked_cnt);"
|
|
3115
|
+
if k[:all_nan_result]
|
|
3116
|
+
emit_all_nan_fixup(io, k, src, si, limit_init_state_vars(k, si, oi, src),
|
|
3117
|
+
"masked_cnt < st.slab_elements", indent: " ")
|
|
3118
|
+
end
|
|
2865
3119
|
# Same trigger as single-output :min_count: legacy default (all_masked)
|
|
2866
3120
|
# when min_count < 0, otherwise need at least min_count valid cells.
|
|
2867
3121
|
trigger = "(min_count < 0 ? masked_cnt == st.slab_elements " \
|
|
@@ -2937,7 +3191,7 @@ module MkKernel
|
|
|
2937
3191
|
# - mask present: ca_has_mask(ca)
|
|
2938
3192
|
def self.emit_reduce_streaming(io, k, si, oi, ruby_wrap, acc_var,
|
|
2939
3193
|
acc_init, decls, reduce_stmt,
|
|
2940
|
-
finish_expr, extra_args)
|
|
3194
|
+
finish_expr, extra_args, src)
|
|
2941
3195
|
name = k[:name]
|
|
2942
3196
|
min_count = (k[:mask_policy] == :min_count)
|
|
2943
3197
|
has_mp = !k[:mask_policy].nil?
|
|
@@ -2968,6 +3222,14 @@ module MkKernel
|
|
|
2968
3222
|
# acc_var requires explicit init (= the macro normally does this).
|
|
2969
3223
|
# Other state vars in decls already include `= init` per line.
|
|
2970
3224
|
io.puts " #{acc_var} = (#{acc_init});"
|
|
3225
|
+
# all-NaN fix-up, streaming variant. The other two paths confirm a
|
|
3226
|
+
# suspicious answer by re-reading the data; here re-reading means
|
|
3227
|
+
# evaluating the lazy chain a second time, so the flag rides along in
|
|
3228
|
+
# the chunk loop instead. The loop already pays for producing each
|
|
3229
|
+
# cell, which is what makes one more compare affordable here and not
|
|
3230
|
+
# in the tiled core.
|
|
3231
|
+
all_nan_stream = k[:all_nan_result] == :nan && FLOAT_DTYPES.include?(src)
|
|
3232
|
+
io.puts " int64_t __anf_seen = 0;" if all_nan_stream
|
|
2971
3233
|
if has_mp
|
|
2972
3234
|
# Mask-policy reductions need masked_cnt to satisfy the macro/
|
|
2973
3235
|
# finish_expr signature. On streaming we have no mask, so it's
|
|
@@ -2976,7 +3238,15 @@ module MkKernel
|
|
|
2976
3238
|
io.puts " (void) masked_cnt;"
|
|
2977
3239
|
end
|
|
2978
3240
|
io.puts " ca_lazy_arena_enter();"
|
|
2979
|
-
|
|
3241
|
+
if si[:c] == "VALUE"
|
|
3242
|
+
# Object lane: the chunk holds VALUEs pulled from the source, and
|
|
3243
|
+
# the fold below calls rb_funcall per cell. A collection there
|
|
3244
|
+
# would free cells a lazy source produced into the chunk, so the
|
|
3245
|
+
# slot has to stay marked for as long as it is held.
|
|
3246
|
+
io.puts " VALUE *__chunk = (VALUE *) ca_lazy_arena_acquire_object(__chunk_elems);"
|
|
3247
|
+
else
|
|
3248
|
+
io.puts " #{si[:c]} *__chunk = (#{si[:c]} *) ca_lazy_arena_acquire(__chunk_elems * sizeof(#{si[:c]}));"
|
|
3249
|
+
end
|
|
2980
3250
|
io.puts " while ( __outer_off < __outer ) {"
|
|
2981
3251
|
io.puts " ca_size_t __r = (__outer - __outer_off < __rows) ? (__outer - __outer_off) : __rows;"
|
|
2982
3252
|
io.puts " ca_size_t __n = __r * __inner;"
|
|
@@ -2987,11 +3257,15 @@ module MkKernel
|
|
|
2987
3257
|
io.puts " for ( __i = 0; __i < __n; __i++ ) {"
|
|
2988
3258
|
io.puts " #{si[:c]} v = __chunk[__i];"
|
|
2989
3259
|
io.puts " #{reduce_stmt};"
|
|
3260
|
+
io.puts " __anf_seen |= (v == v);" if all_nan_stream
|
|
2990
3261
|
io.puts " }"
|
|
2991
3262
|
io.puts " __outer_off += __r;"
|
|
2992
3263
|
io.puts " }"
|
|
2993
3264
|
io.puts " ca_lazy_arena_release(__chunk);"
|
|
2994
3265
|
io.puts " ca_lazy_arena_exit();"
|
|
3266
|
+
if all_nan_stream
|
|
3267
|
+
io.puts " if ( ! __anf_seen && ca->elements > 0 ) #{acc_var} = (#{si[:c]}) NAN;"
|
|
3268
|
+
end
|
|
2995
3269
|
if has_mp
|
|
2996
3270
|
# Streaming path has no mask source, so masked_cnt is 0; min_count
|
|
2997
3271
|
# / strict / all_masked triggers all evaluate to false except
|
|
@@ -3235,7 +3509,17 @@ module MkKernel
|
|
|
3235
3509
|
k[:source].each do |s|
|
|
3236
3510
|
si = DTYPES[s]
|
|
3237
3511
|
# Per-src value_arg cast: NUM2LL / NUM2ULL / NUM2DBL -> (T_IN).
|
|
3238
|
-
varg_decl = has_varg
|
|
3512
|
+
varg_decl = if !has_varg
|
|
3513
|
+
""
|
|
3514
|
+
elsif s == :fixlen
|
|
3515
|
+
# A fixlen query is a runtime-width byte blob with no
|
|
3516
|
+
# scalar cast; pack it the way the search family does,
|
|
3517
|
+
# which NUL-pads a short String to the cell width.
|
|
3518
|
+
" char *value_arg = ALLOCA_N(char, src->bytes);\n" \
|
|
3519
|
+
" rb_ca_obj2ptr(self, rval, value_arg);\n"
|
|
3520
|
+
else
|
|
3521
|
+
" #{si[:c]} value_arg = (#{si[:c]}) #{si[:num2c]}(rval);\n"
|
|
3522
|
+
end
|
|
3239
3523
|
if use_result_var || has_varg
|
|
3240
3524
|
io.puts " case #{si[:ca]}: {"
|
|
3241
3525
|
io.print varg_decl unless varg_decl.empty?
|
|
@@ -3794,7 +4078,7 @@ module MkKernel
|
|
|
3794
4078
|
# `masked_last` parameter. Sets `sort_lo` / `sort_n` to the unmasked
|
|
3795
4079
|
# sub-range so the downstream sort/quickselect call only ever compares
|
|
3796
4080
|
# unmasked pairs -- masked cells are an incomparable sentinel, the same
|
|
3797
|
-
# role NaN plays for nan_policy: :end, but
|
|
4081
|
+
# role NaN plays for nan_policy: :end, but type-agnostic and runtime-
|
|
3798
4082
|
# selectable. `payload_expr(k)` computes the `.i` payload (fiber-local
|
|
3799
4083
|
# index for :fiber_local semantics, view-flat address for :view_flat)
|
|
3800
4084
|
# given the Ruby string `k` naming the C loop variable.
|
|
@@ -4829,7 +5113,7 @@ module MkKernel
|
|
|
4829
5113
|
{
|
|
4830
5114
|
/* rev4 A1 via single-element CArray: convert CScalar / [1] 1-D /
|
|
4831
5115
|
all dim==1 etc. to a Ruby Float and route it through the Case A
|
|
4832
|
-
scalar path. The
|
|
5116
|
+
scalar path. The data type matches ca (= rb_ca_obj2ptr coerces it
|
|
4833
5117
|
downstream). */
|
|
4834
5118
|
if ( rb_obj_is_carray(rval) ) {
|
|
4835
5119
|
CArray *cv_pre_;
|
|
@@ -5250,7 +5534,7 @@ module MkKernel
|
|
|
5250
5534
|
io.puts " }"
|
|
5251
5535
|
io.puts " if ( self_face_comparable ) {"
|
|
5252
5536
|
io.puts " if ( rval_is_face ) {"
|
|
5253
|
-
io.puts
|
|
5537
|
+
io.puts %Q[ rval = ca_face_operand_descend(rval, "#{name}_ki");]
|
|
5254
5538
|
io.puts " }"
|
|
5255
5539
|
io.puts " } else if ( self_was_face ) {"
|
|
5256
5540
|
io.puts " if ( rb_respond_to(self_ref, rb_intern(\"to_comparable\")) ) {"
|
|
@@ -6055,6 +6339,90 @@ module MkKernel
|
|
|
6055
6339
|
# The aggregator init.c, where Init_carray_kernels() calls each per-tag
|
|
6056
6340
|
# Init_<suffix>() in file_tags order. No tag depends on another: an alias
|
|
6057
6341
|
# already sits after its target within its own file.
|
|
6342
|
+
# ---------------------------------------------------------------------
|
|
6343
|
+
# The kernel bodies, as text, for a caller that has to compute the same
|
|
6344
|
+
# thing somewhere other than in these kernels -- a compiler handed an
|
|
6345
|
+
# expression tree, which must produce what the eager kernel produces.
|
|
6346
|
+
# Emitting the table here rather than reading this generator at runtime
|
|
6347
|
+
# keeps a build-time tool out of the running process.
|
|
6348
|
+
#
|
|
6349
|
+
# The bodies keep their `#1` / `#2` / `#3` and `<type>` placeholders: the
|
|
6350
|
+
# caller substitutes its own operands. Object-lane bodies are left out,
|
|
6351
|
+
# since they call back into the interpreter and cannot be compiled apart
|
|
6352
|
+
# from it.
|
|
6353
|
+
# ---------------------------------------------------------------------
|
|
6354
|
+
|
|
6355
|
+
BODY_TABLE_DTYPES = %i[i8 u8 i16 u16 i32 u32 i64 u64 f32 f64
|
|
6356
|
+
bool cmplx64 cmplx128].freeze
|
|
6357
|
+
|
|
6358
|
+
def self.body_table_rows
|
|
6359
|
+
rows = []
|
|
6360
|
+
KERNELS.each do |k|
|
|
6361
|
+
next unless %i[monop binop triop].include?(k[:kind])
|
|
6362
|
+
BODY_TABLE_DTYPES.each do |src|
|
|
6363
|
+
body = monop_expr_for(k, src)
|
|
6364
|
+
next unless body.is_a?(String)
|
|
6365
|
+
next if body.include?("rb_funcall")
|
|
6366
|
+
rows << [k[:kind].to_s, k[:name].to_s,
|
|
6367
|
+
DTYPES[src][:ca].sub(/\ACA_/, "").downcase, body]
|
|
6368
|
+
end
|
|
6369
|
+
end
|
|
6370
|
+
rows
|
|
6371
|
+
end
|
|
6372
|
+
|
|
6373
|
+
def self.c_string_literal(text)
|
|
6374
|
+
'"' + text.gsub("\\", "\\\\").gsub('"', '\\"').gsub("\n", '\\n') + '"'
|
|
6375
|
+
end
|
|
6376
|
+
|
|
6377
|
+
def self.emit_kernel_bodies(io)
|
|
6378
|
+
rows = body_table_rows
|
|
6379
|
+
io.puts
|
|
6380
|
+
io.puts "/* The text of every kernel body, for a caller that compiles the same"
|
|
6381
|
+
io.puts " operation elsewhere. Placeholders are left in place. */"
|
|
6382
|
+
io.puts
|
|
6383
|
+
io.puts "typedef struct {"
|
|
6384
|
+
io.puts " const char *kind;"
|
|
6385
|
+
io.puts " const char *name;"
|
|
6386
|
+
io.puts " const char *data_type;"
|
|
6387
|
+
io.puts " const char *body;"
|
|
6388
|
+
io.puts "} ca_kernel_body_t;"
|
|
6389
|
+
io.puts
|
|
6390
|
+
io.puts "static const ca_kernel_body_t ca_kernel_bodies[] = {"
|
|
6391
|
+
rows.each do |kind, name, dtype, body|
|
|
6392
|
+
io.puts " { #{c_string_literal(kind)}, #{c_string_literal(name)}, " \
|
|
6393
|
+
"#{c_string_literal(dtype)},"
|
|
6394
|
+
io.puts " #{c_string_literal(body)} },"
|
|
6395
|
+
end
|
|
6396
|
+
io.puts "};"
|
|
6397
|
+
io.puts
|
|
6398
|
+
io.puts "static const int ca_kernel_bodies_count = #{rows.size};"
|
|
6399
|
+
io.puts
|
|
6400
|
+
io.puts <<~C
|
|
6401
|
+
/* CArray.__kernel_body__(kind, name, data_type) -> String, or nil where
|
|
6402
|
+
this operation has no body at that data type. */
|
|
6403
|
+
static VALUE
|
|
6404
|
+
rb_ca_s_kernel_body (VALUE klass, VALUE rkind, VALUE rname, VALUE rtype)
|
|
6405
|
+
{
|
|
6406
|
+
VALUE kind = rb_obj_as_string(rkind);
|
|
6407
|
+
VALUE name = rb_obj_as_string(rname);
|
|
6408
|
+
VALUE type = rb_obj_as_string(rtype);
|
|
6409
|
+
const char *k = StringValueCStr(kind);
|
|
6410
|
+
const char *n = StringValueCStr(name);
|
|
6411
|
+
const char *t = StringValueCStr(type);
|
|
6412
|
+
int i;
|
|
6413
|
+
for ( i = 0; i < ca_kernel_bodies_count; i++ ) {
|
|
6414
|
+
const ca_kernel_body_t *e = &ca_kernel_bodies[i];
|
|
6415
|
+
if ( strcmp(e->kind, k) == 0 &&
|
|
6416
|
+
strcmp(e->name, n) == 0 &&
|
|
6417
|
+
strcmp(e->data_type, t) == 0 ) {
|
|
6418
|
+
return rb_str_new_cstr(e->body);
|
|
6419
|
+
}
|
|
6420
|
+
}
|
|
6421
|
+
return Qnil;
|
|
6422
|
+
}
|
|
6423
|
+
C
|
|
6424
|
+
end
|
|
6425
|
+
|
|
6058
6426
|
def self.emit_aggregator_init(io, tags)
|
|
6059
6427
|
io.puts "/* GENERATED aggregator: dispatches to per-tag Init_carray_kernels_<tag>() */"
|
|
6060
6428
|
io.puts "#include \"carray.h\""
|
|
@@ -6063,23 +6431,30 @@ module MkKernel
|
|
|
6063
6431
|
io.puts "void Init_carray_kernels_#{file_suffix(kind, sub)} (void);"
|
|
6064
6432
|
end
|
|
6065
6433
|
io.puts
|
|
6434
|
+
emit_kernel_bodies(io)
|
|
6435
|
+
io.puts
|
|
6066
6436
|
io.puts "void"
|
|
6067
6437
|
io.puts "Init_carray_kernels (void)"
|
|
6068
6438
|
io.puts "{"
|
|
6069
6439
|
tags.each do |kind, sub|
|
|
6070
6440
|
io.puts " Init_carray_kernels_#{file_suffix(kind, sub)}();"
|
|
6071
6441
|
end
|
|
6442
|
+
io.puts " rb_define_singleton_method(rb_cCArray, \"__kernel_body__\","
|
|
6443
|
+
io.puts " rb_ca_s_kernel_body, 3);"
|
|
6072
6444
|
io.puts "}"
|
|
6073
6445
|
end
|
|
6074
6446
|
|
|
6075
6447
|
# Single-stream Init_carray_kernels(): every kind emitted in order inside
|
|
6076
6448
|
# one function.
|
|
6077
6449
|
def self.emit_init(io)
|
|
6450
|
+
emit_kernel_bodies(io)
|
|
6078
6451
|
io.puts
|
|
6079
6452
|
io.puts "void"
|
|
6080
6453
|
io.puts "Init_carray_kernels (void)"
|
|
6081
6454
|
io.puts "{"
|
|
6082
6455
|
KERNELS.each { |k| emit_init_line(io, k) }
|
|
6456
|
+
io.puts " rb_define_singleton_method(rb_cCArray, \"__kernel_body__\","
|
|
6457
|
+
io.puts " rb_ca_s_kernel_body, 3);"
|
|
6083
6458
|
io.puts "}"
|
|
6084
6459
|
end
|
|
6085
6460
|
|
|
@@ -6155,7 +6530,7 @@ module MkKernel
|
|
|
6155
6530
|
# bench A/B comparison during Phase E (E.1-E.5); after E.7 they
|
|
6156
6531
|
# are retired. Maps and scans keep their `_ki` names because
|
|
6157
6532
|
# they have no user-facing equivalent yet (cumsum etc. pending
|
|
6158
|
-
# rewire
|
|
6533
|
+
# rewire).
|
|
6159
6534
|
#
|
|
6160
6535
|
# SO.2 rev6 (2026-06-04): sort kernels can opt out of the _ki
|
|
6161
6536
|
# binding via bind_ruby: false (= internal-only kernels consumed
|
|
@@ -6269,6 +6644,7 @@ MkKernel.reduce :min,
|
|
|
6269
6644
|
bool: "acc = ((uint64_t) v < acc) ? (uint64_t) v : acc",
|
|
6270
6645
|
object: 'if (acc == Qundef) acc = v; else if (RTEST(rb_funcall(v, rb_intern("<"), 1, acc))) acc = v;' },
|
|
6271
6646
|
reduction_kind: :min, # SL.1.2
|
|
6647
|
+
all_nan_result: :nan,
|
|
6272
6648
|
# CA_FIXLEN: memcmp lexicographic min (byte order == the fixlen sort
|
|
6273
6649
|
# order); the numeric reduce/init above are unused for fixlen (bespoke
|
|
6274
6650
|
# slab walk, see the fixlen: option in MkKernel.reduce).
|
|
@@ -6293,6 +6669,7 @@ MkKernel.reduce :max,
|
|
|
6293
6669
|
bool: "acc = ((uint64_t) v > acc) ? (uint64_t) v : acc",
|
|
6294
6670
|
object: 'if (acc == Qundef) acc = v; else if (RTEST(rb_funcall(v, rb_intern(">"), 1, acc))) acc = v;' },
|
|
6295
6671
|
reduction_kind: :max, # SL.1.2
|
|
6672
|
+
all_nan_result: :nan,
|
|
6296
6673
|
# CA_FIXLEN: memcmp lexicographic max (byte order == the fixlen sort order).
|
|
6297
6674
|
fixlen: :max,
|
|
6298
6675
|
source: MkKernel::ALL_NUMERIC + [:bool, :object, :fixlen],
|
|
@@ -6500,11 +6877,11 @@ MkKernel.reduce :stddev,
|
|
|
6500
6877
|
# -fopenmp-simd). The 2x f64 speedup survives SL.1.2's reduction(min/max:)
|
|
6501
6878
|
# vectorizer reject because fminnm + fmaxnm dual-issue on M2's two FP pipes.
|
|
6502
6879
|
#
|
|
6503
|
-
# if-form discipline
|
|
6880
|
+
# if-form discipline for multi-reduction fused kernels:
|
|
6504
6881
|
# the body uses `if (v < lo) lo = v;` etc, NOT ternary `lo = (v < lo) ? v : lo;`.
|
|
6505
6882
|
# DO NOT change to ternary — pragma-less ILP path depends on if-form.
|
|
6506
6883
|
#
|
|
6507
|
-
# FM.1.0 scope: mask not yet propagated (FM.1.5). Numeric
|
|
6884
|
+
# FM.1.0 scope: mask not yet propagated (FM.1.5). Numeric data types only.
|
|
6508
6885
|
|
|
6509
6886
|
MkKernel.reduce :minmax,
|
|
6510
6887
|
state: { lo: :load_type, hi: :load_type },
|
|
@@ -6518,6 +6895,7 @@ MkKernel.reduce :minmax,
|
|
|
6518
6895
|
# together on init, get set together on first reduce).
|
|
6519
6896
|
object: 'if (lo == Qundef) { lo = v; hi = v; } else { if (RTEST(rb_funcall(v, rb_intern("<"), 1, lo))) lo = v; if (RTEST(rb_funcall(v, rb_intern(">"), 1, hi))) hi = v; }' },
|
|
6520
6897
|
outputs: 2,
|
|
6898
|
+
all_nan_result: :nan,
|
|
6521
6899
|
finish: { min: "lo", max: "hi" },
|
|
6522
6900
|
source: MkKernel::ALL_NUMERIC + [:bool, :object],
|
|
6523
6901
|
# bool: u64 (Integer 0/1) so minmax returns [0/1, 0/1], not
|
|
@@ -6541,9 +6919,9 @@ MkKernel.reduce :minmax,
|
|
|
6541
6919
|
# actual name (`best_v` here) through to the macro -- the macro accepts
|
|
6542
6920
|
# any identifier as its accumulator argument.
|
|
6543
6921
|
#
|
|
6544
|
-
# Exposed as Ruby `min_index` / `max_index` (= naming
|
|
6545
|
-
#
|
|
6546
|
-
#
|
|
6922
|
+
# Exposed as Ruby `min_index` / `max_index` (= the naming rule that a
|
|
6923
|
+
# method returning a position uses the `_index` suffix; see the rules
|
|
6924
|
+
# table in guides/devel/00_glossary.md). These
|
|
6547
6925
|
# replace the legacy `min_addr` / `max_addr` retired in E.7 stat_proc
|
|
6548
6926
|
# retire (commit f5c7ecd). 3.0 breaking: name change from `*_addr` to
|
|
6549
6927
|
# `*_index` is intentional.
|
|
@@ -6566,6 +6944,7 @@ MkKernel.reduce :argmin,
|
|
|
6566
6944
|
# An ORDERABLE Face descends to its numeric storage (position output needs
|
|
6567
6945
|
# no re-lift; the axis-local index is identical for Face and storage).
|
|
6568
6946
|
face_gate: :strip,
|
|
6947
|
+
all_nan_result: :undef,
|
|
6569
6948
|
public_method: :min_index
|
|
6570
6949
|
|
|
6571
6950
|
MkKernel.reduce :argmax,
|
|
@@ -6584,6 +6963,7 @@ MkKernel.reduce :argmax,
|
|
|
6584
6963
|
fallback: :raise,
|
|
6585
6964
|
mask_policy: :min_count,
|
|
6586
6965
|
face_gate: :strip,
|
|
6966
|
+
all_nan_result: :undef,
|
|
6587
6967
|
public_method: :max_index
|
|
6588
6968
|
|
|
6589
6969
|
# ---- argmin_addr / argmax_addr (view-flat address variants) ---------
|
|
@@ -6595,10 +6975,10 @@ MkKernel.reduce :argmax,
|
|
|
6595
6975
|
# axis-local-to-flat-addr round-trip).
|
|
6596
6976
|
#
|
|
6597
6977
|
# Paired with the sort family's sort_addr(axis:) (= already public)
|
|
6598
|
-
# and partition family's partition_addr_ki (= internal).
|
|
6599
|
-
#
|
|
6600
|
-
#
|
|
6601
|
-
#
|
|
6978
|
+
# and partition family's partition_addr_ki (= internal). `_addr`
|
|
6979
|
+
# is OK to expose for a per-axis primitive that returns a real flat
|
|
6980
|
+
# address: the dual API is `_index` for an axis-local position, `_addr`
|
|
6981
|
+
# for a view-flat one.
|
|
6602
6982
|
#
|
|
6603
6983
|
# Runtime constraints:
|
|
6604
6984
|
# - naxes == 1 (single-axis reduce): view-flat transform applied
|
|
@@ -6627,6 +7007,7 @@ MkKernel.reduce :argmin_addr,
|
|
|
6627
7007
|
mask_policy: :min_count,
|
|
6628
7008
|
semantics: :view_flat,
|
|
6629
7009
|
face_gate: :strip,
|
|
7010
|
+
all_nan_result: :undef,
|
|
6630
7011
|
public_method: :min_addr
|
|
6631
7012
|
|
|
6632
7013
|
MkKernel.reduce :argmax_addr,
|
|
@@ -6644,6 +7025,7 @@ MkKernel.reduce :argmax_addr,
|
|
|
6644
7025
|
mask_policy: :min_count,
|
|
6645
7026
|
semantics: :view_flat,
|
|
6646
7027
|
face_gate: :strip,
|
|
7028
|
+
all_nan_result: :undef,
|
|
6647
7029
|
public_method: :max_addr
|
|
6648
7030
|
|
|
6649
7031
|
# ---- mask_policy demos ------------------------------------------------
|
|
@@ -6771,9 +7153,13 @@ MkKernel.reduce :count_false,
|
|
|
6771
7153
|
|
|
6772
7154
|
MkKernel.reduce :count_equal,
|
|
6773
7155
|
init: "0",
|
|
6774
|
-
|
|
7156
|
+
# The object lane compares with rb_equal (= Ruby ==), not the C ==, which
|
|
7157
|
+
# on two VALUEs would ask whether they are the same object.
|
|
7158
|
+
reduce: { numeric: "if (v == value_arg) acc += 1",
|
|
7159
|
+
object: "if (RTEST(rb_equal(v, value_arg))) acc += 1" },
|
|
6775
7160
|
reduction_kind: :plus, # SL.1.4 (conditional predication; clang predicates safely under reduction(+:acc))
|
|
6776
|
-
source: MkKernel::ALL_NUMERIC,
|
|
7161
|
+
source: MkKernel::ALL_NUMERIC + [:object, :fixlen],
|
|
7162
|
+
fixlen: :count_equal, # bespoke memcmp walk (a blob has no scalar C type)
|
|
6777
7163
|
output: :i64,
|
|
6778
7164
|
ruby_scalar: :LL2NUM,
|
|
6779
7165
|
fallback: :raise,
|
|
@@ -6806,7 +7192,8 @@ MkKernel.reduce :count_equal,
|
|
|
6806
7192
|
# paths removed (= ALL_NUMERIC + :raise fallback). Re-add via demand-
|
|
6807
7193
|
# driven complex specialization or CA_OBJECT bridge phase.
|
|
6808
7194
|
# - **per-axis support gained**: `a.wsum(w, 0)`, `a.wsum(w, 0, 1)`, etc.
|
|
6809
|
-
# (= original "open per-axis" goal
|
|
7195
|
+
# (= the original "open per-axis" goal: every reduction gains an
|
|
7196
|
+
# axis form).
|
|
6810
7197
|
#
|
|
6811
7198
|
# public_method: true rebinds `wsum` from legacy rb_ca_wsum to
|
|
6812
7199
|
# rb_ca_wsum_ki at Init time.
|
|
@@ -6965,22 +7352,38 @@ MkKernel.scan :cumprod,
|
|
|
6965
7352
|
# init Qnil is the "no running extremum yet" sentinel, also never leaked
|
|
6966
7353
|
# (unseen cells are masked). First unmasked cell adopts v as acc;
|
|
6967
7354
|
# subsequent unmasked cells compare via rb_funcall(:>) / rb_funcall(:<).
|
|
7355
|
+
# The float lane starts at NaN and folds with C99 fmin / fmax, whose
|
|
7356
|
+
# rule is exactly the one wanted: a lone number beats NaN, two NaNs give
|
|
7357
|
+
# NaN. So a prefix that has seen only NaN answers NaN instead of leaking
|
|
7358
|
+
# +/-INFINITY -- the running form of the rule min / max follow
|
|
7359
|
+
# (all_nan_result:). fmin / fmax are exact on a float at either width,
|
|
7360
|
+
# so the f32 lane needs no narrowing. Integer and boolean have no NaN
|
|
7361
|
+
# and keep the limit init and the plain compare.
|
|
6968
7362
|
MkKernel.scan :cummax,
|
|
6969
7363
|
source: MkKernel::ALL_NUMERIC + [:bool, :object],
|
|
6970
7364
|
output: { bool: :u64, default: :preserve },
|
|
6971
|
-
init: { numeric: "T_LIMIT_LO", bool: "T_LIMIT_LO", object: "Qnil" },
|
|
6972
|
-
step: {
|
|
7365
|
+
init: { float: "NAN", numeric: "T_LIMIT_LO", bool: "T_LIMIT_LO", object: "Qnil" },
|
|
7366
|
+
step: { float: "acc = fmax(acc, v); r = acc",
|
|
7367
|
+
numeric: "if (v > acc) acc = v; r = acc",
|
|
6973
7368
|
bool: "if ((uint64_t) v > acc) acc = v; r = acc",
|
|
6974
7369
|
object: 'if (acc == Qnil) acc = v; else if (RTEST(rb_funcall(v, rb_intern(">"), 1, acc))) acc = v; r = acc' },
|
|
6975
7370
|
fallback: :raise,
|
|
6976
7371
|
axis_default: :flatten,
|
|
6977
7372
|
empty: :undef
|
|
6978
7373
|
|
|
7374
|
+
# The float lane starts at NaN and folds with C99 fmin / fmax, whose
|
|
7375
|
+
# rule is exactly the one wanted: a lone number beats NaN, two NaNs give
|
|
7376
|
+
# NaN. So a prefix that has seen only NaN answers NaN instead of leaking
|
|
7377
|
+
# +/-INFINITY -- the running form of the rule min / max follow
|
|
7378
|
+
# (all_nan_result:). fmin / fmax are exact on a float at either width,
|
|
7379
|
+
# so the f32 lane needs no narrowing. Integer and boolean have no NaN
|
|
7380
|
+
# and keep the limit init and the plain compare.
|
|
6979
7381
|
MkKernel.scan :cummin,
|
|
6980
7382
|
source: MkKernel::ALL_NUMERIC + [:bool, :object],
|
|
6981
7383
|
output: { bool: :u64, default: :preserve },
|
|
6982
|
-
init: { numeric: "T_LIMIT_HI", bool: "T_LIMIT_HI", object: "Qnil" },
|
|
6983
|
-
step: {
|
|
7384
|
+
init: { float: "NAN", numeric: "T_LIMIT_HI", bool: "T_LIMIT_HI", object: "Qnil" },
|
|
7385
|
+
step: { float: "acc = fmin(acc, v); r = acc",
|
|
7386
|
+
numeric: "if (v < acc) acc = v; r = acc",
|
|
6984
7387
|
bool: "if ((uint64_t) v < acc) acc = v; r = acc",
|
|
6985
7388
|
object: 'if (acc == Qnil) acc = v; else if (RTEST(rb_funcall(v, rb_intern("<"), 1, acc))) acc = v; r = acc' },
|
|
6986
7389
|
fallback: :raise,
|
|
@@ -7015,7 +7418,7 @@ MkKernel.scan :cumcount,
|
|
|
7015
7418
|
# acc_type: :load_type -> acc carries T_LOAD (= last seen input value).
|
|
7016
7419
|
# STEP additionally sees `first` (int) marking the first live cell of
|
|
7017
7420
|
# the fiber. Output data_type = :bool. No production consumer remains:
|
|
7018
|
-
# every
|
|
7421
|
+
# every data type's mask_duplicates now uses the O(distinct) seen-set hash
|
|
7019
7422
|
# lane (__mask_duplicates__, with boolean riding its uint8 lane), which
|
|
7020
7423
|
# has no sort buffers. The numeric widths are kept as a standalone scan
|
|
7021
7424
|
# kernel (a sort-path reference oracle in the mask_duplicates tests).
|
|
@@ -7368,12 +7771,32 @@ MkKernel.search :search_nearest,
|
|
|
7368
7771
|
object: <<~C,
|
|
7369
7772
|
/* CA_OBJECT nearest: minimum of query_val.distance(cell), compared
|
|
7370
7773
|
with `<` (matches the legacy flat proc_nearest_addr_VALUE). */
|
|
7774
|
+
/* Nearest needs a metric. #distance is the protocol the 2.0 flat
|
|
7775
|
+
kernel used, back when Numeric#distance was a monkey patch; it
|
|
7776
|
+
is an opt-in refinement now, and a refinement does not reach an
|
|
7777
|
+
rb_funcall from C -- so a number reaching here answers no more
|
|
7778
|
+
than a String does. Measure a number the way #distance itself
|
|
7779
|
+
does, keep #distance for anything that defines a real one, and
|
|
7780
|
+
refuse the rest by name rather than let a bare NoMethodError out
|
|
7781
|
+
of the loop below. */
|
|
7782
|
+
ID nearest_id = rb_intern("distance");
|
|
7783
|
+
int nearest_by_distance = rb_respond_to(query_val, nearest_id);
|
|
7784
|
+
if ( ! nearest_by_distance && ! rb_obj_is_kind_of(query_val, rb_cNumeric) ) {
|
|
7785
|
+
rb_raise(rb_eCADataTypeError,
|
|
7786
|
+
"search_nearest: nearest needs a distance, and %s is neither a "
|
|
7787
|
+
"number nor answers #distance (define one on the stored "
|
|
7788
|
+
"objects, or use search / bsearch for an exact match)",
|
|
7789
|
+
rb_obj_classname(query_val));
|
|
7790
|
+
}
|
|
7371
7791
|
result = (ca_size_t) -1;
|
|
7372
7792
|
VALUE best = Qnil;
|
|
7373
7793
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7374
7794
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7375
7795
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7376
|
-
VALUE dist =
|
|
7796
|
+
VALUE dist = nearest_by_distance
|
|
7797
|
+
? rb_funcall(query_val, nearest_id, 1, v)
|
|
7798
|
+
: rb_funcall(rb_funcall(query_val, '-', 1, v),
|
|
7799
|
+
rb_intern("abs"), 0);
|
|
7377
7800
|
if ( NIL_P(best) || RTEST(rb_funcall(dist, rb_intern("<"), 1, best)) ) {
|
|
7378
7801
|
best = dist; result = i;
|
|
7379
7802
|
}
|
|
@@ -7392,8 +7815,8 @@ MkKernel.search :search_nearest,
|
|
|
7392
7815
|
# (= axis-local position) is transformed to a view-flat (row-major) address
|
|
7393
7816
|
# into self by the dispatcher. Paired with the *_index family
|
|
7394
7817
|
# (= bsearch / search / search_nearest already returning axis-local
|
|
7395
|
-
# positions per the dual `_index` / `_addr` API
|
|
7396
|
-
#
|
|
7818
|
+
# positions per the dual `_index` / `_addr` API: `_index` for an
|
|
7819
|
+
# axis-local position, `_addr` for a view-flat one).
|
|
7397
7820
|
#
|
|
7398
7821
|
# Scope: scalar val path only (= case A). CArray val + broadcast
|
|
7399
7822
|
# path (case B/C) raises NotImpError until extended (= the per-element
|
|
@@ -7549,12 +7972,32 @@ MkKernel.search :search_nearest_addr,
|
|
|
7549
7972
|
object: <<~C,
|
|
7550
7973
|
/* CA_OBJECT nearest (view_flat addr): minimum of
|
|
7551
7974
|
query_val.distance(cell), compared with `<`. */
|
|
7975
|
+
/* Nearest needs a metric. #distance is the protocol the 2.0 flat
|
|
7976
|
+
kernel used, back when Numeric#distance was a monkey patch; it
|
|
7977
|
+
is an opt-in refinement now, and a refinement does not reach an
|
|
7978
|
+
rb_funcall from C -- so a number reaching here answers no more
|
|
7979
|
+
than a String does. Measure a number the way #distance itself
|
|
7980
|
+
does, keep #distance for anything that defines a real one, and
|
|
7981
|
+
refuse the rest by name rather than let a bare NoMethodError out
|
|
7982
|
+
of the loop below. */
|
|
7983
|
+
ID nearest_id = rb_intern("distance");
|
|
7984
|
+
int nearest_by_distance = rb_respond_to(query_val, nearest_id);
|
|
7985
|
+
if ( ! nearest_by_distance && ! rb_obj_is_kind_of(query_val, rb_cNumeric) ) {
|
|
7986
|
+
rb_raise(rb_eCADataTypeError,
|
|
7987
|
+
"search_nearest_addr: nearest needs a distance, and %s is neither a "
|
|
7988
|
+
"number nor answers #distance (define one on the stored "
|
|
7989
|
+
"objects, or use search / bsearch for an exact match)",
|
|
7990
|
+
rb_obj_classname(query_val));
|
|
7991
|
+
}
|
|
7552
7992
|
result = (ca_size_t) -1;
|
|
7553
7993
|
VALUE best = Qnil;
|
|
7554
7994
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7555
7995
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7556
7996
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7557
|
-
VALUE dist =
|
|
7997
|
+
VALUE dist = nearest_by_distance
|
|
7998
|
+
? rb_funcall(query_val, nearest_id, 1, v)
|
|
7999
|
+
: rb_funcall(rb_funcall(query_val, '-', 1, v),
|
|
8000
|
+
rb_intern("abs"), 0);
|
|
7558
8001
|
if ( NIL_P(best) || RTEST(rb_funcall(dist, rb_intern("<"), 1, best)) ) {
|
|
7559
8002
|
best = dist; result = i;
|
|
7560
8003
|
}
|
|
@@ -7805,13 +8248,16 @@ MkKernel.monop :abs_i,
|
|
|
7805
8248
|
MkKernel::SINT64_DTYPES => "(#2) = llabs(#1);",
|
|
7806
8249
|
MkKernel::UINT_DTYPES => "(#2) = (#1);",
|
|
7807
8250
|
MkKernel::FLOAT_DTYPES => "(#2) = fabs((float64_t)#1);",
|
|
7808
|
-
|
|
8251
|
+
[:cmplx64] => "(#2) = cabsf(#1);",
|
|
8252
|
+
[:cmplx128] => "(#2) = cabs(#1);",
|
|
7809
8253
|
[:object] => '(#2) = rb_funcall((#1), rb_intern("abs"), 0);',
|
|
7810
8254
|
}
|
|
7811
8255
|
|
|
7812
8256
|
# abs: data_type-changing monop (the framework-piece test customer for monop
|
|
7813
8257
|
# Hash output form). numeric input -> preserve data_type (= int/float abs),
|
|
7814
|
-
# complex input ->
|
|
8258
|
+
# complex input -> the real component width of that complex data_type
|
|
8259
|
+
# (= magnitude is real, and a cmplx64 magnitude is a float32 just as
|
|
8260
|
+
# `.real` and `.imag` are). Replaces the hand-
|
|
7815
8261
|
# written rb_ca_abs / rb_ca_abs_bang in ext/carray_math.c. Object data_type
|
|
7816
8262
|
# kept on abs_i (= bind: false on object would need a different output
|
|
7817
8263
|
# rule; deferred). The abs_i monop above remains the primary kernel for
|
|
@@ -7820,13 +8266,15 @@ MkKernel.monop :abs_i,
|
|
|
7820
8266
|
# returns the real-valued magnitude entity.
|
|
7821
8267
|
MkKernel.monop :abs,
|
|
7822
8268
|
source: MkKernel::ALL_NUMERIC + MkKernel::CMPLX_DTYPES,
|
|
7823
|
-
output: { numeric: :preserve, complex: :
|
|
8269
|
+
output: { numeric: :preserve, complex: :real_of_source },
|
|
7824
8270
|
expr: {
|
|
7825
8271
|
MkKernel::SINT_SMALL_DTYPES => "(#2) = abs(#1);",
|
|
7826
8272
|
MkKernel::SINT64_DTYPES => "(#2) = llabs(#1);",
|
|
7827
8273
|
MkKernel::UINT_DTYPES => "(#2) = (#1);",
|
|
7828
8274
|
MkKernel::FLOAT_DTYPES => "(#2) = fabs(#1);",
|
|
7829
|
-
|
|
8275
|
+
# complex -> the real magnitude, at that complex type's own width
|
|
8276
|
+
[:cmplx64] => "(#2) = cabsf(#1);",
|
|
8277
|
+
[:cmplx128] => "(#2) = cabs(#1);",
|
|
7830
8278
|
}
|
|
7831
8279
|
|
|
7832
8280
|
# abs2: squared magnitude. For real x this is x*x (identical to :square
|
|
@@ -7837,22 +8285,22 @@ MkKernel.monop :abs,
|
|
|
7837
8285
|
# optics, signal processing) do not pay for a sqrt they immediately
|
|
7838
8286
|
# square away.
|
|
7839
8287
|
#
|
|
7840
|
-
# Output data_type follows :abs: numeric preserved, complex
|
|
8288
|
+
# Output data_type follows :abs: numeric preserved, complex demoted to
|
|
8289
|
+
# its own real component width.
|
|
7841
8290
|
MkKernel.monop :abs2,
|
|
7842
8291
|
source: MkKernel::ALL_NUMERIC + MkKernel::CMPLX_DTYPES,
|
|
7843
|
-
output: { numeric: :preserve, complex: :
|
|
8292
|
+
output: { numeric: :preserve, complex: :real_of_source },
|
|
7844
8293
|
expr: {
|
|
7845
8294
|
numeric: "(#2) = (#1) * (#1);",
|
|
7846
|
-
|
|
7847
|
-
|
|
8295
|
+
}.merge(MkKernel.cmplx_widths(
|
|
8296
|
+
"{ <t> _r = creal<f>(#1); <t> _i = cimag<f>(#1); (#2) = _r * _r + _i * _i; }"))
|
|
7848
8297
|
|
|
7849
8298
|
MkKernel.monop :conj,
|
|
7850
8299
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
7851
8300
|
expr: {
|
|
7852
8301
|
numeric: "(#2) = (#1);",
|
|
7853
|
-
complex: "(#2) = conj(#1);",
|
|
7854
8302
|
object: '(#2) = rb_funcall((#1), rb_intern("conj"), 0);',
|
|
7855
|
-
}
|
|
8303
|
+
}.merge(MkKernel.cmplx_widths("(#2) = conj<f>(#1);"))
|
|
7856
8304
|
|
|
7857
8305
|
# arg: data_type-changing monop — phase angle of the complex plane.
|
|
7858
8306
|
# Mathematically `arg(z)` for z = re + im*i is `atan2(im, re)` in
|
|
@@ -7863,11 +8311,11 @@ MkKernel.monop :conj,
|
|
|
7863
8311
|
# Replaces the hand-written rb_ca_arg in ext/carray_numeric.c (which
|
|
7864
8312
|
# was f64-only, float-or-complex parent, also computed `carg`).
|
|
7865
8313
|
#
|
|
7866
|
-
# Output data_type
|
|
7867
|
-
#
|
|
7868
|
-
#
|
|
7869
|
-
#
|
|
7870
|
-
#
|
|
8314
|
+
# Output data_type keeps the width the input carries its real values in:
|
|
8315
|
+
# a float stays that float, a complex demotes to its own real component
|
|
8316
|
+
# width (cmplx64 -> f32, cmplx128 -> f64). Integers are the exception
|
|
8317
|
+
# and go to f64, because pi does not fit any integer slot — preserving
|
|
8318
|
+
# the int data_type would silently truncate `arg(-1) = pi` to 3.
|
|
7871
8319
|
#
|
|
7872
8320
|
# 3.0 breaking (vs hand-written rb_ca_arg):
|
|
7873
8321
|
# - integer input is now accepted (was a raise). Returns f64
|
|
@@ -7897,16 +8345,15 @@ MkKernel.monop :sign,
|
|
|
7897
8345
|
MkKernel::UINT_DTYPES => "(#2) = ((#1) > 0) ? 1 : 0;",
|
|
7898
8346
|
MkKernel::SINT_DTYPES => "(#2) = ((#1) > 0) - ((#1) < 0);",
|
|
7899
8347
|
MkKernel::FLOAT_DTYPES => "(#2) = isnan(#1) ? (#1) : (((#1) > 0) - ((#1) < 0));",
|
|
7900
|
-
|
|
7901
|
-
|
|
8348
|
+
}.merge(MkKernel.cmplx_widths(
|
|
8349
|
+
"{ <t> _m = cabs<f>(#1); (#2) = (_m == 0) ? 0 : ((#1) / _m); }"))
|
|
7902
8350
|
|
|
7903
8351
|
MkKernel.monop :arg,
|
|
7904
8352
|
source: MkKernel::ALL_NUMERIC + MkKernel::CMPLX_DTYPES,
|
|
7905
|
-
output: {
|
|
8353
|
+
output: { int: :f64, float: :preserve, complex: :real_of_source },
|
|
7906
8354
|
expr: {
|
|
7907
8355
|
numeric: "(#2) = carg((cmplx128_t)(#1));",
|
|
7908
|
-
|
|
7909
|
-
}
|
|
8356
|
+
}.merge(MkKernel.cmplx_widths("(#2) = carg<f>(#1);"))
|
|
7910
8357
|
|
|
7911
8358
|
# imag_i: data_type-preserving kernel that places the imag part in the
|
|
7912
8359
|
# slot (= cimag for complex stores into the real component since cmplx
|
|
@@ -7920,9 +8367,8 @@ MkKernel.monop :imag_i,
|
|
|
7920
8367
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
7921
8368
|
expr: {
|
|
7922
8369
|
numeric: "(#2) = 0;",
|
|
7923
|
-
complex: "(#2) = cimag(#1);",
|
|
7924
8370
|
object: '(#2) = rb_funcall((#1), rb_intern("imaginary"), 0);',
|
|
7925
|
-
}
|
|
8371
|
+
}.merge(MkKernel.cmplx_widths("(#2) = cimag<f>(#1);"))
|
|
7926
8372
|
|
|
7927
8373
|
# arg_i: data_type-preserving kernel that writes the complex argument
|
|
7928
8374
|
# (phase angle) into the slot. For complex input, cassignment from a
|
|
@@ -7937,9 +8383,9 @@ MkKernel.monop :imag_i,
|
|
|
7937
8383
|
MkKernel.monop :arg_i,
|
|
7938
8384
|
source: MkKernel::FLOAT_DTYPES + MkKernel::CMPLX_DTYPES,
|
|
7939
8385
|
expr: {
|
|
7940
|
-
[:f32] => "(#2) = (
|
|
8386
|
+
[:f32] => "(#2) = cargf((cmplx64_t)(#1));",
|
|
7941
8387
|
[:f64] => "(#2) = carg((cmplx128_t)(#1));",
|
|
7942
|
-
[:cmplx64] => "(#2) = (
|
|
8388
|
+
[:cmplx64] => "(#2) = cargf(#1);",
|
|
7943
8389
|
[:cmplx128] => "(#2) = carg(#1);",
|
|
7944
8390
|
}
|
|
7945
8391
|
|
|
@@ -7995,7 +8441,8 @@ MkKernel.monfunc :rcp,
|
|
|
7995
8441
|
expr: {
|
|
7996
8442
|
int: "if ((#1)==0) {ca_zerodiv();}; (#2) = 1/(#1);",
|
|
7997
8443
|
float: "(#2) = 1/(#1);",
|
|
7998
|
-
|
|
8444
|
+
[:cmplx64] => "(#2) = op_crcp_cmplx64(#1);",
|
|
8445
|
+
[:cmplx128] => "(#2) = 1/(#1);",
|
|
7999
8446
|
object: '(#2) = rb_funcall(INT2NUM(1), rb_intern("/"), 1, (#1));',
|
|
8000
8447
|
}
|
|
8001
8448
|
|
|
@@ -8004,7 +8451,6 @@ MkKernel.monfunc :rcp,
|
|
|
8004
8451
|
{
|
|
8005
8452
|
sqrt: "sqrt",
|
|
8006
8453
|
exp: "exp",
|
|
8007
|
-
log: "log",
|
|
8008
8454
|
sin: "sin",
|
|
8009
8455
|
cos: "cos",
|
|
8010
8456
|
tan: "tan",
|
|
@@ -8015,21 +8461,37 @@ MkKernel.monfunc :rcp,
|
|
|
8015
8461
|
MkKernel.monfunc op,
|
|
8016
8462
|
source: MkKernel::FLOAT_DTYPES + MkKernel::CMPLX_DTYPES + [:object],
|
|
8017
8463
|
expr: {
|
|
8018
|
-
float: "(#2) = #{c_fn}(#1);",
|
|
8019
|
-
complex: "(#2) = c#{c_fn}(#1);",
|
|
8020
8464
|
object: MkKernel.obj_float_math("#{c_fn}(<v>)", c_fn),
|
|
8021
|
-
}
|
|
8465
|
+
}.merge(MkKernel.float_widths("(#2) = #{c_fn}<f>(#1);"))
|
|
8466
|
+
.merge(MkKernel.cmplx_widths("(#2) = c#{c_fn}<f>(#1);"))
|
|
8022
8467
|
end
|
|
8023
8468
|
|
|
8469
|
+
# log is the one member of the family whose complex form cancels, so it
|
|
8470
|
+
# does not follow the others onto `clogf`. The real part of `clog(z)` is
|
|
8471
|
+
# `log|z|`, which vanishes on the unit circle; computing |z| at the
|
|
8472
|
+
# operand's width rounds away everything the logarithm then needs, and
|
|
8473
|
+
# the answer comes back with a relative error near 400 instead of 1e-7.
|
|
8474
|
+
# The complex branch therefore stays on the double-taking `clog`, the way
|
|
8475
|
+
# `*` and `/` do -- see ca_op_cmplx64.h. Real `log` has no such step:
|
|
8476
|
+
# its operand is the argument itself, and `logf` measures at one float
|
|
8477
|
+
# epsilon right through x = 1.
|
|
8478
|
+
MkKernel.monfunc :log,
|
|
8479
|
+
source: MkKernel::FLOAT_DTYPES + MkKernel::CMPLX_DTYPES + [:object],
|
|
8480
|
+
expr: {
|
|
8481
|
+
complex: "(#2) = clog(#1);",
|
|
8482
|
+
object: MkKernel.obj_float_math("log(<v>)", "log"),
|
|
8483
|
+
}.merge(MkKernel.float_widths("(#2) = log<f>(#1);"))
|
|
8484
|
+
|
|
8024
8485
|
# exp2 special case: complex variant uses cpow(2, x), not cexp2 (which
|
|
8025
8486
|
# isn't standardized in C99/POSIX).
|
|
8026
8487
|
MkKernel.monfunc :exp2,
|
|
8027
8488
|
source: MkKernel::FLOAT_DTYPES + MkKernel::CMPLX_DTYPES + [:object],
|
|
8028
8489
|
expr: {
|
|
8029
|
-
|
|
8490
|
+
# cpow(a, z) is cexp(z * clog(a)), so it inherits clog's cancellation
|
|
8491
|
+
# and stays on the double-taking form for both complex data_types.
|
|
8030
8492
|
complex: "(#2) = cpow(2, (#1));",
|
|
8031
8493
|
object: MkKernel.obj_float_math("exp2(<v>)", "exp2"),
|
|
8032
|
-
}
|
|
8494
|
+
}.merge(MkKernel.float_widths("(#2) = exp2<f>(#1);"))
|
|
8033
8495
|
|
|
8034
8496
|
# log10, log2, logb: no complex variant in the original mkmath emit
|
|
8035
8497
|
{
|
|
@@ -8040,9 +8502,8 @@ MkKernel.monfunc :exp2,
|
|
|
8040
8502
|
MkKernel.monfunc op,
|
|
8041
8503
|
source: MkKernel::FLOAT_DTYPES + [:object],
|
|
8042
8504
|
expr: {
|
|
8043
|
-
float: "(#2) = #{c_fn}(#1);",
|
|
8044
8505
|
object: MkKernel.obj_float_math("#{c_fn}(<v>)", c_fn),
|
|
8045
|
-
}
|
|
8506
|
+
}.merge(MkKernel.float_widths("(#2) = #{c_fn}<f>(#1);"))
|
|
8046
8507
|
end
|
|
8047
8508
|
|
|
8048
8509
|
# exp10: special object expr (= bypass OBJ_FLOAT_MATH for the
|
|
@@ -8050,8 +8511,6 @@ end
|
|
|
8050
8511
|
MkKernel.monfunc :exp10,
|
|
8051
8512
|
source: MkKernel::FLOAT_DTYPES + MkKernel::CMPLX_DTYPES + [:object],
|
|
8052
8513
|
expr: {
|
|
8053
|
-
float: "(#2) = pow(10, (#1));",
|
|
8054
|
-
complex: "(#2) = cpow(10, (#1));",
|
|
8055
8514
|
object: <<~SNIPPET,
|
|
8056
8515
|
{
|
|
8057
8516
|
VALUE _obj_arg = (#1);
|
|
@@ -8062,10 +8521,13 @@ MkKernel.monfunc :exp10,
|
|
|
8062
8521
|
}
|
|
8063
8522
|
}
|
|
8064
8523
|
SNIPPET
|
|
8065
|
-
|
|
8524
|
+
complex: "(#2) = cpow(10, (#1));", # see exp2 on why cpow stays wide
|
|
8525
|
+
}.merge(MkKernel.float_widths("(#2) = pow<f>(10, (#1));"))
|
|
8066
8526
|
|
|
8067
|
-
# Hyperbolic family:
|
|
8068
|
-
#
|
|
8527
|
+
# Hyperbolic family: float uses the real-typed C function, complex uses
|
|
8528
|
+
# the C99 `c`-prefixed one. Passing a `double _Complex` to `sinh(double)`
|
|
8529
|
+
# discards the imaginary part, so the complex branch must not share the
|
|
8530
|
+
# real-typed name.
|
|
8069
8531
|
{
|
|
8070
8532
|
sinh: "sinh",
|
|
8071
8533
|
cosh: "cosh",
|
|
@@ -8077,10 +8539,9 @@ MkKernel.monfunc :exp10,
|
|
|
8077
8539
|
MkKernel.monfunc op,
|
|
8078
8540
|
source: MkKernel::FLOAT_DTYPES + MkKernel::CMPLX_DTYPES + [:object],
|
|
8079
8541
|
expr: {
|
|
8080
|
-
float: "(#2) = #{c_fn}(#1);",
|
|
8081
|
-
complex: "(#2) = #{c_fn}(#1);",
|
|
8082
8542
|
object: MkKernel.obj_float_math("#{c_fn}(<v>)", c_fn),
|
|
8083
|
-
}
|
|
8543
|
+
}.merge(MkKernel.float_widths("(#2) = #{c_fn}<f>(#1);"))
|
|
8544
|
+
.merge(MkKernel.cmplx_widths("(#2) = c#{c_fn}<f>(#1);"))
|
|
8084
8545
|
end
|
|
8085
8546
|
|
|
8086
8547
|
# ---- M.1 (PyTorch alignment): additional monfunc / monop ------------------
|
|
@@ -8094,19 +8555,17 @@ end
|
|
|
8094
8555
|
MkKernel.monfunc op,
|
|
8095
8556
|
source: MkKernel::FLOAT_DTYPES + [:object],
|
|
8096
8557
|
expr: {
|
|
8097
|
-
float: "(#2) = #{c_fn}(#1);",
|
|
8098
8558
|
object: MkKernel.obj_float_math("#{c_fn}(<v>)", c_fn),
|
|
8099
|
-
}
|
|
8559
|
+
}.merge(MkKernel.float_widths("(#2) = #{c_fn}<f>(#1);"))
|
|
8100
8560
|
end
|
|
8101
8561
|
|
|
8102
8562
|
# rsqrt: 1 / sqrt(x). float + complex + object (complex via 1.0 / csqrt).
|
|
8103
8563
|
MkKernel.monfunc :rsqrt,
|
|
8104
8564
|
source: MkKernel::FLOAT_DTYPES + MkKernel::CMPLX_DTYPES + [:object],
|
|
8105
8565
|
expr: {
|
|
8106
|
-
float: "(#2) = 1.0 / sqrt(#1);",
|
|
8107
|
-
complex: "(#2) = 1.0 / csqrt(#1);",
|
|
8108
8566
|
object: MkKernel.obj_float_math("1.0 / sqrt(<v>)", "rsqrt"),
|
|
8109
|
-
}
|
|
8567
|
+
}.merge(MkKernel.float_widths("(#2) = ((<t>)1.0) / sqrt<f>(#1);"))
|
|
8568
|
+
.merge(MkKernel.cmplx_widths("(#2) = ((<t>)1.0) / csqrt<f>(#1);"))
|
|
8110
8569
|
|
|
8111
8570
|
# trunc: toward-zero rounding. Preserve-data_type form like ceil / floor /
|
|
8112
8571
|
# round — int branch is identity, float branch uses C99 trunc, object
|
|
@@ -8136,7 +8595,7 @@ MkKernel.monop :square,
|
|
|
8136
8595
|
# with f64-forced input/output via ca_call_cfunc_1_1; the mkkernel form
|
|
8137
8596
|
# preserves the same numeric behaviour but rides the lazy substrate +
|
|
8138
8597
|
# kernel_iterator engine. Widening monfunc: integer input auto-casts
|
|
8139
|
-
# to f64, float input preserves
|
|
8598
|
+
# to f64, float input preserves data type.
|
|
8140
8599
|
|
|
8141
8600
|
# deg_360: fold into [0, 360). Use double-typed local for the fold
|
|
8142
8601
|
# computation regardless of input precision (matches legacy hand-written
|
|
@@ -8315,12 +8774,12 @@ MkKernel.binop :minimum,
|
|
|
8315
8774
|
object: '(#3) = rb_funcall(rb_assoc_new((#1),(#2)), rb_intern("min"), 0);',
|
|
8316
8775
|
}
|
|
8317
8776
|
|
|
8318
|
-
#
|
|
8777
|
+
# + and - work on the parts independently, so one generic expression
|
|
8778
|
+
# covers every numeric data_type at any width.
|
|
8319
8779
|
{
|
|
8320
|
-
add: ["+", "+"
|
|
8321
|
-
sub: ["-", "-"
|
|
8322
|
-
|
|
8323
|
-
}.each do |name, (op, c_op, _ruby_op)|
|
|
8780
|
+
add: ["+", "+"],
|
|
8781
|
+
sub: ["-", "-"],
|
|
8782
|
+
}.each do |name, (op, c_op)|
|
|
8324
8783
|
MkKernel.binop name,
|
|
8325
8784
|
op: op,
|
|
8326
8785
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
@@ -8331,13 +8790,48 @@ MkKernel.binop :minimum,
|
|
|
8331
8790
|
}
|
|
8332
8791
|
end
|
|
8333
8792
|
|
|
8793
|
+
# * does not: a complex product subtracts two products of the parts, and
|
|
8794
|
+
# that subtraction cancels. A cmplx64 gets it in double, where there are
|
|
8795
|
+
# bits left underneath -- see ca_op_cmplx64.h. Unlike the divide this
|
|
8796
|
+
# costs speed rather than saving it, and is paid for the accuracy alone.
|
|
8797
|
+
MkKernel.binop :mul,
|
|
8798
|
+
op: "*",
|
|
8799
|
+
source: MkKernel::MATH_NUMERIC + [:object],
|
|
8800
|
+
expr: {
|
|
8801
|
+
numeric: "(#3) = (#1) * (#2);",
|
|
8802
|
+
[:cmplx64] => "(#3) = op_cmul_cmplx64((#1), (#2));",
|
|
8803
|
+
[:cmplx128] => "(#3) = (#1) * (#2);",
|
|
8804
|
+
object: '(#3) = rb_funcall((#1), rb_intern("*"), 1, (#2));',
|
|
8805
|
+
}
|
|
8806
|
+
|
|
8807
|
+
# div: `/`. Integer division floors toward -inf (= Ruby `Integer#/` and
|
|
8808
|
+
# NumPy `floor_divide`), so that `(a / b) * b + a % b == a` holds for
|
|
8809
|
+
# every sign combination. C's `/` truncates toward zero, so the signed
|
|
8810
|
+
# lanes correct the quotient by one when the division is inexact and the
|
|
8811
|
+
# operands have opposite signs. Unsigned operands can never disagree in
|
|
8812
|
+
# sign, so they keep the bare C form. Float `/` is true division and is
|
|
8813
|
+
# unchanged (matching Ruby `Float#/`); the identity above therefore holds
|
|
8814
|
+
# for integers only, exactly as in Ruby.
|
|
8334
8815
|
MkKernel.binop :div,
|
|
8335
8816
|
op: "/",
|
|
8336
8817
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
8337
8818
|
expr: {
|
|
8338
|
-
|
|
8819
|
+
MkKernel::UINT_DTYPES => "if ((#2)==0) {ca_zerodiv();}; (#3) = (#1) / (#2);",
|
|
8820
|
+
int: %{
|
|
8821
|
+
if ((#2)==0) {ca_zerodiv();};
|
|
8822
|
+
{
|
|
8823
|
+
<type> _q = (#1) / (#2);
|
|
8824
|
+
if ( (#1) % (#2) != 0 && (((#1) < 0) != ((#2) < 0)) ) { _q -= 1; }
|
|
8825
|
+
(#3) = _q;
|
|
8826
|
+
}
|
|
8827
|
+
},
|
|
8339
8828
|
float: "(#3) = (#1) / (#2);",
|
|
8340
|
-
|
|
8829
|
+
# A cmplx64 divide is computed in double and rounded once: see
|
|
8830
|
+
# ca_op_cmplx64.h for why that is both faster and correctly rounded,
|
|
8831
|
+
# and how Annex G survives it. cmplx128 has no wider type to
|
|
8832
|
+
# borrow, so it stays on the compiler's helper.
|
|
8833
|
+
[:cmplx64] => "(#3) = op_cdiv_cmplx64((#1), (#2));",
|
|
8834
|
+
[:cmplx128] => "(#3) = (#1) / (#2);",
|
|
8341
8835
|
object: '(#3) = rb_funcall((#1), rb_intern("/"), 1, (#2));',
|
|
8342
8836
|
}
|
|
8343
8837
|
|
|
@@ -8357,27 +8851,57 @@ MkKernel.binop :rcp_mul,
|
|
|
8357
8851
|
expr: {
|
|
8358
8852
|
int: "if ((#1)==0) {ca_zerodiv();}; (#3) = (#2) / (#1);",
|
|
8359
8853
|
float: "(#3) = (#2) / (#1);",
|
|
8360
|
-
|
|
8854
|
+
[:cmplx64] => "(#3) = op_cdiv_cmplx64((#2), (#1));",
|
|
8855
|
+
[:cmplx128] => "(#3) = (#2) / (#1);",
|
|
8361
8856
|
object: '(#3) = rb_funcall((#2), rb_intern("/"), 1, (#1));',
|
|
8362
8857
|
}
|
|
8363
8858
|
|
|
8859
|
+
# mod: `%`. Floored modulo -- the result carries the sign of the divisor
|
|
8860
|
+
# (= Ruby `%` and NumPy `np.mod`), and pairs with the floored `/` above.
|
|
8861
|
+
# C's `%` and `fmod` carry the sign of the dividend instead, so the signed
|
|
8862
|
+
# lanes add the divisor back when the remainder is non-zero and disagrees
|
|
8863
|
+
# in sign with it. A zero remainder is given the divisor's sign so the
|
|
8864
|
+
# rule holds without exception (float only; integers have no signed zero).
|
|
8865
|
+
# Unsigned operands already satisfy the rule. The truncating form stays
|
|
8866
|
+
# available as `fmod`.
|
|
8364
8867
|
MkKernel.binop :mod,
|
|
8365
8868
|
op: "%",
|
|
8366
8869
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8367
8870
|
expr: {
|
|
8368
|
-
|
|
8369
|
-
|
|
8871
|
+
MkKernel::UINT_DTYPES => "if ((#2)==0) {ca_zerodiv();}; (#3) = (#1) % (#2);",
|
|
8872
|
+
int: %{
|
|
8873
|
+
if ((#2)==0) {ca_zerodiv();};
|
|
8874
|
+
{
|
|
8875
|
+
<type> _r = (#1) % (#2);
|
|
8876
|
+
if ( _r != 0 && ((_r < 0) != ((#2) < 0)) ) { _r += (#2); }
|
|
8877
|
+
(#3) = _r;
|
|
8878
|
+
}
|
|
8879
|
+
},
|
|
8880
|
+
float: %{
|
|
8881
|
+
{
|
|
8882
|
+
<type> _r = fmod((#1), (#2));
|
|
8883
|
+
if ( _r != 0 ) {
|
|
8884
|
+
if ( (_r < 0) != ((#2) < 0) ) { _r += (#2); }
|
|
8885
|
+
}
|
|
8886
|
+
else { _r = copysign((<type>) 0, (#2)); }
|
|
8887
|
+
(#3) = _r;
|
|
8888
|
+
}
|
|
8889
|
+
},
|
|
8370
8890
|
object: '(#3) = rb_funcall((#1), rb_intern("%"), 1, (#2));',
|
|
8371
8891
|
}
|
|
8372
8892
|
|
|
8373
|
-
|
|
8374
|
-
|
|
8893
|
+
# fmod: the truncating counterpart of `%` -- the remainder carries the
|
|
8894
|
+
# sign of the dividend, as C's `fmod` and `%` do. Defined over integers
|
|
8895
|
+
# too (`%` no longer reaches that form since it floors), where it is C's
|
|
8896
|
+
# `%` with the same zero-divisor guard. Ruby's `Numeric#remainder` is
|
|
8897
|
+
# this same operation, so the object lane delegates to it and keeps the
|
|
8898
|
+
# element's class (an Integer stays an Integer).
|
|
8899
|
+
MkKernel.binop :fmod,
|
|
8375
8900
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8376
8901
|
expr: {
|
|
8377
8902
|
int: "if ((#2)==0) {ca_zerodiv();}; (#3) = (#1) % (#2);",
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
}
|
|
8903
|
+
object: '(#3) = rb_funcall((#1), rb_intern("remainder"), 1, (#2));',
|
|
8904
|
+
}.merge(MkKernel.float_widths("(#3) = fmod<f>((#1), (#2));"))
|
|
8381
8905
|
|
|
8382
8906
|
MkKernel.binop :bit_and_i,
|
|
8383
8907
|
op: "&",
|
|
@@ -8482,6 +9006,7 @@ MkKernel.alias_binop :bit_rshift, :">>"
|
|
|
8482
9006
|
# pow / cpow. Object uses Ruby's `**`.
|
|
8483
9007
|
MkKernel.header_block <<~C
|
|
8484
9008
|
#include "ca_op_powi.h"
|
|
9009
|
+
#include "ca_op_cmplx64.h"
|
|
8485
9010
|
C
|
|
8486
9011
|
|
|
8487
9012
|
# ---- triop family ---------------------------------------------------------
|
|
@@ -8554,10 +9079,9 @@ MkKernel.binop :power,
|
|
|
8554
9079
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
8555
9080
|
expr: {
|
|
8556
9081
|
int: "(#3) = op_powi_<type>((#1), (#2));",
|
|
8557
|
-
|
|
8558
|
-
complex: "(#3) = cpow((#1), (#2));",
|
|
9082
|
+
complex: "(#3) = cpow((#1), (#2));", # see exp2 on why cpow stays wide
|
|
8559
9083
|
object: '(#3) = rb_funcall((#1), rb_intern("**"), 1, (#2));',
|
|
8560
|
-
}
|
|
9084
|
+
}.merge(MkKernel.float_widths("(#3) = pow<f>((#1), (#2));"))
|
|
8561
9085
|
|
|
8562
9086
|
# ---- M.2 + M.3 (PyTorch alignment): float-only binop family --------------
|
|
8563
9087
|
#
|
|
@@ -8573,7 +9097,6 @@ MkKernel.binop :power,
|
|
|
8573
9097
|
copysign: ["copysign", "copysign"],
|
|
8574
9098
|
logaddexp: [nil, nil], # custom expr — see below
|
|
8575
9099
|
nextafter: ["nextafter", "nextafter"],
|
|
8576
|
-
fmod: ["fmod", "fmod"],
|
|
8577
9100
|
atan2: ["atan2", "atan2"],
|
|
8578
9101
|
hypot: ["hypot", "hypot"],
|
|
8579
9102
|
}.each do |op_name, (c_fn, ruby_fb)|
|
|
@@ -8581,7 +9104,6 @@ MkKernel.binop :power,
|
|
|
8581
9104
|
MkKernel.binop op_name,
|
|
8582
9105
|
source: MkKernel::FLOAT_DTYPES + [:object],
|
|
8583
9106
|
expr: {
|
|
8584
|
-
float: "(#3) = #{c_fn}((#1), (#2));",
|
|
8585
9107
|
object: <<~SNIPPET,
|
|
8586
9108
|
{
|
|
8587
9109
|
VALUE _l = (#1);
|
|
@@ -8594,7 +9116,7 @@ MkKernel.binop :power,
|
|
|
8594
9116
|
}
|
|
8595
9117
|
}
|
|
8596
9118
|
SNIPPET
|
|
8597
|
-
}
|
|
9119
|
+
}.merge(MkKernel.float_widths("(#3) = #{c_fn}<f>((#1), (#2));"))
|
|
8598
9120
|
end
|
|
8599
9121
|
|
|
8600
9122
|
# logaddexp: log(exp(x) + exp(y)). Numerically stable form:
|
|
@@ -8603,7 +9125,6 @@ end
|
|
|
8603
9125
|
MkKernel.binop :logaddexp,
|
|
8604
9126
|
source: MkKernel::FLOAT_DTYPES + [:object],
|
|
8605
9127
|
expr: {
|
|
8606
|
-
float: "(#3) = fmax((#1), (#2)) + log1p(exp(-fabs((#1) - (#2))));",
|
|
8607
9128
|
object: <<~SNIPPET,
|
|
8608
9129
|
{
|
|
8609
9130
|
VALUE _l = (#1);
|
|
@@ -8619,7 +9140,8 @@ MkKernel.binop :logaddexp,
|
|
|
8619
9140
|
}
|
|
8620
9141
|
}
|
|
8621
9142
|
SNIPPET
|
|
8622
|
-
}
|
|
9143
|
+
}.merge(MkKernel.float_widths(
|
|
9144
|
+
"(#3) = fmax<f>((#1), (#2)) + log1p<f>(exp<f>(-fabs<f>((#1) - (#2))));"))
|
|
8623
9145
|
|
|
8624
9146
|
# ---- P.5b.4: moncmp family (predicates returning bool) ----------------
|
|
8625
9147
|
|
|
@@ -8669,7 +9191,7 @@ MkKernel.moncmp :is_invalid,
|
|
|
8669
9191
|
# -0.0). Integer branch: sint = (#1) < 0, uint = always 0. Float branch
|
|
8670
9192
|
# uses C99 signbit (handles -0.0 / NaN sign correctly). No complex
|
|
8671
9193
|
# variant (signbit on a complex is ambiguous; rejected at this layer).
|
|
8672
|
-
# `:sint` / `:uint` aren't family aliases — use array-of-
|
|
9194
|
+
# `:sint` / `:uint` aren't family aliases — use the array-of-types form.
|
|
8673
9195
|
MkKernel.moncmp :signbit,
|
|
8674
9196
|
source: MkKernel::SINT_DTYPES + MkKernel::UINT_DTYPES +
|
|
8675
9197
|
MkKernel::FLOAT_DTYPES + [:object],
|