carray 3.0.1 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +824 -0
- data/README.md +5 -6
- data/carray.gemspec +1 -1
- data/ext/ca_axis_dispatch.c +258 -4
- data/ext/ca_axis_group.c +368 -184
- data/ext/ca_binop_dispatch.c +54 -32
- data/ext/ca_binop_dispatch.h +1 -0
- data/ext/ca_categorical_iterator.c +90 -41
- data/ext/ca_compare.c +8 -2
- data/ext/ca_for_buffer.h +21 -68
- data/ext/ca_group_iter.c +27 -11
- data/ext/ca_kernel_iterator.c +960 -245
- data/ext/ca_kernel_iterator.h +352 -181
- data/ext/ca_monop_dispatch.c +4 -4
- data/ext/ca_monop_dispatch.h +4 -0
- data/ext/ca_obj_array.c +194 -42
- data/ext/ca_obj_bincmp.c +152 -121
- data/ext/ca_obj_binop.c +107 -210
- data/ext/ca_obj_bitarray.c +37 -24
- data/ext/ca_obj_bitfield.c +95 -62
- data/ext/ca_obj_block.c +5 -4
- data/ext/ca_obj_byte_swap.c +40 -40
- data/ext/ca_obj_const_string.c +180 -50
- data/ext/ca_obj_face.c +428 -62
- data/ext/ca_obj_face.h +107 -47
- data/ext/ca_obj_fake.c +21 -50
- data/ext/ca_obj_farray.c +1 -0
- data/ext/ca_obj_field.c +16 -3
- data/ext/ca_obj_fixlen_string.c +39 -26
- data/ext/ca_obj_grid.c +181 -124
- data/ext/ca_obj_meld.c +235 -55
- data/ext/ca_obj_moncmp.c +61 -61
- data/ext/ca_obj_monop.c +125 -107
- data/ext/ca_obj_object.c +551 -233
- data/ext/ca_obj_record.c +51 -16
- data/ext/ca_obj_reduce.c +5 -8
- data/ext/ca_obj_refer.c +117 -33
- data/ext/ca_obj_remap.c +57 -17
- data/ext/ca_obj_repeat.c +21 -5
- data/ext/ca_obj_roll.c +115 -11
- data/ext/ca_obj_select.c +88 -75
- data/ext/ca_obj_select_axis.c +58 -60
- data/ext/ca_obj_shift.c +22 -102
- data/ext/ca_obj_stack.c +223 -123
- data/ext/ca_obj_stride.c +267 -35
- data/ext/ca_obj_string.c +25 -22
- data/ext/ca_obj_tile.c +32 -16
- data/ext/ca_obj_time.c +23 -25
- data/ext/ca_obj_timedelta.c +21 -23
- data/ext/ca_obj_transpose.c +12 -3
- data/ext/ca_obj_triop.c +110 -117
- data/ext/ca_obj_window.c +157 -29
- data/ext/ca_op_ipower.c +25 -13
- data/ext/ca_op_powi.h +57 -30
- data/ext/ca_pow5_table.h +656 -0
- data/ext/ca_rng_normal.h +42 -0
- data/ext/ca_rng_xoshiro256pp.h +105 -0
- data/ext/ca_sort_kernels.h +70 -0
- data/ext/ca_sweep_engine.c +385 -198
- data/ext/ca_sweep_engine.h +31 -23
- data/ext/ca_triop_dispatch.c +4 -1
- data/ext/ca_triop_dispatch.h +5 -2
- data/ext/caframe_csv_reader.c +463 -0
- data/ext/carray.h +90 -28
- data/ext/carray_access.c +487 -231
- data/ext/carray_address_basis.c +731 -0
- data/ext/carray_attribute.c +45 -3
- data/ext/carray_bincount.c +32 -2
- data/ext/carray_broadcast.c +49 -17
- data/ext/carray_call_cfunc.c +667 -511
- data/ext/carray_cast.c +1556 -247
- data/ext/carray_cast_func.rb +34 -21
- data/ext/carray_conversion.c +197 -73
- data/ext/carray_copy.c +84 -38
- data/ext/carray_core.c +591 -119
- data/ext/carray_count.c +148 -96
- data/ext/carray_element.c +18 -27
- data/ext/carray_factorize.c +604 -291
- data/ext/carray_generate.c +76 -40
- data/ext/carray_histogram.c +152 -94
- data/ext/carray_hold.c +2 -1
- data/ext/carray_index_classifier.c +63 -96
- data/ext/carray_internal.h +400 -1
- data/ext/carray_lazy.c +834 -79
- data/ext/carray_loop.c +88 -66
- data/ext/carray_mask.c +705 -324
- data/ext/carray_math_kernel.h +1 -0
- data/ext/carray_median_percentile.c +263 -63
- data/ext/carray_memory_view.c +254 -248
- data/ext/carray_operator.c +1093 -363
- data/ext/carray_order.c +133 -69
- data/ext/carray_partition.c +102 -56
- data/ext/carray_random.c +582 -204
- data/ext/carray_scatter.c +308 -65
- data/ext/carray_segment.c +257 -0
- data/ext/carray_slab.c +183 -70
- data/ext/carray_sort.c +235 -240
- data/ext/carray_sort_kernel.c +8 -1
- data/ext/carray_struct.c +99 -21
- data/ext/carray_test.c +167 -64
- data/ext/carray_time_text.c +1182 -0
- data/ext/carray_utils.c +363 -98
- data/ext/extconf.rb +27 -2
- data/ext/mk_call_cfunc.rb +103 -118
- data/ext/mk_pow5_table.rb +35 -0
- data/ext/mkkernel.rb +1349 -851
- data/ext/ruby_carray.c +17 -2
- data/ext/version.h +4 -4
- data/ext/version.rb +4 -2
- data/lib/carray/attribute.rb +39 -0
- data/lib/carray/autoload_carray.rb +24 -3
- data/lib/carray/autoload_method_extension.rb +18 -0
- data/lib/carray/axis_group.rb +245 -20
- data/lib/carray/basics.rb +95 -25
- data/lib/carray/bincount_nd.rb +6 -0
- data/lib/carray/block_iterator.rb +200 -54
- data/lib/carray/boolean_reduce.rb +21 -29
- data/lib/carray/categorical.rb +244 -59
- data/lib/carray/categorical_iterator.rb +391 -757
- data/lib/carray/complex.rb +36 -29
- data/lib/carray/conditional.rb +94 -7
- data/lib/carray/const_string.rb +177 -27
- data/lib/carray/construct.rb +40 -0
- data/lib/carray/data_type_extension.rb +3 -0
- data/lib/carray/data_type_limits.rb +91 -0
- data/lib/carray/fixlen_string.rb +1 -1
- data/lib/carray/frame/concat.rb +157 -6
- data/lib/carray/frame/convert.rb +13 -7
- data/lib/carray/frame/csv_parser.rb +590 -76
- data/lib/carray/frame/describe.rb +79 -0
- data/lib/carray/frame/frame.rb +205 -35
- data/lib/carray/frame/group.rb +199 -40
- data/lib/carray/frame/io.rb +452 -60
- data/lib/carray/frame/join.rb +134 -46
- data/lib/carray/frame/records.rb +54 -19
- data/lib/carray/frame/reshape.rb +448 -0
- data/lib/carray/frame/sort.rb +26 -2
- data/lib/carray/frame/verbs.rb +726 -56
- data/lib/carray/frame.rb +2 -0
- data/lib/carray/fuse_source.rb +111 -2
- data/lib/carray/fusion.rb +229 -38
- data/lib/carray/inspect.rb +71 -20
- data/lib/carray/iterator.rb +157 -2
- data/lib/carray/lazy.rb +54 -67
- data/lib/carray/mask_gap_fill.rb +66 -50
- data/lib/carray/math.rb +32 -19
- data/lib/carray/meld_reduce.rb +40 -7
- data/lib/carray/methods/align_addr.rb +8 -4
- data/lib/carray/methods/bincount.rb +2 -2
- data/lib/carray/methods/bit_string.rb +14 -3
- data/lib/carray/methods/composition.rb +16 -10
- data/lib/carray/methods/discovery_along.rb +74 -0
- data/lib/carray/methods/factorize.rb +50 -0
- data/lib/carray/methods/gather_nd.rb +13 -2
- data/lib/carray/methods/insert_block.rb +9 -14
- data/lib/carray/methods/is_in.rb +130 -6
- data/lib/carray/methods/join.rb +1 -1
- data/lib/carray/methods/locate_addr.rb +235 -21
- data/lib/carray/methods/mask_duplicates.rb +35 -1
- data/lib/carray/methods/mode.rb +18 -1
- data/lib/carray/methods/nunique.rb +22 -1
- data/lib/carray/methods/repeat.rb +91 -0
- data/lib/carray/methods/resize.rb +21 -23
- data/lib/carray/methods/snap.rb +34 -12
- data/lib/carray/methods/unique.rb +52 -3
- data/lib/carray/record_order.rb +208 -0
- data/lib/carray/rng.rb +86 -0
- data/lib/carray/runtime.rb +14 -11
- data/lib/carray/segment_iterator.rb +990 -0
- data/lib/carray/serialize.rb +197 -27
- data/lib/carray/slab_iterator.rb +109 -40
- data/lib/carray/stack.rb +10 -4
- data/lib/carray/string_operation_extension.rb +9 -4
- data/lib/carray/struct.rb +142 -51
- data/lib/carray/struct_builder.rb +110 -14
- data/lib/carray/table.rb +2 -2
- data/lib/carray/time.rb +535 -105
- data/lib/carray/window_iterator.rb +259 -82
- data/lib/carray.rb +4 -2
- data/yard-stubs/ca_obj_array.rb +4 -4
- data/yard-stubs/ca_obj_bitarray.rb +7 -0
- data/yard-stubs/ca_obj_bitfield.rb +23 -12
- data/yard-stubs/ca_obj_block.rb +2 -7
- data/yard-stubs/ca_obj_fake.rb +1 -1
- data/yard-stubs/ca_obj_field.rb +1 -1
- data/yard-stubs/ca_obj_refer.rb +3 -3
- data/yard-stubs/ca_obj_roll.rb +0 -1
- data/yard-stubs/ca_obj_stride.rb +10 -7
- data/yard-stubs/ca_obj_transpose.rb +1 -1
- data/yard-stubs/ca_obj_window.rb +11 -4
- data/yard-stubs/carray_access.rb +6 -5
- data/yard-stubs/carray_broadcast.rb +2 -2
- data/yard-stubs/carray_cast.rb +78 -43
- data/yard-stubs/carray_conversion.rb +13 -1
- data/yard-stubs/carray_copy.rb +8 -3
- data/yard-stubs/carray_core.rb +3 -83
- data/yard-stubs/carray_count.rb +16 -2
- data/yard-stubs/carray_lazy.rb +210 -1
- data/yard-stubs/carray_loop.rb +4 -1
- data/yard-stubs/carray_mask.rb +34 -10
- data/yard-stubs/carray_math.rb +1499 -4
- data/yard-stubs/carray_median_percentile.rb +26 -8
- data/yard-stubs/carray_order.rb +43 -82
- data/yard-stubs/carray_scatter.rb +10 -3
- data/yard-stubs/carray_segment.rb +72 -0
- data/yard-stubs/carray_slab.rb +11 -8
- data/yard-stubs/carray_sort.rb +50 -8
- data/yard-stubs/carray_test.rb +10 -0
- data/yard-stubs/carray_utils.rb +0 -26
- metadata +19 -19
- data/ext/ca_for_each_element.h +0 -239
- data/ext/carray_build_flags.h +0 -3
- data/ext/carray_config.h +0 -28
- data/ext/carray_kernels_bincmp.c +0 -4446
- data/ext/carray_kernels_binop.c +0 -11001
- data/ext/carray_kernels_init.c +0 -1131
- data/ext/carray_kernels_map.c +0 -3467
- data/ext/carray_kernels_moncmp.c +0 -2097
- data/ext/carray_kernels_monop.c +0 -18313
- data/ext/carray_kernels_reduce_aggregate.c +0 -25837
- data/ext/carray_kernels_reduce_boolean.c +0 -330
- data/ext/carray_kernels_reduce_cumulative.c +0 -14593
- data/ext/carray_kernels_reduce_extreme.c +0 -16948
- data/ext/carray_kernels_reduce_variance.c +0 -3910
- data/ext/carray_kernels_scan.c +0 -3693
- data/ext/carray_kernels_search.c +0 -32138
- data/ext/carray_kernels_sort.c +0 -10626
- data/ext/carray_kernels_triop.c +0 -1392
data/ext/mkkernel.rb
CHANGED
|
@@ -360,6 +360,7 @@ module MkKernel
|
|
|
360
360
|
face_gate: nil,
|
|
361
361
|
object_escape: nil,
|
|
362
362
|
identity_on_empty: false,
|
|
363
|
+
all_nan_result: nil,
|
|
363
364
|
outputs: 1,
|
|
364
365
|
# Two-pass centred algorithm (variance / stddev family).
|
|
365
366
|
# When algorithm: :two_pass_centred is set, `state / init /
|
|
@@ -405,8 +406,8 @@ module MkKernel
|
|
|
405
406
|
# only, output (an axis-local index) needs no re-lift.
|
|
406
407
|
# :relift -- value-returning kernels (min/max): descend, then re-lift the
|
|
407
408
|
# output back into the Face -- a full-reduction scalar via the
|
|
408
|
-
# registered
|
|
409
|
-
#
|
|
409
|
+
# registered storage_to_element hook, a per-axis CArray via
|
|
410
|
+
# ca_face_relift_entity (carries the subclass tail, e.g. unit).
|
|
410
411
|
raise "reduce: unknown face_gate #{face_gate} (expected nil, :strip, or :relift)" \
|
|
411
412
|
unless [nil, :strip, :relift].include?(face_gate)
|
|
412
413
|
# object_escape: a Symbol naming a Ruby instance method that handles the
|
|
@@ -465,6 +466,30 @@ module MkKernel
|
|
|
465
466
|
end
|
|
466
467
|
end
|
|
467
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
|
|
468
493
|
if value_arg
|
|
469
494
|
raise "#{name}: value_arg must be a Hash" unless value_arg.is_a?(Hash)
|
|
470
495
|
raise "#{name}: value_arg: target must be :T_IN" unless value_arg[:target] == :T_IN
|
|
@@ -478,16 +503,29 @@ module MkKernel
|
|
|
478
503
|
# selects direction (:min / :max) and output kind:
|
|
479
504
|
# :min / :max -> extremum blob, output data_type = CA_FIXLEN
|
|
480
505
|
# :argmin / :argmax -> position of the extremum, output i64
|
|
506
|
+
# :count_equal -> how many cells memcmp-equal value_arg, output i64
|
|
481
507
|
# Author must also list :fixlen in source: (parallel to how :object
|
|
482
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).
|
|
483
515
|
if fixlen
|
|
484
|
-
unless %i[min max argmin argmax].include?(fixlen)
|
|
485
|
-
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})"
|
|
486
518
|
end
|
|
487
519
|
raise "#{name}: fixlen: requires :fixlen in source:" unless source.include?(:fixlen)
|
|
488
520
|
raise "#{name}: fixlen: requires mask_policy: :min_count" unless mask_policy == :min_count
|
|
489
521
|
raise "#{name}: fixlen: requires outputs: 1" unless outputs == 1
|
|
490
|
-
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
|
|
491
529
|
elsif source.include?(:fixlen)
|
|
492
530
|
raise "#{name}: source includes :fixlen but no fixlen: spec given"
|
|
493
531
|
end
|
|
@@ -612,6 +650,7 @@ module MkKernel
|
|
|
612
650
|
face_gate: face_gate,
|
|
613
651
|
object_escape: object_escape,
|
|
614
652
|
identity_on_empty: identity_on_empty,
|
|
653
|
+
all_nan_result: all_nan_result,
|
|
615
654
|
outputs: outputs,
|
|
616
655
|
algorithm: algorithm,
|
|
617
656
|
divisor: divisor,
|
|
@@ -620,41 +659,6 @@ module MkKernel
|
|
|
620
659
|
}
|
|
621
660
|
end
|
|
622
661
|
|
|
623
|
-
# Register a map kernel (element-wise transform: input shape == output
|
|
624
|
-
# shape, one cell at a time). Each generated helper walks the input
|
|
625
|
-
# and output slabs in lockstep using CA_SLAB_MAP_T(T_LOAD, T_OUT, ...).
|
|
626
|
-
#
|
|
627
|
-
# DSL:
|
|
628
|
-
# MkKernel.map :name,
|
|
629
|
-
# source: MkKernel::ALL_NUMERIC,
|
|
630
|
-
# output: :f64 | :preserve,
|
|
631
|
-
# expr: "r = sqrt((double) v)", # binds v (input) + r (output)
|
|
632
|
-
# fallback: :wrap_to_f64 | :raise
|
|
633
|
-
#
|
|
634
|
-
# Generated method takes no arguments (`a.sqrt_ki` not `a.sqrt_ki(0)`)
|
|
635
|
-
# since the operation is per-cell across the whole array.
|
|
636
|
-
def self.map(name, source:, output:, expr:, fallback: :raise)
|
|
637
|
-
raise "duplicate kernel #{name}" if KERNELS.any? { |k| k[:name] == name }
|
|
638
|
-
source.each do |s|
|
|
639
|
-
raise "unknown source data_type #{s}" unless DTYPES.key?(s)
|
|
640
|
-
end
|
|
641
|
-
raise "unknown output #{output}" unless output == :preserve || DTYPES.key?(output) || output.is_a?(Hash)
|
|
642
|
-
raise "unknown fallback #{fallback}" unless %i[raise wrap_to_f64].include?(fallback)
|
|
643
|
-
if output == :preserve && fallback == :wrap_to_f64
|
|
644
|
-
raise "#{name}: :preserve + :wrap_to_f64 is a semantic conflict " \
|
|
645
|
-
"(fallback path produces f64 output, contradicting :preserve)"
|
|
646
|
-
end
|
|
647
|
-
|
|
648
|
-
KERNELS << {
|
|
649
|
-
kind: :map,
|
|
650
|
-
name: name,
|
|
651
|
-
source: source,
|
|
652
|
-
output: output,
|
|
653
|
-
expr: expr,
|
|
654
|
-
fallback: fallback,
|
|
655
|
-
}
|
|
656
|
-
end
|
|
657
|
-
|
|
658
662
|
# Register a scan kernel (cumulative / prefix-scan: input shape ==
|
|
659
663
|
# output shape, running accumulator along one axis). The generated
|
|
660
664
|
# method takes exactly one axis argument (e.g., `a.cumsum_ki(0)`).
|
|
@@ -665,7 +669,11 @@ module MkKernel
|
|
|
665
669
|
# output: :f64 | :preserve,
|
|
666
670
|
# init: "0", # initial acc value (per fiber)
|
|
667
671
|
# step: "acc += v; r = acc", # binds v (T_LOAD), r (T_OUT), acc
|
|
668
|
-
# fallback: :wrap_to_f64 | :raise
|
|
672
|
+
# fallback: :wrap_to_f64 | :raise | :mask_count
|
|
673
|
+
#
|
|
674
|
+
# :mask_count is for a scan that reads only the mask (cumcount): a data
|
|
675
|
+
# type outside source: is answered as is_not_masked.cumsum(axis:) in
|
|
676
|
+
# int64, so the values are never converted.
|
|
669
677
|
#
|
|
670
678
|
# Init supports the same special tokens as reduce: T_LIMIT_HI /
|
|
671
679
|
# T_LIMIT_LO / T_ACC / T_OUT. Each scan slab (= fiber along the
|
|
@@ -699,13 +707,13 @@ module MkKernel
|
|
|
699
707
|
# the reduction contract (empty max / min = UNDEF, no
|
|
700
708
|
# identity) and the axis-group scan family.
|
|
701
709
|
def self.scan(name, source:, output:, init:, step:, fallback: :raise,
|
|
702
|
-
acc_type: nil, axis_default: nil, empty: nil)
|
|
710
|
+
acc_type: nil, axis_default: nil, empty: nil, face_gate: nil)
|
|
703
711
|
raise "duplicate kernel #{name}" if KERNELS.any? { |k| k[:name] == name }
|
|
704
712
|
source.each do |s|
|
|
705
713
|
raise "unknown source data_type #{s}" unless DTYPES.key?(s)
|
|
706
714
|
end
|
|
707
715
|
raise "unknown output #{output}" unless output == :preserve || DTYPES.key?(output) || output.is_a?(Hash)
|
|
708
|
-
raise "unknown fallback #{fallback}" unless %i[raise wrap_to_f64].include?(fallback)
|
|
716
|
+
raise "unknown fallback #{fallback}" unless %i[raise wrap_to_f64 mask_count].include?(fallback)
|
|
709
717
|
if output == :preserve && fallback == :wrap_to_f64
|
|
710
718
|
raise "#{name}: :preserve + :wrap_to_f64 is a semantic conflict"
|
|
711
719
|
end
|
|
@@ -721,6 +729,13 @@ module MkKernel
|
|
|
721
729
|
if empty == :undef && acc_type == :load_type
|
|
722
730
|
raise "#{name}: empty: :undef is not supported with acc_type: :load_type"
|
|
723
731
|
end
|
|
732
|
+
# face_gate: :relift descends an ORDERABLE Face to its storage, scans
|
|
733
|
+
# the storage and puts the Face back on the result (the reduce family's
|
|
734
|
+
# policy). Only a scan whose output is the source's own values may take
|
|
735
|
+
# it: a running sum of instants is not an instant.
|
|
736
|
+
unless face_gate.nil? || face_gate == :relift
|
|
737
|
+
raise "#{name}: unknown face_gate #{face_gate.inspect} (nil | :relift)"
|
|
738
|
+
end
|
|
724
739
|
|
|
725
740
|
KERNELS << {
|
|
726
741
|
kind: :scan,
|
|
@@ -733,6 +748,7 @@ module MkKernel
|
|
|
733
748
|
acc_type: acc_type,
|
|
734
749
|
axis_default: axis_default,
|
|
735
750
|
empty: empty,
|
|
751
|
+
face_gate: face_gate,
|
|
736
752
|
}
|
|
737
753
|
end
|
|
738
754
|
|
|
@@ -1351,6 +1367,68 @@ module MkKernel
|
|
|
1351
1367
|
io.puts "#undef #{expr_macro}"
|
|
1352
1368
|
end
|
|
1353
1369
|
|
|
1370
|
+
# State vars that start at the type's limit (T_LIMIT_HI / T_LIMIT_LO)
|
|
1371
|
+
# and so can leak their init as an answer. A position state (argmin's
|
|
1372
|
+
# best_i, init "0") is deliberately not one of them: index 0 is a value
|
|
1373
|
+
# a real minimum can legitimately produce.
|
|
1374
|
+
def self.limit_init_state_vars(k, si, oi, src)
|
|
1375
|
+
return [] unless k[:state]
|
|
1376
|
+
k[:state].keys.filter_map do |var|
|
|
1377
|
+
token = pick_family_string(k[:init][var], src, "init")
|
|
1378
|
+
next unless %w[T_LIMIT_HI T_LIMIT_LO].include?(token)
|
|
1379
|
+
[var, resolve_init_expr(k[:init][var], oi, si, src)]
|
|
1380
|
+
end
|
|
1381
|
+
end
|
|
1382
|
+
|
|
1383
|
+
# Emit the all-NaN fix-up for an extreme-value reduction (see the
|
|
1384
|
+
# all_nan_result: validation in MkKernel.reduce for why it exists).
|
|
1385
|
+
#
|
|
1386
|
+
# The per-cell reduce body is left alone: it stays the SIMD-licensed
|
|
1387
|
+
# ternary, so the 8-way horizontal split (reduce_8way_eligible?) and
|
|
1388
|
+
# the `reduction(min:acc)` clause are untouched, and ordinary data pays
|
|
1389
|
+
# one comparison per slab. Carrying a "saw a number" flag as a second
|
|
1390
|
+
# DSL state var was the alternative and costs more than it looks: it
|
|
1391
|
+
# takes the kernel over reduce_8way_eligible?'s single-state gate and
|
|
1392
|
+
# drops min / max back onto the legacy single-accumulator macro.
|
|
1393
|
+
#
|
|
1394
|
+
# acc still sitting at its init means either nothing updated it or the
|
|
1395
|
+
# data genuinely held only +/-INFINITY. Those are told apart by
|
|
1396
|
+
# walking the slab once more for a non-NaN cell -- O(n), and only for
|
|
1397
|
+
# a slab whose answer came out equal to the init.
|
|
1398
|
+
#
|
|
1399
|
+
# Returns true when it emitted anything.
|
|
1400
|
+
def self.emit_all_nan_fixup(io, k, src, si, pairs, valid_guard,
|
|
1401
|
+
indent: " ", flag_var: nil)
|
|
1402
|
+
mode = k[:all_nan_result]
|
|
1403
|
+
return false unless mode
|
|
1404
|
+
return false unless FLOAT_DTYPES.include?(src)
|
|
1405
|
+
cond = pairs.map { |var, init| "#{var} == (#{init})" }.join(" && ")
|
|
1406
|
+
cond = "(#{cond}) && #{valid_guard}" if valid_guard
|
|
1407
|
+
io.puts "#{indent}/* all-NaN fix-up (all_nan_result: #{mode.inspect}). */"
|
|
1408
|
+
io.puts "#{indent}if ( #{cond} ) {"
|
|
1409
|
+
io.puts "#{indent} int64_t __anf_seen = 0;"
|
|
1410
|
+
io.puts "#{indent} ca_size_t __anf_mc = 0;"
|
|
1411
|
+
io.puts "#{indent} CA_SLAB_REDUCE_T_EX(#{si[:c]}, st, p, m, __anf_seen, 0, " \
|
|
1412
|
+
"__anf_seen |= (v == v), __anf_mc);"
|
|
1413
|
+
io.puts "#{indent} (void) __anf_mc;"
|
|
1414
|
+
io.puts "#{indent} if ( ! __anf_seen ) {"
|
|
1415
|
+
if mode == :nan
|
|
1416
|
+
pairs.each { |var, _| io.puts "#{indent} #{var} = (#{si[:c]}) NAN;" }
|
|
1417
|
+
else
|
|
1418
|
+
raise "#{k[:name]}: all_nan_result: :undef needs flag_var" unless flag_var
|
|
1419
|
+
io.puts "#{indent} #{flag_var} = 1;"
|
|
1420
|
+
end
|
|
1421
|
+
io.puts "#{indent} }"
|
|
1422
|
+
io.puts "#{indent}}"
|
|
1423
|
+
true
|
|
1424
|
+
end
|
|
1425
|
+
|
|
1426
|
+
# The CArray names of the data types a kernel loads, for its refusal
|
|
1427
|
+
# message ("int8, uint8, ..."), not the generator's own short keys.
|
|
1428
|
+
def self.type_names(sources)
|
|
1429
|
+
sources.map { |s| s == :fixlen ? "fixlen" : DTYPES[s][:ca].sub(/\ACA_/, "").downcase }.join(", ")
|
|
1430
|
+
end
|
|
1431
|
+
|
|
1354
1432
|
def self.reduce_macro_suffix(k, src = nil)
|
|
1355
1433
|
# CA_OBJECT cannot ride the SIMD-licensed macros (= _PLUS / _MIN / _MAX
|
|
1356
1434
|
# / _STAR), which assume C operators (= acc is a VALUE, so
|
|
@@ -1529,9 +1607,11 @@ module MkKernel
|
|
|
1529
1607
|
require "fileutils"
|
|
1530
1608
|
FileUtils.mkdir_p(outdir)
|
|
1531
1609
|
tags = file_tags
|
|
1610
|
+
written = ["carray_kernels_init.c"]
|
|
1532
1611
|
tags.each do |kind, subgroup|
|
|
1533
1612
|
suffix = file_suffix(kind, subgroup)
|
|
1534
1613
|
path = File.join(outdir, "carray_kernels_#{suffix}.c")
|
|
1614
|
+
written << File.basename(path)
|
|
1535
1615
|
File.open(path, "w") do |io|
|
|
1536
1616
|
io.puts header
|
|
1537
1617
|
KERNELS.each do |k|
|
|
@@ -1545,6 +1625,11 @@ module MkKernel
|
|
|
1545
1625
|
File.open(File.join(outdir, "carray_kernels_init.c"), "w") do |io|
|
|
1546
1626
|
emit_aggregator_init(io, tags)
|
|
1547
1627
|
end
|
|
1628
|
+
# extconf compiles every carray_kernels_*.c it finds, so a file left
|
|
1629
|
+
# by a kind or sub-group that no longer exists has to go.
|
|
1630
|
+
Dir.glob(File.join(outdir, "carray_kernels_*.c")).each do |f|
|
|
1631
|
+
File.delete(f) unless written.include?(File.basename(f))
|
|
1632
|
+
end
|
|
1548
1633
|
end
|
|
1549
1634
|
|
|
1550
1635
|
# Which file a kernel belongs to. The three alias kinds live with the kind
|
|
@@ -1563,7 +1648,6 @@ module MkKernel
|
|
|
1563
1648
|
def self.emit_kernel_dispatch(io, k)
|
|
1564
1649
|
case k[:kind]
|
|
1565
1650
|
when :reduce then emit_reduce(io, k)
|
|
1566
|
-
when :map then emit_map(io, k)
|
|
1567
1651
|
when :scan then emit_scan(io, k)
|
|
1568
1652
|
when :sort then emit_sort(io, k)
|
|
1569
1653
|
when :search then emit_search(io, k)
|
|
@@ -1580,7 +1664,7 @@ module MkKernel
|
|
|
1580
1664
|
end
|
|
1581
1665
|
end
|
|
1582
1666
|
|
|
1583
|
-
KINDS = [:reduce, :
|
|
1667
|
+
KINDS = [:reduce, :scan, :sort, :search,
|
|
1584
1668
|
:monop, :binop, :triop, :moncmp, :bincmp].freeze
|
|
1585
1669
|
|
|
1586
1670
|
# Sub-split map for the reduce kind. As one file, reduce accounted for 71%
|
|
@@ -1591,17 +1675,16 @@ module MkKernel
|
|
|
1591
1675
|
# rather than the guess that monop and binop would dominate. A kernel added
|
|
1592
1676
|
# later needs an entry here; emit asserts, so a missing one is loud.
|
|
1593
1677
|
REDUCE_SUB_GROUPS = {
|
|
1594
|
-
# aggregate: arithmetic folds (sum / prod / mean and their
|
|
1595
|
-
#
|
|
1678
|
+
# aggregate: arithmetic folds (sum / prod / mean and their weighted
|
|
1679
|
+
# variants)
|
|
1596
1680
|
sum: :aggregate, prod: :aggregate, mean: :aggregate,
|
|
1597
|
-
sum_strict: :aggregate, mean_safe: :aggregate,
|
|
1598
1681
|
wsum: :aggregate, wmean: :aggregate,
|
|
1599
1682
|
# extreme: order-based (min / max, argmin / argmax, minmax)
|
|
1600
1683
|
min: :extreme, max: :extreme, minmax: :extreme,
|
|
1601
1684
|
argmin: :extreme, argmax: :extreme,
|
|
1602
1685
|
argmin_addr: :extreme, argmax_addr: :extreme,
|
|
1603
1686
|
# cumulative: counter accumulation (the count family and accumulate)
|
|
1604
|
-
|
|
1687
|
+
accumulate: :cumulative,
|
|
1605
1688
|
count_true: :cumulative, count_false: :cumulative,
|
|
1606
1689
|
count_equal: :cumulative,
|
|
1607
1690
|
# variance family: multi-state (Chan / Welford), and the one that
|
|
@@ -1689,11 +1772,116 @@ module MkKernel
|
|
|
1689
1772
|
base + HEADER_BLOCKS.join("\n")
|
|
1690
1773
|
end
|
|
1691
1774
|
|
|
1775
|
+
# Emits the native function of one source data type. The object lane
|
|
1776
|
+
# calls Ruby for every cell, so any cell can raise in the middle of a walk;
|
|
1777
|
+
# its function is rebuilt around ca_iter_ensure (see guard_object_native).
|
|
1778
|
+
# Every other lane is emitted as the emitter writes it.
|
|
1779
|
+
def self.emit_native_for(io, k, src)
|
|
1780
|
+
if src == :object
|
|
1781
|
+
buf = StringIO.new
|
|
1782
|
+
yield buf
|
|
1783
|
+
io.print guard_object_native(buf.string, "#{k[:name]}_ki_native_object")
|
|
1784
|
+
else
|
|
1785
|
+
yield io
|
|
1786
|
+
end
|
|
1787
|
+
end
|
|
1788
|
+
|
|
1789
|
+
# Index of the brace that closes the one at text[open], skipping string
|
|
1790
|
+
# and character literals and comments.
|
|
1791
|
+
def self.matching_brace(text, open)
|
|
1792
|
+
depth = 0
|
|
1793
|
+
i = open
|
|
1794
|
+
while i < text.length
|
|
1795
|
+
c = text[i]
|
|
1796
|
+
case c
|
|
1797
|
+
when '"', "'"
|
|
1798
|
+
i += 1
|
|
1799
|
+
i += (text[i] == "\\") ? 2 : 1 while i < text.length && text[i] != c
|
|
1800
|
+
when "/"
|
|
1801
|
+
if text[i + 1] == "/"
|
|
1802
|
+
i = text.index("\n", i) || text.length
|
|
1803
|
+
elsif text[i + 1] == "*"
|
|
1804
|
+
i = (text.index("*/", i + 2) || text.length) + 1
|
|
1805
|
+
end
|
|
1806
|
+
when "{"
|
|
1807
|
+
depth += 1
|
|
1808
|
+
when "}"
|
|
1809
|
+
depth -= 1
|
|
1810
|
+
return i if depth == 0
|
|
1811
|
+
end
|
|
1812
|
+
i += 1
|
|
1813
|
+
end
|
|
1814
|
+
raise "mkkernel: unbalanced braces"
|
|
1815
|
+
end
|
|
1816
|
+
|
|
1817
|
+
# Rebuilds the native function +fn+ found in +text+ so that its walks are
|
|
1818
|
+
# finished however it leaves. The body moves into a function of its own
|
|
1819
|
+
# that ca_iter_ensure runs; each `ca_iter_state` it declared becomes a
|
|
1820
|
+
# slot of a frame kept by the caller, since a state in the body's own
|
|
1821
|
+
# frame is gone once the body raises. The body text is kept as written:
|
|
1822
|
+
# the parameters are unpacked from the frame under their own names, and
|
|
1823
|
+
# each state name is defined to its slot for the length of the body.
|
|
1824
|
+
def self.guard_object_native(text, fn)
|
|
1825
|
+
header = /^static VALUE\n#{fn} \(([^)]*)\)\n\{\n/
|
|
1826
|
+
m = header.match(text) or raise "mkkernel: #{fn} not found"
|
|
1827
|
+
close = matching_brace(text, m.end(0) - 2)
|
|
1828
|
+
body = text[m.end(0)...close]
|
|
1829
|
+
rest = text[(close + 1)..]
|
|
1830
|
+
|
|
1831
|
+
states = []
|
|
1832
|
+
body = body.gsub(/^[ \t]*ca_iter_state[ \t]+(\w+(?:[ \t]*,[ \t]*\w+)*)[ \t]*;[ \t]*\n/) do
|
|
1833
|
+
states.concat($1.split(/[ \t]*,[ \t]*/))
|
|
1834
|
+
""
|
|
1835
|
+
end
|
|
1836
|
+
states.uniq!
|
|
1837
|
+
return text if states.empty?
|
|
1838
|
+
|
|
1839
|
+
params = m[1].split(",").map do |p|
|
|
1840
|
+
pm = p.strip.match(/\A(.*?)(\w+)\z/) or raise "mkkernel: #{fn} parameter #{p}"
|
|
1841
|
+
[pm[1].strip, pm[2]]
|
|
1842
|
+
end
|
|
1843
|
+
frame = "#{fn}_frame"
|
|
1844
|
+
|
|
1845
|
+
out = StringIO.new
|
|
1846
|
+
out.print m.pre_match
|
|
1847
|
+
out.puts "/* The object lane calls Ruby for every cell, so a walk can raise part"
|
|
1848
|
+
out.puts " way. Its states live in the caller's frame, and ca_iter_ensure"
|
|
1849
|
+
out.puts " finishes them however the body leaves. */"
|
|
1850
|
+
out.puts "typedef struct {"
|
|
1851
|
+
out.puts " ca_iter_state states[#{states.size}];"
|
|
1852
|
+
params.each { |type, name| out.puts " #{type} #{name};".sub(/\* /, "*") }
|
|
1853
|
+
out.puts "} #{frame};"
|
|
1854
|
+
out.puts
|
|
1855
|
+
out.puts "static VALUE"
|
|
1856
|
+
out.puts "#{fn}_body (VALUE arg)"
|
|
1857
|
+
out.puts "{"
|
|
1858
|
+
out.puts " #{frame} *fr = (#{frame} *) arg;"
|
|
1859
|
+
params.each do |type, name|
|
|
1860
|
+
out.puts " #{type} #{name} = fr->#{name};".sub(/\* /, "*")
|
|
1861
|
+
out.puts " (void) #{name};"
|
|
1862
|
+
end
|
|
1863
|
+
states.each_with_index { |s, i| out.puts "#define #{s} (fr->states[#{i}])" }
|
|
1864
|
+
out.print body.sub(/[ \t]*\z/, "")
|
|
1865
|
+
out.puts unless body.match?(/\n[ \t]*\z/)
|
|
1866
|
+
states.each { |s| out.puts "#undef #{s}" }
|
|
1867
|
+
out.puts "}"
|
|
1868
|
+
out.puts
|
|
1869
|
+
out.puts "static VALUE"
|
|
1870
|
+
out.puts "#{fn} (#{m[1]})"
|
|
1871
|
+
out.puts "{"
|
|
1872
|
+
out.puts " #{frame} fr;"
|
|
1873
|
+
params.each { |_, name| out.puts " fr.#{name} = #{name};" }
|
|
1874
|
+
out.puts " return ca_iter_ensure(#{states.size}, fr.states, #{fn}_body, (VALUE) &fr);"
|
|
1875
|
+
out.puts "}"
|
|
1876
|
+
out.print rest
|
|
1877
|
+
out.string
|
|
1878
|
+
end
|
|
1879
|
+
|
|
1692
1880
|
def self.emit_reduce(io, k)
|
|
1693
1881
|
io.puts
|
|
1694
1882
|
io.puts "/* ===== #{k[:name]}_ki ============================================ */"
|
|
1695
1883
|
k[:source].each do |src|
|
|
1696
|
-
|
|
1884
|
+
emit_native_for(io, k, src) { |o| emit_reduce_native(o, k, src) }
|
|
1697
1885
|
end
|
|
1698
1886
|
emit_reduce_dispatch(io, k)
|
|
1699
1887
|
end
|
|
@@ -1765,6 +1953,11 @@ module MkKernel
|
|
|
1765
1953
|
|
|
1766
1954
|
view_flat = (k[:semantics] == :view_flat)
|
|
1767
1955
|
|
|
1956
|
+
if streamable
|
|
1957
|
+
emit_reduce_streaming(io, k, si, oi, ruby_wrap, acc_var, acc_init,
|
|
1958
|
+
decls, reduce_stmt, finish_expr, src)
|
|
1959
|
+
end
|
|
1960
|
+
|
|
1768
1961
|
io.puts
|
|
1769
1962
|
io.puts <<~C
|
|
1770
1963
|
static VALUE
|
|
@@ -1773,8 +1966,7 @@ module MkKernel
|
|
|
1773
1966
|
C
|
|
1774
1967
|
|
|
1775
1968
|
if streamable
|
|
1776
|
-
|
|
1777
|
-
decls, reduce_stmt, finish_expr, extra_args)
|
|
1969
|
+
emit_reduce_streaming_call(io, k, src)
|
|
1778
1970
|
end
|
|
1779
1971
|
|
|
1780
1972
|
# L.1 / L.3 / L.4 (PROPOSAL_REDUCTION_LOOP_INTERCHANGE):
|
|
@@ -1822,12 +2014,22 @@ module MkKernel
|
|
|
1822
2014
|
else
|
|
1823
2015
|
mixed_buf = false
|
|
1824
2016
|
end
|
|
1825
|
-
|
|
2017
|
+
# The object lane is left out: its cell is a Ruby call, so the
|
|
2018
|
+
# interchange's contiguous SIMD loads buy nothing, and each output cell
|
|
2019
|
+
# accumulates in the same order on the generic walk.
|
|
2020
|
+
li_eligible = src != :object &&
|
|
2021
|
+
!k[:array_arg] && !k[:value_arg] &&
|
|
1826
2022
|
buf_count >= 1 && induction_count <= 1 && other_count == 0 &&
|
|
1827
2023
|
!mixed_buf
|
|
1828
2024
|
if li_eligible
|
|
2025
|
+
# The interchange folds every cell of an unmasked source, so it can
|
|
2026
|
+
# answer only when the reduced axis holds at least min_count cells;
|
|
2027
|
+
# otherwise the general walk writes the UNDEF (or fill_value) cells.
|
|
2028
|
+
li_gate = k[:mask_policy] == :min_count
|
|
2029
|
+
io.puts " if ( naxes != 1 || ! ca_too_few_present(ca->dim[slab_axes[0]], min_count, 0) ) {" if li_gate
|
|
1829
2030
|
emit_reduce_loop_interchange(io, k, si, oi, ruby_wrap, src,
|
|
1830
2031
|
acc_var, acc_init, reduce_stmt, finish_expr)
|
|
2032
|
+
io.puts " }" if li_gate
|
|
1831
2033
|
end
|
|
1832
2034
|
|
|
1833
2035
|
if view_flat
|
|
@@ -1837,7 +2039,7 @@ module MkKernel
|
|
|
1837
2039
|
# (slab walks the whole array in view-flat row-major); other
|
|
1838
2040
|
# naxes are rejected at entry.
|
|
1839
2041
|
io.puts " if ( naxes != 1 && naxes != ca->ndim ) {"
|
|
1840
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
2042
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: view_flat semantics requires single-axis or full reduction (got naxes=%d for ndim=%d)", ca_calling_method_name(), (int)naxes, (int)ca->ndim);]
|
|
1841
2043
|
io.puts " }"
|
|
1842
2044
|
end
|
|
1843
2045
|
|
|
@@ -1851,7 +2053,7 @@ module MkKernel
|
|
|
1851
2053
|
slab_axes, naxes, 0);
|
|
1852
2054
|
if ( rc != CA_ITER_OK ) {
|
|
1853
2055
|
rb_raise(rb_eRuntimeError,
|
|
1854
|
-
"
|
|
2056
|
+
"%s: kernel_iterator init failed rc=%d", ca_calling_method_name(), rc);
|
|
1855
2057
|
}
|
|
1856
2058
|
char *p;
|
|
1857
2059
|
boolean8_t *m;
|
|
@@ -1889,7 +2091,7 @@ module MkKernel
|
|
|
1889
2091
|
io.puts " slab_axes, naxes, 0);"
|
|
1890
2092
|
io.puts " if ( rcw != CA_ITER_OK ) {"
|
|
1891
2093
|
io.puts " ca_iter_state_finish(&st);"
|
|
1892
|
-
io.puts %Q[ rb_raise(rb_eRuntimeError, "
|
|
2094
|
+
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: weights kernel_iterator init failed rc=%d", ca_calling_method_name(), rcw);]
|
|
1893
2095
|
io.puts " }"
|
|
1894
2096
|
io.puts " char *p_w;"
|
|
1895
2097
|
end
|
|
@@ -1904,7 +2106,7 @@ module MkKernel
|
|
|
1904
2106
|
io.puts " if ( ! ca_iter_state_next_slab_axes(&st_w, &p_w, NULL) ) {"
|
|
1905
2107
|
io.puts " ca_iter_state_finish(&st);"
|
|
1906
2108
|
io.puts " ca_iter_state_finish(&st_w);"
|
|
1907
|
-
io.puts %Q[ rb_raise(rb_eRuntimeError, "
|
|
2109
|
+
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: weights iter exhausted early (shape invariant violated)", ca_calling_method_name());]
|
|
1908
2110
|
io.puts " }"
|
|
1909
2111
|
else
|
|
1910
2112
|
io.puts " while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {"
|
|
@@ -1934,27 +2136,32 @@ module MkKernel
|
|
|
1934
2136
|
emit_reduce_slab_call(io, k, src, si, oi, suffix, acc_var, acc_init,
|
|
1935
2137
|
reduce_stmt, "masked_cnt", indent: " ")
|
|
1936
2138
|
end
|
|
2139
|
+
all_nan_flag = nil
|
|
2140
|
+
if k[:all_nan_result]
|
|
2141
|
+
pairs = limit_init_state_vars(k, si, oi, src)
|
|
2142
|
+
if k[:all_nan_result] == :undef && FLOAT_DTYPES.include?(src)
|
|
2143
|
+
all_nan_flag = "__anf_all_nan"
|
|
2144
|
+
io.puts " int #{all_nan_flag} = 0;"
|
|
2145
|
+
elsif k[:all_nan_result] == :undef && src == :object
|
|
2146
|
+
# The object body takes no NaN cell, so a slab of present NaN
|
|
2147
|
+
# cells leaves the position unset.
|
|
2148
|
+
all_nan_flag = "__anf_all_nan"
|
|
2149
|
+
io.puts " int #{all_nan_flag} = ( masked_cnt < st.slab_elements && (#{finish_expr}) < 0 );"
|
|
2150
|
+
end
|
|
2151
|
+
emit_all_nan_fixup(io, k, src, si, pairs,
|
|
2152
|
+
"masked_cnt < st.slab_elements",
|
|
2153
|
+
indent: " ", flag_var: all_nan_flag)
|
|
2154
|
+
end
|
|
1937
2155
|
trigger = case k[:mask_policy]
|
|
1938
2156
|
when :strict then "masked_cnt > 0"
|
|
1939
|
-
when :all_masked then "masked_cnt
|
|
2157
|
+
when :all_masked then "ca_too_few_present(st.slab_elements - masked_cnt, -1, 1)"
|
|
1940
2158
|
when :min_count
|
|
1941
|
-
#
|
|
1942
|
-
#
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
# ERI.0: identity_on_empty kernels do NOT fire UNDEF for the
|
|
1946
|
-
# default (min_count < 0) zero-contribution case; the trigger
|
|
1947
|
-
# is false there, so the else branch writes finish (= acc =
|
|
1948
|
-
# identity) for empty / all-masked slabs. The explicit
|
|
1949
|
-
# min_count >= 0 opt-in ("require K valid cells") is unchanged.
|
|
1950
|
-
if k[:identity_on_empty]
|
|
1951
|
-
"(min_count < 0 ? 0 " \
|
|
1952
|
-
": st.slab_elements - masked_cnt < min_count)"
|
|
1953
|
-
else
|
|
1954
|
-
"(min_count < 0 ? masked_cnt == st.slab_elements " \
|
|
1955
|
-
": st.slab_elements - masked_cnt < min_count)"
|
|
1956
|
-
end
|
|
2159
|
+
# An identity_on_empty kernel needs no cell (the else
|
|
2160
|
+
# branch writes finish = acc = identity for an empty slab).
|
|
2161
|
+
"ca_too_few_present(st.slab_elements - masked_cnt, min_count, " \
|
|
2162
|
+
"#{k[:identity_on_empty] ? 0 : 1})"
|
|
1957
2163
|
end
|
|
2164
|
+
trigger = "(#{trigger}) || #{all_nan_flag}" if all_nan_flag
|
|
1958
2165
|
finish_emit = view_flat \
|
|
1959
2166
|
? "(transform_active ? (outer_off + ((ca_size_t)(#{finish_expr})) * axis_vstride) : ((ca_size_t)(#{finish_expr})))" \
|
|
1960
2167
|
: "(#{finish_expr})"
|
|
@@ -2037,13 +2244,15 @@ module MkKernel
|
|
|
2037
2244
|
# the numeric argmin's best_i. mask_policy is always :min_count here.
|
|
2038
2245
|
def self.emit_reduce_native_fixlen(io, k, src)
|
|
2039
2246
|
name = k[:name]
|
|
2247
|
+
counting = k[:fixlen] == :count_equal
|
|
2040
2248
|
want_max = %i[max argmax].include?(k[:fixlen])
|
|
2041
|
-
index_out = %i[argmin argmax].include?(k[:fixlen])
|
|
2249
|
+
index_out = %i[argmin argmax].include?(k[:fixlen]) || counting
|
|
2042
2250
|
cmp = want_max ? ">" : "<"
|
|
2251
|
+
varg = counting ? ", const char *value_arg" : ""
|
|
2043
2252
|
|
|
2044
2253
|
io.puts
|
|
2045
2254
|
io.puts "static VALUE"
|
|
2046
|
-
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)"
|
|
2255
|
+
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)"
|
|
2047
2256
|
io.puts "{"
|
|
2048
2257
|
io.puts " ca_size_t K = ca->bytes; /* uniform fixlen byte width */"
|
|
2049
2258
|
if index_out
|
|
@@ -2057,16 +2266,20 @@ module MkKernel
|
|
|
2057
2266
|
io.puts " ca_iter_state st;"
|
|
2058
2267
|
io.puts " int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES, slab_axes, naxes, 0);"
|
|
2059
2268
|
io.puts " if ( rc != CA_ITER_OK ) {"
|
|
2060
|
-
io.puts %Q[ rb_raise(rb_eRuntimeError, "
|
|
2269
|
+
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: kernel_iterator init failed rc=%d", ca_calling_method_name(), rc);]
|
|
2061
2270
|
io.puts " }"
|
|
2062
2271
|
io.puts " char *p;"
|
|
2063
2272
|
io.puts " boolean8_t *m;"
|
|
2064
2273
|
io.puts " ca_size_t out_i = 0;"
|
|
2065
2274
|
io.puts " boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */"
|
|
2066
2275
|
io.puts " while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {"
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2276
|
+
if counting
|
|
2277
|
+
io.puts " int64_t cnt = 0;"
|
|
2278
|
+
else
|
|
2279
|
+
io.puts " const char *best = NULL;"
|
|
2280
|
+
io.puts " ca_size_t best_i = 0;"
|
|
2281
|
+
io.puts " (void) best_i;" unless index_out # value output ignores the index
|
|
2282
|
+
end
|
|
2070
2283
|
io.puts " ca_size_t masked_cnt = 0;"
|
|
2071
2284
|
io.puts " int8_t sndim = st.slab_ndim;"
|
|
2072
2285
|
io.puts " ca_size_t sidx[CA_RANK_MAX] = { 0 };"
|
|
@@ -2082,12 +2295,18 @@ module MkKernel
|
|
|
2082
2295
|
io.puts " }"
|
|
2083
2296
|
io.puts " else {"
|
|
2084
2297
|
io.puts " const char *q = (const char *) p + doff;"
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2298
|
+
if counting
|
|
2299
|
+
io.puts " if ( memcmp(q, value_arg, (size_t) K) == 0 ) {"
|
|
2300
|
+
io.puts " cnt++;"
|
|
2301
|
+
io.puts " }"
|
|
2302
|
+
else
|
|
2303
|
+
io.puts " if ( best == NULL ) {"
|
|
2304
|
+
io.puts " best = q; best_i = idx;"
|
|
2305
|
+
io.puts " }"
|
|
2306
|
+
io.puts " else if ( memcmp(q, best, (size_t) K) #{cmp} 0 ) {"
|
|
2307
|
+
io.puts " best = q; best_i = idx;"
|
|
2308
|
+
io.puts " }"
|
|
2309
|
+
end
|
|
2091
2310
|
io.puts " }"
|
|
2092
2311
|
io.puts " /* row-major odometer (innermost slab axis fastest) so idx"
|
|
2093
2312
|
io.puts " matches CA_SLAB_REDUCE_T's flat slab index. */"
|
|
@@ -2096,8 +2315,9 @@ module MkKernel
|
|
|
2096
2315
|
io.puts " sidx[sk] = 0;"
|
|
2097
2316
|
io.puts " }"
|
|
2098
2317
|
io.puts " }"
|
|
2099
|
-
|
|
2100
|
-
io.puts "
|
|
2318
|
+
# A count over nothing is 0, not UNDEF: it needs no cell.
|
|
2319
|
+
io.puts " if ( ca_too_few_present(st.slab_elements - masked_cnt, min_count, " \
|
|
2320
|
+
"#{counting ? 0 : 1}) ) {"
|
|
2101
2321
|
io.puts " if ( ! op_mask ) {"
|
|
2102
2322
|
io.puts " ca_create_mask(co);"
|
|
2103
2323
|
io.puts " op_mask = (boolean8_t *) co->mask->ptr;"
|
|
@@ -2111,7 +2331,9 @@ module MkKernel
|
|
|
2111
2331
|
io.puts " out_i++;"
|
|
2112
2332
|
io.puts " }"
|
|
2113
2333
|
io.puts " else {"
|
|
2114
|
-
if
|
|
2334
|
+
if counting
|
|
2335
|
+
io.puts " ((int64_t *) op)[out_i] = cnt;"
|
|
2336
|
+
elsif index_out
|
|
2115
2337
|
io.puts " ((int64_t *) op)[out_i] = (int64_t) best_i;"
|
|
2116
2338
|
else
|
|
2117
2339
|
io.puts " memcpy(op + out_i * K, best, (size_t) K);"
|
|
@@ -2148,10 +2370,8 @@ module MkKernel
|
|
|
2148
2370
|
# Layout per slab:
|
|
2149
2371
|
# Pass 1: sum = Σ x_i via CA_SLAB_REDUCE_T_PLUS_EX (SIMD :plus)
|
|
2150
2372
|
# masked_cnt is populated as a side effect.
|
|
2151
|
-
# mask trigger:
|
|
2152
|
-
#
|
|
2153
|
-
# :n_minus_1 divisor) if n_valid < 2.
|
|
2154
|
-
# min_count >= 0 -> UNDEF iff n_valid < min_count.
|
|
2373
|
+
# mask trigger: ca_too_few_present(n_valid, min_count, 1 or 2), 2 for
|
|
2374
|
+
# the :n_minus_1 divisor.
|
|
2155
2375
|
# Pass 2: M2 = Σ (x_i - mean)² via the same macro with a centred
|
|
2156
2376
|
# REDUCE stmt.
|
|
2157
2377
|
# Finish: variance = M2 / DIVISOR (n or n-1); stddev variant applies
|
|
@@ -2258,9 +2478,9 @@ module MkKernel
|
|
|
2258
2478
|
finish_expr = if k[:output_transform] == :sqrt_clamp
|
|
2259
2479
|
if src == :object
|
|
2260
2480
|
# Object stddev outputs f64 (BigMath.sqrt is user's job).
|
|
2261
|
-
"
|
|
2481
|
+
"ca_sqrt_clamp(NUM2DBL(#{inner_var}))"
|
|
2262
2482
|
else
|
|
2263
|
-
"
|
|
2483
|
+
"ca_sqrt_clamp(#{inner_var})"
|
|
2264
2484
|
end
|
|
2265
2485
|
else
|
|
2266
2486
|
inner_var
|
|
@@ -2288,13 +2508,9 @@ module MkKernel
|
|
|
2288
2508
|
# min_count sentinel arg is always present (all four kernels use it).
|
|
2289
2509
|
extra_args = ", ca_size_t min_count"
|
|
2290
2510
|
|
|
2291
|
-
#
|
|
2292
|
-
#
|
|
2293
|
-
#
|
|
2294
|
-
# (not UNDEF) for n_valid < required_min. Preserve that: the
|
|
2295
|
-
# default trigger only checks the all-masked case; the sub-required
|
|
2296
|
-
# case falls through to the "return zero" branch below.
|
|
2297
|
-
default_trigger = "masked_cnt == st.slab_elements"
|
|
2511
|
+
# Fewer than required_min valid cells (2 for the n-1 divisor) leave the
|
|
2512
|
+
# statistic undefined, so the cell is UNDEF: the sample variance of one
|
|
2513
|
+
# value is 0/0.
|
|
2298
2514
|
|
|
2299
2515
|
# Reduce-call plumbing. :object keeps the scalar rb_funcall loop
|
|
2300
2516
|
# (CA_SLAB_REDUCE_T_EX). Every other src uses the 8-way accumulator
|
|
@@ -2335,7 +2551,7 @@ module MkKernel
|
|
|
2335
2551
|
slab_axes, naxes, 0);
|
|
2336
2552
|
if ( rc != CA_ITER_OK ) {
|
|
2337
2553
|
rb_raise(rb_eRuntimeError,
|
|
2338
|
-
"
|
|
2554
|
+
"%s: kernel_iterator init failed rc=%d", ca_calling_method_name(), rc);
|
|
2339
2555
|
}
|
|
2340
2556
|
char *p;
|
|
2341
2557
|
boolean8_t *m;
|
|
@@ -2347,7 +2563,7 @@ module MkKernel
|
|
|
2347
2563
|
ca_size_t masked_cnt = 0;
|
|
2348
2564
|
#{p1_reduce_call}
|
|
2349
2565
|
ca_size_t n_valid = st.slab_elements - masked_cnt;
|
|
2350
|
-
int __trigger = (min_count
|
|
2566
|
+
int __trigger = ca_too_few_present(n_valid, min_count, #{required_min});
|
|
2351
2567
|
if ( __trigger ) {
|
|
2352
2568
|
if ( ! op_mask ) {
|
|
2353
2569
|
ca_create_mask(co);
|
|
@@ -2355,11 +2571,6 @@ module MkKernel
|
|
|
2355
2571
|
}
|
|
2356
2572
|
op_mask[out_i] = 1;
|
|
2357
2573
|
op[out_i++] = (#{out_c}) #{out_zero_typed};
|
|
2358
|
-
} else if ( n_valid < #{required_min} ) {
|
|
2359
|
-
/* Legacy degenerate case (e.g. sample variance with 1 unmasked
|
|
2360
|
-
cell): return zero rather than UNDEF, matching pre-migration
|
|
2361
|
-
behaviour of `cnt > 1 ? formula : 0`. */
|
|
2362
|
-
op[out_i++] = (#{out_c}) #{out_zero_typed};
|
|
2363
2574
|
} else {
|
|
2364
2575
|
#{mean_setup}
|
|
2365
2576
|
/* Pass 2: M2 = Σ (x - mean)² */
|
|
@@ -2439,6 +2650,49 @@ module MkKernel
|
|
|
2439
2650
|
buf_decls = plus_info.map do |pi|
|
|
2440
2651
|
" #{pi[:c_type]} #{pi[:buf]}[512];\n"
|
|
2441
2652
|
end.join
|
|
2653
|
+
|
|
2654
|
+
# all-NaN fix-up for the tiled core (see emit_all_nan_fixup for the
|
|
2655
|
+
# reduction counterpart). Each output cell of a tile has its own
|
|
2656
|
+
# accumulator, so the check is per column: a column whose accumulator
|
|
2657
|
+
# is still at the init either saw only NaN or only +/-INFINITY.
|
|
2658
|
+
#
|
|
2659
|
+
# The `_Pragma("omp simd")` j-loop is left alone -- carrying a
|
|
2660
|
+
# "saw a number" flag beside the accumulator would double its loads
|
|
2661
|
+
# and stores on every cell of every column. Instead the tile is
|
|
2662
|
+
# tested once after the M loop (tile_len comparisons against M *
|
|
2663
|
+
# tile_len already done), and only a tile that holds a suspicious
|
|
2664
|
+
# column re-reads its rows.
|
|
2665
|
+
li_nan_fixup =
|
|
2666
|
+
if k[:all_nan_result] == :nan && FLOAT_DTYPES.include?(src) &&
|
|
2667
|
+
plus_info.size == 1
|
|
2668
|
+
pi = plus_info.first
|
|
2669
|
+
seen_buf = "__li_buf_seen"
|
|
2670
|
+
lambda do |m_step|
|
|
2671
|
+
<<~C.rstrip
|
|
2672
|
+
{
|
|
2673
|
+
int __li_susp = 0;
|
|
2674
|
+
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {
|
|
2675
|
+
if ( #{pi[:buf]}[__j] == (#{pi[:c_type]}) (#{pi[:init]}) ) { __li_susp = 1; break; }
|
|
2676
|
+
}
|
|
2677
|
+
if ( __li_susp ) {
|
|
2678
|
+
int8_t #{seen_buf}[512];
|
|
2679
|
+
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) #{seen_buf}[__j] = 0;
|
|
2680
|
+
for ( ca_size_t __li_i = 0; __li_i < __li_M; __li_i++ ) {
|
|
2681
|
+
const #{si[:c]} *__li_row = __li_plane + __li_i * #{m_step} + __li_tile;
|
|
2682
|
+
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {
|
|
2683
|
+
#{seen_buf}[__j] |= (__li_row[__j] == __li_row[__j]);
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {
|
|
2687
|
+
if ( ! #{seen_buf}[__j] ) #{pi[:buf]}[__j] = (#{pi[:c_type]}) NAN;
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
C
|
|
2692
|
+
end
|
|
2693
|
+
else
|
|
2694
|
+
lambda { |_m_step| "" }
|
|
2695
|
+
end
|
|
2442
2696
|
init_loops = plus_info.map do |pi|
|
|
2443
2697
|
" for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {\n" \
|
|
2444
2698
|
" #{pi[:buf]}[__j] = (#{pi[:c_type]}) (#{pi[:init]});\n" \
|
|
@@ -2468,6 +2722,7 @@ module MkKernel
|
|
|
2468
2722
|
(void) v;
|
|
2469
2723
|
}
|
|
2470
2724
|
}
|
|
2725
|
+
#{li_nan_fixup.call("__li_INNER")}
|
|
2471
2726
|
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {
|
|
2472
2727
|
#{opv}[__li_o * __li_INNER + __li_tile + __j] = (#{oi[:c]}) (#{finish_li});
|
|
2473
2728
|
}
|
|
@@ -2487,8 +2742,9 @@ module MkKernel
|
|
|
2487
2742
|
exposes ca->ptr as that buffer at O(1) cost (no materialise). */
|
|
2488
2743
|
if ( naxes == 1
|
|
2489
2744
|
&& slab_axes[0] != (int8_t)(ca->ndim - 1)
|
|
2490
|
-
&& ca
|
|
2491
|
-
&& ca_attach_is_alias(ca)
|
|
2745
|
+
&& ! ca_has_mask(ca)
|
|
2746
|
+
&& ca_attach_is_alias(ca)
|
|
2747
|
+
&& ca_fast_paths_enabled() ) {
|
|
2492
2748
|
int8_t __li_ax = slab_axes[0];
|
|
2493
2749
|
ca_size_t __li_M = ca->dim[__li_ax];
|
|
2494
2750
|
ca_size_t __li_INNER = 1;
|
|
@@ -2502,7 +2758,7 @@ module MkKernel
|
|
|
2502
2758
|
CArray *co;
|
|
2503
2759
|
GetCArray(vout, co);
|
|
2504
2760
|
#{oi[:c]} *op = (#{oi[:c]} *) co->ptr;
|
|
2505
|
-
ca_attach(ca);
|
|
2761
|
+
ca_attach(ca); /* window: numeric lanes only, nothing raises inside; O(1) alias attach */
|
|
2506
2762
|
const #{si[:c]} *p_root = (const #{si[:c]} *) ca->ptr;
|
|
2507
2763
|
|
|
2508
2764
|
/* Per-buffer stack accumulators (one per :plus state, sized
|
|
@@ -2536,9 +2792,10 @@ module MkKernel
|
|
|
2536
2792
|
if ( naxes == 1
|
|
2537
2793
|
&& slab_axes[0] >= 1
|
|
2538
2794
|
&& slab_axes[0] != (int8_t)(ca->ndim - 1)
|
|
2539
|
-
&& ca
|
|
2795
|
+
&& ! ca_has_mask(ca)
|
|
2540
2796
|
&& ca_func[ca->obj_type].attach == ca_stack_func.attach
|
|
2541
|
-
&& ((CAStack *) ca)->k_axis == 0
|
|
2797
|
+
&& ((CAStack *) ca)->k_axis == 0
|
|
2798
|
+
&& ca_fast_paths_enabled() ) {
|
|
2542
2799
|
CAStack *__li_st = (CAStack *) ca;
|
|
2543
2800
|
int8_t __li_ax = slab_axes[0];
|
|
2544
2801
|
ca_size_t __li_M = ca->dim[__li_ax];
|
|
@@ -2551,7 +2808,7 @@ module MkKernel
|
|
|
2551
2808
|
core reads parent->ptr without mask awareness). */
|
|
2552
2809
|
int __li_ok = 1;
|
|
2553
2810
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2554
|
-
if ( __li_st->parents[__kk]
|
|
2811
|
+
if ( ca_has_mask(__li_st->parents[__kk]) ) { __li_ok = 0; break; }
|
|
2555
2812
|
}
|
|
2556
2813
|
|
|
2557
2814
|
if ( __li_ok && __li_INNER >= 64 && __li_M * __li_INNER >= 1024 ) {
|
|
@@ -2566,7 +2823,7 @@ module MkKernel
|
|
|
2566
2823
|
ca_size_t __li_parent_out = __li_OUTER * __li_INNER;
|
|
2567
2824
|
const ca_size_t __li_TILE = 512;
|
|
2568
2825
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2569
|
-
ca_attach(__li_st->parents[__kk]);
|
|
2826
|
+
ca_attach(__li_st->parents[__kk]); /* window: numeric lanes only, nothing raises inside */
|
|
2570
2827
|
const #{si[:c]} *p_root = (const #{si[:c]} *) __li_st->parents[__kk]->ptr;
|
|
2571
2828
|
#{oi[:c]} *__li_op_k = op + (ca_size_t) __kk * __li_parent_out;
|
|
2572
2829
|
#{core.call("p_root", "__li_op_k", "__li_OUTER")}
|
|
@@ -2599,11 +2856,12 @@ module MkKernel
|
|
|
2599
2856
|
if ( naxes == 1
|
|
2600
2857
|
&& slab_axes[0] >= 1
|
|
2601
2858
|
&& slab_axes[0] != (int8_t)(ca->ndim - 1)
|
|
2602
|
-
&& ca
|
|
2859
|
+
&& ! ca_has_mask(ca)
|
|
2603
2860
|
&& ca_func[ca->obj_type].attach == ca_stack_func.attach
|
|
2604
2861
|
&& ((CAStack *) ca)->k_axis > 0
|
|
2605
2862
|
&& ( slab_axes[0] > ((CAStack *) ca)->k_axis
|
|
2606
|
-
|| slab_axes[0] + 1 == ((CAStack *) ca)->k_axis )
|
|
2863
|
+
|| slab_axes[0] + 1 == ((CAStack *) ca)->k_axis )
|
|
2864
|
+
&& ca_fast_paths_enabled() ) {
|
|
2607
2865
|
CAStack *__li_st = (CAStack *) ca;
|
|
2608
2866
|
int8_t __li_ax = slab_axes[0];
|
|
2609
2867
|
int8_t __li_kax = (int8_t) __li_st->k_axis;
|
|
@@ -2619,7 +2877,7 @@ module MkKernel
|
|
|
2619
2877
|
/* Bail to the generic path if any parent carries a mask. */
|
|
2620
2878
|
int __li_ok = 1;
|
|
2621
2879
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2622
|
-
if ( __li_st->parents[__kk]
|
|
2880
|
+
if ( ca_has_mask(__li_st->parents[__kk]) ) { __li_ok = 0; break; }
|
|
2623
2881
|
}
|
|
2624
2882
|
|
|
2625
2883
|
if ( __li_ok && __li_INNER >= 64 && __li_M * __li_INNER >= 1024 ) {
|
|
@@ -2660,7 +2918,7 @@ module MkKernel
|
|
|
2660
2918
|
ca_size_t __li_plane_elems = __li_M * __li_INNER;
|
|
2661
2919
|
const ca_size_t __li_TILE = 512;
|
|
2662
2920
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2663
|
-
ca_attach(__li_st->parents[__kk]);
|
|
2921
|
+
ca_attach(__li_st->parents[__kk]); /* window: numeric lanes only, nothing raises inside */
|
|
2664
2922
|
const #{si[:c]} *p_root = (const #{si[:c]} *) __li_st->parents[__kk]->ptr;
|
|
2665
2923
|
ca_size_t __li_k_base = (ca_size_t) __kk * __li_k_stride;
|
|
2666
2924
|
|
|
@@ -2688,6 +2946,7 @@ module MkKernel
|
|
|
2688
2946
|
(void) v;
|
|
2689
2947
|
}
|
|
2690
2948
|
}
|
|
2949
|
+
#{li_nan_fixup.call("__li_INNER")}
|
|
2691
2950
|
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {
|
|
2692
2951
|
__li_op_k[__li_tile + __j] = (#{oi[:c]}) (#{finish_li});
|
|
2693
2952
|
}
|
|
@@ -2741,10 +3000,11 @@ module MkKernel
|
|
|
2741
3000
|
if ( naxes == 1
|
|
2742
3001
|
&& slab_axes[0] >= 1
|
|
2743
3002
|
&& slab_axes[0] != (int8_t)(ca->ndim - 1)
|
|
2744
|
-
&& ca
|
|
3003
|
+
&& ! ca_has_mask(ca)
|
|
2745
3004
|
&& ca_func[ca->obj_type].attach == ca_stack_func.attach
|
|
2746
3005
|
&& ((CAStack *) ca)->k_axis > 0
|
|
2747
|
-
&& slab_axes[0] + 1 < ((CAStack *) ca)->k_axis
|
|
3006
|
+
&& slab_axes[0] + 1 < ((CAStack *) ca)->k_axis
|
|
3007
|
+
&& ca_fast_paths_enabled() ) {
|
|
2748
3008
|
CAStack *__li_st = (CAStack *) ca;
|
|
2749
3009
|
int8_t __li_ax = slab_axes[0];
|
|
2750
3010
|
int8_t __li_kax = (int8_t) __li_st->k_axis;
|
|
@@ -2762,7 +3022,7 @@ module MkKernel
|
|
|
2762
3022
|
/* Bail to the generic path if any parent carries a mask. */
|
|
2763
3023
|
int __li_ok = 1;
|
|
2764
3024
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2765
|
-
if ( __li_st->parents[__kk]
|
|
3025
|
+
if ( ca_has_mask(__li_st->parents[__kk]) ) { __li_ok = 0; break; }
|
|
2766
3026
|
}
|
|
2767
3027
|
|
|
2768
3028
|
if ( __li_ok && __li_eff_INNER >= 32 && __li_M * __li_eff_INNER >= 1024 ) {
|
|
@@ -2816,7 +3076,7 @@ module MkKernel
|
|
|
2816
3076
|
|
|
2817
3077
|
const ca_size_t __li_TILE = 512;
|
|
2818
3078
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2819
|
-
ca_attach(__li_st->parents[__kk]);
|
|
3079
|
+
ca_attach(__li_st->parents[__kk]); /* window: numeric lanes only, nothing raises inside */
|
|
2820
3080
|
const #{si[:c]} *p_root = (const #{si[:c]} *) __li_st->parents[__kk]->ptr;
|
|
2821
3081
|
ca_size_t __li_k_base = (ca_size_t) __kk * __li_k_stride;
|
|
2822
3082
|
|
|
@@ -2846,6 +3106,7 @@ module MkKernel
|
|
|
2846
3106
|
(void) v;
|
|
2847
3107
|
}
|
|
2848
3108
|
}
|
|
3109
|
+
#{li_nan_fixup.call("__li_M_stride")}
|
|
2849
3110
|
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {
|
|
2850
3111
|
__li_op_k[__li_tile + __j] = (#{oi[:c]}) (#{finish_li});
|
|
2851
3112
|
}
|
|
@@ -2922,7 +3183,7 @@ module MkKernel
|
|
|
2922
3183
|
slab_axes, naxes, 0);
|
|
2923
3184
|
if ( rc != CA_ITER_OK ) {
|
|
2924
3185
|
rb_raise(rb_eRuntimeError,
|
|
2925
|
-
"
|
|
3186
|
+
"%s: kernel_iterator init failed rc=%d", ca_calling_method_name(), rc);
|
|
2926
3187
|
}
|
|
2927
3188
|
char *p;
|
|
2928
3189
|
boolean8_t *m;
|
|
@@ -2937,10 +3198,11 @@ module MkKernel
|
|
|
2937
3198
|
if min_count
|
|
2938
3199
|
io.puts " ca_size_t masked_cnt = 0;"
|
|
2939
3200
|
io.puts " CA_SLAB_REDUCE_T_EX(#{si[:c]}, st, p, m, #{acc_var}, #{acc_init}, #{reduce_stmt}, masked_cnt);"
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
3201
|
+
if k[:all_nan_result]
|
|
3202
|
+
emit_all_nan_fixup(io, k, src, si, limit_init_state_vars(k, si, oi, src),
|
|
3203
|
+
"masked_cnt < st.slab_elements", indent: " ")
|
|
3204
|
+
end
|
|
3205
|
+
trigger = "ca_too_few_present(st.slab_elements - masked_cnt, min_count, 1)"
|
|
2944
3206
|
io.puts " if ( #{trigger} ) {"
|
|
2945
3207
|
io.puts " if ( ! op_mask_a ) {"
|
|
2946
3208
|
io.puts " ca_create_mask(co_a);"
|
|
@@ -2974,10 +3236,11 @@ module MkKernel
|
|
|
2974
3236
|
# pair of [1,...,1] entities instead.
|
|
2975
3237
|
io.puts " if ( naxes == ca->ndim && !keep_axis ) {"
|
|
2976
3238
|
if min_count
|
|
2977
|
-
# Full reduction: if the sole output cell is masked,
|
|
2978
|
-
#
|
|
2979
|
-
#
|
|
2980
|
-
|
|
3239
|
+
# Full reduction: if the sole output cell is masked, both answers are
|
|
3240
|
+
# UNDEF, still as a pair -- the shape of the answer does not depend on
|
|
3241
|
+
# the data. (Both op_mask_a and op_mask_b share the same bit since
|
|
3242
|
+
# both reductions consume the same slab, so checking _a is sufficient.)
|
|
3243
|
+
io.puts " if ( op_mask_a && op_mask_a[0] ) return rb_assoc_new(CA_UNDEF, CA_UNDEF);"
|
|
2981
3244
|
end
|
|
2982
3245
|
io.puts " return rb_assoc_new(#{ruby_wrap}(op_a[0]), #{ruby_wrap}(op_b[0]));"
|
|
2983
3246
|
io.puts " }"
|
|
@@ -2985,42 +3248,40 @@ module MkKernel
|
|
|
2985
3248
|
io.puts " }"
|
|
2986
3249
|
end
|
|
2987
3250
|
|
|
2988
|
-
#
|
|
2989
|
-
# early-return branch at the top of name_ki_native_<src> that pulls
|
|
2990
|
-
# the lazy view in fixed-size chunks via ca_xfer_stride instead of
|
|
2991
|
-
# full-materialising via SRC_ATTACH. Falls through to the existing
|
|
2992
|
-
# path when conditions aren't met.
|
|
2993
|
-
#
|
|
2994
|
-
# Strategy: chunk along the outermost axis. inner = prod(dim[1..]);
|
|
2995
|
-
# rows_per_chunk = max(1, target_elems / inner). 1-D is the natural
|
|
2996
|
-
# sub-case (inner == 1, rows = target_elems).
|
|
2997
|
-
#
|
|
2998
|
-
# Memory peak: O(max(target_elems, inner) * sizeof T) instead of O(N).
|
|
2999
|
-
# For N-D, this drops the outermost dim from the materialised buffer,
|
|
3000
|
-
# which is typically the dominant factor (e.g. 1000x10000 f64 reduction:
|
|
3001
|
-
# 80MB -> 80KB). Wall-clock perf at large N matches or beats
|
|
3002
|
-
# SRC_ATTACH because each chunk fits in L1d (or L2 for wide last dims).
|
|
3003
|
-
#
|
|
3004
|
-
# Restrictions handled by short-circuit checks (= falls through to
|
|
3005
|
-
# SRC_ATTACH path):
|
|
3006
|
-
# - axis kwarg (= partial reduction): naxes != ca->ndim
|
|
3007
|
-
# (structural blocker: CABinOp/CABinCmp xfer_stride rebuilds
|
|
3008
|
-
# right_strides from counts, so non-rectangular slab pulls
|
|
3009
|
-
# produce wrong values for the right operand. See proposal
|
|
3010
|
-
# §future-work / partial streaming blocker.)
|
|
3011
|
-
# - non-lazy source: !ca_is_lazy_view(ca)
|
|
3012
|
-
# - mask present: ca_has_mask(ca)
|
|
3251
|
+
# The walk of the streaming reduce (see emit_reduce_streaming_call).
|
|
3013
3252
|
def self.emit_reduce_streaming(io, k, si, oi, ruby_wrap, acc_var,
|
|
3014
3253
|
acc_init, decls, reduce_stmt,
|
|
3015
|
-
finish_expr,
|
|
3254
|
+
finish_expr, src)
|
|
3016
3255
|
name = k[:name]
|
|
3017
|
-
min_count = (k[:mask_policy] == :min_count)
|
|
3018
3256
|
has_mp = !k[:mask_policy].nil?
|
|
3257
|
+
fn = "#{name}_ki_stream_#{src}"
|
|
3019
3258
|
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3259
|
+
# The walk runs under ca_lazy_arena_protect: the transfer from the lazy
|
|
3260
|
+
# source and the object lane's fold can both raise, and the arena must
|
|
3261
|
+
# be left however the walk ends. So it is a function of its own, with
|
|
3262
|
+
# the native's arguments carried in a context whose first member is
|
|
3263
|
+
# the chunk (released by ca_lazy_arena_release_held).
|
|
3264
|
+
io.puts
|
|
3265
|
+
io.puts "typedef struct {"
|
|
3266
|
+
io.puts " void *chunk;"
|
|
3267
|
+
io.puts " CArray *ca;"
|
|
3268
|
+
io.puts " #{si[:c]} value_arg;" if k[:value_arg]
|
|
3269
|
+
io.puts " ca_size_t min_count;" if k[:mask_policy] == :min_count
|
|
3270
|
+
io.puts "} #{fn}_t;"
|
|
3271
|
+
io.puts
|
|
3272
|
+
io.puts "static VALUE"
|
|
3273
|
+
io.puts "#{fn} (VALUE arg)"
|
|
3274
|
+
io.puts "{"
|
|
3275
|
+
io.puts " #{fn}_t *__ctx = (#{fn}_t *) arg;"
|
|
3276
|
+
io.puts " CArray *ca = __ctx->ca;"
|
|
3277
|
+
if k[:value_arg]
|
|
3278
|
+
io.puts " #{si[:c]} value_arg = __ctx->value_arg;"
|
|
3279
|
+
io.puts " (void) value_arg;"
|
|
3280
|
+
end
|
|
3281
|
+
if k[:mask_policy] == :min_count
|
|
3282
|
+
io.puts " ca_size_t min_count = __ctx->min_count;"
|
|
3283
|
+
io.puts " (void) min_count;"
|
|
3284
|
+
end
|
|
3024
3285
|
io.puts " ca_size_t __inner = 1;"
|
|
3025
3286
|
io.puts " int __k;"
|
|
3026
3287
|
io.puts " for ( __k = 1; __k < ca->ndim; __k++ ) __inner *= ca->dim[__k];"
|
|
@@ -3043,6 +3304,14 @@ module MkKernel
|
|
|
3043
3304
|
# acc_var requires explicit init (= the macro normally does this).
|
|
3044
3305
|
# Other state vars in decls already include `= init` per line.
|
|
3045
3306
|
io.puts " #{acc_var} = (#{acc_init});"
|
|
3307
|
+
# all-NaN fix-up, streaming variant. The other two paths confirm a
|
|
3308
|
+
# suspicious answer by re-reading the data; here re-reading means
|
|
3309
|
+
# evaluating the lazy chain a second time, so the flag rides along in
|
|
3310
|
+
# the chunk loop instead. The loop already pays for producing each
|
|
3311
|
+
# cell, which is what makes one more compare affordable here and not
|
|
3312
|
+
# in the tiled core.
|
|
3313
|
+
all_nan_stream = k[:all_nan_result] == :nan && FLOAT_DTYPES.include?(src)
|
|
3314
|
+
io.puts " int64_t __anf_seen = 0;" if all_nan_stream
|
|
3046
3315
|
if has_mp
|
|
3047
3316
|
# Mask-policy reductions need masked_cnt to satisfy the macro/
|
|
3048
3317
|
# finish_expr signature. On streaming we have no mask, so it's
|
|
@@ -3050,7 +3319,6 @@ module MkKernel
|
|
|
3050
3319
|
io.puts " ca_size_t masked_cnt = 0; /* unused on streaming (no mask) */"
|
|
3051
3320
|
io.puts " (void) masked_cnt;"
|
|
3052
3321
|
end
|
|
3053
|
-
io.puts " ca_lazy_arena_enter();"
|
|
3054
3322
|
if si[:c] == "VALUE"
|
|
3055
3323
|
# Object lane: the chunk holds VALUEs pulled from the source, and
|
|
3056
3324
|
# the fold below calls rb_funcall per cell. A collection there
|
|
@@ -3060,6 +3328,7 @@ module MkKernel
|
|
|
3060
3328
|
else
|
|
3061
3329
|
io.puts " #{si[:c]} *__chunk = (#{si[:c]} *) ca_lazy_arena_acquire(__chunk_elems * sizeof(#{si[:c]}));"
|
|
3062
3330
|
end
|
|
3331
|
+
io.puts " __ctx->chunk = __chunk;"
|
|
3063
3332
|
io.puts " while ( __outer_off < __outer ) {"
|
|
3064
3333
|
io.puts " ca_size_t __r = (__outer - __outer_off < __rows) ? (__outer - __outer_off) : __rows;"
|
|
3065
3334
|
io.puts " ca_size_t __n = __r * __inner;"
|
|
@@ -3070,23 +3339,90 @@ module MkKernel
|
|
|
3070
3339
|
io.puts " for ( __i = 0; __i < __n; __i++ ) {"
|
|
3071
3340
|
io.puts " #{si[:c]} v = __chunk[__i];"
|
|
3072
3341
|
io.puts " #{reduce_stmt};"
|
|
3342
|
+
io.puts " __anf_seen |= (v == v);" if all_nan_stream
|
|
3073
3343
|
io.puts " }"
|
|
3074
3344
|
io.puts " __outer_off += __r;"
|
|
3075
3345
|
io.puts " }"
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
if has_mp
|
|
3079
|
-
# Streaming path has no mask source, so masked_cnt is 0; min_count
|
|
3080
|
-
# / strict / all_masked triggers all evaluate to false except
|
|
3081
|
-
# min_count when explicit > 0 is set on an all-clean source (= no
|
|
3082
|
-
# trigger). Output is the finish expression directly.
|
|
3083
|
-
io.puts " return #{ruby_wrap}((#{oi[:c]}) (#{finish_expr}));"
|
|
3084
|
-
else
|
|
3085
|
-
io.puts " return #{ruby_wrap}((#{oi[:c]}) (#{finish_expr}));"
|
|
3346
|
+
if all_nan_stream
|
|
3347
|
+
io.puts " if ( ! __anf_seen && ca->elements > 0 ) #{acc_var} = (#{si[:c]}) NAN;"
|
|
3086
3348
|
end
|
|
3349
|
+
# Streaming has no mask source, so masked_cnt is 0 and none of the
|
|
3350
|
+
# mask-policy triggers fire: the answer is the finish expression.
|
|
3351
|
+
io.puts " return #{ruby_wrap}((#{oi[:c]}) (#{finish_expr}));"
|
|
3352
|
+
io.puts "}"
|
|
3353
|
+
end
|
|
3354
|
+
|
|
3355
|
+
# The streaming branch at the top of name_ki_native_<src>: a flat
|
|
3356
|
+
# reduction of an unmasked lazy view pulls the source in fixed-size
|
|
3357
|
+
# chunks via ca_xfer_stride instead of materialising it whole. Falls
|
|
3358
|
+
# through to the existing path when the conditions are not met.
|
|
3359
|
+
#
|
|
3360
|
+
# Strategy: chunk along the outermost axis. inner = prod(dim[1..]);
|
|
3361
|
+
# rows_per_chunk = max(1, target_elems / inner). 1-D is the natural
|
|
3362
|
+
# sub-case (inner == 1, rows = target_elems).
|
|
3363
|
+
#
|
|
3364
|
+
# Memory peak: O(max(target_elems, inner) * sizeof T) instead of O(N).
|
|
3365
|
+
# For N-D, this drops the outermost dim from the materialised buffer,
|
|
3366
|
+
# which is typically the dominant factor (e.g. 1000x10000 f64 reduction:
|
|
3367
|
+
# 80MB -> 80KB). Wall-clock perf at large N matches or beats
|
|
3368
|
+
# SRC_ATTACH because each chunk fits in L1d (or L2 for wide last dims).
|
|
3369
|
+
#
|
|
3370
|
+
# Restrictions handled by short-circuit checks (= falls through to
|
|
3371
|
+
# SRC_ATTACH path):
|
|
3372
|
+
# - axis kwarg (= partial reduction): naxes != ca->ndim
|
|
3373
|
+
# (structural blocker: CABinOp/CABinCmp xfer_stride rebuilds
|
|
3374
|
+
# right_strides from counts, so non-rectangular slab pulls
|
|
3375
|
+
# produce wrong values for the right operand.)
|
|
3376
|
+
# - non-lazy source: !ca_is_lazy_view(ca)
|
|
3377
|
+
# - mask present: ca_has_mask(ca)
|
|
3378
|
+
# - nothing to fold, or fewer cells than min_count asks for: the walk
|
|
3379
|
+
# has no mask, so it answers the finish expression, which is right
|
|
3380
|
+
# only when the mask policy cannot fire. An empty source and a
|
|
3381
|
+
# min_count above the cell count are the two ways it can without a
|
|
3382
|
+
# mask, and the full path answers them.
|
|
3383
|
+
def self.emit_reduce_streaming_call(io, k, src)
|
|
3384
|
+
fn = "#{k[:name]}_ki_stream_#{src}"
|
|
3385
|
+
io.puts " if ( naxes == ca->ndim && ca->ndim >= 1 && !keep_axis &&"
|
|
3386
|
+
io.puts " ca->elements > 0 &&"
|
|
3387
|
+
io.puts " ! ca_too_few_present(ca->elements, min_count, 0) &&" if k[:mask_policy] == :min_count
|
|
3388
|
+
io.puts " ca_is_lazy_view(ca) && ! ca_has_mask(ca) &&"
|
|
3389
|
+
io.puts " ca_fast_paths_enabled() ) {"
|
|
3390
|
+
io.puts " #{fn}_t __ctx;"
|
|
3391
|
+
io.puts " __ctx.chunk = NULL;"
|
|
3392
|
+
io.puts " __ctx.ca = ca;"
|
|
3393
|
+
io.puts " __ctx.value_arg = value_arg;" if k[:value_arg]
|
|
3394
|
+
io.puts " __ctx.min_count = min_count;" if k[:mask_policy] == :min_count
|
|
3395
|
+
io.puts " return ca_lazy_arena_protect(#{fn}, ca_lazy_arena_release_held,"
|
|
3396
|
+
io.puts " (VALUE) &__ctx);"
|
|
3087
3397
|
io.puts " }"
|
|
3088
3398
|
end
|
|
3089
3399
|
|
|
3400
|
+
# Where the source is a lazy expression the kernel is about to compute
|
|
3401
|
+
# whole, a registered expression evaluator may compute it instead; the
|
|
3402
|
+
# dispatcher then goes on with that array. `unless_streaming` is the C
|
|
3403
|
+
# condition under which the reduction streams the expression in chunks
|
|
3404
|
+
# rather than making it whole -- there it is left to stream, since asking
|
|
3405
|
+
# would make it whole and take that much more memory.
|
|
3406
|
+
def self.emit_lazy_evaluated(io, unless_streaming: nil)
|
|
3407
|
+
cond = "ca_is_lazy_view(src)"
|
|
3408
|
+
cond += " && ! ( #{unless_streaming} )" if unless_streaming
|
|
3409
|
+
io.puts " if ( #{cond} ) {"
|
|
3410
|
+
io.puts " volatile VALUE evaluated = ca_lazy_evaluated(self);"
|
|
3411
|
+
io.puts " if ( ! NIL_P(evaluated) ) {"
|
|
3412
|
+
io.puts " self = evaluated;"
|
|
3413
|
+
io.puts " GetCArray(self, src);"
|
|
3414
|
+
io.puts " }"
|
|
3415
|
+
io.puts " }"
|
|
3416
|
+
end
|
|
3417
|
+
|
|
3418
|
+
# Whether a reduction's native walk has the streaming branch
|
|
3419
|
+
# (emit_reduce_streaming_call): the conditions emit_reduce_native checks
|
|
3420
|
+
# before it emits one.
|
|
3421
|
+
def self.reduce_streams?(k)
|
|
3422
|
+
k[:outputs] != 2 && k[:algorithm] != :two_pass_centred &&
|
|
3423
|
+
!k[:array_arg] && !k[:reduce].to_s.include?("idx")
|
|
3424
|
+
end
|
|
3425
|
+
|
|
3090
3426
|
def self.emit_reduce_dispatch(io, k)
|
|
3091
3427
|
name = k[:name]
|
|
3092
3428
|
min_count = (k[:mask_policy] == :min_count)
|
|
@@ -3112,12 +3448,13 @@ module MkKernel
|
|
|
3112
3448
|
io.puts " GetCArray(self, src);"
|
|
3113
3449
|
|
|
3114
3450
|
if k[:object_escape]
|
|
3115
|
-
# CA_OBJECT escape:
|
|
3116
|
-
#
|
|
3117
|
-
#
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
io.puts "
|
|
3451
|
+
# CA_OBJECT escape: the Ruby composition helper gets the original
|
|
3452
|
+
# argc/argv (weights + trailing options hash, untouched). The escape
|
|
3453
|
+
# itself comes after the options and axes are checked below, so a
|
|
3454
|
+
# wrong keyword or axis is reported here, in this method's name, as
|
|
3455
|
+
# on the numeric path.
|
|
3456
|
+
io.puts " int escape_argc = argc;"
|
|
3457
|
+
io.puts " VALUE *escape_argv = argv;"
|
|
3121
3458
|
end
|
|
3122
3459
|
|
|
3123
3460
|
if k[:face_gate]
|
|
@@ -3151,7 +3488,18 @@ module MkKernel
|
|
|
3151
3488
|
io.puts " int keep_axis = RTEST(rkeep_axis);"
|
|
3152
3489
|
io.puts " int8_t slab_axes[CA_RANK_MAX];"
|
|
3153
3490
|
io.puts " int8_t naxes = rb_ca_parse_reduce_axes_kw(raxis, src, slab_axes);"
|
|
3491
|
+
if k[:object_escape]
|
|
3492
|
+
io.puts " if ( src->data_type == CA_OBJECT ) {"
|
|
3493
|
+
if min_count
|
|
3494
|
+
io.puts " ca_min_count_arg(rmin_count, NULL);"
|
|
3495
|
+
end
|
|
3496
|
+
io.puts " return rb_funcallv(self, rb_intern(\"#{k[:object_escape]}\"), escape_argc, escape_argv);"
|
|
3497
|
+
io.puts " }"
|
|
3498
|
+
end
|
|
3499
|
+
emit_lazy_evaluated(io, unless_streaming:
|
|
3500
|
+
reduce_streams?(k) ? "naxes == src->ndim && src->ndim >= 1 && ! keep_axis && ! ca_has_mask(src)" : nil)
|
|
3154
3501
|
|
|
3502
|
+
weights_io = nil
|
|
3155
3503
|
if has_aarg
|
|
3156
3504
|
# Weight storage type: :promote materializes weights at the f64
|
|
3157
3505
|
# computation type (so a float weight against an int source is not
|
|
@@ -3166,7 +3514,7 @@ module MkKernel
|
|
|
3166
3514
|
# at dispatch (= simple, uniform kernel path). rev5 strict acceptance
|
|
3167
3515
|
# mirrors PROPOSAL_LINEAR_INTERP_PER_FIBER_MATCHED rev5 in spirit.
|
|
3168
3516
|
io.puts " if ( argc < 1 ) {"
|
|
3169
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
3517
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: missing weights argument (1+ required, got 0)", ca_calling_method_name());]
|
|
3170
3518
|
io.puts " }"
|
|
3171
3519
|
io.puts " volatile VALUE rweights = argv[0];"
|
|
3172
3520
|
io.puts " argc--; argv++;"
|
|
@@ -3179,98 +3527,103 @@ module MkKernel
|
|
|
3179
3527
|
io.puts " rweights = rb_funcall(rweights, rb_intern(\"[]\"), 1, INT2NUM(0));"
|
|
3180
3528
|
io.puts " }"
|
|
3181
3529
|
io.puts " }"
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3530
|
+
# The weights are attached from here to the end of the call, so they
|
|
3531
|
+
# are made only once every check that can raise has passed; see the
|
|
3532
|
+
# data type check below.
|
|
3533
|
+
wio = StringIO.new
|
|
3534
|
+
wio.puts " volatile VALUE vcw;"
|
|
3535
|
+
wio.puts " CArray *cw;"
|
|
3536
|
+
wio.puts " if ( ! rb_obj_is_carray(rweights) ) {"
|
|
3537
|
+
wio.puts " /* W-A1: Ruby Numeric scalar -> full-shape constant weight CArray */"
|
|
3538
|
+
wio.puts " vcw = rb_carray_new(#{waca}, src->ndim, src->dim, 0, NULL);"
|
|
3539
|
+
wio.puts " GetCArray(vcw, cw); /* a new entity: its ptr needs no attach */"
|
|
3540
|
+
wio.puts " rb_ca_obj2ptr(vcw, rweights, cw->ptr); /* fill first cell */"
|
|
3541
|
+
wio.puts " /* broadcast first cell to all elements (= constant fill) */"
|
|
3542
|
+
wio.puts " {"
|
|
3543
|
+
wio.puts " char *p0 = cw->ptr;"
|
|
3544
|
+
wio.puts " for ( ca_size_t i = 1; i < cw->elements; i++ ) {"
|
|
3545
|
+
wio.puts " memcpy(cw->ptr + i * cw->bytes, p0, cw->bytes);"
|
|
3546
|
+
wio.puts " }"
|
|
3547
|
+
wio.puts " }"
|
|
3548
|
+
wio.puts " }"
|
|
3549
|
+
wio.puts " else {"
|
|
3550
|
+
wio.puts " /* CArray val: coerce to the weight storage type (f64 for :promote, so a"
|
|
3551
|
+
wio.puts " float weight against an int source is not truncated) */"
|
|
3552
|
+
wio.puts " vcw = rb_ca_wrap_readonly(rweights, INT2NUM(#{waca}));"
|
|
3553
|
+
wio.puts " CArray *cv;"
|
|
3554
|
+
wio.puts " GetCArray(vcw, cv);"
|
|
3555
|
+
wio.puts ""
|
|
3556
|
+
wio.puts " /* rev5 form detection: A3 (same ndim) -> A2 (1-D axis-broadcast) -> raise */"
|
|
3557
|
+
wio.puts " if ( cv->ndim == src->ndim ) {"
|
|
3558
|
+
wio.puts " /* W-A3 commit-or-raise: shape strict match */"
|
|
3559
|
+
wio.puts " for ( int8_t i = 0; i < src->ndim; i++ ) {"
|
|
3560
|
+
wio.puts " if ( cv->dim[i] != src->dim[i] ) {"
|
|
3561
|
+
wio.puts %Q[ rb_raise(rb_eArgError, "%s: weights shape mismatch (dim[%d]=%ld != self.dim[%d]=%ld; expected a scalar, a 1-D array along the reduced axis, or self.shape)", ca_calling_method_name(), (int)i, (long)cv->dim[i], (int)i, (long)src->dim[i]);]
|
|
3562
|
+
wio.puts " }"
|
|
3563
|
+
wio.puts " }"
|
|
3564
|
+
wio.puts " /* W-A3 path: use cv directly (= existing) */"
|
|
3565
|
+
wio.puts " cw = cv;"
|
|
3566
|
+
wio.puts " }"
|
|
3567
|
+
wio.puts " else if ( naxes == 1 && cv->ndim == 1 && cv->dim[0] == src->dim[slab_axes[0]] ) {"
|
|
3568
|
+
wio.puts " /* W-A2: 1-D axis-broadcast -> materialize to full shape */"
|
|
3569
|
+
wio.puts " ca_attach(cv); /* window: nothing raises inside */"
|
|
3570
|
+
wio.puts " vcw = rb_carray_new(#{waca}, src->ndim, src->dim, 0, NULL);"
|
|
3571
|
+
wio.puts " GetCArray(vcw, cw);"
|
|
3572
|
+
wio.puts " /* broadcast cv (1-D, length src->dim[slab_axes[0]]) along reduce axis */"
|
|
3573
|
+
wio.puts " {"
|
|
3574
|
+
wio.puts " int8_t baxis = slab_axes[0];"
|
|
3575
|
+
wio.puts " ca_size_t inner_stride = 1;"
|
|
3576
|
+
wio.puts " for ( int8_t ii = baxis + 1; ii < src->ndim; ii++ ) inner_stride *= src->dim[ii];"
|
|
3577
|
+
wio.puts " ca_size_t axis_n = src->dim[baxis];"
|
|
3578
|
+
wio.puts " ca_size_t outer = src->elements / (axis_n * inner_stride);"
|
|
3579
|
+
wio.puts " for ( ca_size_t o = 0; o < outer; o++ ) {"
|
|
3580
|
+
wio.puts " for ( ca_size_t a = 0; a < axis_n; a++ ) {"
|
|
3581
|
+
wio.puts " for ( ca_size_t in = 0; in < inner_stride; in++ ) {"
|
|
3582
|
+
wio.puts " ca_size_t flat = (o * axis_n + a) * inner_stride + in;"
|
|
3583
|
+
wio.puts " memcpy(cw->ptr + flat * cw->bytes, cv->ptr + a * cv->bytes, cw->bytes);"
|
|
3584
|
+
wio.puts " }"
|
|
3585
|
+
wio.puts " }"
|
|
3586
|
+
wio.puts " }"
|
|
3587
|
+
wio.puts " }"
|
|
3588
|
+
wio.puts " /* Propagate cv mask via W-A3 overlay path below (= mask materialize) */"
|
|
3589
|
+
wio.puts " if ( ca_has_mask(cv) ) {"
|
|
3590
|
+
wio.puts " /* Allocate cw mask + broadcast cv mask along axis */"
|
|
3591
|
+
wio.puts " ca_create_mask(cw);"
|
|
3592
|
+
wio.puts " boolean8_t *cw_m = (boolean8_t *)cw->mask->ptr;"
|
|
3593
|
+
wio.puts " boolean8_t *cv_m = (boolean8_t *)cv->mask->ptr;"
|
|
3594
|
+
wio.puts " int8_t baxis = slab_axes[0];"
|
|
3595
|
+
wio.puts " ca_size_t inner_stride = 1;"
|
|
3596
|
+
wio.puts " for ( int8_t ii = baxis + 1; ii < src->ndim; ii++ ) inner_stride *= src->dim[ii];"
|
|
3597
|
+
wio.puts " ca_size_t axis_n = src->dim[baxis];"
|
|
3598
|
+
wio.puts " ca_size_t outer = src->elements / (axis_n * inner_stride);"
|
|
3599
|
+
wio.puts " for ( ca_size_t o = 0; o < outer; o++ ) {"
|
|
3600
|
+
wio.puts " for ( ca_size_t a = 0; a < axis_n; a++ ) {"
|
|
3601
|
+
wio.puts " for ( ca_size_t in = 0; in < inner_stride; in++ ) {"
|
|
3602
|
+
wio.puts " ca_size_t flat = (o * axis_n + a) * inner_stride + in;"
|
|
3603
|
+
wio.puts " cw_m[flat] = cv_m[a];"
|
|
3604
|
+
wio.puts " }"
|
|
3605
|
+
wio.puts " }"
|
|
3606
|
+
wio.puts " }"
|
|
3607
|
+
wio.puts " }"
|
|
3608
|
+
wio.puts " ca_detach(cv);"
|
|
3609
|
+
wio.puts " }"
|
|
3610
|
+
wio.puts " else {"
|
|
3611
|
+
wio.puts %Q[ rb_raise(rb_eArgError, "%s: w shape not accepted (val.ndim=%d, val.dim[0]=%ld; expected scalar / [M=%ld] / self.shape)", ca_calling_method_name(), (int)cv->ndim, (long)(cv->ndim >= 1 ? cv->dim[0] : 0), (long)(naxes == 1 ? src->dim[slab_axes[0]] : -1));]
|
|
3612
|
+
wio.puts " }"
|
|
3613
|
+
wio.puts " }"
|
|
3265
3614
|
# Mask overlay (= legacy W-A3 path; if the materialized cw carries a mask, overlay it)
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3615
|
+
wio.puts " volatile VALUE __vsrc_keep = self; /* GC guard */ (void) __vsrc_keep;"
|
|
3616
|
+
wio.puts " if ( ca_has_mask(cw) ) {"
|
|
3617
|
+
wio.puts " CArray *src_copy = ca_copy(src);"
|
|
3618
|
+
wio.puts " VALUE vsrc_copy = ca_wrap_struct(src_copy);"
|
|
3619
|
+
wio.puts " ca_copy_mask_overlay(src_copy, src_copy->elements, 1, cw);"
|
|
3620
|
+
wio.puts " src = src_copy;"
|
|
3621
|
+
wio.puts " self = vsrc_copy;"
|
|
3622
|
+
wio.puts " }"
|
|
3623
|
+
# Attached last, after the overlay's copy of the source (which can
|
|
3624
|
+
# raise), and detached right after the switch.
|
|
3625
|
+
wio.puts " ca_attach(cw); /* window: nothing raises inside; the kernels are numeric */"
|
|
3626
|
+
weights_io = wio
|
|
3274
3627
|
end
|
|
3275
3628
|
|
|
3276
3629
|
if has_varg
|
|
@@ -3278,7 +3631,7 @@ module MkKernel
|
|
|
3278
3631
|
# runtime value_arg (e.g. `a.count_equal(5, 0)` -- 5 is value_arg,
|
|
3279
3632
|
# 0 is axis). Per-src cast happens inside the switch case below.
|
|
3280
3633
|
io.puts " if ( argc < 1 ) {"
|
|
3281
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
3634
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: missing value argument (1+ required, got 0)", ca_calling_method_name());]
|
|
3282
3635
|
io.puts " }"
|
|
3283
3636
|
io.puts " volatile VALUE rval = argv[0];"
|
|
3284
3637
|
io.puts " argc--; argv++;"
|
|
@@ -3286,16 +3639,16 @@ module MkKernel
|
|
|
3286
3639
|
|
|
3287
3640
|
if min_count
|
|
3288
3641
|
# rmin_count / rfval were scanned up front (PoC scan-merge); here we
|
|
3289
|
-
# only interpret them
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
io.puts "
|
|
3295
|
-
io.puts
|
|
3296
|
-
io.puts "
|
|
3297
|
-
io.puts "
|
|
3298
|
-
io.puts "
|
|
3642
|
+
# only interpret them; min_count is -1 when not given.
|
|
3643
|
+
io.puts " ca_size_t min_count = ca_min_count_arg(rmin_count, NULL);"
|
|
3644
|
+
# fill_value: UNDEF leaves the result undefined, as no fill_value does.
|
|
3645
|
+
# A full reduction with a fill keeps its axes so the fill goes into a
|
|
3646
|
+
# typed cell, as it does per axis; ca_reduce_fill takes the cell out.
|
|
3647
|
+
io.puts " if ( rfval == CA_UNDEF ) rfval = Qnil;"
|
|
3648
|
+
io.puts " int fill_whole = 0;"
|
|
3649
|
+
io.puts " if ( ! NIL_P(rfval) && naxes == src->ndim && ! keep_axis ) {"
|
|
3650
|
+
io.puts " keep_axis = 1;"
|
|
3651
|
+
io.puts " fill_whole = 1;"
|
|
3299
3652
|
io.puts " }"
|
|
3300
3653
|
io.puts " VALUE result;"
|
|
3301
3654
|
elsif has_aarg
|
|
@@ -3311,26 +3664,57 @@ module MkKernel
|
|
|
3311
3664
|
# axis is allowed. Variadic `a.sum(0, 1)` raises here with a migration
|
|
3312
3665
|
# hint pointing at the kwarg form.
|
|
3313
3666
|
io.puts " if ( argc > 0 ) {"
|
|
3314
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
3667
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: positional axis arguments are no longer accepted (got %d); use the axis: keyword, e.g. a.%s(axis: 0) or a.%s(axis: [0, 1])", ca_calling_method_name(), argc, ca_calling_method_name(), ca_calling_method_name());]
|
|
3315
3668
|
io.puts " }"
|
|
3316
3669
|
|
|
3670
|
+
if weights_io
|
|
3671
|
+
# Refuse an unsupported source before the weights are attached: the
|
|
3672
|
+
# switch below would raise with them held.
|
|
3673
|
+
if k[:fallback] == :raise
|
|
3674
|
+
io.puts " switch ( src->data_type ) {"
|
|
3675
|
+
io.puts " " + k[:source].map { |s| "case #{DTYPES[s][:ca]}:" }.join(" ") + " break;"
|
|
3676
|
+
io.puts " default:"
|
|
3677
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
3678
|
+
io.puts %Q[ rb_raise(rb_eCADataTypeError, "%s: source data_type :%s not supported (expected one of: #{MkKernel.type_names(k[:source])})", ca_calling_method_name(), ca_type_name[src->data_type]);]
|
|
3679
|
+
io.puts " }"
|
|
3680
|
+
end
|
|
3681
|
+
io.print weights_io.string
|
|
3682
|
+
end
|
|
3683
|
+
|
|
3317
3684
|
io.puts " switch ( src->data_type ) {"
|
|
3318
3685
|
k[:source].each do |s|
|
|
3319
3686
|
si = DTYPES[s]
|
|
3320
3687
|
# Per-src value_arg cast: NUM2LL / NUM2ULL / NUM2DBL -> (T_IN).
|
|
3321
|
-
varg_decl = has_varg
|
|
3688
|
+
varg_decl = if !has_varg
|
|
3689
|
+
""
|
|
3690
|
+
elsif s == :fixlen
|
|
3691
|
+
# A fixlen query is a runtime-width byte blob with no
|
|
3692
|
+
# scalar cast; pack it the way the search family does,
|
|
3693
|
+
# which NUL-pads a short String to the cell width.
|
|
3694
|
+
# ALLOCV, not the stack: a cell can be wide. The holder
|
|
3695
|
+
# keeps the buffer until the GC takes it.
|
|
3696
|
+
" volatile VALUE value_arg_holder = 0;\n" \
|
|
3697
|
+
" char *value_arg = ALLOCV_N(char, value_arg_holder, src->bytes);\n" \
|
|
3698
|
+
" rb_ca_obj2ptr(self, rval, value_arg);\n"
|
|
3699
|
+
elsif INT_DTYPES.include?(s)
|
|
3700
|
+
" ca_check_integer_fits(rval, #{si[:ca]});\n" \
|
|
3701
|
+
" #{si[:c]} value_arg = (#{si[:c]}) #{si[:num2c]}(rval);\n"
|
|
3702
|
+
else
|
|
3703
|
+
" #{si[:c]} value_arg = (#{si[:c]}) #{si[:num2c]}(rval);\n"
|
|
3704
|
+
end
|
|
3322
3705
|
if use_result_var || has_varg
|
|
3323
3706
|
io.puts " case #{si[:ca]}: {"
|
|
3324
3707
|
io.print varg_decl unless varg_decl.empty?
|
|
3325
3708
|
if use_result_var
|
|
3326
3709
|
io.puts " result = #{name}_ki_native_#{s}(self, src, slab_axes, naxes, keep_axis#{extra_call});"
|
|
3710
|
+
io.puts " RB_GC_GUARD(self);"
|
|
3327
3711
|
io.puts " break;"
|
|
3328
3712
|
else
|
|
3329
|
-
io.puts "
|
|
3713
|
+
io.puts " CA_RETURN_KEEPING(self, #{name}_ki_native_#{s}(self, src, slab_axes, naxes, keep_axis#{extra_call}));"
|
|
3330
3714
|
end
|
|
3331
3715
|
io.puts " }"
|
|
3332
3716
|
else
|
|
3333
|
-
io.puts " case #{si[:ca]}:
|
|
3717
|
+
io.puts " case #{si[:ca]}: CA_RETURN_KEEPING(self, #{name}_ki_native_#{s}(self, src, slab_axes, naxes, keep_axis));"
|
|
3334
3718
|
end
|
|
3335
3719
|
end
|
|
3336
3720
|
case k[:fallback]
|
|
@@ -3343,7 +3727,8 @@ module MkKernel
|
|
|
3343
3727
|
io.puts " switch ( src->data_type ) {"
|
|
3344
3728
|
io.puts " case CA_BOOLEAN: break; /* wrap: count-of-trues semantic */"
|
|
3345
3729
|
io.puts " default:"
|
|
3346
|
-
io.puts %Q[
|
|
3730
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
3731
|
+
io.puts %Q[ rb_raise(rb_eCADataTypeError, "%s: source data_type :%s not supported (expected one of: #{MkKernel.type_names(k[:source])}, or boolean)", ca_calling_method_name(), ca_type_name[src->data_type]);]
|
|
3347
3732
|
io.puts " }"
|
|
3348
3733
|
io.puts " VALUE vsrc = rb_ca_wrap_readonly(self, INT2NUM(CA_FLOAT64));"
|
|
3349
3734
|
io.puts " CArray *casted;"
|
|
@@ -3355,42 +3740,44 @@ module MkKernel
|
|
|
3355
3740
|
end
|
|
3356
3741
|
if use_result_var
|
|
3357
3742
|
io.puts " result = #{name}_ki_native_f64(vsrc, casted, slab_axes, naxes, keep_axis#{extra_call});"
|
|
3743
|
+
io.puts " RB_GC_GUARD(vsrc);"
|
|
3358
3744
|
io.puts " break;"
|
|
3359
3745
|
else
|
|
3360
|
-
io.puts "
|
|
3746
|
+
io.puts " CA_RETURN_KEEPING(vsrc, #{name}_ki_native_f64(vsrc, casted, slab_axes, naxes, keep_axis#{extra_call}));"
|
|
3361
3747
|
end
|
|
3362
3748
|
io.puts " }"
|
|
3363
3749
|
when :raise
|
|
3364
3750
|
io.puts " default:"
|
|
3365
|
-
io.puts %Q[
|
|
3751
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
3752
|
+
io.puts %Q[ rb_raise(rb_eCADataTypeError, "%s: source data_type :%s not supported (expected one of: #{MkKernel.type_names(k[:source])})", ca_calling_method_name(), ca_type_name[src->data_type]);]
|
|
3366
3753
|
end
|
|
3367
3754
|
io.puts " }"
|
|
3368
3755
|
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
# or per-axis CArray with mask bits.
|
|
3372
|
-
io.puts " if ( ! NIL_P(rfval) ) {"
|
|
3373
|
-
io.puts " if ( result == CA_UNDEF ) {"
|
|
3374
|
-
io.puts " result = rfval;"
|
|
3375
|
-
io.puts " } else if ( TYPE(result) != T_FLOAT && rb_obj_is_kind_of(result, rb_cCArray) ) {"
|
|
3376
|
-
io.puts " CArray *cr;"
|
|
3377
|
-
io.puts " GetCArray(result, cr);"
|
|
3378
|
-
io.puts " if ( ca_has_mask(cr) ) {"
|
|
3379
|
-
io.puts " result = rb_ca_mask_fill_copy(result, rfval);"
|
|
3380
|
-
io.puts " }"
|
|
3381
|
-
io.puts " }"
|
|
3382
|
-
io.puts " }"
|
|
3383
|
-
end
|
|
3384
|
-
|
|
3385
|
-
# array_arg cleanup: detach weights (paired with ca_attach above).
|
|
3756
|
+
# array_arg cleanup: detach weights (paired with ca_attach above),
|
|
3757
|
+
# ahead of the fill_value substitution, which can raise.
|
|
3386
3758
|
if has_aarg
|
|
3387
3759
|
io.puts " ca_detach(cw);"
|
|
3388
3760
|
end
|
|
3389
3761
|
|
|
3390
|
-
|
|
3762
|
+
relift = (k[:face_gate] == :relift)
|
|
3763
|
+
if min_count
|
|
3764
|
+
# Apply fill_value through the one rule the per-axis result uses. A
|
|
3765
|
+
# Face's fill is a value of the Face, so it goes in after the re-lift
|
|
3766
|
+
# below, as an assignment to the Face would put it.
|
|
3767
|
+
io.puts relift ? " if ( ! NIL_P(rfval) && _reduce_face == Qnil ) {" : " if ( ! NIL_P(rfval) ) {"
|
|
3768
|
+
io.puts " result = ca_reduce_fill(result, rfval, fill_whole);"
|
|
3769
|
+
io.puts " }"
|
|
3770
|
+
end
|
|
3771
|
+
|
|
3772
|
+
if relift
|
|
3391
3773
|
# Descended through emit_reduce_face_gate: re-lift the storage result
|
|
3392
3774
|
# back into the Face before returning.
|
|
3393
3775
|
emit_reduce_face_relift(io)
|
|
3776
|
+
if min_count
|
|
3777
|
+
io.puts " if ( ! NIL_P(rfval) && _reduce_face != Qnil ) {"
|
|
3778
|
+
io.puts " result = ca_reduce_fill(result, rfval, fill_whole);"
|
|
3779
|
+
io.puts " }"
|
|
3780
|
+
end
|
|
3394
3781
|
end
|
|
3395
3782
|
|
|
3396
3783
|
if use_result_var
|
|
@@ -3401,117 +3788,13 @@ module MkKernel
|
|
|
3401
3788
|
io.puts "}"
|
|
3402
3789
|
end
|
|
3403
3790
|
|
|
3404
|
-
# ---- map emitter ------------------------------------------------------
|
|
3405
|
-
|
|
3406
|
-
def self.emit_map(io, k)
|
|
3407
|
-
io.puts
|
|
3408
|
-
io.puts "/* ===== #{k[:name]}_ki ============================================ */"
|
|
3409
|
-
k[:source].each do |src|
|
|
3410
|
-
emit_map_native(io, k, src)
|
|
3411
|
-
end
|
|
3412
|
-
emit_map_dispatch(io, k)
|
|
3413
|
-
end
|
|
3414
|
-
|
|
3415
|
-
def self.emit_map_native(io, k, src)
|
|
3416
|
-
si = DTYPES[src]
|
|
3417
|
-
oi = output_info(k, src)
|
|
3418
|
-
name = k[:name]
|
|
3419
|
-
expr = resolve_expr(k[:expr], oi, src)
|
|
3420
|
-
|
|
3421
|
-
io.puts
|
|
3422
|
-
io.puts <<~C
|
|
3423
|
-
static VALUE
|
|
3424
|
-
#{name}_ki_native_#{src} (VALUE self, CArray *ca)
|
|
3425
|
-
{
|
|
3426
|
-
VALUE vout = rb_ca_template_with_type(self, INT2NUM(#{oi[:ca]}),
|
|
3427
|
-
INT2NUM(sizeof(#{oi[:c]})));
|
|
3428
|
-
CArray *co;
|
|
3429
|
-
GetCArray(vout, co);
|
|
3430
|
-
|
|
3431
|
-
/* All-axes slab = whole array as one K-D walk via CA_SLAB_AXES.
|
|
3432
|
-
CA_SLAB_WHOLE uses the flat next_slab path, which doesn't
|
|
3433
|
-
populate slab_dims; CA_SLAB_AXES with naxes == ndim is the
|
|
3434
|
-
K-D-walkable form. */
|
|
3435
|
-
int8_t slab_axes[CA_RANK_MAX];
|
|
3436
|
-
int8_t naxes = ca->ndim;
|
|
3437
|
-
for ( int8_t k = 0; k < naxes; k++ ) slab_axes[k] = k;
|
|
3438
|
-
|
|
3439
|
-
ca_iter_state st_in, st_out;
|
|
3440
|
-
int rc;
|
|
3441
|
-
rc = ca_iter_state_init_l2(&st_in, ca, CA_SLAB_AXES,
|
|
3442
|
-
slab_axes, naxes, 0);
|
|
3443
|
-
if ( rc != CA_ITER_OK ) {
|
|
3444
|
-
rb_raise(rb_eRuntimeError,
|
|
3445
|
-
"#{name}_ki: input init failed rc=%d", rc);
|
|
3446
|
-
}
|
|
3447
|
-
rc = ca_iter_state_init_l2(&st_out, co, CA_SLAB_AXES,
|
|
3448
|
-
slab_axes, naxes, CA_KERNEL_WRITE);
|
|
3449
|
-
if ( rc != CA_ITER_OK ) {
|
|
3450
|
-
ca_iter_state_finish(&st_in);
|
|
3451
|
-
rb_raise(rb_eRuntimeError,
|
|
3452
|
-
"#{name}_ki: output init failed rc=%d", rc);
|
|
3453
|
-
}
|
|
3454
|
-
|
|
3455
|
-
char *pi, *po;
|
|
3456
|
-
boolean8_t *mi, *mo;
|
|
3457
|
-
while ( ca_iter_state_next_slab_axes(&st_in, &pi, &mi) &&
|
|
3458
|
-
ca_iter_state_next_slab_axes(&st_out, &po, &mo) ) {
|
|
3459
|
-
CA_SLAB_MAP_T(#{si[:c]}, #{oi[:c]}, st_in, pi, st_out, po, #{expr});
|
|
3460
|
-
ca_iter_state_sync_slab(&st_out);
|
|
3461
|
-
}
|
|
3462
|
-
ca_iter_state_finish(&st_in);
|
|
3463
|
-
ca_iter_state_finish(&st_out);
|
|
3464
|
-
return vout;
|
|
3465
|
-
}
|
|
3466
|
-
C
|
|
3467
|
-
end
|
|
3468
|
-
|
|
3469
|
-
def self.emit_map_dispatch(io, k)
|
|
3470
|
-
name = k[:name]
|
|
3471
|
-
io.puts
|
|
3472
|
-
io.puts "static VALUE"
|
|
3473
|
-
io.puts "rb_ca_#{name}_ki (VALUE self)"
|
|
3474
|
-
io.puts "{"
|
|
3475
|
-
io.puts " CArray *src;"
|
|
3476
|
-
io.puts " GetCArray(self, src);"
|
|
3477
|
-
io.puts " switch ( src->data_type ) {"
|
|
3478
|
-
k[:source].each do |s|
|
|
3479
|
-
si = DTYPES[s]
|
|
3480
|
-
io.puts " case #{si[:ca]}: return #{name}_ki_native_#{s}(self, src);"
|
|
3481
|
-
end
|
|
3482
|
-
case k[:fallback]
|
|
3483
|
-
when :wrap_to_f64
|
|
3484
|
-
io.puts " default: {"
|
|
3485
|
-
io.puts " /* Phase E: only wrap data_types that have a meaningful float64"
|
|
3486
|
-
io.puts " representation. Complex / fixlen / object would lose"
|
|
3487
|
-
io.puts " structure (e.g. complex -> float strips imaginary part)"
|
|
3488
|
-
io.puts " so reject explicitly, matching legacy DataTypeError. */"
|
|
3489
|
-
io.puts " switch ( src->data_type ) {"
|
|
3490
|
-
io.puts " case CA_BOOLEAN: break; /* wrap: count-of-trues semantic */"
|
|
3491
|
-
io.puts " default:"
|
|
3492
|
-
io.puts %Q[ rb_raise(rb_eCADataTypeError, "#{name}_ki: source data_type :%s not supported (expected one of: #{k[:source].join(", ")}, or boolean)", ca_type_name[src->data_type]);]
|
|
3493
|
-
io.puts " }"
|
|
3494
|
-
io.puts " VALUE vsrc = rb_ca_wrap_readonly(self, INT2NUM(CA_FLOAT64));"
|
|
3495
|
-
io.puts " CArray *casted;"
|
|
3496
|
-
io.puts " GetCArray(vsrc, casted);"
|
|
3497
|
-
io.puts " return #{name}_ki_native_f64(vsrc, casted);"
|
|
3498
|
-
io.puts " }"
|
|
3499
|
-
when :raise
|
|
3500
|
-
io.puts " default:"
|
|
3501
|
-
io.puts %Q[ rb_raise(rb_eCADataTypeError, "#{name}_ki: source data_type :%s not supported (expected one of: #{k[:source].join(", ")})", ca_type_name[src->data_type]);]
|
|
3502
|
-
end
|
|
3503
|
-
io.puts " }"
|
|
3504
|
-
io.puts " return Qnil; /* unreachable */"
|
|
3505
|
-
io.puts "}"
|
|
3506
|
-
end
|
|
3507
|
-
|
|
3508
3791
|
# ---- scan emitter -----------------------------------------------------
|
|
3509
3792
|
|
|
3510
3793
|
def self.emit_scan(io, k)
|
|
3511
3794
|
io.puts
|
|
3512
3795
|
io.puts "/* ===== #{k[:name]}_ki ============================================ */"
|
|
3513
3796
|
k[:source].each do |src|
|
|
3514
|
-
|
|
3797
|
+
emit_native_for(io, k, src) { |o| emit_scan_native(o, k, src) }
|
|
3515
3798
|
end
|
|
3516
3799
|
emit_scan_dispatch(io, k)
|
|
3517
3800
|
end
|
|
@@ -3582,14 +3865,14 @@ module MkKernel
|
|
|
3582
3865
|
slab_axes, 1, 0);
|
|
3583
3866
|
if ( rc != CA_ITER_OK ) {
|
|
3584
3867
|
rb_raise(rb_eRuntimeError,
|
|
3585
|
-
"
|
|
3868
|
+
"%s: input init failed rc=%d", ca_calling_method_name(), rc);
|
|
3586
3869
|
}
|
|
3587
|
-
rc =
|
|
3870
|
+
rc = ca_iter_state_init_l2_paired(&st_out, &st_in, co, CA_SLAB_AXES,
|
|
3588
3871
|
slab_axes, 1, CA_KERNEL_WRITE);
|
|
3589
3872
|
if ( rc != CA_ITER_OK ) {
|
|
3590
3873
|
ca_iter_state_finish(&st_in);
|
|
3591
3874
|
rb_raise(rb_eRuntimeError,
|
|
3592
|
-
"
|
|
3875
|
+
"%s: output init failed rc=%d", ca_calling_method_name(), rc);
|
|
3593
3876
|
}
|
|
3594
3877
|
#{mask_setup}
|
|
3595
3878
|
char *pi, *po;
|
|
@@ -3614,16 +3897,38 @@ module MkKernel
|
|
|
3614
3897
|
# multi-axis scan is semantically ambiguous (flatten-and-cumsum vs
|
|
3615
3898
|
# 2D prefix sum); users must chain explicitly. Arity flips from 1
|
|
3616
3899
|
# (positional Integer axis) to -1 (kwarg dispatcher).
|
|
3617
|
-
|
|
3618
|
-
|
|
3900
|
+
if k[:face_gate]
|
|
3901
|
+
# The scan proper runs on the storage; the bound entry below gates the
|
|
3902
|
+
# Face and re-lifts the result.
|
|
3903
|
+
io.puts "static VALUE rb_ca_#{name}_ki_storage (int argc, VALUE *argv, VALUE self);"
|
|
3904
|
+
io.puts
|
|
3905
|
+
io.puts "static VALUE"
|
|
3906
|
+
io.puts "rb_ca_#{name}_ki (int argc, VALUE *argv, VALUE self)"
|
|
3907
|
+
io.puts "{"
|
|
3908
|
+
io.puts " CArray *src;"
|
|
3909
|
+
io.puts " VALUE result;"
|
|
3910
|
+
io.puts " GetCArray(self, src);"
|
|
3911
|
+
emit_reduce_face_gate(io, name, true)
|
|
3912
|
+
io.puts " result = rb_ca_#{name}_ki_storage(argc, argv, self);"
|
|
3913
|
+
emit_reduce_face_relift(io)
|
|
3914
|
+
io.puts " return result;"
|
|
3915
|
+
io.puts "}"
|
|
3916
|
+
io.puts
|
|
3917
|
+
io.puts "static VALUE"
|
|
3918
|
+
io.puts "rb_ca_#{name}_ki_storage (int argc, VALUE *argv, VALUE self)"
|
|
3919
|
+
else
|
|
3920
|
+
io.puts "static VALUE"
|
|
3921
|
+
io.puts "rb_ca_#{name}_ki (int argc, VALUE *argv, VALUE self)"
|
|
3922
|
+
end
|
|
3619
3923
|
io.puts "{"
|
|
3620
3924
|
io.puts " CArray *src;"
|
|
3621
3925
|
io.puts " GetCArray(self, src);"
|
|
3926
|
+
emit_lazy_evaluated(io)
|
|
3622
3927
|
io.puts " volatile VALUE ropt = rb_pop_options(&argc, &argv);"
|
|
3623
3928
|
io.puts " volatile VALUE raxis = Qnil;"
|
|
3624
3929
|
io.puts " rb_scan_options(ropt, \"axis\", &raxis);"
|
|
3625
3930
|
io.puts " if ( argc > 0 ) {"
|
|
3626
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
3931
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: positional axis is no longer accepted (got %d); use axis: kwarg, e.g. a.#{name}(axis: 0)", ca_calling_method_name(), argc);]
|
|
3627
3932
|
io.puts " }"
|
|
3628
3933
|
if k[:axis_default] == :flatten
|
|
3629
3934
|
# Legacy compat: nil/omitted axis: flatten source and scan axis 0.
|
|
@@ -3635,7 +3940,7 @@ module MkKernel
|
|
|
3635
3940
|
io.puts " switch ( fsrc->data_type ) {"
|
|
3636
3941
|
k[:source].each do |s|
|
|
3637
3942
|
si = DTYPES[s]
|
|
3638
|
-
io.puts " case #{si[:ca]}:
|
|
3943
|
+
io.puts " case #{si[:ca]}: CA_RETURN_KEEPING(vflat, #{name}_ki_native_#{s}(vflat, fsrc, 0));"
|
|
3639
3944
|
end
|
|
3640
3945
|
case k[:fallback]
|
|
3641
3946
|
when :wrap_to_f64
|
|
@@ -3643,34 +3948,35 @@ module MkKernel
|
|
|
3643
3948
|
io.puts " VALUE vsrc = rb_ca_wrap_readonly(vflat, INT2NUM(CA_FLOAT64));"
|
|
3644
3949
|
io.puts " CArray *casted;"
|
|
3645
3950
|
io.puts " GetCArray(vsrc, casted);"
|
|
3646
|
-
io.puts "
|
|
3951
|
+
io.puts " CA_RETURN_KEEPING(vsrc, #{name}_ki_native_f64(vsrc, casted, 0));"
|
|
3647
3952
|
io.puts " }"
|
|
3648
3953
|
when :raise
|
|
3649
3954
|
io.puts " default:"
|
|
3650
|
-
io.puts %Q[
|
|
3955
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
3956
|
+
io.puts %Q[ rb_raise(rb_eCADataTypeError, "%s: source data_type :%s not supported (expected one of: #{MkKernel.type_names(k[:source])})", ca_calling_method_name(), ca_type_name[fsrc->data_type]);]
|
|
3957
|
+
when :mask_count
|
|
3958
|
+
io.puts " default:"
|
|
3959
|
+
io.puts " return rb_ca_scan_mask_count(self, Qnil);"
|
|
3651
3960
|
end
|
|
3652
3961
|
io.puts " }"
|
|
3653
3962
|
io.puts " }"
|
|
3654
3963
|
else
|
|
3655
3964
|
io.puts " if ( NIL_P(raxis) ) {"
|
|
3656
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
3965
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: axis: is required (a single Integer; a scan over several axes is ambiguous)", ca_calling_method_name());]
|
|
3657
3966
|
io.puts " }"
|
|
3658
3967
|
end
|
|
3659
3968
|
io.puts " if ( TYPE(raxis) == T_ARRAY ) {"
|
|
3660
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
3969
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: axis must be a single Integer (got Array); a scan over several axes is ambiguous, chain explicitly: a.#{name}(axis: 0).#{name}(axis: 1)", ca_calling_method_name());]
|
|
3661
3970
|
io.puts " }"
|
|
3662
|
-
io.puts "
|
|
3663
|
-
io.puts %Q[ rb_raise(rb_eTypeError, "#{name}_ki: axis: must be Integer (got %"PRIsVALUE")", rb_obj_class(raxis));]
|
|
3664
|
-
io.puts " }"
|
|
3665
|
-
io.puts " int axis = NUM2INT(raxis);"
|
|
3971
|
+
io.puts " int axis = (int) ca_axis_integer(raxis, NULL);"
|
|
3666
3972
|
io.puts " if ( axis < 0 ) axis += src->ndim;"
|
|
3667
3973
|
io.puts " if ( axis < 0 || axis >= src->ndim ) {"
|
|
3668
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
3974
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: axis %d out of range for ndim %d", ca_calling_method_name(), NUM2INT(raxis), src->ndim);]
|
|
3669
3975
|
io.puts " }"
|
|
3670
3976
|
io.puts " switch ( src->data_type ) {"
|
|
3671
3977
|
k[:source].each do |s|
|
|
3672
3978
|
si = DTYPES[s]
|
|
3673
|
-
io.puts " case #{si[:ca]}:
|
|
3979
|
+
io.puts " case #{si[:ca]}: CA_RETURN_KEEPING(self, #{name}_ki_native_#{s}(self, src, axis));"
|
|
3674
3980
|
end
|
|
3675
3981
|
case k[:fallback]
|
|
3676
3982
|
when :wrap_to_f64
|
|
@@ -3678,11 +3984,15 @@ module MkKernel
|
|
|
3678
3984
|
io.puts " VALUE vsrc = rb_ca_wrap_readonly(self, INT2NUM(CA_FLOAT64));"
|
|
3679
3985
|
io.puts " CArray *casted;"
|
|
3680
3986
|
io.puts " GetCArray(vsrc, casted);"
|
|
3681
|
-
io.puts "
|
|
3987
|
+
io.puts " CA_RETURN_KEEPING(vsrc, #{name}_ki_native_f64(vsrc, casted, axis));"
|
|
3682
3988
|
io.puts " }"
|
|
3683
3989
|
when :raise
|
|
3684
3990
|
io.puts " default:"
|
|
3685
|
-
io.puts %Q[
|
|
3991
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
3992
|
+
io.puts %Q[ rb_raise(rb_eCADataTypeError, "%s: source data_type :%s not supported (expected one of: #{MkKernel.type_names(k[:source])})", ca_calling_method_name(), ca_type_name[src->data_type]);]
|
|
3993
|
+
when :mask_count
|
|
3994
|
+
io.puts " default:"
|
|
3995
|
+
io.puts " return rb_ca_scan_mask_count(self, INT2NUM(axis));"
|
|
3686
3996
|
end
|
|
3687
3997
|
io.puts " }"
|
|
3688
3998
|
io.puts " return Qnil; /* unreachable */"
|
|
@@ -3697,7 +4007,7 @@ module MkKernel
|
|
|
3697
4007
|
k[:source].each do |src|
|
|
3698
4008
|
emit_sort_cmp(io, k, src)
|
|
3699
4009
|
emit_sort_quickselect(io, k, src) if k[:algorithm] == :partition
|
|
3700
|
-
|
|
4010
|
+
emit_native_for(io, k, src) { |o| emit_sort_native(o, k, src) }
|
|
3701
4011
|
end
|
|
3702
4012
|
emit_sort_dispatch(io, k)
|
|
3703
4013
|
end
|
|
@@ -3829,14 +4139,21 @@ module MkKernel
|
|
|
3829
4139
|
# String, custom class with <=> defined) works. Raises Ruby
|
|
3830
4140
|
# TypeError mid-sort if a cell pair is uncomparable -- consistent
|
|
3831
4141
|
# with Array#sort behavior.
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
io.puts
|
|
3835
|
-
io.puts "
|
|
4142
|
+
# A Float NaN sorts after every other value, as it does in a float
|
|
4143
|
+
# array; two NaNs fall to the index tie-break.
|
|
4144
|
+
io.puts " int nan_a = CA_OBJ_ISNAN(pa->v);"
|
|
4145
|
+
io.puts " int nan_b = CA_OBJ_ISNAN(pb->v);"
|
|
4146
|
+
io.puts " if ( nan_a && !nan_b ) return 1;"
|
|
4147
|
+
io.puts " if ( nan_b && !nan_a ) return -1;"
|
|
4148
|
+
io.puts " if ( !nan_a ) {"
|
|
4149
|
+
io.puts %Q{ VALUE r = rb_funcall(pa->v, rb_intern("<=>"), 1, pb->v);}
|
|
4150
|
+
io.puts " if ( NIL_P(r) ) {"
|
|
4151
|
+
io.puts " ca_obj_compare_refused(pa->v, pb->v);"
|
|
4152
|
+
io.puts " }"
|
|
4153
|
+
io.puts " int c = NUM2INT(r);"
|
|
4154
|
+
io.puts " if ( c > 0 ) return 1;"
|
|
4155
|
+
io.puts " if ( c < 0 ) return -1;"
|
|
3836
4156
|
io.puts " }"
|
|
3837
|
-
io.puts " int c = NUM2INT(r);"
|
|
3838
|
-
io.puts " if ( c > 0 ) return 1;"
|
|
3839
|
-
io.puts " if ( c < 0 ) return -1;"
|
|
3840
4157
|
else
|
|
3841
4158
|
if is_fp
|
|
3842
4159
|
# NaN policy :end -- NaN sorts to the end (matches qcmp_f_type in
|
|
@@ -3967,19 +4284,32 @@ module MkKernel
|
|
|
3967
4284
|
io.puts " rc = ca_iter_state_init_l2(&st_in, ca, CA_SLAB_AXES,"
|
|
3968
4285
|
io.puts " slab_axes, 1, 0);"
|
|
3969
4286
|
io.puts " if ( rc != CA_ITER_OK ) {"
|
|
3970
|
-
io.puts %Q[ rb_raise(rb_eRuntimeError, "
|
|
4287
|
+
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: input init failed rc=%d", ca_calling_method_name(), rc);]
|
|
3971
4288
|
io.puts " }"
|
|
3972
|
-
io.puts " rc =
|
|
4289
|
+
io.puts " rc = ca_iter_state_init_l2_paired(&st_out, &st_in, co, CA_SLAB_AXES,"
|
|
3973
4290
|
io.puts " slab_axes, 1, CA_KERNEL_WRITE);"
|
|
3974
4291
|
io.puts " if ( rc != CA_ITER_OK ) {"
|
|
3975
4292
|
io.puts " ca_iter_state_finish(&st_in);"
|
|
3976
|
-
io.puts %Q[ rb_raise(rb_eRuntimeError, "
|
|
4293
|
+
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: output init failed rc=%d", ca_calling_method_name(), rc);]
|
|
3977
4294
|
io.puts " }"
|
|
3978
4295
|
io.puts
|
|
3979
4296
|
io.puts " ca_size_t fiber_n = st_in.slab_elements;"
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
4297
|
+
if src == :object
|
|
4298
|
+
# The object comparator calls <=>, so the sort can raise part way.
|
|
4299
|
+
# ALLOCV frees these buffers whether the function returns or unwinds.
|
|
4300
|
+
# A full sort also needs aux: ca_sort_merge_pair_object is a mergesort.
|
|
4301
|
+
io.puts " volatile VALUE hbuf = 0, haux = 0;"
|
|
4302
|
+
io.puts " #{name}_pair_#{src} *buf = ALLOCV_N(#{name}_pair_#{src}, hbuf, fiber_n);"
|
|
4303
|
+
if partition
|
|
4304
|
+
io.puts " (void) haux;"
|
|
4305
|
+
else
|
|
4306
|
+
io.puts " #{name}_pair_#{src} *aux = ALLOCV_N(#{name}_pair_#{src}, haux, fiber_n);"
|
|
4307
|
+
end
|
|
4308
|
+
else
|
|
4309
|
+
io.puts " #{name}_pair_#{src} *buf ="
|
|
4310
|
+
io.puts " xmalloc(sizeof(#{name}_pair_#{src}) * (size_t) fiber_n);"
|
|
4311
|
+
end
|
|
4312
|
+
if has_kind && src != :object
|
|
3983
4313
|
# PROPOSAL_PORTABLE_TEXTBOOK_SORT §9.5.2 Option γ: aux pair buffer
|
|
3984
4314
|
# for ca_sort_merge_pair_<src> (= ping-pong bottom-up mergesort).
|
|
3985
4315
|
# Allocated once outside the fiber loop, reused across fibers.
|
|
@@ -4124,16 +4454,15 @@ module MkKernel
|
|
|
4124
4454
|
io.puts " */"
|
|
4125
4455
|
end
|
|
4126
4456
|
if is_object
|
|
4127
|
-
#
|
|
4128
|
-
#
|
|
4129
|
-
#
|
|
4130
|
-
#
|
|
4131
|
-
#
|
|
4132
|
-
#
|
|
4133
|
-
#
|
|
4134
|
-
|
|
4135
|
-
io.puts " (
|
|
4136
|
-
io.puts " qsort(#{buf_expr}, (size_t) #{sort_n_expr}, sizeof(#{name}_pair_#{src}), #{name}_cmp_#{src});"
|
|
4457
|
+
# CA_OBJECT sorts with the emitted _cmp_object (rb_funcall(<=>)
|
|
4458
|
+
# based) through ca_sort_merge_pair_object rather than libc qsort: the
|
|
4459
|
+
# comparator raises on a pair that does not compare, and the raise
|
|
4460
|
+
# must not unwind through libc's frames. The cmp tie-breaks on the
|
|
4461
|
+
# original index, so the order is the same for both kinds, and
|
|
4462
|
+
# do_stable is ignored. NaN partition is skipped (Float#<=> answers
|
|
4463
|
+
# nil for NaN, which the comparator reports as ArgumentError).
|
|
4464
|
+
io.puts " (void) do_stable; /* CA_OBJECT: one stable sort serves both kinds */"
|
|
4465
|
+
io.puts " ca_sort_merge_pair_object((ca_pair_object *) #{buf_expr}, (ca_pair_object *) aux, (ca_size_t) #{sort_n_expr}, #{name}_cmp_#{src});"
|
|
4137
4466
|
elsif is_fixlen
|
|
4138
4467
|
io.puts " /* CA_FIXLEN: libc qsort with the memcmp comparator. qsort isn't"
|
|
4139
4468
|
io.puts " stable, but #{name}_cmp_#{src} tie-breaks on the original index,"
|
|
@@ -4180,7 +4509,7 @@ module MkKernel
|
|
|
4180
4509
|
io.puts " if ( r > 0 ) {"
|
|
4181
4510
|
io.puts " if ( dense ) {"
|
|
4182
4511
|
if is_object
|
|
4183
|
-
io.puts " if ( !
|
|
4512
|
+
io.puts " if ( ! ca_obj_equal(buf[r].v, buf[r - 1].v) ) out_r++;"
|
|
4184
4513
|
elsif is_fixlen
|
|
4185
4514
|
io.puts " if ( memcmp(buf[r].vp, buf[r - 1].vp, (size_t) buf[r].nb) != 0 ) out_r++;"
|
|
4186
4515
|
else
|
|
@@ -4226,9 +4555,14 @@ module MkKernel
|
|
|
4226
4555
|
end
|
|
4227
4556
|
|
|
4228
4557
|
io.puts " }"
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
io.puts "
|
|
4558
|
+
if src == :object
|
|
4559
|
+
io.puts " ALLOCV_END(hbuf);"
|
|
4560
|
+
io.puts " ALLOCV_END(haux);"
|
|
4561
|
+
else
|
|
4562
|
+
io.puts " xfree(buf);"
|
|
4563
|
+
if has_kind
|
|
4564
|
+
io.puts " if ( aux ) xfree(aux);"
|
|
4565
|
+
end
|
|
4232
4566
|
end
|
|
4233
4567
|
io.puts " ca_iter_state_finish(&st_in);"
|
|
4234
4568
|
io.puts " ca_iter_state_finish(&st_out);"
|
|
@@ -4250,10 +4584,7 @@ module MkKernel
|
|
|
4250
4584
|
io.puts " CArray *_sc;"
|
|
4251
4585
|
io.puts " GetCArray(_st, _sc);"
|
|
4252
4586
|
io.puts " if ( _sc->data_type != CA_FIXLEN && ! _orderable ) {"
|
|
4253
|
-
io.puts %Q[
|
|
4254
|
-
io.puts %Q[ "#{name}_ki: Face-typed input (%s) is not orderable "]
|
|
4255
|
-
io.puts %Q[ "by storage; use ca.parent to descend to storage",]
|
|
4256
|
-
io.puts %Q[ rb_obj_classname(_face));]
|
|
4587
|
+
io.puts %Q[ ca_face_refuse_order(ca_calling_method_name(), _face, "order");]
|
|
4257
4588
|
io.puts " }"
|
|
4258
4589
|
io.puts " self = _st;"
|
|
4259
4590
|
io.puts " src = _sc;"
|
|
@@ -4276,10 +4607,7 @@ module MkKernel
|
|
|
4276
4607
|
io.puts " CArray *_sc;"
|
|
4277
4608
|
io.puts " GetCArray(_st, _sc);"
|
|
4278
4609
|
io.puts " if ( _sc->data_type != CA_FIXLEN && ! _orderable ) {"
|
|
4279
|
-
io.puts %Q[
|
|
4280
|
-
io.puts %Q[ "#{name}_ki: Face-typed input (%s) is not orderable "]
|
|
4281
|
-
io.puts %Q[ "by storage; use ca.parent to descend to storage",]
|
|
4282
|
-
io.puts %Q[ rb_obj_classname(_face));]
|
|
4610
|
+
io.puts %Q[ ca_face_refuse_order(ca_calling_method_name(), _face, "order");]
|
|
4283
4611
|
io.puts " }"
|
|
4284
4612
|
if relift
|
|
4285
4613
|
io.puts " _reduce_face = _face;"
|
|
@@ -4290,32 +4618,37 @@ module MkKernel
|
|
|
4290
4618
|
end
|
|
4291
4619
|
|
|
4292
4620
|
# Re-lift a value-returning reduction result back into the Face captured by
|
|
4293
|
-
# emit_reduce_face_gate. Full-reduction scalar ->
|
|
4294
|
-
# per-axis CArray ->
|
|
4621
|
+
# emit_reduce_face_gate. Full-reduction scalar -> storage_to_element hook;
|
|
4622
|
+
# per-axis CArray -> ca_face_relift_entity (tail, mask and copy_state).
|
|
4295
4623
|
# All-masked full reductions leave CA_UNDEF untouched.
|
|
4296
4624
|
def self.emit_reduce_face_relift(io)
|
|
4297
|
-
io.puts " if ( _reduce_face != Qnil && result
|
|
4298
|
-
io.puts "
|
|
4299
|
-
io.puts "
|
|
4300
|
-
io.puts "
|
|
4301
|
-
io.puts "
|
|
4302
|
-
io.puts "
|
|
4303
|
-
io.puts "
|
|
4304
|
-
io.puts "
|
|
4305
|
-
io.puts " if ( ca_has_mask(_rca) ) {"
|
|
4306
|
-
io.puts " /* rb_ca_face_template resets mask to NULL; carry the reduced"
|
|
4307
|
-
io.puts " mask bits (all-masked slabs) onto the lifted Face. */"
|
|
4308
|
-
io.puts " CArray *_lca;"
|
|
4309
|
-
io.puts " GetCArray(_lifted, _lca);"
|
|
4310
|
-
io.puts " ca_copy_mask(_lca, _rca);"
|
|
4625
|
+
io.puts " if ( _reduce_face != Qnil && RB_TYPE_P(result, T_ARRAY) ) {"
|
|
4626
|
+
io.puts " /* a multi-output reduction (minmax): re-lift each output. */"
|
|
4627
|
+
io.puts " long _i;"
|
|
4628
|
+
io.puts " for ( _i = 0; _i < RARRAY_LEN(result); _i++ ) {"
|
|
4629
|
+
io.puts " VALUE _r = rb_ary_entry(result, _i);"
|
|
4630
|
+
io.puts " if ( _r == CA_UNDEF || _r == Qnil ) continue;"
|
|
4631
|
+
io.puts " if ( rb_obj_is_kind_of(_r, rb_cCArray) ) {"
|
|
4632
|
+
io.puts " _r = ca_face_relift_entity(_reduce_face, _r);"
|
|
4311
4633
|
io.puts " }"
|
|
4312
|
-
io.puts "
|
|
4634
|
+
io.puts " else {"
|
|
4635
|
+
io.puts " CArray *_fca;"
|
|
4636
|
+
io.puts " GetCArray(_reduce_face, _fca);"
|
|
4637
|
+
io.puts " CA_FACE_STORAGE_TO_ELEMENT_IF_FACE(_r, _reduce_face, _fca);"
|
|
4638
|
+
io.puts " }"
|
|
4639
|
+
io.puts " rb_ary_store(result, _i, _r);"
|
|
4640
|
+
io.puts " }"
|
|
4641
|
+
io.puts " }"
|
|
4642
|
+
io.puts " else if ( _reduce_face != Qnil && result != CA_UNDEF && result != Qnil ) {"
|
|
4643
|
+
io.puts " if ( rb_obj_is_kind_of(result, rb_cCArray) ) {"
|
|
4644
|
+
io.puts " /* per-axis: re-lift the storage result as a Face of the same class. */"
|
|
4645
|
+
io.puts " result = ca_face_relift_entity(_reduce_face, result);"
|
|
4313
4646
|
io.puts " }"
|
|
4314
4647
|
io.puts " else {"
|
|
4315
4648
|
io.puts " /* full reduction: decode the scalar via the registered hook. */"
|
|
4316
4649
|
io.puts " CArray *_fca;"
|
|
4317
4650
|
io.puts " GetCArray(_reduce_face, _fca);"
|
|
4318
|
-
io.puts "
|
|
4651
|
+
io.puts " CA_FACE_STORAGE_TO_ELEMENT_IF_FACE(result, _reduce_face, _fca);"
|
|
4319
4652
|
io.puts " }"
|
|
4320
4653
|
io.puts " }"
|
|
4321
4654
|
end
|
|
@@ -4327,7 +4660,7 @@ module MkKernel
|
|
|
4327
4660
|
has_kind = !partition # :full and :rank gain kind: dispatch (Option γ)
|
|
4328
4661
|
mask_sentinel = (k[:mask_self] == :sentinel)
|
|
4329
4662
|
# bind_ruby: false kernels are consumed by other .c files at the C
|
|
4330
|
-
# level (
|
|
4663
|
+
# level (carray_sort.c / carray_partition.c call the _mp twins).
|
|
4331
4664
|
# Emit extern linkage so the symbol is reachable across translation
|
|
4332
4665
|
# units. bind_ruby: true kernels stay static (only referenced by
|
|
4333
4666
|
# Init_carray_kernels in this file).
|
|
@@ -4339,6 +4672,10 @@ module MkKernel
|
|
|
4339
4672
|
# partition_index / rank_index, which are consumed at the C level
|
|
4340
4673
|
# by carray_order.c (= ordering helpers like nlargest, order).
|
|
4341
4674
|
storage = (k[:bind_ruby] == false || k[:c_callable]) ? "" : "static "
|
|
4675
|
+
# bind_ruby: false + mask_self: :sentinel kernels are reached only
|
|
4676
|
+
# through their _mp twins, so the fixed-masked_last entries below
|
|
4677
|
+
# would have no caller.
|
|
4678
|
+
mp_only = (k[:mask_self] == :sentinel && k[:bind_ruby] == false)
|
|
4342
4679
|
# PROPOSAL_PORTABLE_TEXTBOOK_SORT §9.5.2 Option γ:
|
|
4343
4680
|
# For algorithm :full / :rank, dispatch emits THREE C-level entries:
|
|
4344
4681
|
# rb_ca_<name>_ki_quick (self, vaxis) -> do_stable=0
|
|
@@ -4375,6 +4712,7 @@ module MkKernel
|
|
|
4375
4712
|
io.puts "{"
|
|
4376
4713
|
io.puts " CArray *src;"
|
|
4377
4714
|
io.puts " GetCArray(self, src);"
|
|
4715
|
+
emit_lazy_evaluated(io)
|
|
4378
4716
|
io.puts " /* Face gate: descend to storage (fixlen -> memcmp default,"
|
|
4379
4717
|
io.puts " numeric -> ORDERABLE required). The strip also makes the"
|
|
4380
4718
|
io.puts " switch below and every index/addr template output build on"
|
|
@@ -4384,23 +4722,24 @@ module MkKernel
|
|
|
4384
4722
|
io.puts " /* sort family rejects masked sources globally (R3 / Q3): "
|
|
4385
4723
|
io.puts " masked elements would break the ordering invariant. */"
|
|
4386
4724
|
io.puts " if ( ca_has_mask(src) ) {"
|
|
4387
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
4725
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: masked input not supported (use ca.value or ca.strip_mask(fill))", ca_calling_method_name());]
|
|
4388
4726
|
io.puts " }"
|
|
4389
4727
|
end
|
|
4390
|
-
io.puts " int axis =
|
|
4728
|
+
io.puts " int axis = (int) ca_axis_integer(vaxis, NULL);"
|
|
4391
4729
|
io.puts " if ( axis < 0 ) axis += src->ndim;"
|
|
4392
4730
|
io.puts " if ( axis < 0 || axis >= src->ndim ) {"
|
|
4393
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
4731
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: axis %d out of range for ndim %d", ca_calling_method_name(), NUM2INT(vaxis), src->ndim);]
|
|
4394
4732
|
io.puts " }"
|
|
4395
4733
|
io.puts " switch ( src->data_type ) {"
|
|
4396
4734
|
k[:source].each do |s|
|
|
4397
4735
|
si = DTYPES[s]
|
|
4398
4736
|
native_extra = mask_sentinel ? ", masked_last" : ""
|
|
4399
4737
|
native_extra += rank ? ", dense" : ""
|
|
4400
|
-
io.puts " case #{si[:ca]}:
|
|
4738
|
+
io.puts " case #{si[:ca]}: CA_RETURN_KEEPING(self, #{name}_ki_native_#{s}(self, src, axis, do_stable#{native_extra}));"
|
|
4401
4739
|
end
|
|
4402
4740
|
io.puts " default:"
|
|
4403
|
-
io.puts %Q[
|
|
4741
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
4742
|
+
io.puts %Q[ rb_raise(rb_eCADataTypeError, "%s: source data_type :%s not supported (expected one of: #{MkKernel.type_names(k[:source])})", ca_calling_method_name(), ca_type_name[src->data_type]);]
|
|
4404
4743
|
io.puts " }"
|
|
4405
4744
|
io.puts " return Qnil; /* unreachable */"
|
|
4406
4745
|
io.puts "}"
|
|
@@ -4413,30 +4752,33 @@ module MkKernel
|
|
|
4413
4752
|
dispatch_call_last = mask_sentinel ? ", 1" : ""
|
|
4414
4753
|
dispatch_call_last += rank ? ", 0" : ""
|
|
4415
4754
|
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4755
|
+
unless mp_only
|
|
4756
|
+
# _ki_quick: arity 2 (self, vaxis). Linkage = storage.
|
|
4757
|
+
io.puts
|
|
4758
|
+
io.puts "#{storage}VALUE"
|
|
4759
|
+
io.puts "rb_ca_#{name}_ki_quick (VALUE self, VALUE vaxis)"
|
|
4760
|
+
io.puts "{"
|
|
4761
|
+
io.puts " return #{name}_ki_dispatch(self, vaxis, 0#{dispatch_call_last});"
|
|
4762
|
+
io.puts "}"
|
|
4423
4763
|
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4764
|
+
# _ki_stable: arity 2 (self, vaxis). Linkage = storage.
|
|
4765
|
+
io.puts
|
|
4766
|
+
io.puts "#{storage}VALUE"
|
|
4767
|
+
io.puts "rb_ca_#{name}_ki_stable (VALUE self, VALUE vaxis)"
|
|
4768
|
+
io.puts "{"
|
|
4769
|
+
io.puts " return #{name}_ki_dispatch(self, vaxis, 1#{dispatch_call_last});"
|
|
4770
|
+
io.puts "}"
|
|
4431
4771
|
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4772
|
+
# _ki: arity 2 alias of _ki_quick (= preserves existing arity-1
|
|
4773
|
+
# Ruby binding and existing C extern callers).
|
|
4774
|
+
io.puts
|
|
4775
|
+
io.puts "#{storage}VALUE"
|
|
4776
|
+
io.puts "rb_ca_#{name}_ki (VALUE self, VALUE vaxis)"
|
|
4777
|
+
io.puts "{"
|
|
4778
|
+
io.puts " return rb_ca_#{name}_ki_quick(self, vaxis);"
|
|
4779
|
+
io.puts "}"
|
|
4780
|
+
|
|
4781
|
+
end
|
|
4440
4782
|
|
|
4441
4783
|
if mask_sentinel && k[:bind_ruby] == false
|
|
4442
4784
|
# _mp twins: explicit masked_last, non-Ruby (plain C int, no
|
|
@@ -4458,16 +4800,16 @@ module MkKernel
|
|
|
4458
4800
|
end
|
|
4459
4801
|
|
|
4460
4802
|
if rank
|
|
4461
|
-
#
|
|
4462
|
-
# carray_order.c's `order`
|
|
4463
|
-
# sugar over rank_index, defined in a different file, so it needs
|
|
4803
|
+
# _kind_dense: explicit kind (0 :quick / 1 :stable) and dense
|
|
4804
|
+
# (0 :ordinal / 1 :dense), extern for carray_order.c's `order`
|
|
4805
|
+
# (sugar over rank_index, defined in a different file, so it needs
|
|
4464
4806
|
# a non-static entry point the same way the mask_self: :sentinel
|
|
4465
4807
|
# kernels' _mp twins do).
|
|
4466
4808
|
io.puts
|
|
4467
4809
|
io.puts "VALUE"
|
|
4468
|
-
io.puts "rb_ca_#{name}
|
|
4810
|
+
io.puts "rb_ca_#{name}_ki_kind_dense (VALUE self, VALUE vaxis, int stable, int dense)"
|
|
4469
4811
|
io.puts "{"
|
|
4470
|
-
io.puts " return #{name}_ki_dispatch(self, vaxis,
|
|
4812
|
+
io.puts " return #{name}_ki_dispatch(self, vaxis, stable, dense);"
|
|
4471
4813
|
io.puts "}"
|
|
4472
4814
|
end
|
|
4473
4815
|
else
|
|
@@ -4482,6 +4824,7 @@ module MkKernel
|
|
|
4482
4824
|
io.puts "{"
|
|
4483
4825
|
io.puts " CArray *src;"
|
|
4484
4826
|
io.puts " GetCArray(self, src);"
|
|
4827
|
+
emit_lazy_evaluated(io)
|
|
4485
4828
|
io.puts " /* Face gate (partition): same as the sort dispatcher --"
|
|
4486
4829
|
io.puts " fixlen -> memcmp default, numeric -> ORDERABLE required. */"
|
|
4487
4830
|
emit_sort_face_gate(io, name)
|
|
@@ -4489,38 +4832,42 @@ module MkKernel
|
|
|
4489
4832
|
io.puts " /* sort family rejects masked sources globally (R3 / Q3): "
|
|
4490
4833
|
io.puts " masked elements would break the ordering invariant. */"
|
|
4491
4834
|
io.puts " if ( ca_has_mask(src) ) {"
|
|
4492
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
4835
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: masked input not supported (use ca.value or ca.strip_mask(fill))", ca_calling_method_name());]
|
|
4493
4836
|
io.puts " }"
|
|
4494
4837
|
end
|
|
4495
|
-
io.puts " int axis =
|
|
4838
|
+
io.puts " int axis = (int) ca_axis_integer(vaxis, NULL);"
|
|
4496
4839
|
io.puts " if ( axis < 0 ) axis += src->ndim;"
|
|
4497
4840
|
io.puts " if ( axis < 0 || axis >= src->ndim ) {"
|
|
4498
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
4841
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: axis %d out of range for ndim %d", ca_calling_method_name(), NUM2INT(vaxis), src->ndim);]
|
|
4499
4842
|
io.puts " }"
|
|
4500
|
-
io.puts " ca_size_t
|
|
4843
|
+
io.puts " ca_size_t kth_raw = (ca_size_t) ca_integer_arg(vkth, \"kth\", NULL);"
|
|
4844
|
+
io.puts " ca_size_t kth = kth_raw;"
|
|
4501
4845
|
io.puts " ca_size_t axis_n = src->dim[axis];"
|
|
4502
4846
|
io.puts " if ( kth < 0 ) kth += axis_n;"
|
|
4503
4847
|
io.puts " if ( kth < 0 || kth >= axis_n ) {"
|
|
4504
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
4848
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: kth %lld out of range for length %lld", ca_calling_method_name(), (long long) kth_raw, (long long) axis_n);]
|
|
4505
4849
|
io.puts " }"
|
|
4506
4850
|
io.puts " switch ( src->data_type ) {"
|
|
4507
4851
|
k[:source].each do |s|
|
|
4508
4852
|
si = DTYPES[s]
|
|
4509
4853
|
native_extra = mask_sentinel ? ", masked_last" : ""
|
|
4510
|
-
io.puts " case #{si[:ca]}:
|
|
4854
|
+
io.puts " case #{si[:ca]}: CA_RETURN_KEEPING(self, #{name}_ki_native_#{s}(self, src, axis, kth#{native_extra}));"
|
|
4511
4855
|
end
|
|
4512
4856
|
io.puts " default:"
|
|
4513
|
-
io.puts %Q[
|
|
4857
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
4858
|
+
io.puts %Q[ rb_raise(rb_eCADataTypeError, "%s: source data_type :%s not supported (expected one of: #{MkKernel.type_names(k[:source])})", ca_calling_method_name(), ca_type_name[src->data_type]);]
|
|
4514
4859
|
io.puts " }"
|
|
4515
4860
|
io.puts " return Qnil; /* unreachable */"
|
|
4516
4861
|
io.puts "}"
|
|
4517
4862
|
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4863
|
+
unless mp_only
|
|
4864
|
+
io.puts
|
|
4865
|
+
io.puts "#{storage}VALUE"
|
|
4866
|
+
io.puts "rb_ca_#{name}_ki (VALUE self, VALUE vaxis, VALUE vkth)"
|
|
4867
|
+
io.puts "{"
|
|
4868
|
+
io.puts " return #{name}_ki_dispatch(self, vaxis, vkth#{mask_sentinel ? ", 1" : ""});"
|
|
4869
|
+
io.puts "}"
|
|
4870
|
+
end
|
|
4524
4871
|
|
|
4525
4872
|
if mask_sentinel && k[:bind_ruby] == false
|
|
4526
4873
|
# _mp twin: explicit masked_last, extern for carray_partition.c's
|
|
@@ -4538,10 +4885,10 @@ module MkKernel
|
|
|
4538
4885
|
# wrapper). When public_method: is set, emit a -1-argc
|
|
4539
4886
|
# entry that parses `axis:` (and `kind:` for has_kind, and
|
|
4540
4887
|
# `masked_position:` for mask_self: :sentinel, and `method:` for
|
|
4541
|
-
# :rank) kwarg with default
|
|
4542
|
-
# dispatches to the appropriate positional entry.
|
|
4543
|
-
# algorithm, kth is accepted as a single positional
|
|
4544
|
-
# before the kwarg.
|
|
4888
|
+
# :rank) kwarg with default nil (= whole array) / :quick / :last /
|
|
4889
|
+
# :ordinal and dispatches to the appropriate positional entry.
|
|
4890
|
+
# For :partition algorithm, kth is accepted as a single positional
|
|
4891
|
+
# argument before the kwarg.
|
|
4545
4892
|
if k[:public_method]
|
|
4546
4893
|
legacy_name = k[:public_method].to_s
|
|
4547
4894
|
io.puts
|
|
@@ -4568,7 +4915,21 @@ module MkKernel
|
|
|
4568
4915
|
scan_targets << "&rmasked_position" if mask_sentinel
|
|
4569
4916
|
scan_targets << "&rmethod" if rank
|
|
4570
4917
|
io.puts " rb_scan_options(ropt, \"#{scan_keys.join(",")}\", #{scan_targets.join(", ")});"
|
|
4571
|
-
|
|
4918
|
+
# No axis means the whole array, as for sort: flatten and walk the
|
|
4919
|
+
# single fiber. rank_index keeps self's shape (a rank belongs to a
|
|
4920
|
+
# cell, as for order); the position lists keep the flat shape.
|
|
4921
|
+
io.puts " volatile VALUE whole_src = Qnil;" if rank
|
|
4922
|
+
io.puts " if ( NIL_P(raxis) ) { /* no axis: the whole array */"
|
|
4923
|
+
io.puts " whole_src = self;" if rank
|
|
4924
|
+
# The flattened view of a lazy expression is not one itself, so the
|
|
4925
|
+
# dispatcher would not see an expression to hand over: ask here.
|
|
4926
|
+
io.puts " {"
|
|
4927
|
+
io.puts " volatile VALUE evaluated = ca_lazy_evaluated(self);"
|
|
4928
|
+
io.puts " if ( ! NIL_P(evaluated) ) self = evaluated;"
|
|
4929
|
+
io.puts " }"
|
|
4930
|
+
io.puts " self = rb_ca_flatten(self);"
|
|
4931
|
+
io.puts " raxis = INT2NUM(0);"
|
|
4932
|
+
io.puts " }"
|
|
4572
4933
|
if rank
|
|
4573
4934
|
# method: :ordinal (default) / :dense -> dense int. :ordinal
|
|
4574
4935
|
# assigns every cell a distinct rank (ties broken by original
|
|
@@ -4576,37 +4937,15 @@ module MkKernel
|
|
|
4576
4937
|
# the standard "dense rank" used to compose rank as a sort_addr
|
|
4577
4938
|
# priority key without silently dropping lower-priority keys on
|
|
4578
4939
|
# ties (see MASKED_POSITION's sibling proposal thread).
|
|
4579
|
-
io.puts " int dense = 0
|
|
4580
|
-
io.puts " if ( ! NIL_P(rmethod) ) {"
|
|
4581
|
-
io.puts " static ID sym_ordinal = 0, sym_dense = 0;"
|
|
4582
|
-
io.puts " if ( ! sym_ordinal ) sym_ordinal = rb_intern(\"ordinal\");"
|
|
4583
|
-
io.puts " if ( ! sym_dense ) sym_dense = rb_intern(\"dense\");"
|
|
4584
|
-
io.puts " ID method_id = SYM2ID(rmethod);"
|
|
4585
|
-
io.puts " if ( method_id == sym_ordinal ) dense = 0;"
|
|
4586
|
-
io.puts " else if ( method_id == sym_dense ) dense = 1;"
|
|
4587
|
-
io.puts " else {"
|
|
4588
|
-
io.puts %Q[ rb_raise(rb_eArgError, "#{legacy_name}: unknown method %s (expected :ordinal or :dense)", rb_id2name(method_id));]
|
|
4589
|
-
io.puts " }"
|
|
4590
|
-
io.puts " }"
|
|
4940
|
+
io.puts " int dense = NIL_P(rmethod) ? 0 : ca_symbol_choice(rmethod, \"method\", \"ordinal\", \"dense\", NULL);"
|
|
4591
4941
|
end
|
|
4592
4942
|
if mask_sentinel
|
|
4593
4943
|
# masked_position: :last (default) / :first -> masked_last int.
|
|
4594
4944
|
# Shared by both the has_kind and :partition branches below.
|
|
4595
|
-
io.puts " int masked_last = 1
|
|
4596
|
-
io.puts " if ( ! NIL_P(rmasked_position) ) {"
|
|
4597
|
-
io.puts " static ID sym_first = 0, sym_last = 0;"
|
|
4598
|
-
io.puts " if ( ! sym_first ) sym_first = rb_intern(\"first\");"
|
|
4599
|
-
io.puts " if ( ! sym_last ) sym_last = rb_intern(\"last\");"
|
|
4600
|
-
io.puts " ID mp_id = SYM2ID(rmasked_position);"
|
|
4601
|
-
io.puts " if ( mp_id == sym_last ) masked_last = 1;"
|
|
4602
|
-
io.puts " else if ( mp_id == sym_first ) masked_last = 0;"
|
|
4603
|
-
io.puts " else {"
|
|
4604
|
-
io.puts %Q[ rb_raise(rb_eArgError, "#{legacy_name}: unknown masked_position %s (expected :first or :last)", rb_id2name(mp_id));]
|
|
4605
|
-
io.puts " }"
|
|
4606
|
-
io.puts " }"
|
|
4945
|
+
io.puts " int masked_last = NIL_P(rmasked_position) ? 1 : ca_symbol_choice(rmasked_position, \"masked_position\", \"first\", \"last\", NULL);"
|
|
4607
4946
|
end
|
|
4608
4947
|
if partition
|
|
4609
|
-
# partition_index(kth, axis:
|
|
4948
|
+
# partition_index(kth, axis: nil, masked_position: :last) -- kth
|
|
4610
4949
|
# positional, axis/masked_position kwargs.
|
|
4611
4950
|
io.puts " if ( argc != 1 ) {"
|
|
4612
4951
|
io.puts %Q[ rb_raise(rb_eArgError, "#{legacy_name}: wrong number of positional arguments (given %d, expected 1: kth)", argc);]
|
|
@@ -4617,28 +4956,27 @@ module MkKernel
|
|
|
4617
4956
|
io.puts " return rb_ca_#{name}_ki(self, raxis, argv[0]);"
|
|
4618
4957
|
end
|
|
4619
4958
|
else
|
|
4620
|
-
# sort_index(axis:
|
|
4959
|
+
# sort_index(axis: nil, kind: :quick, masked_position: :last) --
|
|
4621
4960
|
# no positional, kwargs only.
|
|
4622
4961
|
io.puts " if ( argc != 0 ) {"
|
|
4623
4962
|
io.puts %Q[ rb_raise(rb_eArgError, "#{legacy_name}: positional args no longer accepted (given %d args); use axis:/kind: kwargs", argc);]
|
|
4624
4963
|
io.puts " }"
|
|
4625
4964
|
# kind: dispatch (:quick default, :stable optional).
|
|
4626
|
-
io.puts " int do_stable = 0;"
|
|
4627
|
-
io.puts " if ( ! NIL_P(rkind) ) {"
|
|
4628
|
-
io.puts " static ID sym_quick = 0, sym_stable = 0;"
|
|
4629
|
-
io.puts " if ( ! sym_quick ) sym_quick = rb_intern(\"quick\");"
|
|
4630
|
-
io.puts " if ( ! sym_stable ) sym_stable = rb_intern(\"stable\");"
|
|
4631
|
-
io.puts " ID kind_id = SYM2ID(rkind);"
|
|
4632
|
-
io.puts " if ( kind_id == sym_quick ) do_stable = 0;"
|
|
4633
|
-
io.puts " else if ( kind_id == sym_stable ) do_stable = 1;"
|
|
4634
|
-
io.puts " else {"
|
|
4635
|
-
io.puts %Q[ rb_raise(rb_eArgError, "#{legacy_name}: unknown kind %s (expected :quick or :stable)", rb_id2name(kind_id));]
|
|
4636
|
-
io.puts " }"
|
|
4637
|
-
io.puts " }"
|
|
4965
|
+
io.puts " int do_stable = NIL_P(rkind) ? 0 : ca_symbol_choice(rkind, \"kind\", \"quick\", \"stable\", NULL);"
|
|
4638
4966
|
if mask_sentinel
|
|
4639
4967
|
io.puts " return #{name}_ki_dispatch(self, raxis, do_stable, masked_last);"
|
|
4640
4968
|
elsif rank
|
|
4641
|
-
io.puts "
|
|
4969
|
+
io.puts " VALUE out = #{name}_ki_dispatch(self, raxis, do_stable, dense);"
|
|
4970
|
+
io.puts " if ( ! NIL_P(whole_src) ) {"
|
|
4971
|
+
io.puts " CArray *src;"
|
|
4972
|
+
io.puts " VALUE shape_argv[CA_RANK_MAX];"
|
|
4973
|
+
io.puts " GetCArray(whole_src, src);"
|
|
4974
|
+
io.puts " for ( int k = 0; k < src->ndim; k++ ) {"
|
|
4975
|
+
io.puts " shape_argv[k] = SIZE2NUM(src->dim[k]);"
|
|
4976
|
+
io.puts " }"
|
|
4977
|
+
io.puts " out = rb_ca_reshape((int) src->ndim, shape_argv, out);"
|
|
4978
|
+
io.puts " }"
|
|
4979
|
+
io.puts " return out;"
|
|
4642
4980
|
else
|
|
4643
4981
|
io.puts " return do_stable ? rb_ca_#{name}_ki_stable(self, raxis)"
|
|
4644
4982
|
io.puts " : rb_ca_#{name}_ki_quick(self, raxis);"
|
|
@@ -4679,7 +5017,7 @@ module MkKernel
|
|
|
4679
5017
|
io.puts
|
|
4680
5018
|
io.puts "/* ===== #{k[:name]}_ki ============================================ */"
|
|
4681
5019
|
k[:source].each do |src|
|
|
4682
|
-
|
|
5020
|
+
emit_native_for(io, k, src) { |o| emit_search_native(o, k, src) }
|
|
4683
5021
|
end
|
|
4684
5022
|
emit_search_dispatch(io, k)
|
|
4685
5023
|
end
|
|
@@ -4702,7 +5040,7 @@ module MkKernel
|
|
|
4702
5040
|
# body: Hash form picks per-data_type-kind branch (= :int / :float /
|
|
4703
5041
|
# :object / :fixlen); String form is uniform.
|
|
4704
5042
|
# PROPOSAL_MKKERNEL_OBJECT_DTYPE_BRANCH Phase 4: :object branch added for
|
|
4705
|
-
# CA_OBJECT (equality via
|
|
5043
|
+
# CA_OBJECT (equality via ca_obj_equal, ordering via rb_funcall(<=>)).
|
|
4706
5044
|
# PROPOSAL_SEARCH_SEMANTICS_UNIFY S2: :fixlen branch added for CA_FIXLEN
|
|
4707
5045
|
# (the cell is a runtime-width byte blob; comparison is memcmp over
|
|
4708
5046
|
# ca->bytes, the same total order fixlen bincmp uses).
|
|
@@ -4736,7 +5074,7 @@ module MkKernel
|
|
|
4736
5074
|
<<~C.chomp
|
|
4737
5075
|
int eps_is_default = NIL_P(reps);
|
|
4738
5076
|
double user_eps = eps_is_default ? 0.0 : NUM2DBL(reps);
|
|
4739
|
-
double query_eps = eps_is_default ? (double)(#{eps_def}) * fabs((double)query_val) : user_eps;
|
|
5077
|
+
double query_eps = eps_is_default ? (isfinite((double)query_val) ? (double)(#{eps_def}) * fabs((double)query_val) : 0.0) : user_eps;
|
|
4740
5078
|
C
|
|
4741
5079
|
elsif has_eps && no_eps
|
|
4742
5080
|
"(void) reps; /* #{is_fixlen ? "CA_FIXLEN" : "CA_OBJECT"}: eps ignored, exact match only */\n double query_eps = 0.0; (void) query_eps;"
|
|
@@ -4751,7 +5089,7 @@ module MkKernel
|
|
|
4751
5089
|
""
|
|
4752
5090
|
end
|
|
4753
5091
|
eps_percell = if has_eps && !no_eps
|
|
4754
|
-
"double query_eps = eps_is_default ? (double)(#{eps_def}) * fabs((double)query_val) : user_eps;"
|
|
5092
|
+
"double query_eps = eps_is_default ? (isfinite((double)query_val) ? (double)(#{eps_def}) * fabs((double)query_val) : 0.0) : user_eps;"
|
|
4755
5093
|
else
|
|
4756
5094
|
"double query_eps = 0.0; (void) query_eps;"
|
|
4757
5095
|
end
|
|
@@ -4763,14 +5101,36 @@ module MkKernel
|
|
|
4763
5101
|
# and let rb_ca_obj2ptr pack it; `query_val` is then a char* the memcmp
|
|
4764
5102
|
# body reads (mirrors the legacy flat bsearch ALLOCA_N path).
|
|
4765
5103
|
query_setup_a = if is_fixlen
|
|
4766
|
-
"
|
|
5104
|
+
"volatile VALUE query_val_holder = 0;\n" \
|
|
5105
|
+
" char *query_val = ALLOCV_N(char, query_val_holder, ca->bytes);\n" \
|
|
4767
5106
|
" rb_ca_obj2ptr(self, rval, query_val);"
|
|
4768
5107
|
else
|
|
5108
|
+
(INT_DTYPES.include?(src) ? "ca_check_integer_fits(rval, #{si[:ca]});\n " : "") +
|
|
4769
5109
|
"#{si[:c]} query_val_buf;\n" \
|
|
4770
5110
|
" rb_ca_obj2ptr(self, rval, &query_val_buf);\n" \
|
|
4771
5111
|
" #{si[:c]} query_val = query_val_buf;"
|
|
4772
5112
|
end
|
|
4773
5113
|
|
|
5114
|
+
# The object lane calls Ruby for every cell of the case B/C walk, which
|
|
5115
|
+
# runs between a direct attach and detach of self and val; a raise there
|
|
5116
|
+
# would leave both attached. An entity holds no attach, so the lane
|
|
5117
|
+
# reads an entity copy of whichever is a view.
|
|
5118
|
+
entity_bc = if is_object
|
|
5119
|
+
<<~C.chomp
|
|
5120
|
+
volatile VALUE vca_entity = self;
|
|
5121
|
+
if ( ! ca_is_entity(ca) ) {
|
|
5122
|
+
vca_entity = rb_ca_copy(self);
|
|
5123
|
+
GetCArray(vca_entity, ca);
|
|
5124
|
+
}
|
|
5125
|
+
if ( ! ca_is_entity(cv) ) {
|
|
5126
|
+
vsrc_val = rb_ca_copy(vsrc_val);
|
|
5127
|
+
GetCArray(vsrc_val, cv);
|
|
5128
|
+
}
|
|
5129
|
+
C
|
|
5130
|
+
else
|
|
5131
|
+
""
|
|
5132
|
+
end
|
|
5133
|
+
|
|
4774
5134
|
# case B/C query coercion. numeric/object: wrap-readonly to self's
|
|
4775
5135
|
# data_type when they differ. fixlen: require a CA_FIXLEN query of the
|
|
4776
5136
|
# same byte width (no scalar coercion exists for blobs), so the per-cell
|
|
@@ -4780,7 +5140,7 @@ module MkKernel
|
|
|
4780
5140
|
(void) vsrc_val; /* fixlen: no wrap-readonly coercion */
|
|
4781
5141
|
if ( cv->data_type != CA_FIXLEN || cv->bytes != ca->bytes ) {
|
|
4782
5142
|
rb_raise(rb_eArgError,
|
|
4783
|
-
"
|
|
5143
|
+
"%s: CA_FIXLEN query must be CA_FIXLEN of the same byte width (= %ld)", ca_calling_method_name(), (long) ca->bytes);
|
|
4784
5144
|
}
|
|
4785
5145
|
C
|
|
4786
5146
|
else
|
|
@@ -4939,7 +5299,7 @@ module MkKernel
|
|
|
4939
5299
|
slab_axes, 1, 0);
|
|
4940
5300
|
if ( rc != CA_ITER_OK ) {
|
|
4941
5301
|
rb_raise(rb_eRuntimeError,
|
|
4942
|
-
"
|
|
5302
|
+
"%s: kernel_iterator init failed rc=%d", ca_calling_method_name(), rc);
|
|
4943
5303
|
}
|
|
4944
5304
|
C
|
|
4945
5305
|
|
|
@@ -5021,7 +5381,7 @@ module MkKernel
|
|
|
5021
5381
|
# transformation in two more inner loops.
|
|
5022
5382
|
io.puts <<~C
|
|
5023
5383
|
/* view_flat semantics + CArray val: NotImpError (deferred). */
|
|
5024
|
-
rb_raise(rb_eNotImpError, "
|
|
5384
|
+
rb_raise(rb_eNotImpError, "%s: view_flat semantics with CArray val (broadcast) not yet supported", ca_calling_method_name());
|
|
5025
5385
|
C
|
|
5026
5386
|
end
|
|
5027
5387
|
|
|
@@ -5044,7 +5404,9 @@ module MkKernel
|
|
|
5044
5404
|
*/
|
|
5045
5405
|
CArray *cv;
|
|
5046
5406
|
GetCArray(rval, cv);
|
|
5047
|
-
|
|
5407
|
+
/* volatile: once the query is wrapped below, only cv is used, and
|
|
5408
|
+
the wrapper must stay reachable while its cells are read. */
|
|
5409
|
+
volatile VALUE vsrc_val = rval;
|
|
5048
5410
|
#{coerce_bc}
|
|
5049
5411
|
|
|
5050
5412
|
/* base_shape = self.shape with axis removed. base_to_self[bk] =
|
|
@@ -5079,13 +5441,13 @@ module MkKernel
|
|
|
5079
5441
|
if ( i == axis ) continue;
|
|
5080
5442
|
if ( cv->dim[i] != ca->dim[i] ) {
|
|
5081
5443
|
rb_raise(rb_eArgError,
|
|
5082
|
-
"
|
|
5444
|
+
"%s: val shape mismatch (A3 candidate, axis %d non-target dim[%d] = %ld != self.dim[%d] = %ld; expected scalar / [M] / base_shape (ndim>=2) / self.shape with axis dim free)", ca_calling_method_name(),
|
|
5083
5445
|
axis, (int) i, (long) cv->dim[i], (int) i, (long) ca->dim[i]);
|
|
5084
5446
|
}
|
|
5085
5447
|
}
|
|
5086
5448
|
if ( cv->dim[axis] < 1 ) {
|
|
5087
5449
|
rb_raise(rb_eArgError,
|
|
5088
|
-
"
|
|
5450
|
+
"%s: empty query (val.dim[%d] = 0)", ca_calling_method_name(), axis);
|
|
5089
5451
|
}
|
|
5090
5452
|
cv_form = CV_FORM_A3;
|
|
5091
5453
|
cv_form_set = 1;
|
|
@@ -5095,7 +5457,7 @@ module MkKernel
|
|
|
5095
5457
|
/* A2: 1-D shared M-query (M >= 1); placed above A2.5 in rev5 */
|
|
5096
5458
|
if ( cv->dim[0] < 1 ) {
|
|
5097
5459
|
rb_raise(rb_eArgError,
|
|
5098
|
-
"
|
|
5460
|
+
"%s: empty query (val.dim[0] = 0)", ca_calling_method_name());
|
|
5099
5461
|
}
|
|
5100
5462
|
cv_form = CV_FORM_A2;
|
|
5101
5463
|
cv_form_set = 1;
|
|
@@ -5116,7 +5478,7 @@ module MkKernel
|
|
|
5116
5478
|
|
|
5117
5479
|
if ( ! cv_form_set ) {
|
|
5118
5480
|
rb_raise(rb_eArgError,
|
|
5119
|
-
"
|
|
5481
|
+
"%s: val shape not accepted (val.ndim = %d; expected scalar / [M] / base_shape (ndim>=2, = %d) / self.shape with axis dim free (ndim = %d))", ca_calling_method_name(),
|
|
5120
5482
|
(int) cv->ndim, (int) base_ndim, (int) ca->ndim);
|
|
5121
5483
|
}
|
|
5122
5484
|
|
|
@@ -5151,9 +5513,9 @@ module MkKernel
|
|
|
5151
5513
|
#{oi[:c]} *op = (#{oi[:c]} *) co->ptr;
|
|
5152
5514
|
#{op_mask_decl}
|
|
5153
5515
|
|
|
5516
|
+
#{entity_bc}
|
|
5154
5517
|
/* Attach self + val; rev4 uses raw row-major byte addressing. */
|
|
5155
|
-
|
|
5156
|
-
ca_attach(cv);
|
|
5518
|
+
ca_attach_n(2, ca, cv); /* window: numeric bodies do not raise; the object lane reads entities */
|
|
5157
5519
|
|
|
5158
5520
|
/* self_byte_stride[j] = bytes to advance along self axis j (row-major). */
|
|
5159
5521
|
ca_size_t self_byte_stride[CA_RANK_MAX];
|
|
@@ -5281,25 +5643,18 @@ module MkKernel
|
|
|
5281
5643
|
io.puts "{"
|
|
5282
5644
|
io.puts " CArray *src;"
|
|
5283
5645
|
io.puts " VALUE self_ref = self; /* pre-strip Face, for to_comparable */"
|
|
5284
|
-
io.puts " int self_face_comparable = 0;"
|
|
5285
5646
|
io.puts " int self_was_face = 0;"
|
|
5286
5647
|
io.puts " GetCArray(self, src);"
|
|
5648
|
+
emit_lazy_evaluated(io)
|
|
5287
5649
|
io.puts " /* Face gate (self): descend a Face reference to storage so the"
|
|
5288
5650
|
io.puts " search runs on the numeric storage. ORDERABLE licenses the"
|
|
5289
|
-
io.puts " ordered/searchable descent;
|
|
5290
|
-
io.puts "
|
|
5291
|
-
io.puts " gate below). A unit-bearing Face (CATime) is ORDERABLE"
|
|
5292
|
-
io.puts " but not COMPARABLE, so its query is reconciled by to_comparable"
|
|
5293
|
-
io.puts " -- no per-Face Ruby override needed. */"
|
|
5651
|
+
io.puts " ordered/searchable descent; the query gate below brings the"
|
|
5652
|
+
io.puts " query into the same space. */"
|
|
5294
5653
|
io.puts " if ( ca_is_face(src) ) {"
|
|
5295
5654
|
io.puts " if ( ! ca_test_flag(src, CA_FLAG_FACE_ORDERABLE_STORAGE) ) {"
|
|
5296
|
-
io.puts %Q[
|
|
5297
|
-
io.puts %Q[ "#{name}_ki: Face-typed input (%s) is not orderable "]
|
|
5298
|
-
io.puts %Q[ "by storage; use ca.parent to descend to storage",]
|
|
5299
|
-
io.puts %Q[ rb_obj_classname(self));]
|
|
5655
|
+
io.puts %Q[ ca_face_refuse_order(ca_calling_method_name(), self, "order");]
|
|
5300
5656
|
io.puts " }"
|
|
5301
5657
|
io.puts " self_was_face = 1;"
|
|
5302
|
-
io.puts " self_face_comparable = ca_test_flag(src, CA_FLAG_FACE_COMPARABLE_STORAGE);"
|
|
5303
5658
|
io.puts " self = rb_ca_strip_face_value(self);"
|
|
5304
5659
|
io.puts " GetCArray(self, src);"
|
|
5305
5660
|
io.puts " }"
|
|
@@ -5313,64 +5668,61 @@ module MkKernel
|
|
|
5313
5668
|
io.puts " VALUE raxis = argv[1];"
|
|
5314
5669
|
io.puts " VALUE reps = (argc >= 3) ? argv[2] : Qnil;"
|
|
5315
5670
|
end
|
|
5316
|
-
io.puts " /* Query gate
|
|
5317
|
-
io.puts "
|
|
5318
|
-
io.puts "
|
|
5319
|
-
io.puts "
|
|
5320
|
-
io.puts "
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
io.puts "
|
|
5331
|
-
io.puts "
|
|
5332
|
-
io.puts "
|
|
5333
|
-
io.puts "
|
|
5334
|
-
io.puts " if (
|
|
5335
|
-
io.puts "
|
|
5336
|
-
io.puts "
|
|
5337
|
-
io.puts "
|
|
5338
|
-
io.puts "
|
|
5339
|
-
io.puts "
|
|
5340
|
-
io.puts " rval = rb_funcall(self_ref, rb_intern(\"to_comparable\"), 1, rval);"
|
|
5341
|
-
io.puts " rval = rb_ca_strip_face_value(rval);"
|
|
5342
|
-
io.puts " } else {"
|
|
5343
|
-
io.puts %Q[ rb_raise(rb_eArgError,]
|
|
5344
|
-
io.puts %Q[ "#{name}_ki: non-comparable Face reference (%s) has no "]
|
|
5345
|
-
io.puts %Q[ "to_comparable to reconcile the query; use ca.parent "]
|
|
5346
|
-
io.puts %Q[ "to search the hidden storage explicitly",]
|
|
5347
|
-
io.puts %Q[ rb_obj_classname(self_ref));]
|
|
5671
|
+
io.puts " /* Query gate: the reference Face reconciles the query into its own"
|
|
5672
|
+
io.puts " space (ca_face_reconcile_operand). */"
|
|
5673
|
+
io.puts " if ( self_was_face ) {"
|
|
5674
|
+
io.puts " rval = ca_face_reconcile_operand(self_ref, rval, ca_calling_method_name());"
|
|
5675
|
+
io.puts " }"
|
|
5676
|
+
|
|
5677
|
+
# The query and the reference are compared in the type they share, as
|
|
5678
|
+
# `a.eq(q)` compares, not with the query cast to the reference's type:
|
|
5679
|
+
# 3.9 must not match 3, a float64 query must not be rounded to a float32
|
|
5680
|
+
# reference, and -1 must not read as 255. A scalar takes the type the
|
|
5681
|
+
# operators give it (ca_promote_scalar_type); a CArray query the type
|
|
5682
|
+
# the two match in (ca_value_match_type). The reference is read through
|
|
5683
|
+
# a cast view of that type. An Integer scalar keeps the reference's type
|
|
5684
|
+
# and is refused when it does not fit.
|
|
5685
|
+
io.puts " if ( ca_is_integer_type(src) || ca_is_float_type(src) || src->data_type == CA_BOOLEAN ) {"
|
|
5686
|
+
io.puts " int8_t qt = src->data_type;"
|
|
5687
|
+
io.puts " if ( RB_FLOAT_TYPE_P(rval) ) {"
|
|
5688
|
+
io.puts " qt = ca_promote_scalar_type(src->data_type, rval, CA_FLOAT64);"
|
|
5689
|
+
io.puts " } else if ( rb_obj_is_kind_of(rval, rb_cCArray) ) {"
|
|
5690
|
+
io.puts " CArray *qv;"
|
|
5691
|
+
io.puts " TypedData_Get_Struct(rval, CArray, &carray_data_type, qv);"
|
|
5692
|
+
io.puts " if ( ca_is_integer_type(qv) || ca_is_float_type(qv) || qv->data_type == CA_BOOLEAN ) {"
|
|
5693
|
+
io.puts " qt = ca_value_match_type(ca_promote_type(src->data_type, qv->data_type),"
|
|
5694
|
+
io.puts " src->data_type, qv->data_type);"
|
|
5348
5695
|
io.puts " }"
|
|
5349
5696
|
io.puts " }"
|
|
5697
|
+
io.puts " if ( qt != src->data_type ) {"
|
|
5698
|
+
io.puts " self = rb_ca_wrap_readonly(self, INT2NUM(qt));"
|
|
5699
|
+
io.puts " GetCArray(self, src);"
|
|
5700
|
+
io.puts " }"
|
|
5350
5701
|
io.puts " }"
|
|
5351
5702
|
|
|
5352
5703
|
case k[:mask_self]
|
|
5353
5704
|
when :raise
|
|
5354
5705
|
io.puts " /* mask_self: :raise -- global reject if self has any masked element. */"
|
|
5355
5706
|
io.puts " if ( ca_is_any_masked(src) ) {"
|
|
5356
|
-
io.puts %Q[ rb_raise(
|
|
5707
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: self should not have any masked elements", ca_calling_method_name());]
|
|
5357
5708
|
io.puts " }"
|
|
5358
5709
|
when :skip, :ignore
|
|
5359
5710
|
# body handles mask_in or ignores it
|
|
5360
5711
|
end
|
|
5361
5712
|
|
|
5362
|
-
io.puts " int axis =
|
|
5713
|
+
io.puts " int axis = (int) ca_axis_integer(raxis, NULL);"
|
|
5363
5714
|
io.puts " if ( axis < 0 ) axis += src->ndim;"
|
|
5364
5715
|
io.puts " if ( axis < 0 || axis >= src->ndim ) {"
|
|
5365
|
-
io.puts %Q[ rb_raise(rb_eArgError, "
|
|
5716
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: axis %d out of range for ndim %d", ca_calling_method_name(), NUM2INT(raxis), src->ndim);]
|
|
5366
5717
|
io.puts " }"
|
|
5367
5718
|
io.puts " switch ( src->data_type ) {"
|
|
5368
5719
|
k[:source].each do |s|
|
|
5369
5720
|
si = DTYPES[s]
|
|
5370
|
-
io.puts " case #{si[:ca]}:
|
|
5721
|
+
io.puts " case #{si[:ca]}: CA_RETURN_KEEPING(self, #{name}_ki_native_#{s}(self, src, rval, axis#{extra_arg}));"
|
|
5371
5722
|
end
|
|
5372
5723
|
io.puts " default:"
|
|
5373
|
-
io.puts %Q[
|
|
5724
|
+
io.puts %Q[ ca_face_refuse_numeric(self, ca_calling_method_name(), rb_eCADataTypeError);]
|
|
5725
|
+
io.puts %Q[ rb_raise(rb_eCADataTypeError, "%s: source data_type :%s not supported (expected one of: #{MkKernel.type_names(k[:source])})", ca_calling_method_name(), ca_type_name[src->data_type]);]
|
|
5374
5726
|
io.puts " }"
|
|
5375
5727
|
io.puts " return Qnil; /* unreachable */"
|
|
5376
5728
|
io.puts "}"
|
|
@@ -5620,11 +5972,15 @@ module MkKernel
|
|
|
5620
5972
|
int8_t in_dt;
|
|
5621
5973
|
TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
|
|
5622
5974
|
in_dt = ca->data_type;
|
|
5975
|
+
if ( ca_monop_#{name}_out_data_type[in_dt] <= CA_NONE ) {
|
|
5976
|
+
rb_raise(rb_eCADataTypeError,
|
|
5977
|
+
"#{name}!: source data_type :%s not supported", ca_type_name[in_dt]);
|
|
5978
|
+
}
|
|
5623
5979
|
if ( ca_monop_#{name}_out_data_type[in_dt] != in_dt ) {
|
|
5624
|
-
rb_raise(
|
|
5625
|
-
"#{name}!:
|
|
5626
|
-
"
|
|
5627
|
-
|
|
5980
|
+
rb_raise(rb_eCADataTypeError,
|
|
5981
|
+
"#{name}!: #{name} of :%s is :%s, which cannot be written back in place; "
|
|
5982
|
+
"use #{name}", ca_type_name[in_dt],
|
|
5983
|
+
ca_type_name[ca_monop_#{name}_out_data_type[in_dt]]);
|
|
5628
5984
|
}
|
|
5629
5985
|
return rb_ca_call_monop_bang(self, ca_monop_#{name});
|
|
5630
5986
|
}
|
|
@@ -5878,6 +6234,10 @@ module MkKernel
|
|
|
5878
6234
|
io.print <<~C
|
|
5879
6235
|
VALUE rb_ca_#{name} (VALUE self, VALUE other2, VALUE other3)
|
|
5880
6236
|
{ return rb_ca_call_triop(self, other2, other3, ca_triop_#{name}); }
|
|
6237
|
+
C
|
|
6238
|
+
# bang: false kernels get no bang method, so no bang entry either.
|
|
6239
|
+
return if k[:bang] == false
|
|
6240
|
+
io.print <<~C
|
|
5881
6241
|
|
|
5882
6242
|
VALUE rb_ca_#{name}_bang (VALUE self, VALUE other2, VALUE other3)
|
|
5883
6243
|
{ return rb_ca_call_triop_bang(self, other2, other3, ca_triop_#{name}); }
|
|
@@ -6149,15 +6509,56 @@ module MkKernel
|
|
|
6149
6509
|
# caller substitutes its own operands. Object-lane bodies are left out,
|
|
6150
6510
|
# since they call back into the interpreter and cannot be compiled apart
|
|
6151
6511
|
# from it.
|
|
6512
|
+
#
|
|
6513
|
+
# A comparison is keyed by the data type it compares, not by what it
|
|
6514
|
+
# answers, which is always boolean.
|
|
6152
6515
|
# ---------------------------------------------------------------------
|
|
6153
6516
|
|
|
6154
6517
|
BODY_TABLE_DTYPES = %i[i8 u8 i16 u16 i32 u32 i64 u64 f32 f64
|
|
6155
6518
|
bool cmplx64 cmplx128].freeze
|
|
6156
6519
|
|
|
6520
|
+
# Kernels written outside this generator that a lazy expression can still
|
|
6521
|
+
# hold. A Float or Complex array to an Integer power is CABinOp's ipow
|
|
6522
|
+
# (ext/ca_op_ipower.c, which applies op_powi_<type> cell by cell); its
|
|
6523
|
+
# body is that call.
|
|
6524
|
+
EXTRA_BODY_ROWS = %w[float32 float64 cmplx64 cmplx128].map { |t|
|
|
6525
|
+
["binop", "ipow", t, "(#3) = op_powi_<type>((#1), (#2));"]
|
|
6526
|
+
}.freeze
|
|
6527
|
+
|
|
6528
|
+
# The helpers the bodies call that are not in the C standard library:
|
|
6529
|
+
# the part of ca_op_powi.h between its markers, as it stands.
|
|
6530
|
+
def self.kernel_helpers
|
|
6531
|
+
text = File.read(File.join(__dir__, "ca_op_powi.h"))
|
|
6532
|
+
text[%r{/\* BEGIN kernel helpers \*/\n(.*)/\* END kernel helpers \*/}m, 1] or
|
|
6533
|
+
raise "ca_op_powi.h: kernel helper markers not found"
|
|
6534
|
+
end
|
|
6535
|
+
|
|
6536
|
+
# A lazy cast node (CAMonOp with op_id CAST_BASE + target) converts cell
|
|
6537
|
+
# by cell through ca_cast_block, which for a numeric target is a plain C
|
|
6538
|
+
# conversion (ext/carray_cast_func.rb). It is keyed as cast_<target> at
|
|
6539
|
+
# the source's data type. Left out: a boolean target, which checks its
|
|
6540
|
+
# input and raises, and a float or complex source to an integer target,
|
|
6541
|
+
# where an out-of-range value or NaN has no defined C result for a
|
|
6542
|
+
# second compilation to agree on.
|
|
6543
|
+
CAST_SOURCES = %i[bool i8 u8 i16 u16 i32 u32 i64 u64 f32 f64
|
|
6544
|
+
cmplx64 cmplx128].freeze
|
|
6545
|
+
CAST_TARGETS = %i[i8 u8 i16 u16 i32 u32 i64 u64 f32 f64
|
|
6546
|
+
cmplx64 cmplx128].freeze
|
|
6547
|
+
CAST_INEXACT = %i[f32 f64 cmplx64 cmplx128].freeze
|
|
6548
|
+
|
|
6549
|
+
def self.cast_body_rows
|
|
6550
|
+
CAST_SOURCES.product(CAST_TARGETS).filter_map { |src, dst|
|
|
6551
|
+
next if CAST_INEXACT.include?(src) && !CAST_INEXACT.include?(dst)
|
|
6552
|
+
name = DTYPES[dst][:ca].sub(/\ACA_/, "").downcase
|
|
6553
|
+
["monop", "cast_#{name}", DTYPES[src][:ca].sub(/\ACA_/, "").downcase,
|
|
6554
|
+
"(#2) = (#{DTYPES[dst][:c]})(#1);"]
|
|
6555
|
+
}
|
|
6556
|
+
end
|
|
6557
|
+
|
|
6157
6558
|
def self.body_table_rows
|
|
6158
|
-
rows =
|
|
6559
|
+
rows = EXTRA_BODY_ROWS.map(&:dup) + cast_body_rows
|
|
6159
6560
|
KERNELS.each do |k|
|
|
6160
|
-
next unless %i[monop binop triop].include?(k[:kind])
|
|
6561
|
+
next unless %i[monop binop triop moncmp bincmp].include?(k[:kind])
|
|
6161
6562
|
BODY_TABLE_DTYPES.each do |src|
|
|
6162
6563
|
body = monop_expr_for(k, src)
|
|
6163
6564
|
next unless body.is_a?(String)
|
|
@@ -6169,8 +6570,10 @@ module MkKernel
|
|
|
6169
6570
|
rows
|
|
6170
6571
|
end
|
|
6171
6572
|
|
|
6573
|
+
# The block forms keep gsub from reading the backslashes in the
|
|
6574
|
+
# replacement as references to the match.
|
|
6172
6575
|
def self.c_string_literal(text)
|
|
6173
|
-
'"' + text.gsub("\\"
|
|
6576
|
+
'"' + text.gsub("\\") { "\\\\" }.gsub('"') { '\\"' }.gsub("\n") { '\\n' } + '"'
|
|
6174
6577
|
end
|
|
6175
6578
|
|
|
6176
6579
|
def self.emit_kernel_bodies(io)
|
|
@@ -6219,6 +6622,17 @@ module MkKernel
|
|
|
6219
6622
|
}
|
|
6220
6623
|
return Qnil;
|
|
6221
6624
|
}
|
|
6625
|
+
|
|
6626
|
+
/* CArray.__kernel_helpers__ -> String: the C a body above may call
|
|
6627
|
+
beyond the standard library, for the caller to put ahead of it. */
|
|
6628
|
+
static const char ca_kernel_helpers[] =
|
|
6629
|
+
#{c_string_literal(kernel_helpers)};
|
|
6630
|
+
|
|
6631
|
+
static VALUE
|
|
6632
|
+
rb_ca_s_kernel_helpers (VALUE klass)
|
|
6633
|
+
{
|
|
6634
|
+
return rb_str_new_cstr(ca_kernel_helpers);
|
|
6635
|
+
}
|
|
6222
6636
|
C
|
|
6223
6637
|
end
|
|
6224
6638
|
|
|
@@ -6240,6 +6654,8 @@ module MkKernel
|
|
|
6240
6654
|
end
|
|
6241
6655
|
io.puts " rb_define_singleton_method(rb_cCArray, \"__kernel_body__\","
|
|
6242
6656
|
io.puts " rb_ca_s_kernel_body, 3);"
|
|
6657
|
+
io.puts " rb_define_singleton_method(rb_cCArray, \"__kernel_helpers__\","
|
|
6658
|
+
io.puts " rb_ca_s_kernel_helpers, 0);"
|
|
6243
6659
|
io.puts "}"
|
|
6244
6660
|
end
|
|
6245
6661
|
|
|
@@ -6254,6 +6670,8 @@ module MkKernel
|
|
|
6254
6670
|
KERNELS.each { |k| emit_init_line(io, k) }
|
|
6255
6671
|
io.puts " rb_define_singleton_method(rb_cCArray, \"__kernel_body__\","
|
|
6256
6672
|
io.puts " rb_ca_s_kernel_body, 3);"
|
|
6673
|
+
io.puts " rb_define_singleton_method(rb_cCArray, \"__kernel_helpers__\","
|
|
6674
|
+
io.puts " rb_ca_s_kernel_helpers, 0);"
|
|
6257
6675
|
io.puts "}"
|
|
6258
6676
|
end
|
|
6259
6677
|
|
|
@@ -6314,7 +6732,6 @@ module MkKernel
|
|
|
6314
6732
|
end
|
|
6315
6733
|
arity = case k[:kind]
|
|
6316
6734
|
when :reduce then -1 # axis: kwarg + optional weights/value positional
|
|
6317
|
-
when :map then 0 # no args
|
|
6318
6735
|
when :scan then -1 # axis: kwarg (Integer required)
|
|
6319
6736
|
when :sort
|
|
6320
6737
|
# SO.3+ (rev8): :partition algorithm takes (axis, kth) = arity 2
|
|
@@ -6339,7 +6756,7 @@ module MkKernel
|
|
|
6339
6756
|
# reduce: default false (E.8 retire), opt-in via bind_ruby: true
|
|
6340
6757
|
# (used by CF.1 count_equal smoke + future kernel testing)
|
|
6341
6758
|
# sort: default true, opt-out via bind_ruby: false
|
|
6342
|
-
#
|
|
6759
|
+
# scan: always bound (no opt-out)
|
|
6343
6760
|
bind = case k[:kind]
|
|
6344
6761
|
when :reduce then k[:bind_ruby] == true
|
|
6345
6762
|
when :sort then k[:bind_ruby] != false
|
|
@@ -6441,8 +6858,9 @@ MkKernel.reduce :min,
|
|
|
6441
6858
|
# bool acc is u64 (numeric output); cast the boolean8_t
|
|
6442
6859
|
# load to match and avoid a signed/unsigned compare.
|
|
6443
6860
|
bool: "acc = ((uint64_t) v < acc) ? (uint64_t) v : acc",
|
|
6444
|
-
object: 'if (acc == Qundef
|
|
6861
|
+
object: 'if (acc == Qundef || ca_obj_extremum_replaces(v, acc, rb_intern("<"))) acc = v;' },
|
|
6445
6862
|
reduction_kind: :min, # SL.1.2
|
|
6863
|
+
all_nan_result: :nan,
|
|
6446
6864
|
# CA_FIXLEN: memcmp lexicographic min (byte order == the fixlen sort
|
|
6447
6865
|
# order); the numeric reduce/init above are unused for fixlen (bespoke
|
|
6448
6866
|
# slab walk, see the fixlen: option in MkKernel.reduce).
|
|
@@ -6453,8 +6871,8 @@ MkKernel.reduce :min,
|
|
|
6453
6871
|
fallback: :raise,
|
|
6454
6872
|
mask_policy: :min_count, # Phase E
|
|
6455
6873
|
# An ORDERABLE Face descends to numeric storage, then the result is
|
|
6456
|
-
# re-lifted into the Face (scalar via
|
|
6457
|
-
#
|
|
6874
|
+
# re-lifted into the Face (scalar via storage_to_element, per-axis via
|
|
6875
|
+
# ca_face_relift_entity). This replaces per-Face lib overrides.
|
|
6458
6876
|
face_gate: :relift,
|
|
6459
6877
|
public_method: true # Phase E: rebind "min" -> rb_ca_min_ki
|
|
6460
6878
|
|
|
@@ -6465,8 +6883,9 @@ MkKernel.reduce :max,
|
|
|
6465
6883
|
# 0/1); the boolean-returning twin is `any` (= bool max).
|
|
6466
6884
|
reduce: { numeric: "acc = (v > acc) ? v : acc",
|
|
6467
6885
|
bool: "acc = ((uint64_t) v > acc) ? (uint64_t) v : acc",
|
|
6468
|
-
object: 'if (acc == Qundef
|
|
6886
|
+
object: 'if (acc == Qundef || ca_obj_extremum_replaces(v, acc, rb_intern(">"))) acc = v;' },
|
|
6469
6887
|
reduction_kind: :max, # SL.1.2
|
|
6888
|
+
all_nan_result: :nan,
|
|
6470
6889
|
# CA_FIXLEN: memcmp lexicographic max (byte order == the fixlen sort order).
|
|
6471
6890
|
fixlen: :max,
|
|
6472
6891
|
source: MkKernel::ALL_NUMERIC + [:bool, :object, :fixlen],
|
|
@@ -6477,20 +6896,6 @@ MkKernel.reduce :max,
|
|
|
6477
6896
|
face_gate: :relift,
|
|
6478
6897
|
public_method: true # Phase E: rebind "max" -> rb_ca_max_ki
|
|
6479
6898
|
|
|
6480
|
-
# count_ki — count unmasked elements per slab. The macro skips masked
|
|
6481
|
-
# cells, so REDUCE fires only on contributing cells; `acc += 1` ignores
|
|
6482
|
-
# the value entirely (cast to void to silence unused-variable warnings).
|
|
6483
|
-
# When the input has no mask the macro takes a faster path that loads
|
|
6484
|
-
# `v` but our REDUCE doesn't use it -- compiler optimises the load away.
|
|
6485
|
-
MkKernel.reduce :count,
|
|
6486
|
-
init: "0",
|
|
6487
|
-
reduce: "(void)v; acc += 1",
|
|
6488
|
-
reduction_kind: :plus, # SL.1.4
|
|
6489
|
-
source: MkKernel::ALL_NUMERIC,
|
|
6490
|
-
output: :i64,
|
|
6491
|
-
ruby_scalar: :LL2NUM,
|
|
6492
|
-
fallback: :wrap_to_f64
|
|
6493
|
-
|
|
6494
6899
|
# all / any reduce: bool input -> bool output, axis-aware. Replaces the
|
|
6495
6900
|
# flat-only hand-written rb_ca_all_equal_p / rb_ca_any_equal_p /
|
|
6496
6901
|
# all_close? / any_close? / all_equiv? / any_equiv? in carray_stat.c
|
|
@@ -6507,6 +6912,15 @@ MkKernel.reduce :count,
|
|
|
6507
6912
|
# modern CPUs. For dramatically lopsided inputs (= mostly-false `all`
|
|
6508
6913
|
# or mostly-true `any`) a profile-driven early-break variant can be
|
|
6509
6914
|
# added later as a separate kernel.
|
|
6915
|
+
MkKernel.header_block <<~C
|
|
6916
|
+
/* The square root of a variance: an ε-negative from rounding becomes 0,
|
|
6917
|
+
a NaN stays NaN (fmax(NaN, 0.0) is 0.0). */
|
|
6918
|
+
#ifndef CARRAY_SQRT_CLAMP_DEFINED
|
|
6919
|
+
#define CARRAY_SQRT_CLAMP_DEFINED
|
|
6920
|
+
static inline double ca_sqrt_clamp (double v) { return sqrt(v < 0.0 ? 0.0 : v); }
|
|
6921
|
+
#endif
|
|
6922
|
+
C
|
|
6923
|
+
|
|
6510
6924
|
MkKernel.header_block <<~C
|
|
6511
6925
|
/* BOOL2VAL: bool -> Ruby (Qtrue/Qfalse). Used as ruby_scalar wrapper
|
|
6512
6926
|
for the all/any flat-reduction Ruby surface so `a.all` / `a.any`
|
|
@@ -6690,8 +7104,9 @@ MkKernel.reduce :minmax,
|
|
|
6690
7104
|
# CA_OBJECT minmax via Qundef sentinel + first-cell-init.
|
|
6691
7105
|
# One sentinel guards both lo + hi (they go Qundef
|
|
6692
7106
|
# together on init, get set together on first reduce).
|
|
6693
|
-
object: 'if (lo == Qundef) { lo = v; hi = v; } else { if (
|
|
7107
|
+
object: 'if (lo == Qundef) { lo = v; hi = v; } else { if (ca_obj_extremum_replaces(v, lo, rb_intern("<"))) lo = v; if (ca_obj_extremum_replaces(v, hi, rb_intern(">"))) hi = v; }' },
|
|
6694
7108
|
outputs: 2,
|
|
7109
|
+
all_nan_result: :nan,
|
|
6695
7110
|
finish: { min: "lo", max: "hi" },
|
|
6696
7111
|
source: MkKernel::ALL_NUMERIC + [:bool, :object],
|
|
6697
7112
|
# bool: u64 (Integer 0/1) so minmax returns [0/1, 0/1], not
|
|
@@ -6700,6 +7115,7 @@ MkKernel.reduce :minmax,
|
|
|
6700
7115
|
ruby_scalar: :auto,
|
|
6701
7116
|
fallback: :raise,
|
|
6702
7117
|
mask_policy: :min_count, # FM.1.5: match existing min / max behaviour
|
|
7118
|
+
face_gate: :relift,
|
|
6703
7119
|
public_method: :minmax # bind public name `minmax` -> rb_ca_minmax_ki
|
|
6704
7120
|
# (= surface-name override, no actual legacy)
|
|
6705
7121
|
|
|
@@ -6721,14 +7137,19 @@ MkKernel.reduce :minmax,
|
|
|
6721
7137
|
# replace the legacy `min_addr` / `max_addr` retired in E.7 stat_proc
|
|
6722
7138
|
# retire (commit f5c7ecd). 3.0 breaking: name change from `*_addr` to
|
|
6723
7139
|
# `*_index` is intentional.
|
|
7140
|
+
#
|
|
7141
|
+
# best_i starts at -1 (no cell taken yet), so the first unmasked non-NaN
|
|
7142
|
+
# cell is taken even when it equals the starting limit (an all-+Infinity
|
|
7143
|
+
# or all-INT_MAX fiber). `v == v` keeps a NaN from being taken first; a
|
|
7144
|
+
# fiber of NaN only is left to the all-NaN fixup (UNDEF).
|
|
6724
7145
|
|
|
6725
7146
|
MkKernel.reduce :argmin,
|
|
6726
7147
|
state: { best_v: :load_type, best_i: :int64_t },
|
|
6727
7148
|
init: { best_v: { numeric: "T_LIMIT_HI", bool: "T_LIMIT_HI", object: "Qundef" },
|
|
6728
|
-
best_i: "
|
|
6729
|
-
reduce: { numeric: "if (v < best_v) { best_v = v; best_i = idx; }",
|
|
6730
|
-
bool: "if (v < best_v) { best_v = v; best_i = idx; }",
|
|
6731
|
-
object: 'if (
|
|
7149
|
+
best_i: "-1" },
|
|
7150
|
+
reduce: { numeric: "if (v < best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7151
|
+
bool: "if (v < best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7152
|
+
object: 'if (!CA_OBJ_ISNAN(v) && (best_v == Qundef || ca_obj_cmp(v, best_v) < 0)) { best_v = v; best_i = idx; }' },
|
|
6732
7153
|
finish: "best_i",
|
|
6733
7154
|
# CA_FIXLEN: position of the memcmp-lexicographic min (first-wins on ties).
|
|
6734
7155
|
fixlen: :argmin,
|
|
@@ -6740,15 +7161,16 @@ MkKernel.reduce :argmin,
|
|
|
6740
7161
|
# An ORDERABLE Face descends to its numeric storage (position output needs
|
|
6741
7162
|
# no re-lift; the axis-local index is identical for Face and storage).
|
|
6742
7163
|
face_gate: :strip,
|
|
7164
|
+
all_nan_result: :undef,
|
|
6743
7165
|
public_method: :min_index
|
|
6744
7166
|
|
|
6745
7167
|
MkKernel.reduce :argmax,
|
|
6746
7168
|
state: { best_v: :load_type, best_i: :int64_t },
|
|
6747
7169
|
init: { best_v: { numeric: "T_LIMIT_LO", bool: "T_LIMIT_LO", object: "Qundef" },
|
|
6748
|
-
best_i: "
|
|
6749
|
-
reduce: { numeric: "if (v > best_v) { best_v = v; best_i = idx; }",
|
|
6750
|
-
bool: "if (v > best_v) { best_v = v; best_i = idx; }",
|
|
6751
|
-
object: 'if (
|
|
7170
|
+
best_i: "-1" },
|
|
7171
|
+
reduce: { numeric: "if (v > best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7172
|
+
bool: "if (v > best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7173
|
+
object: 'if (!CA_OBJ_ISNAN(v) && (best_v == Qundef || ca_obj_cmp(v, best_v) > 0)) { best_v = v; best_i = idx; }' },
|
|
6752
7174
|
finish: "best_i",
|
|
6753
7175
|
# CA_FIXLEN: position of the memcmp-lexicographic max (first-wins on ties).
|
|
6754
7176
|
fixlen: :argmax,
|
|
@@ -6758,6 +7180,7 @@ MkKernel.reduce :argmax,
|
|
|
6758
7180
|
fallback: :raise,
|
|
6759
7181
|
mask_policy: :min_count,
|
|
6760
7182
|
face_gate: :strip,
|
|
7183
|
+
all_nan_result: :undef,
|
|
6761
7184
|
public_method: :max_index
|
|
6762
7185
|
|
|
6763
7186
|
# ---- argmin_addr / argmax_addr (view-flat address variants) ---------
|
|
@@ -6789,10 +7212,10 @@ MkKernel.reduce :argmax,
|
|
|
6789
7212
|
MkKernel.reduce :argmin_addr,
|
|
6790
7213
|
state: { best_v: :load_type, best_i: :int64_t },
|
|
6791
7214
|
init: { best_v: { numeric: "T_LIMIT_HI", bool: "T_LIMIT_HI", object: "Qundef" },
|
|
6792
|
-
best_i: "
|
|
6793
|
-
reduce: { numeric: "if (v < best_v) { best_v = v; best_i = idx; }",
|
|
6794
|
-
bool: "if (v < best_v) { best_v = v; best_i = idx; }",
|
|
6795
|
-
object: 'if (
|
|
7215
|
+
best_i: "-1" },
|
|
7216
|
+
reduce: { numeric: "if (v < best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7217
|
+
bool: "if (v < best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7218
|
+
object: 'if (!CA_OBJ_ISNAN(v) && (best_v == Qundef || ca_obj_cmp(v, best_v) < 0)) { best_v = v; best_i = idx; }' },
|
|
6796
7219
|
finish: "best_i",
|
|
6797
7220
|
source: MkKernel::ALL_NUMERIC + [:bool, :object],
|
|
6798
7221
|
output: :i64,
|
|
@@ -6801,15 +7224,16 @@ MkKernel.reduce :argmin_addr,
|
|
|
6801
7224
|
mask_policy: :min_count,
|
|
6802
7225
|
semantics: :view_flat,
|
|
6803
7226
|
face_gate: :strip,
|
|
7227
|
+
all_nan_result: :undef,
|
|
6804
7228
|
public_method: :min_addr
|
|
6805
7229
|
|
|
6806
7230
|
MkKernel.reduce :argmax_addr,
|
|
6807
7231
|
state: { best_v: :load_type, best_i: :int64_t },
|
|
6808
7232
|
init: { best_v: { numeric: "T_LIMIT_LO", bool: "T_LIMIT_LO", object: "Qundef" },
|
|
6809
|
-
best_i: "
|
|
6810
|
-
reduce: { numeric: "if (v > best_v) { best_v = v; best_i = idx; }",
|
|
6811
|
-
bool: "if (v > best_v) { best_v = v; best_i = idx; }",
|
|
6812
|
-
object: 'if (
|
|
7233
|
+
best_i: "-1" },
|
|
7234
|
+
reduce: { numeric: "if (v > best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7235
|
+
bool: "if (v > best_v || (best_i < 0 && v == v)) { best_v = v; best_i = idx; }",
|
|
7236
|
+
object: 'if (!CA_OBJ_ISNAN(v) && (best_v == Qundef || ca_obj_cmp(v, best_v) > 0)) { best_v = v; best_i = idx; }' },
|
|
6813
7237
|
finish: "best_i",
|
|
6814
7238
|
source: MkKernel::ALL_NUMERIC + [:bool, :object],
|
|
6815
7239
|
output: :i64,
|
|
@@ -6818,38 +7242,9 @@ MkKernel.reduce :argmax_addr,
|
|
|
6818
7242
|
mask_policy: :min_count,
|
|
6819
7243
|
semantics: :view_flat,
|
|
6820
7244
|
face_gate: :strip,
|
|
7245
|
+
all_nan_result: :undef,
|
|
6821
7246
|
public_method: :max_addr
|
|
6822
7247
|
|
|
6823
|
-
# ---- mask_policy demos ------------------------------------------------
|
|
6824
|
-
|
|
6825
|
-
# sum_strict_ki -- :strict policy. Any masked input cell makes the
|
|
6826
|
-
# output cell UNDEF. Use case: "this is sensor data, any gap invalidates
|
|
6827
|
-
# the aggregate" (= NaN-propagation semantics). Sibling of
|
|
6828
|
-
# sum_ki which silently treats masked as 0.
|
|
6829
|
-
MkKernel.reduce :sum_strict,
|
|
6830
|
-
init: "0",
|
|
6831
|
-
reduce: "acc += v",
|
|
6832
|
-
source: MkKernel::ALL_NUMERIC + MkKernel::CMPLX_DTYPES,
|
|
6833
|
-
output: { numeric: :f64, complex: :cmplx128 },
|
|
6834
|
-
ruby_scalar: :auto,
|
|
6835
|
-
fallback: :raise,
|
|
6836
|
-
mask_policy: :strict
|
|
6837
|
-
|
|
6838
|
-
# mean_safe_ki -- :all_masked policy. Output UNDEF only when every
|
|
6839
|
-
# input cell is masked, otherwise mean over visible cells. Use case:
|
|
6840
|
-
# "tolerate gaps but flag if nothing is observed". Sibling of mean_ki
|
|
6841
|
-
# which returns 0.0 for all-masked slabs.
|
|
6842
|
-
MkKernel.reduce :mean_safe,
|
|
6843
|
-
state: { acc: :acc_type, cnt: :int64_t },
|
|
6844
|
-
init: { acc: "0", cnt: "0" },
|
|
6845
|
-
reduce: "(acc += v, cnt++)",
|
|
6846
|
-
finish: "cnt ? acc / (T_OUT) cnt : 0",
|
|
6847
|
-
source: MkKernel::ALL_NUMERIC + MkKernel::CMPLX_DTYPES,
|
|
6848
|
-
output: { numeric: :f64, complex: :cmplx128 },
|
|
6849
|
-
ruby_scalar: :auto,
|
|
6850
|
-
fallback: :raise,
|
|
6851
|
-
mask_policy: :all_masked
|
|
6852
|
-
|
|
6853
7248
|
# accumulate -- sum that preserves input data_type (legacy rb_ca_accum,
|
|
6854
7249
|
# retired in E.7). Same machinery as sum_ki but output: :preserve so
|
|
6855
7250
|
# int8 stays int8 (will overflow on long arrays = legacy behaviour).
|
|
@@ -6945,9 +7340,14 @@ MkKernel.reduce :count_false,
|
|
|
6945
7340
|
|
|
6946
7341
|
MkKernel.reduce :count_equal,
|
|
6947
7342
|
init: "0",
|
|
6948
|
-
|
|
7343
|
+
# The object lane compares with ca_obj_equal (= Ruby ==, a NaN equal to
|
|
7344
|
+
# nothing), not the C ==, which on two VALUEs would ask whether they are
|
|
7345
|
+
# the same object.
|
|
7346
|
+
reduce: { numeric: "if (v == value_arg) acc += 1",
|
|
7347
|
+
object: "if (ca_obj_equal(v, value_arg)) acc += 1" },
|
|
6949
7348
|
reduction_kind: :plus, # SL.1.4 (conditional predication; clang predicates safely under reduction(+:acc))
|
|
6950
|
-
source: MkKernel::ALL_NUMERIC,
|
|
7349
|
+
source: MkKernel::ALL_NUMERIC + [:object, :fixlen],
|
|
7350
|
+
fixlen: :count_equal, # bespoke memcmp walk (a blob has no scalar C type)
|
|
6951
7351
|
output: :i64,
|
|
6952
7352
|
ruby_scalar: :LL2NUM,
|
|
6953
7353
|
fallback: :raise,
|
|
@@ -7036,69 +7436,6 @@ MkKernel.reduce :wmean,
|
|
|
7036
7436
|
bind_ruby: true,
|
|
7037
7437
|
public_method: true
|
|
7038
7438
|
|
|
7039
|
-
# ---- map kernels (element-wise transforms) ----------------------------
|
|
7040
|
-
|
|
7041
|
-
# Float-output transcendentals -- input widens to double inside the
|
|
7042
|
-
# expression, output is float64. The :wrap_to_f64 fallback handles
|
|
7043
|
-
# any other numeric data_type (int16, bool, etc.) by promoting to f64 first.
|
|
7044
|
-
|
|
7045
|
-
MkKernel.map :sqrt,
|
|
7046
|
-
source: MkKernel::ALL_NUMERIC,
|
|
7047
|
-
output: :f64,
|
|
7048
|
-
expr: "r = sqrt((double) v)",
|
|
7049
|
-
fallback: :wrap_to_f64
|
|
7050
|
-
|
|
7051
|
-
MkKernel.map :sin,
|
|
7052
|
-
source: MkKernel::ALL_NUMERIC,
|
|
7053
|
-
output: :f64,
|
|
7054
|
-
expr: "r = sin((double) v)",
|
|
7055
|
-
fallback: :wrap_to_f64
|
|
7056
|
-
|
|
7057
|
-
MkKernel.map :cos,
|
|
7058
|
-
source: MkKernel::ALL_NUMERIC,
|
|
7059
|
-
output: :f64,
|
|
7060
|
-
expr: "r = cos((double) v)",
|
|
7061
|
-
fallback: :wrap_to_f64
|
|
7062
|
-
|
|
7063
|
-
MkKernel.map :exp,
|
|
7064
|
-
source: MkKernel::ALL_NUMERIC,
|
|
7065
|
-
output: :f64,
|
|
7066
|
-
expr: "r = exp((double) v)",
|
|
7067
|
-
fallback: :wrap_to_f64
|
|
7068
|
-
|
|
7069
|
-
MkKernel.map :log,
|
|
7070
|
-
source: MkKernel::ALL_NUMERIC,
|
|
7071
|
-
output: :f64,
|
|
7072
|
-
expr: "r = log((double) v)",
|
|
7073
|
-
fallback: :wrap_to_f64
|
|
7074
|
-
|
|
7075
|
-
# Source-data_type-preserving transforms -- arithmetic ops that don't need
|
|
7076
|
-
# floating-point. Fallback raises rather than silently widening.
|
|
7077
|
-
|
|
7078
|
-
MkKernel.map :square,
|
|
7079
|
-
source: MkKernel::ALL_NUMERIC,
|
|
7080
|
-
output: :preserve,
|
|
7081
|
-
expr: "r = v * v",
|
|
7082
|
-
fallback: :raise
|
|
7083
|
-
|
|
7084
|
-
MkKernel.map :abs,
|
|
7085
|
-
# Signed-only: `v < 0` is always false on unsigned types (compiler
|
|
7086
|
-
# warning + identity result). Use `negate` semantics for unsigned
|
|
7087
|
-
# only if you genuinely want wrap-around.
|
|
7088
|
-
source: MkKernel::SIGNED_NUMERIC,
|
|
7089
|
-
output: :preserve,
|
|
7090
|
-
expr: "r = (v < 0) ? -v : v",
|
|
7091
|
-
fallback: :raise
|
|
7092
|
-
|
|
7093
|
-
MkKernel.map :negate,
|
|
7094
|
-
# Signed-only: -v on unsigned types wraps (C semantics), which is
|
|
7095
|
-
# mathematically wrong for "negate". If you want bitwise inversion
|
|
7096
|
-
# on unsigned, use a different kernel (~v).
|
|
7097
|
-
source: MkKernel::SIGNED_NUMERIC,
|
|
7098
|
-
output: :preserve,
|
|
7099
|
-
expr: "r = -v",
|
|
7100
|
-
fallback: :raise
|
|
7101
|
-
|
|
7102
7439
|
# ---- scan kernels (cumulative / prefix scan along one axis) ----------
|
|
7103
7440
|
|
|
7104
7441
|
# cumsum / cumprod: data_type-conditional output (numeric -> f64 widening,
|
|
@@ -7140,37 +7477,58 @@ MkKernel.scan :cumprod,
|
|
|
7140
7477
|
# init Qnil is the "no running extremum yet" sentinel, also never leaked
|
|
7141
7478
|
# (unseen cells are masked). First unmasked cell adopts v as acc;
|
|
7142
7479
|
# subsequent unmasked cells compare via rb_funcall(:>) / rb_funcall(:<).
|
|
7480
|
+
# The float lane starts at NaN and folds with C99 fmin / fmax, whose
|
|
7481
|
+
# rule is exactly the one wanted: a lone number beats NaN, two NaNs give
|
|
7482
|
+
# NaN. So a prefix that has seen only NaN answers NaN instead of leaking
|
|
7483
|
+
# +/-INFINITY -- the running form of the rule min / max follow
|
|
7484
|
+
# (all_nan_result:). fmin / fmax are exact on a float at either width,
|
|
7485
|
+
# so the f32 lane needs no narrowing. Integer and boolean have no NaN
|
|
7486
|
+
# and keep the limit init and the plain compare.
|
|
7143
7487
|
MkKernel.scan :cummax,
|
|
7488
|
+
face_gate: :relift,
|
|
7144
7489
|
source: MkKernel::ALL_NUMERIC + [:bool, :object],
|
|
7145
7490
|
output: { bool: :u64, default: :preserve },
|
|
7146
|
-
init: { numeric: "T_LIMIT_LO", bool: "T_LIMIT_LO", object: "Qnil" },
|
|
7147
|
-
step: {
|
|
7491
|
+
init: { float: "NAN", numeric: "T_LIMIT_LO", bool: "T_LIMIT_LO", object: "Qnil" },
|
|
7492
|
+
step: { float: "acc = fmax(acc, v); r = acc",
|
|
7493
|
+
numeric: "if (v > acc) acc = v; r = acc",
|
|
7148
7494
|
bool: "if ((uint64_t) v > acc) acc = v; r = acc",
|
|
7149
|
-
object: 'if (acc == Qnil
|
|
7495
|
+
object: 'if (acc == Qnil || ca_obj_extremum_replaces(v, acc, rb_intern(">"))) acc = v; r = acc' },
|
|
7150
7496
|
fallback: :raise,
|
|
7151
7497
|
axis_default: :flatten,
|
|
7152
7498
|
empty: :undef
|
|
7153
7499
|
|
|
7500
|
+
# The float lane starts at NaN and folds with C99 fmin / fmax, whose
|
|
7501
|
+
# rule is exactly the one wanted: a lone number beats NaN, two NaNs give
|
|
7502
|
+
# NaN. So a prefix that has seen only NaN answers NaN instead of leaking
|
|
7503
|
+
# +/-INFINITY -- the running form of the rule min / max follow
|
|
7504
|
+
# (all_nan_result:). fmin / fmax are exact on a float at either width,
|
|
7505
|
+
# so the f32 lane needs no narrowing. Integer and boolean have no NaN
|
|
7506
|
+
# and keep the limit init and the plain compare.
|
|
7154
7507
|
MkKernel.scan :cummin,
|
|
7508
|
+
face_gate: :relift,
|
|
7155
7509
|
source: MkKernel::ALL_NUMERIC + [:bool, :object],
|
|
7156
7510
|
output: { bool: :u64, default: :preserve },
|
|
7157
|
-
init: { numeric: "T_LIMIT_HI", bool: "T_LIMIT_HI", object: "Qnil" },
|
|
7158
|
-
step: {
|
|
7511
|
+
init: { float: "NAN", numeric: "T_LIMIT_HI", bool: "T_LIMIT_HI", object: "Qnil" },
|
|
7512
|
+
step: { float: "acc = fmin(acc, v); r = acc",
|
|
7513
|
+
numeric: "if (v < acc) acc = v; r = acc",
|
|
7159
7514
|
bool: "if ((uint64_t) v < acc) acc = v; r = acc",
|
|
7160
|
-
object: 'if (acc == Qnil
|
|
7515
|
+
object: 'if (acc == Qnil || ca_obj_extremum_replaces(v, acc, rb_intern("<"))) acc = v; r = acc' },
|
|
7161
7516
|
fallback: :raise,
|
|
7162
7517
|
axis_default: :flatten,
|
|
7163
7518
|
empty: :undef
|
|
7164
7519
|
|
|
7165
7520
|
# cumcount: running count of unmasked cells. Output data_type = int64.
|
|
7166
7521
|
# `(void) v` silences unused-variable warning for the unmasked path
|
|
7167
|
-
# (compiler DCEs the v load when STEP doesn't reference it).
|
|
7522
|
+
# (compiler DCEs the v load when STEP doesn't reference it). The count
|
|
7523
|
+
# reads only the mask, so a data type outside source: (object, complex,
|
|
7524
|
+
# boolean, fixlen, a Face) is counted off the mask without converting its
|
|
7525
|
+
# values (fallback: :mask_count).
|
|
7168
7526
|
MkKernel.scan :cumcount,
|
|
7169
7527
|
source: MkKernel::ALL_NUMERIC,
|
|
7170
7528
|
output: :i64,
|
|
7171
7529
|
init: "0",
|
|
7172
7530
|
step: "(void) v; r = ++acc",
|
|
7173
|
-
fallback: :
|
|
7531
|
+
fallback: :mask_count,
|
|
7174
7532
|
axis_default: :flatten
|
|
7175
7533
|
|
|
7176
7534
|
# uniq_scan: per-axis adjacent-compare scan returning a boolean
|
|
@@ -7218,7 +7576,7 @@ MkKernel.sort :sort_index,
|
|
|
7218
7576
|
nan_policy: :end,
|
|
7219
7577
|
fallback: :raise,
|
|
7220
7578
|
mask_self: :sentinel, # masked cells sort to masked_position: :first/:last (default :last)
|
|
7221
|
-
public_method: :sort_index, # binds public sort_index(axis:
|
|
7579
|
+
public_method: :sort_index, # binds public sort_index(axis: nil, masked_position: :last) via _kw trampoline
|
|
7222
7580
|
c_callable: true # extern linkage for carray_order.c (nlargest / order family)
|
|
7223
7581
|
|
|
7224
7582
|
# sort_addr: per-axis argsort returning view-flat addresses (=
|
|
@@ -7226,7 +7584,7 @@ MkKernel.sort :sort_index,
|
|
|
7226
7584
|
# sort_index but the kernel writes view-flat addresses instead of
|
|
7227
7585
|
# fiber-local indices, so the output can feed directly into
|
|
7228
7586
|
# ca_remap_new as a CARemap.idx. Internal-only (bind_ruby: false):
|
|
7229
|
-
# used by `a.sort(axis: k)` at the C level via
|
|
7587
|
+
# used by `a.sort(axis: k)` at the C level via rb_ca_sort_addr_ki_*_mp.
|
|
7230
7588
|
# rev3 of PROPOSAL_SORT_AXIS defers `sort_addr(axis: k)` as a public
|
|
7231
7589
|
# method to a future phase; here we just need the kernel machinery.
|
|
7232
7590
|
MkKernel.sort :sort_addr,
|
|
@@ -7253,7 +7611,7 @@ MkKernel.sort :partition_index,
|
|
|
7253
7611
|
fallback: :raise,
|
|
7254
7612
|
algorithm: :partition,
|
|
7255
7613
|
mask_self: :sentinel, # masked cells sort to masked_position: :first/:last (default :last)
|
|
7256
|
-
public_method: :partition_index, # binds partition_index(kth, axis:
|
|
7614
|
+
public_method: :partition_index, # binds partition_index(kth, axis: nil, masked_position: :last) via _kw trampoline
|
|
7257
7615
|
c_callable: true # extern linkage for carray_order.c (nlargest / nsmallest)
|
|
7258
7616
|
|
|
7259
7617
|
# partition_addr: view-flat-address variant of partition_index, parallel
|
|
@@ -7295,7 +7653,7 @@ MkKernel.sort :rank_index,
|
|
|
7295
7653
|
fallback: :raise,
|
|
7296
7654
|
algorithm: :rank,
|
|
7297
7655
|
mask_self: :skip,
|
|
7298
|
-
public_method: :rank_index, # binds public rank_index(axis:
|
|
7656
|
+
public_method: :rank_index, # binds public rank_index(axis: nil) via _kw trampoline
|
|
7299
7657
|
c_callable: true # extern linkage for carray_order.c (order method)
|
|
7300
7658
|
|
|
7301
7659
|
# ---- search family ------------------------------------------------------
|
|
@@ -7332,7 +7690,7 @@ MkKernel.search :find_value_index,
|
|
|
7332
7690
|
result = (ca_size_t) -1;
|
|
7333
7691
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7334
7692
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7335
|
-
if (
|
|
7693
|
+
if ( ca_obj_equal(v, query_val) ) { result = i; break; }
|
|
7336
7694
|
}
|
|
7337
7695
|
C
|
|
7338
7696
|
fixlen: <<~C,
|
|
@@ -7398,19 +7756,18 @@ MkKernel.search :bsearch,
|
|
|
7398
7756
|
C
|
|
7399
7757
|
object: <<~C,
|
|
7400
7758
|
/* CA_OBJECT bsearch: ordering via rb_funcall(<=>). Assumes the
|
|
7401
|
-
slab is sorted by the same comparator used to build it
|
|
7402
|
-
|
|
7403
|
-
a Float-cell NaN would raise from <=>). */
|
|
7759
|
+
slab is sorted by the same comparator used to build it, which puts
|
|
7760
|
+
a Float NaN last; a NaN query matches nothing, as in float. */
|
|
7404
7761
|
result = (ca_size_t) -1;
|
|
7405
7762
|
ca_size_t lo = 0;
|
|
7406
|
-
ca_size_t hi = slab_n;
|
|
7763
|
+
ca_size_t hi = CA_OBJ_ISNAN(query_val) ? 0 : slab_n; /* NaN matches nothing */
|
|
7407
7764
|
while ( lo < hi ) {
|
|
7408
7765
|
ca_size_t mid = lo + (hi - lo) / 2;
|
|
7409
7766
|
T_LOAD v = *(T_LOAD *)(slab_ptr + mid * slab_stride);
|
|
7767
|
+
if ( CA_OBJ_ISNAN(v) ) { hi = mid; continue; } /* NaN sorts last */
|
|
7410
7768
|
VALUE r = rb_funcall(v, rb_intern("<=>"), 1, query_val);
|
|
7411
7769
|
if ( NIL_P(r) ) {
|
|
7412
|
-
|
|
7413
|
-
rb_obj_classname(v), rb_obj_classname(query_val));
|
|
7770
|
+
ca_obj_compare_refused(v, query_val);
|
|
7414
7771
|
}
|
|
7415
7772
|
int c = NUM2INT(r);
|
|
7416
7773
|
if ( c < 0 ) lo = mid + 1;
|
|
@@ -7447,7 +7804,7 @@ MkKernel.search :bsearch,
|
|
|
7447
7804
|
#
|
|
7448
7805
|
# Per-data_type body: int uses exact `v == query_val` (eps ignored); float
|
|
7449
7806
|
# uses `fabs(v - query_val) <= query_eps` (eps_default = FLT_EPSILON*|val|
|
|
7450
|
-
# or DBL_EPSILON*|val|); object uses
|
|
7807
|
+
# or DBL_EPSILON*|val|); object uses ca_obj_equal (eps ignored); fixlen uses
|
|
7451
7808
|
# memcmp exact match (eps ignored). Sole search implementation: the
|
|
7452
7809
|
# no-axis (flat) surface routes here via flatten + axis 0
|
|
7453
7810
|
# (PROPOSAL_SEARCH_SEMANTICS_UNIFY S2/S3; the legacy flat scan was removed).
|
|
@@ -7469,18 +7826,18 @@ MkKernel.search :search,
|
|
|
7469
7826
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7470
7827
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7471
7828
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7472
|
-
if ( fabs((double)(v - query_val)) <= query_eps ) { result = i; break; }
|
|
7829
|
+
if ( v == query_val || fabs((double)(v - query_val)) <= query_eps ) { result = i; break; }
|
|
7473
7830
|
}
|
|
7474
7831
|
C
|
|
7475
7832
|
object: <<~C,
|
|
7476
|
-
/* CA_OBJECT search: exact equality via
|
|
7833
|
+
/* CA_OBJECT search: exact equality via ca_obj_equal. eps is silently
|
|
7477
7834
|
ignored (= no meaningful tolerance on arbitrary Ruby objects). */
|
|
7478
7835
|
result = (ca_size_t) -1;
|
|
7479
7836
|
(void) query_eps;
|
|
7480
7837
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7481
7838
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7482
7839
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7483
|
-
if (
|
|
7840
|
+
if ( ca_obj_equal(v, query_val) ) { result = i; break; }
|
|
7484
7841
|
}
|
|
7485
7842
|
C
|
|
7486
7843
|
fixlen: <<~C,
|
|
@@ -7519,13 +7876,16 @@ MkKernel.search :search_nearest,
|
|
|
7519
7876
|
output: :ca_size,
|
|
7520
7877
|
body: {
|
|
7521
7878
|
int: <<~C,
|
|
7879
|
+
/* The distance is exact in uint64: a double loses the difference
|
|
7880
|
+
between neighbouring int64 values above 2**53. */
|
|
7522
7881
|
result = (ca_size_t) -1;
|
|
7523
|
-
|
|
7882
|
+
uint64_t best_dist = 0;
|
|
7524
7883
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7525
7884
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7526
7885
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7527
|
-
|
|
7528
|
-
|
|
7886
|
+
uint64_t dist = ( v >= query_val ) ? (uint64_t) v - (uint64_t) query_val
|
|
7887
|
+
: (uint64_t) query_val - (uint64_t) v;
|
|
7888
|
+
if ( result == (ca_size_t) -1 || dist < best_dist ) { best_dist = dist; result = i; }
|
|
7529
7889
|
}
|
|
7530
7890
|
(void) query_eps; /* unused: search_nearest has no eps semantics */
|
|
7531
7891
|
C
|
|
@@ -7535,20 +7895,45 @@ MkKernel.search :search_nearest,
|
|
|
7535
7895
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7536
7896
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7537
7897
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7538
|
-
|
|
7539
|
-
|
|
7898
|
+
/* An equal value is at distance 0 even when both are infinite
|
|
7899
|
+
(Inf - Inf is NaN); a NaN distance never wins. */
|
|
7900
|
+
double dist = ( v == query_val ) ? 0.0 : fabs((double)v - (double)query_val);
|
|
7901
|
+
if ( dist != dist ) continue;
|
|
7902
|
+
if ( result == (ca_size_t) -1 || dist < best_dist ) { best_dist = dist; result = i; }
|
|
7540
7903
|
}
|
|
7541
7904
|
(void) query_eps; /* unused: search_nearest has no eps semantics */
|
|
7542
7905
|
C
|
|
7543
7906
|
object: <<~C,
|
|
7544
7907
|
/* CA_OBJECT nearest: minimum of query_val.distance(cell), compared
|
|
7545
7908
|
with `<` (matches the legacy flat proc_nearest_addr_VALUE). */
|
|
7909
|
+
/* Nearest needs a metric. #distance is the protocol the 2.0 flat
|
|
7910
|
+
kernel used, back when Numeric#distance was a monkey patch; it
|
|
7911
|
+
is an opt-in refinement now, and a refinement does not reach an
|
|
7912
|
+
rb_funcall from C -- so a number reaching here answers no more
|
|
7913
|
+
than a String does. Measure a number the way #distance itself
|
|
7914
|
+
does, keep #distance for anything that defines a real one, and
|
|
7915
|
+
refuse the rest by name rather than let a bare NoMethodError out
|
|
7916
|
+
of the loop below. */
|
|
7917
|
+
ID nearest_id = rb_intern("distance");
|
|
7918
|
+
int nearest_by_distance = rb_respond_to(query_val, nearest_id);
|
|
7919
|
+
if ( ! nearest_by_distance && ! rb_obj_is_kind_of(query_val, rb_cNumeric) ) {
|
|
7920
|
+
rb_raise(rb_eCADataTypeError,
|
|
7921
|
+
"%s: nearest needs a distance, and %s is neither a "
|
|
7922
|
+
"number nor answers #distance (define one on the stored "
|
|
7923
|
+
"objects, or use search / bsearch for an exact match)",
|
|
7924
|
+
ca_calling_method_name(), rb_obj_classname(query_val));
|
|
7925
|
+
}
|
|
7546
7926
|
result = (ca_size_t) -1;
|
|
7547
7927
|
VALUE best = Qnil;
|
|
7548
7928
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7549
7929
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7550
7930
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7551
|
-
VALUE dist =
|
|
7931
|
+
VALUE dist = ca_obj_equal(v, query_val) ? INT2FIX(0)
|
|
7932
|
+
: nearest_by_distance
|
|
7933
|
+
? rb_funcall(query_val, nearest_id, 1, v)
|
|
7934
|
+
: rb_funcall(rb_funcall(query_val, '-', 1, v),
|
|
7935
|
+
rb_intern("abs"), 0);
|
|
7936
|
+
if ( CA_OBJ_ISNAN(dist) ) continue; /* a NaN is no distance, as in float */
|
|
7552
7937
|
if ( NIL_P(best) || RTEST(rb_funcall(dist, rb_intern("<"), 1, best)) ) {
|
|
7553
7938
|
best = dist; result = i;
|
|
7554
7939
|
}
|
|
@@ -7611,14 +7996,14 @@ MkKernel.search :bsearch_addr,
|
|
|
7611
7996
|
object: <<~C,
|
|
7612
7997
|
result = (ca_size_t) -1;
|
|
7613
7998
|
ca_size_t lo = 0;
|
|
7614
|
-
ca_size_t hi = slab_n;
|
|
7999
|
+
ca_size_t hi = CA_OBJ_ISNAN(query_val) ? 0 : slab_n; /* NaN matches nothing */
|
|
7615
8000
|
while ( lo < hi ) {
|
|
7616
8001
|
ca_size_t mid = lo + (hi - lo) / 2;
|
|
7617
8002
|
T_LOAD v = *(T_LOAD *)(slab_ptr + mid * slab_stride);
|
|
8003
|
+
if ( CA_OBJ_ISNAN(v) ) { hi = mid; continue; } /* NaN sorts last */
|
|
7618
8004
|
VALUE r = rb_funcall(v, rb_intern("<=>"), 1, query_val);
|
|
7619
8005
|
if ( NIL_P(r) ) {
|
|
7620
|
-
|
|
7621
|
-
rb_obj_classname(v), rb_obj_classname(query_val));
|
|
8006
|
+
ca_obj_compare_refused(v, query_val);
|
|
7622
8007
|
}
|
|
7623
8008
|
int c = NUM2INT(r);
|
|
7624
8009
|
if ( c < 0 ) lo = mid + 1;
|
|
@@ -7665,7 +8050,7 @@ MkKernel.search :search_addr,
|
|
|
7665
8050
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7666
8051
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7667
8052
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7668
|
-
if ( fabs((double)v - (double)query_val) <= query_eps ) { result = i; break; }
|
|
8053
|
+
if ( v == query_val || fabs((double)v - (double)query_val) <= query_eps ) { result = i; break; }
|
|
7669
8054
|
}
|
|
7670
8055
|
C
|
|
7671
8056
|
object: <<~C,
|
|
@@ -7674,7 +8059,7 @@ MkKernel.search :search_addr,
|
|
|
7674
8059
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7675
8060
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7676
8061
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7677
|
-
if (
|
|
8062
|
+
if ( ca_obj_equal(v, query_val) ) { result = i; break; }
|
|
7678
8063
|
}
|
|
7679
8064
|
C
|
|
7680
8065
|
fixlen: <<~C,
|
|
@@ -7700,13 +8085,16 @@ MkKernel.search :search_nearest_addr,
|
|
|
7700
8085
|
output: :ca_size,
|
|
7701
8086
|
body: {
|
|
7702
8087
|
int: <<~C,
|
|
8088
|
+
/* The distance is exact in uint64: a double loses the difference
|
|
8089
|
+
between neighbouring int64 values above 2**53. */
|
|
7703
8090
|
result = (ca_size_t) -1;
|
|
7704
|
-
|
|
8091
|
+
uint64_t best_dist = 0;
|
|
7705
8092
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7706
8093
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7707
8094
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7708
|
-
|
|
7709
|
-
|
|
8095
|
+
uint64_t dist = ( v >= query_val ) ? (uint64_t) v - (uint64_t) query_val
|
|
8096
|
+
: (uint64_t) query_val - (uint64_t) v;
|
|
8097
|
+
if ( result == (ca_size_t) -1 || dist < best_dist ) { best_dist = dist; result = i; }
|
|
7710
8098
|
}
|
|
7711
8099
|
(void) query_eps;
|
|
7712
8100
|
C
|
|
@@ -7716,20 +8104,45 @@ MkKernel.search :search_nearest_addr,
|
|
|
7716
8104
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7717
8105
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7718
8106
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7719
|
-
|
|
7720
|
-
|
|
8107
|
+
/* An equal value is at distance 0 even when both are infinite
|
|
8108
|
+
(Inf - Inf is NaN); a NaN distance never wins. */
|
|
8109
|
+
double dist = ( v == query_val ) ? 0.0 : fabs((double)v - (double)query_val);
|
|
8110
|
+
if ( dist != dist ) continue;
|
|
8111
|
+
if ( result == (ca_size_t) -1 || dist < best_dist ) { best_dist = dist; result = i; }
|
|
7721
8112
|
}
|
|
7722
8113
|
(void) query_eps;
|
|
7723
8114
|
C
|
|
7724
8115
|
object: <<~C,
|
|
7725
8116
|
/* CA_OBJECT nearest (view_flat addr): minimum of
|
|
7726
8117
|
query_val.distance(cell), compared with `<`. */
|
|
8118
|
+
/* Nearest needs a metric. #distance is the protocol the 2.0 flat
|
|
8119
|
+
kernel used, back when Numeric#distance was a monkey patch; it
|
|
8120
|
+
is an opt-in refinement now, and a refinement does not reach an
|
|
8121
|
+
rb_funcall from C -- so a number reaching here answers no more
|
|
8122
|
+
than a String does. Measure a number the way #distance itself
|
|
8123
|
+
does, keep #distance for anything that defines a real one, and
|
|
8124
|
+
refuse the rest by name rather than let a bare NoMethodError out
|
|
8125
|
+
of the loop below. */
|
|
8126
|
+
ID nearest_id = rb_intern("distance");
|
|
8127
|
+
int nearest_by_distance = rb_respond_to(query_val, nearest_id);
|
|
8128
|
+
if ( ! nearest_by_distance && ! rb_obj_is_kind_of(query_val, rb_cNumeric) ) {
|
|
8129
|
+
rb_raise(rb_eCADataTypeError,
|
|
8130
|
+
"%s: nearest needs a distance, and %s is neither a "
|
|
8131
|
+
"number nor answers #distance (define one on the stored "
|
|
8132
|
+
"objects, or use search / bsearch for an exact match)",
|
|
8133
|
+
ca_calling_method_name(), rb_obj_classname(query_val));
|
|
8134
|
+
}
|
|
7727
8135
|
result = (ca_size_t) -1;
|
|
7728
8136
|
VALUE best = Qnil;
|
|
7729
8137
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7730
8138
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7731
8139
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7732
|
-
VALUE dist =
|
|
8140
|
+
VALUE dist = ca_obj_equal(v, query_val) ? INT2FIX(0)
|
|
8141
|
+
: nearest_by_distance
|
|
8142
|
+
? rb_funcall(query_val, nearest_id, 1, v)
|
|
8143
|
+
: rb_funcall(rb_funcall(query_val, '-', 1, v),
|
|
8144
|
+
rb_intern("abs"), 0);
|
|
8145
|
+
if ( CA_OBJ_ISNAN(dist) ) continue; /* a NaN is no distance, as in float */
|
|
7733
8146
|
if ( NIL_P(best) || RTEST(rb_funcall(dist, rb_intern("<"), 1, best)) ) {
|
|
7734
8147
|
best = dist; result = i;
|
|
7735
8148
|
}
|
|
@@ -7779,7 +8192,11 @@ MkKernel.search :linear_section_binary,
|
|
|
7779
8192
|
body: <<~C,
|
|
7780
8193
|
result = NAN;
|
|
7781
8194
|
if ( slab_n <= 1 ) {
|
|
7782
|
-
|
|
8195
|
+
/* A one-point axis has no segment: only the point itself is in
|
|
8196
|
+
* range, at 0. An empty one has nothing in range. */
|
|
8197
|
+
if ( slab_n == 1 && query_val == *(double *)slab_ptr ) {
|
|
8198
|
+
result = 0.0;
|
|
8199
|
+
}
|
|
7783
8200
|
}
|
|
7784
8201
|
else {
|
|
7785
8202
|
double y0 = *(double *)(slab_ptr + 0 * slab_stride);
|
|
@@ -7867,7 +8284,11 @@ MkKernel.search :linear_section_linear,
|
|
|
7867
8284
|
body: <<~C,
|
|
7868
8285
|
result = NAN;
|
|
7869
8286
|
if ( slab_n <= 1 ) {
|
|
7870
|
-
|
|
8287
|
+
/* A one-point axis has no segment: only the point itself is in
|
|
8288
|
+
* range, at 0. An empty one has nothing in range. */
|
|
8289
|
+
if ( slab_n == 1 && query_val == *(double *)slab_ptr ) {
|
|
8290
|
+
result = 0.0;
|
|
8291
|
+
}
|
|
7871
8292
|
}
|
|
7872
8293
|
else {
|
|
7873
8294
|
ca_size_t x1 = 0;
|
|
@@ -7997,9 +8418,10 @@ MkKernel.monop :abs_i,
|
|
|
7997
8418
|
# (= used by .real chain composition); abs is the user-facing op that
|
|
7998
8419
|
# returns the real-valued magnitude entity.
|
|
7999
8420
|
MkKernel.monop :abs,
|
|
8000
|
-
source: MkKernel::ALL_NUMERIC + MkKernel::CMPLX_DTYPES,
|
|
8001
|
-
output: { numeric: :preserve, complex: :real_of_source },
|
|
8421
|
+
source: MkKernel::ALL_NUMERIC + MkKernel::CMPLX_DTYPES + [:object],
|
|
8422
|
+
output: { numeric: :preserve, complex: :real_of_source, object: :preserve },
|
|
8002
8423
|
expr: {
|
|
8424
|
+
[:object] => '(#2) = rb_funcall((#1), rb_intern("abs"), 0);',
|
|
8003
8425
|
MkKernel::SINT_SMALL_DTYPES => "(#2) = abs(#1);",
|
|
8004
8426
|
MkKernel::SINT64_DTYPES => "(#2) = llabs(#1);",
|
|
8005
8427
|
MkKernel::UINT_DTYPES => "(#2) = (#1);",
|
|
@@ -8115,7 +8537,9 @@ MkKernel.monop :imag_i,
|
|
|
8115
8537
|
MkKernel.monop :arg_i,
|
|
8116
8538
|
source: MkKernel::FLOAT_DTYPES + MkKernel::CMPLX_DTYPES,
|
|
8117
8539
|
expr: {
|
|
8118
|
-
|
|
8540
|
+
# A real cell's argument is 0, pi or NaN. Taken in double and rounded,
|
|
8541
|
+
# as the eager arg does; cargf returns pi one ulp low.
|
|
8542
|
+
[:f32] => "(#2) = carg((cmplx128_t)(#1));",
|
|
8119
8543
|
[:f64] => "(#2) = carg((cmplx128_t)(#1));",
|
|
8120
8544
|
[:cmplx64] => "(#2) = cargf(#1);",
|
|
8121
8545
|
[:cmplx128] => "(#2) = carg(#1);",
|
|
@@ -8171,7 +8595,9 @@ MkKernel.monfunc :round,
|
|
|
8171
8595
|
MkKernel.monfunc :rcp,
|
|
8172
8596
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
8173
8597
|
expr: {
|
|
8174
|
-
|
|
8598
|
+
MkKernel::UINT_DTYPES => "if ((#1)==0) {ca_zerodiv();}; (#2) = 1/(#1);",
|
|
8599
|
+
# 1 / x floored: 1 for 1, -1 for every negative x, 0 otherwise.
|
|
8600
|
+
int: "if ((#1)==0) {ca_zerodiv();}; (#2) = ((#1)==1) ? 1 : ((#1) < 0) ? -1 : 0;",
|
|
8175
8601
|
float: "(#2) = 1/(#1);",
|
|
8176
8602
|
[:cmplx64] => "(#2) = op_crcp_cmplx64(#1);",
|
|
8177
8603
|
[:cmplx128] => "(#2) = 1/(#1);",
|
|
@@ -8280,11 +8706,13 @@ end
|
|
|
8280
8706
|
|
|
8281
8707
|
# expm1, log1p: float + object only (C99 doesn't standardise complex
|
|
8282
8708
|
# variants). Widening monfunc — integer input auto-casts to f64.
|
|
8709
|
+
# CAMath.expm1 / log1p are defined in lib/carray/math.rb instead: Ruby's
|
|
8710
|
+
# Math has neither, so the generated front-end could not take a Numeric.
|
|
8283
8711
|
{
|
|
8284
8712
|
expm1: "expm1",
|
|
8285
8713
|
log1p: "log1p",
|
|
8286
8714
|
}.each do |op, c_fn|
|
|
8287
|
-
MkKernel.monfunc op,
|
|
8715
|
+
MkKernel.monfunc op, cmath: false,
|
|
8288
8716
|
source: MkKernel::FLOAT_DTYPES + [:object],
|
|
8289
8717
|
expr: {
|
|
8290
8718
|
object: MkKernel.obj_float_math("#{c_fn}(<v>)", c_fn),
|
|
@@ -8469,14 +8897,15 @@ MkKernel.monfunc :rad_pi,
|
|
|
8469
8897
|
# NaN the result is NaN. Distinct kernel — cannot be
|
|
8470
8898
|
# aliased onto pmax.
|
|
8471
8899
|
#
|
|
8472
|
-
# Integer
|
|
8473
|
-
#
|
|
8900
|
+
# Integer branches are identical across all three (no NaN). The float
|
|
8901
|
+
# and object branches differ: pmax / pmin let a NaN lose (an object cell's
|
|
8902
|
+
# Float NaN included), maximum / minimum let it win.
|
|
8474
8903
|
MkKernel.binop :pmax,
|
|
8475
8904
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8476
8905
|
expr: {
|
|
8477
8906
|
int: "(#3) = (#1) > (#2) ? (#1) : (#2);",
|
|
8478
8907
|
float: "(#3) = fmax(#1, #2);",
|
|
8479
|
-
object: '(#3) =
|
|
8908
|
+
object: '(#3) = ca_obj_extremum_replaces((#2), (#1), rb_intern(">")) ? (#2) : (#1);',
|
|
8480
8909
|
}
|
|
8481
8910
|
|
|
8482
8911
|
MkKernel.binop :pmin,
|
|
@@ -8484,26 +8913,28 @@ MkKernel.binop :pmin,
|
|
|
8484
8913
|
expr: {
|
|
8485
8914
|
int: "(#3) = (#1) < (#2) ? (#1) : (#2);",
|
|
8486
8915
|
float: "(#3) = fmin(#1, #2);",
|
|
8487
|
-
object: '(#3) =
|
|
8916
|
+
object: '(#3) = ca_obj_extremum_replaces((#2), (#1), rb_intern("<")) ? (#2) : (#1);',
|
|
8488
8917
|
}
|
|
8489
8918
|
|
|
8490
8919
|
# NaN-propagate variants. Float
|
|
8491
8920
|
# branch tests both operands for NaN and short-circuits to NaN; non-
|
|
8492
|
-
# float branches reuse the comparison form.
|
|
8921
|
+
# float branches reuse the comparison form. Of two zeros, maximum is +0
|
|
8922
|
+
# and minimum is -0 in either order: a bare comparison would answer with
|
|
8923
|
+
# whichever came second.
|
|
8493
8924
|
MkKernel.binop :maximum,
|
|
8494
8925
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8495
8926
|
expr: {
|
|
8496
8927
|
int: "(#3) = (#1) > (#2) ? (#1) : (#2);",
|
|
8497
|
-
float: "(#3) = isnan(#1) ? (#1) :
|
|
8498
|
-
object: '(#3) =
|
|
8928
|
+
float: "(#3) = isnan(#1) ? (#1) : isnan(#2) ? (#2) : ((#1) > (#2) || ((#1) == (#2) && !signbit(#1))) ? (#1) : (#2);",
|
|
8929
|
+
object: '(#3) = CA_OBJ_ISNAN(#1) ? (#1) : CA_OBJ_ISNAN(#2) ? (#2) : ca_obj_cmp((#2), (#1)) > 0 ? (#2) : (#1);',
|
|
8499
8930
|
}
|
|
8500
8931
|
|
|
8501
8932
|
MkKernel.binop :minimum,
|
|
8502
8933
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8503
8934
|
expr: {
|
|
8504
8935
|
int: "(#3) = (#1) < (#2) ? (#1) : (#2);",
|
|
8505
|
-
float: "(#3) = isnan(#1) ? (#1) :
|
|
8506
|
-
object: '(#3) =
|
|
8936
|
+
float: "(#3) = isnan(#1) ? (#1) : isnan(#2) ? (#2) : ((#1) < (#2) || ((#1) == (#2) && signbit(#1))) ? (#1) : (#2);",
|
|
8937
|
+
object: '(#3) = CA_OBJ_ISNAN(#1) ? (#1) : CA_OBJ_ISNAN(#2) ? (#2) : ca_obj_cmp((#2), (#1)) < 0 ? (#2) : (#1);',
|
|
8507
8938
|
}
|
|
8508
8939
|
|
|
8509
8940
|
# + and - work on the parts independently, so one generic expression
|
|
@@ -8544,6 +8975,11 @@ MkKernel.binop :mul,
|
|
|
8544
8975
|
# sign, so they keep the bare C form. Float `/` is true division and is
|
|
8545
8976
|
# unchanged (matching Ruby `Float#/`); the identity above therefore holds
|
|
8546
8977
|
# for integers only, exactly as in Ruby.
|
|
8978
|
+
#
|
|
8979
|
+
# A signed divisor of -1 is taken apart from C's `/`: MIN / -1 does not
|
|
8980
|
+
# fit, and the hardware's answer to it differs (x86 traps, ARM returns
|
|
8981
|
+
# MIN). The quotient is the negation wrapped like any other overflow, so
|
|
8982
|
+
# MIN / -1 is MIN and MIN % -1 is 0 on every machine.
|
|
8547
8983
|
MkKernel.binop :div,
|
|
8548
8984
|
op: "/",
|
|
8549
8985
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
@@ -8551,7 +8987,8 @@ MkKernel.binop :div,
|
|
|
8551
8987
|
MkKernel::UINT_DTYPES => "if ((#2)==0) {ca_zerodiv();}; (#3) = (#1) / (#2);",
|
|
8552
8988
|
int: %{
|
|
8553
8989
|
if ((#2)==0) {ca_zerodiv();};
|
|
8554
|
-
{
|
|
8990
|
+
if ((#2)==-1) { (#3) = (<type>) (0 - (uint64_t) (#1)); }
|
|
8991
|
+
else {
|
|
8555
8992
|
<type> _q = (#1) / (#2);
|
|
8556
8993
|
if ( (#1) % (#2) != 0 && (((#1) < 0) != ((#2) < 0)) ) { _q -= 1; }
|
|
8557
8994
|
(#3) = _q;
|
|
@@ -8577,11 +9014,21 @@ MkKernel.binop :quo_i,
|
|
|
8577
9014
|
object: '(#3) = rb_funcall((#1), rb_intern("quo"), 1, (#2));',
|
|
8578
9015
|
}
|
|
8579
9016
|
|
|
9017
|
+
# rcp_mul: `a.rcp_mul(b)` is `b / a`, and divides as `/` does.
|
|
8580
9018
|
MkKernel.binop :rcp_mul,
|
|
8581
9019
|
op: "rcp_mul",
|
|
8582
9020
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
8583
9021
|
expr: {
|
|
8584
|
-
|
|
9022
|
+
MkKernel::UINT_DTYPES => "if ((#1)==0) {ca_zerodiv();}; (#3) = (#2) / (#1);",
|
|
9023
|
+
int: %{
|
|
9024
|
+
if ((#1)==0) {ca_zerodiv();};
|
|
9025
|
+
if ((#1)==-1) { (#3) = (<type>) (0 - (uint64_t) (#2)); }
|
|
9026
|
+
else {
|
|
9027
|
+
<type> _q = (#2) / (#1);
|
|
9028
|
+
if ( (#2) % (#1) != 0 && (((#2) < 0) != ((#1) < 0)) ) { _q -= 1; }
|
|
9029
|
+
(#3) = _q;
|
|
9030
|
+
}
|
|
9031
|
+
},
|
|
8585
9032
|
float: "(#3) = (#2) / (#1);",
|
|
8586
9033
|
[:cmplx64] => "(#3) = op_cdiv_cmplx64((#2), (#1));",
|
|
8587
9034
|
[:cmplx128] => "(#3) = (#2) / (#1);",
|
|
@@ -8603,7 +9050,8 @@ MkKernel.binop :mod,
|
|
|
8603
9050
|
MkKernel::UINT_DTYPES => "if ((#2)==0) {ca_zerodiv();}; (#3) = (#1) % (#2);",
|
|
8604
9051
|
int: %{
|
|
8605
9052
|
if ((#2)==0) {ca_zerodiv();};
|
|
8606
|
-
{
|
|
9053
|
+
if ((#2)==-1) { (#3) = 0; }
|
|
9054
|
+
else {
|
|
8607
9055
|
<type> _r = (#1) % (#2);
|
|
8608
9056
|
if ( _r != 0 && ((_r < 0) != ((#2) < 0)) ) { _r += (#2); }
|
|
8609
9057
|
(#3) = _r;
|
|
@@ -8631,7 +9079,8 @@ MkKernel.binop :mod,
|
|
|
8631
9079
|
MkKernel.binop :fmod,
|
|
8632
9080
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8633
9081
|
expr: {
|
|
8634
|
-
|
|
9082
|
+
MkKernel::UINT_DTYPES => "if ((#2)==0) {ca_zerodiv();}; (#3) = (#1) % (#2);",
|
|
9083
|
+
int: "if ((#2)==0) {ca_zerodiv();}; (#3) = ((#2)==-1) ? 0 : (#1) % (#2);",
|
|
8635
9084
|
object: '(#3) = rb_funcall((#1), rb_intern("remainder"), 1, (#2));',
|
|
8636
9085
|
}.merge(MkKernel.float_widths("(#3) = fmod<f>((#1), (#2));"))
|
|
8637
9086
|
|
|
@@ -8664,11 +9113,29 @@ MkKernel.binop :bit_xor_i,
|
|
|
8664
9113
|
object: '(#3) = rb_funcall((#1), rb_intern("^"), 1, (#2));',
|
|
8665
9114
|
}
|
|
8666
9115
|
|
|
9116
|
+
# Shifts take the meaning Integer#<< and #>> give them: a negative count
|
|
9117
|
+
# shifts the other way, and a count of the width or more shifts every bit
|
|
9118
|
+
# out (0, or -1 for a negative value shifted right). C leaves both, and a
|
|
9119
|
+
# left shift of a negative value, undefined. The value is widened to 64
|
|
9120
|
+
# bits and shifted there -- unsigned for a left shift -- and the result
|
|
9121
|
+
# narrowed back, which keeps the low bits a shift within the width gives.
|
|
9122
|
+
# The bodies are self-contained, since carray-jit compiles them as they are.
|
|
8667
9123
|
MkKernel.binop :bit_lshift,
|
|
8668
9124
|
op: "<<",
|
|
8669
9125
|
source: MkKernel::INT_DTYPES + [:object],
|
|
8670
9126
|
expr: {
|
|
8671
|
-
|
|
9127
|
+
MkKernel::UINT_DTYPES => "(#3) = ((uint64_t) (#2) >= 8 * sizeof(<type>)) ? 0 : (<type>) ((uint64_t) (#1) << (#2));",
|
|
9128
|
+
int: %{
|
|
9129
|
+
{
|
|
9130
|
+
int64_t _x = (int64_t) (#1), _n = (int64_t) (#2), _r;
|
|
9131
|
+
const int64_t _w = (int64_t) (8 * sizeof(<type>));
|
|
9132
|
+
if ( _n >= _w ) _r = 0;
|
|
9133
|
+
else if ( _n <= -_w ) _r = _x < 0 ? -1 : 0;
|
|
9134
|
+
else if ( _n < 0 ) _r = _x >> -_n;
|
|
9135
|
+
else _r = (int64_t) ((uint64_t) _x << _n);
|
|
9136
|
+
(#3) = (<type>) _r;
|
|
9137
|
+
}
|
|
9138
|
+
},
|
|
8672
9139
|
object: '(#3) = rb_funcall((#1), rb_intern("<<"), 1, (#2));',
|
|
8673
9140
|
}
|
|
8674
9141
|
|
|
@@ -8676,7 +9143,18 @@ MkKernel.binop :bit_rshift,
|
|
|
8676
9143
|
op: ">>",
|
|
8677
9144
|
source: MkKernel::INT_DTYPES + [:object],
|
|
8678
9145
|
expr: {
|
|
8679
|
-
|
|
9146
|
+
MkKernel::UINT_DTYPES => "(#3) = ((uint64_t) (#2) >= 8 * sizeof(<type>)) ? 0 : (<type>) ((uint64_t) (#1) >> (#2));",
|
|
9147
|
+
int: %{
|
|
9148
|
+
{
|
|
9149
|
+
int64_t _x = (int64_t) (#1), _n = (int64_t) (#2), _r;
|
|
9150
|
+
const int64_t _w = (int64_t) (8 * sizeof(<type>));
|
|
9151
|
+
if ( _n >= _w ) _r = _x < 0 ? -1 : 0;
|
|
9152
|
+
else if ( _n <= -_w ) _r = 0;
|
|
9153
|
+
else if ( _n < 0 ) _r = (int64_t) ((uint64_t) _x << -_n);
|
|
9154
|
+
else _r = _x >> _n;
|
|
9155
|
+
(#3) = (<type>) _r;
|
|
9156
|
+
}
|
|
9157
|
+
},
|
|
8680
9158
|
object: '(#3) = rb_funcall((#1), rb_intern(">>"), 1, (#2));',
|
|
8681
9159
|
}
|
|
8682
9160
|
|
|
@@ -8746,16 +9224,16 @@ C
|
|
|
8746
9224
|
# fma: fused multiply-add — `self * op2 + op3` in a single rounding step
|
|
8747
9225
|
# (= C99 `fma(x, y, z)` and HW FMA3/FMA4 / ARM FMA instructions). More
|
|
8748
9226
|
# accurate than the equivalent two-op `a*b + c` expression which rounds
|
|
8749
|
-
# twice.
|
|
9227
|
+
# twice. float32 calls fmaf: fma in double followed by a narrowing is a
|
|
9228
|
+
# second rounding, and can land 1 ulp off. Used in Horner evaluation, linear interpolation, dot product,
|
|
8750
9229
|
# Newton iteration.
|
|
8751
9230
|
MkKernel.triop :fma,
|
|
8752
9231
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
8753
9232
|
expr: {
|
|
8754
9233
|
int: "(#4) = (#1) * (#2) + (#3);",
|
|
8755
|
-
float: "(#4) = fma((#1), (#2), (#3));",
|
|
8756
9234
|
complex: "(#4) = (#1) * (#2) + (#3);",
|
|
8757
9235
|
object: '(#4) = rb_funcall(rb_funcall((#1), rb_intern("*"), 1, (#2)), rb_intern("+"), 1, (#3));',
|
|
8758
|
-
}
|
|
9236
|
+
}.merge(MkKernel.float_widths("(#4) = fma<f>((#1), (#2), (#3));"))
|
|
8759
9237
|
|
|
8760
9238
|
# fms: fused multiply-subtract — `self * op2 - op3` in a single rounding.
|
|
8761
9239
|
# Useful in cross products and complex multiplication primitives.
|
|
@@ -8763,11 +9241,10 @@ MkKernel.triop :fms,
|
|
|
8763
9241
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
8764
9242
|
expr: {
|
|
8765
9243
|
int: "(#4) = (#1) * (#2) - (#3);",
|
|
8766
|
-
# fma(a, b, -c) = a*b - c with single rounding (= C99 / IEEE 754 hint).
|
|
8767
|
-
float: "(#4) = fma((#1), (#2), -(#3));",
|
|
8768
9244
|
complex: "(#4) = (#1) * (#2) - (#3);",
|
|
8769
9245
|
object: '(#4) = rb_funcall(rb_funcall((#1), rb_intern("*"), 1, (#2)), rb_intern("-"), 1, (#3));',
|
|
8770
|
-
|
|
9246
|
+
# fma(a, b, -c) = a*b - c with single rounding (= C99 / IEEE 754 hint).
|
|
9247
|
+
}.merge(MkKernel.float_widths("(#4) = fma<f>((#1), (#2), -(#3));"))
|
|
8771
9248
|
|
|
8772
9249
|
# clip: per-element clamp into [lo, hi]. 3.0 migration: was a hand-
|
|
8773
9250
|
# written eager-only method in ext/carray_generate.c that only accepted
|
|
@@ -8806,6 +9283,20 @@ MkKernel.triop :clip,
|
|
|
8806
9283
|
'(#4) = _v; }',
|
|
8807
9284
|
}
|
|
8808
9285
|
|
|
9286
|
+
# select: the per-cell choice a lazy `then_else` makes (#1 is the
|
|
9287
|
+
# condition, converted to the branches' common type; #2 / #3 are the
|
|
9288
|
+
# branches). No Ruby method: CArray#then_else builds the node.
|
|
9289
|
+
MkKernel.triop :select,
|
|
9290
|
+
bind: false,
|
|
9291
|
+
bang: false,
|
|
9292
|
+
source: MkKernel::MATH_NUMERIC + [:bool],
|
|
9293
|
+
expr: {
|
|
9294
|
+
int: "(#4) = (#1) ? (#2) : (#3);",
|
|
9295
|
+
float: "(#4) = (#1) ? (#2) : (#3);",
|
|
9296
|
+
complex: "(#4) = (#1) ? (#2) : (#3);",
|
|
9297
|
+
bool: "(#4) = (#1) ? (#2) : (#3);",
|
|
9298
|
+
}
|
|
9299
|
+
|
|
8809
9300
|
MkKernel.binop :power,
|
|
8810
9301
|
op: :power,
|
|
8811
9302
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
@@ -8883,7 +9374,11 @@ MkKernel.moncmp :is_nan,
|
|
|
8883
9374
|
int: "(#2) = 0;",
|
|
8884
9375
|
float: "(#2) = isnan(#1);",
|
|
8885
9376
|
complex: "(#2) = (isnan(creal(#1)) || isnan(cimag(#1)));",
|
|
8886
|
-
|
|
9377
|
+
# A Float answers by its bits; an Integer or a Rational is never NaN;
|
|
9378
|
+
# anything else answers through its own #nan?.
|
|
9379
|
+
object: '(#2) = RB_FLOAT_TYPE_P(#1) ? isnan(RFLOAT_VALUE(#1)) : '\
|
|
9380
|
+
'(RB_INTEGER_TYPE_P(#1) || RB_TYPE_P((#1), T_RATIONAL)) ? 0 : '\
|
|
9381
|
+
'RTEST(rb_funcall((#1), rb_intern("nan?"), 0));',
|
|
8887
9382
|
}
|
|
8888
9383
|
|
|
8889
9384
|
MkKernel.moncmp :is_inf,
|
|
@@ -8892,7 +9387,7 @@ MkKernel.moncmp :is_inf,
|
|
|
8892
9387
|
int: "(#2) = 0;",
|
|
8893
9388
|
float: "(#2) = isinf(#1);",
|
|
8894
9389
|
complex: "(#2) = (isinf(creal(#1)) || isinf(cimag(#1)));",
|
|
8895
|
-
object: '(#2) = rb_funcall((#1), rb_intern("infinite?"), 0);',
|
|
9390
|
+
object: '(#2) = RTEST(rb_funcall((#1), rb_intern("infinite?"), 0));',
|
|
8896
9391
|
}
|
|
8897
9392
|
|
|
8898
9393
|
MkKernel.moncmp :is_finite,
|
|
@@ -8901,7 +9396,7 @@ MkKernel.moncmp :is_finite,
|
|
|
8901
9396
|
int: "(#2) = 1;",
|
|
8902
9397
|
float: "(#2) = isfinite(#1);",
|
|
8903
9398
|
complex: "(#2) = (isfinite(creal(#1)) && isfinite(cimag(#1)));",
|
|
8904
|
-
object: '(#2) = rb_funcall((#1), rb_intern("finite?"), 0);',
|
|
9399
|
+
object: '(#2) = RTEST(rb_funcall((#1), rb_intern("finite?"), 0));',
|
|
8905
9400
|
}
|
|
8906
9401
|
|
|
8907
9402
|
# is_invalid = !is_finite (MS.1 mask SET family redesign): NaN or Inf
|
|
@@ -8931,7 +9426,10 @@ MkKernel.moncmp :signbit,
|
|
|
8931
9426
|
MkKernel::SINT_DTYPES => "(#2) = ((#1) < 0);",
|
|
8932
9427
|
MkKernel::UINT_DTYPES => "(#2) = 0;",
|
|
8933
9428
|
float: "(#2) = signbit(#1);",
|
|
8934
|
-
|
|
9429
|
+
# A Float answers by its sign bit (-0.0 and -NaN are negative), as
|
|
9430
|
+
# the float branch does; anything else through #negative?.
|
|
9431
|
+
object: '(#2) = RB_FLOAT_TYPE_P(#1) ? (signbit(RFLOAT_VALUE(#1)) != 0) : '\
|
|
9432
|
+
'(RTEST(rb_funcall((#1), rb_intern("negative?"), 0)) ? 1 : 0);',
|
|
8935
9433
|
}
|
|
8936
9434
|
|
|
8937
9435
|
# ---- P.5b.4: bincmp family (predicates returning bool) ----------------
|