carray 3.0.4 → 3.0.6
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 +438 -0
- data/README.md +1 -1
- data/carray.gemspec +3 -2
- data/ext/ca_array_pool.c +25 -1
- data/ext/ca_axis_dispatch.c +19 -20
- data/ext/ca_axis_group.c +1 -1
- data/ext/ca_categorical_iterator.c +34 -27
- data/ext/ca_compare.c +4 -4
- data/ext/ca_composite_dispatch.c +9 -9
- data/ext/ca_kernel_iterator.c +122 -237
- data/ext/ca_kernel_iterator.h +522 -337
- data/ext/ca_obj_array.c +69 -19
- data/ext/ca_obj_bincmp.c +10 -1
- data/ext/ca_obj_binop.c +11 -1
- data/ext/ca_obj_bitarray.c +5 -1
- data/ext/ca_obj_bitfield.c +60 -43
- data/ext/ca_obj_byte_swap.c +4 -1
- data/ext/ca_obj_const_string.c +15 -3
- data/ext/ca_obj_face.c +44 -1
- data/ext/ca_obj_fake.c +3 -1
- data/ext/ca_obj_field.c +13 -3
- data/ext/ca_obj_grid.c +219 -17
- data/ext/ca_obj_meld.c +69 -113
- data/ext/ca_obj_moncmp.c +9 -1
- data/ext/ca_obj_monop.c +86 -12
- data/ext/ca_obj_object.c +118 -32
- data/ext/ca_obj_record.c +3 -0
- data/ext/ca_obj_reduce.c +19 -1
- data/ext/ca_obj_refer.c +80 -26
- data/ext/ca_obj_remap.c +69 -10
- data/ext/ca_obj_roll.c +27 -6
- data/ext/ca_obj_select.c +38 -6
- data/ext/ca_obj_select_axis.c +1 -0
- data/ext/ca_obj_shift.c +32 -7
- data/ext/ca_obj_stack.c +311 -111
- data/ext/ca_obj_stride.c +159 -64
- data/ext/ca_obj_string.c +27 -2
- data/ext/ca_obj_tile.c +28 -7
- data/ext/ca_obj_transpose.c +8 -4
- data/ext/ca_obj_triop.c +12 -1
- data/ext/ca_obj_window.c +80 -50
- data/ext/ca_op_ipower.c +12 -12
- data/ext/ca_sort_kernels.h +6 -3
- data/ext/caframe_csv_reader.c +1 -1
- data/ext/carray.h +35 -1
- data/ext/carray_access.c +43 -67
- data/ext/carray_address_basis.c +134 -0
- data/ext/carray_attribute.c +32 -0
- data/ext/carray_broadcast.c +3 -4
- data/ext/carray_build_flags.h +1 -1
- data/ext/carray_cast.c +251 -93
- data/ext/carray_cast_func.rb +56 -26
- data/ext/carray_conversion.c +11 -7
- data/ext/carray_core.c +814 -72
- data/ext/carray_count.c +6 -0
- data/ext/carray_element.c +22 -13
- data/ext/carray_factorize.c +20 -20
- data/ext/carray_generate.c +8 -2
- data/ext/carray_histogram.c +26 -4
- data/ext/carray_index_classifier.c +4 -4
- data/ext/carray_internal.h +159 -12
- data/ext/carray_kernels_bincmp.c +2174 -170
- data/ext/carray_kernels_binop.c +25653 -5487
- data/ext/carray_kernels_init.c +8 -8
- data/ext/carray_kernels_moncmp.c +96 -96
- data/ext/carray_kernels_monop.c +12253 -5235
- data/ext/carray_kernels_reduce_aggregate.c +6150 -2423
- data/ext/carray_kernels_reduce_boolean.c +1533 -15
- data/ext/carray_kernels_reduce_cumulative.c +2442 -882
- data/ext/carray_kernels_reduce_extreme.c +5658 -1661
- data/ext/carray_kernels_reduce_variance.c +2534 -402
- data/ext/carray_kernels_scan.c +368 -368
- data/ext/carray_kernels_search.c +4551 -604
- data/ext/carray_kernels_sort.c +659 -751
- data/ext/carray_kernels_triop.c +114 -114
- data/ext/carray_key_windows.c +4 -0
- data/ext/carray_lazy.c +47 -0
- data/ext/carray_loop.c +1 -1
- data/ext/carray_mask.c +175 -161
- data/ext/carray_median_percentile.c +4 -4
- data/ext/carray_memory_view.c +15 -9
- data/ext/carray_operator.c +152 -49
- data/ext/carray_order.c +230 -10
- data/ext/carray_random.c +22 -19
- data/ext/carray_scatter.c +24 -7
- data/ext/carray_slab.c +128 -29
- data/ext/carray_sort.c +224 -16
- data/ext/carray_sort_kernel.c +5 -7
- data/ext/carray_struct.c +35 -43
- data/ext/carray_test.c +74 -19
- data/ext/carray_time_text.c +6 -4
- data/ext/carray_utils.c +63 -21
- data/ext/extconf.rb +11 -0
- data/ext/mkkernel.rb +546 -199
- data/ext/ruby_carray.c +4 -1
- data/ext/version.h +4 -4
- data/lib/carray/attribute.rb +42 -5
- data/lib/carray/autoload_carray.rb +3 -3
- data/lib/carray/autoload_method_extension.rb +5 -3
- data/lib/carray/axis_group.rb +13 -7
- data/lib/carray/basics.rb +28 -8
- data/lib/carray/block_iterator.rb +17 -3
- data/lib/carray/categorical.rb +35 -23
- data/lib/carray/categorical_iterator.rb +8 -8
- data/lib/carray/complex.rb +2 -2
- data/lib/carray/conditional.rb +3 -2
- data/lib/carray/const_string.rb +10 -0
- data/lib/carray/data_type_extension.rb +8 -1
- data/lib/carray/fixlen_string.rb +41 -2
- data/lib/carray/frame/column_verbs.rb +48 -16
- data/lib/carray/frame/concat.rb +17 -3
- data/lib/carray/frame/convert.rb +19 -0
- data/lib/carray/frame/csv_parser.rb +32 -32
- data/lib/carray/frame/describe.rb +4 -2
- data/lib/carray/frame/frame.rb +405 -85
- data/lib/carray/frame/group.rb +777 -64
- data/lib/carray/frame/io.rb +212 -72
- data/lib/carray/frame/join.rb +146 -37
- data/lib/carray/frame/records.rb +3 -3
- data/lib/carray/frame/reshape.rb +89 -11
- data/lib/carray/frame/sort.rb +57 -9
- data/lib/carray/frame/verbs.rb +384 -49
- data/lib/carray/frame/windows.rb +44 -6
- data/lib/carray/fuse_source.rb +31 -2
- data/lib/carray/inspect.rb +7 -2
- data/lib/carray/iterator.rb +32 -0
- data/lib/carray/lazy.rb +52 -8
- data/lib/carray/mask_gap_fill.rb +37 -17
- data/lib/carray/meld_reduce.rb +1 -1
- data/lib/carray/methods/choose.rb +3 -2
- data/lib/carray/methods/is_in.rb +8 -1
- data/lib/carray/methods/meshgrid.rb +3 -1
- data/lib/carray/runtime.rb +47 -8
- data/lib/carray/segment_iterator.rb +35 -16
- data/lib/carray/serialize.rb +53 -19
- data/lib/carray/stack.rb +33 -18
- data/lib/carray/string.rb +8 -0
- data/lib/carray/string_operation_extension.rb +13 -5
- data/lib/carray/struct_builder.rb +5 -3
- data/lib/carray/time.rb +112 -68
- data/lib/carray/window_iterator.rb +16 -4
- data/yard-stubs/ca_obj_array.rb +26 -1
- data/yard-stubs/ca_obj_transpose.rb +5 -4
- data/yard-stubs/carray_attribute.rb +3 -1
- data/yard-stubs/carray_cast.rb +10 -5
- data/yard-stubs/carray_conversion.rb +13 -3
- data/yard-stubs/carray_element.rb +7 -6
- data/yard-stubs/carray_mask.rb +28 -4
- data/yard-stubs/carray_math.rb +45 -20
- data/yard-stubs/carray_memory_view.rb +7 -0
- data/yard-stubs/carray_order.rb +100 -4
- data/yard-stubs/carray_scatter.rb +7 -5
- data/yard-stubs/carray_test.rb +7 -4
- metadata +18 -4
data/ext/mkkernel.rb
CHANGED
|
@@ -44,26 +44,29 @@ module MkKernel
|
|
|
44
44
|
# value_arg: dispatchers (CF.1) to cast a positional Ruby argument to
|
|
45
45
|
# the kernel's T_IN type. Signed integers use NUM2LL, unsigned
|
|
46
46
|
# NUM2ULL, floats NUM2DBL, with the dispatcher adding the per-type cast.
|
|
47
|
+
# `c` of a multi-byte type is its unaligned typedef from carray.h
|
|
48
|
+
# (ca_u_float64_t): a view's cells may sit at any byte, and a kernel
|
|
49
|
+
# reads them through `c *`.
|
|
47
50
|
DTYPES = {
|
|
48
51
|
i8: { c: "int8_t", ca: "CA_INT8", ruby: "LL2NUM", num2c: "NUM2LL",
|
|
49
52
|
limit_hi: "INT8_MAX", limit_lo: "INT8_MIN" },
|
|
50
53
|
u8: { c: "uint8_t", ca: "CA_UINT8", ruby: "ULL2NUM", num2c: "NUM2ULL",
|
|
51
54
|
limit_hi: "UINT8_MAX", limit_lo: "0" },
|
|
52
|
-
i16: { c: "
|
|
55
|
+
i16: { c: "ca_u_int16_t", ca: "CA_INT16", ruby: "LL2NUM", num2c: "NUM2LL",
|
|
53
56
|
limit_hi: "INT16_MAX", limit_lo: "INT16_MIN" },
|
|
54
|
-
u16: { c: "
|
|
57
|
+
u16: { c: "ca_u_uint16_t", ca: "CA_UINT16", ruby: "ULL2NUM", num2c: "NUM2ULL",
|
|
55
58
|
limit_hi: "UINT16_MAX", limit_lo: "0" },
|
|
56
|
-
i32: { c: "
|
|
59
|
+
i32: { c: "ca_u_int32_t", ca: "CA_INT32", ruby: "LL2NUM", num2c: "NUM2LL",
|
|
57
60
|
limit_hi: "INT32_MAX", limit_lo: "INT32_MIN" },
|
|
58
|
-
u32: { c: "
|
|
61
|
+
u32: { c: "ca_u_uint32_t", ca: "CA_UINT32", ruby: "ULL2NUM", num2c: "NUM2ULL",
|
|
59
62
|
limit_hi: "UINT32_MAX", limit_lo: "0" },
|
|
60
|
-
i64: { c: "
|
|
63
|
+
i64: { c: "ca_u_int64_t", ca: "CA_INT64", ruby: "LL2NUM", num2c: "NUM2LL",
|
|
61
64
|
limit_hi: "INT64_MAX", limit_lo: "INT64_MIN" },
|
|
62
|
-
u64: { c: "
|
|
65
|
+
u64: { c: "ca_u_uint64_t", ca: "CA_UINT64", ruby: "ULL2NUM", num2c: "NUM2ULL",
|
|
63
66
|
limit_hi: "UINT64_MAX", limit_lo: "0" },
|
|
64
|
-
f32: { c: "
|
|
67
|
+
f32: { c: "ca_u_float32_t", ca: "CA_FLOAT32", ruby: "rb_float_new", num2c: "NUM2DBL",
|
|
65
68
|
limit_hi: "INFINITY", limit_lo: "-INFINITY" },
|
|
66
|
-
f64: { c: "
|
|
69
|
+
f64: { c: "ca_u_float64_t", ca: "CA_FLOAT64", ruby: "rb_float_new", num2c: "NUM2DBL",
|
|
67
70
|
limit_hi: "INFINITY", limit_lo: "-INFINITY" },
|
|
68
71
|
# E.6a (2026-06-03): boolean as a first-class source DTYPE.
|
|
69
72
|
# Intentionally NOT included in ALL_NUMERIC -- kernels opt boolean in
|
|
@@ -90,11 +93,11 @@ module MkKernel
|
|
|
90
93
|
# CA_NTYPE coverage parity with mkmath, mkkernel needs to know the
|
|
91
94
|
# complex and object data_types too. These entries are NOT included in
|
|
92
95
|
# ALL_NUMERIC; pick up via MATH_TYPES / MATH_NUMERIC aliases.
|
|
93
|
-
cmplx64: { c: "
|
|
96
|
+
cmplx64: { c: "ca_u_cmplx64_t", ca: "CA_CMPLX64", ruby: "CC2NUM",
|
|
94
97
|
num2c: "NUM2CC", limit_hi: "0", limit_lo: "0" },
|
|
95
|
-
cmplx128: { c: "
|
|
98
|
+
cmplx128: { c: "ca_u_cmplx128_t", ca: "CA_CMPLX128", ruby: "CC2NUM",
|
|
96
99
|
num2c: "NUM2CC", limit_hi: "0", limit_lo: "0" },
|
|
97
|
-
object: { c: "VALUE",
|
|
100
|
+
object: { c: "VALUE", ca: "CA_OBJECT", ruby: "(VALUE)",
|
|
98
101
|
num2c: "(VALUE)", limit_hi: "0", limit_lo: "0" },
|
|
99
102
|
# P.5b.4: fixlen for bincmp (= match, eq/ne/gt/lt/ge/le against
|
|
100
103
|
# fixed-byte strings). The c: type is `char` because fixlen kernels
|
|
@@ -325,8 +328,8 @@ module MkKernel
|
|
|
325
328
|
# # :match_source (legacy) reads the weight at
|
|
326
329
|
# # the source type -- wrong for fractional
|
|
327
330
|
# # weights, kept only for DSL forward-compat.
|
|
328
|
-
# shape: :rev5_strict, # W-A1 scalar / W-
|
|
329
|
-
# #
|
|
331
|
+
# shape: :rev5_strict, # W-A1 scalar / W-A4 shape of the reduced axes /
|
|
332
|
+
# # W-A3 self.shape (:match_source = W-A3 only)
|
|
330
333
|
# mask: :overlay, # weights.mask OR'd into self.mask before
|
|
331
334
|
# # kernel call (= legacy parity, Q3 (A))
|
|
332
335
|
# }
|
|
@@ -359,6 +362,13 @@ module MkKernel
|
|
|
359
362
|
object_escape: nil,
|
|
360
363
|
identity_on_empty: false,
|
|
361
364
|
all_nan_result: nil,
|
|
365
|
+
undefined_if: nil,
|
|
366
|
+
# settled: a C expression over `acc` that is true once no
|
|
367
|
+
# further cell can change the result (any: "acc", all:
|
|
368
|
+
# "!acc"). The slab walk then stops at the first block
|
|
369
|
+
# where it holds (CA_SLAB_REDUCE_T_SETTLE). Only for a
|
|
370
|
+
# single-accumulator reduction without mask_policy.
|
|
371
|
+
settled: nil,
|
|
362
372
|
outputs: 1,
|
|
363
373
|
# Two-pass centred algorithm (variance / stddev family).
|
|
364
374
|
# When algorithm: :two_pass_centred is set, `state / init /
|
|
@@ -445,6 +455,10 @@ module MkKernel
|
|
|
445
455
|
raise "#{name}: identity_on_empty requires a single-accumulator reduction (sum/prod/count/accumulate/wsum); mean/variance/wmean/minmax are excluded" \
|
|
446
456
|
unless single_acc && outputs == 1
|
|
447
457
|
end
|
|
458
|
+
if settled
|
|
459
|
+
raise "#{name}: settled requires a single-accumulator reduction without mask_policy, value_arg or array_arg" \
|
|
460
|
+
unless state.nil? && mask_policy.nil? && value_arg.nil? && array_arg.nil? && outputs == 1
|
|
461
|
+
end
|
|
448
462
|
# SL.1.0: reduction_kind: licenses a SIMD-friendly contig-branch macro
|
|
449
463
|
# variant (CA_SLAB_REDUCE_T_{PLUS,MIN,MAX,STAR,VAR}_EX). default
|
|
450
464
|
# :none keeps the existing CA_SLAB_REDUCE_T_EX path (= unchanged
|
|
@@ -488,6 +502,14 @@ module MkKernel
|
|
|
488
502
|
raise "#{name}: all_nan_result: :undef needs a mask_policy " \
|
|
489
503
|
"(UNDEF is written through the mask_policy trigger)"
|
|
490
504
|
end
|
|
505
|
+
# undefined_if: a C expression over the state that, when true for a
|
|
506
|
+
# slab, leaves the answer undefined though cells contributed -- wmean's
|
|
507
|
+
# weights summing to 0, whose quotient has no value. It is folded into
|
|
508
|
+
# the mask_policy trigger, so the cell is UNDEF (or fill_value).
|
|
509
|
+
if undefined_if && mask_policy.nil?
|
|
510
|
+
raise "#{name}: undefined_if needs a mask_policy " \
|
|
511
|
+
"(UNDEF is written through the mask_policy trigger)"
|
|
512
|
+
end
|
|
491
513
|
if value_arg
|
|
492
514
|
raise "#{name}: value_arg must be a Hash" unless value_arg.is_a?(Hash)
|
|
493
515
|
raise "#{name}: value_arg: target must be :T_IN" unless value_arg[:target] == :T_IN
|
|
@@ -567,9 +589,9 @@ module MkKernel
|
|
|
567
589
|
raise "#{name}: array_arg: data_type must be :promote or :match_source" \
|
|
568
590
|
unless [:promote, :match_source].include?(array_arg[:data_type])
|
|
569
591
|
# PROPOSAL_REDUCTION_PER_FIBER_AUX_OPERAND rev1: shape acceptance expanded
|
|
570
|
-
# to W-A1 (scalar) / W-
|
|
592
|
+
# to W-A1 (scalar) / W-A4 (shape of the reduced axes) / W-A3 (self.shape).
|
|
571
593
|
# :match_source = legacy W-A3 only (kept for forward-compat in DSL).
|
|
572
|
-
# :rev5_strict = W-A1 + W-
|
|
594
|
+
# :rev5_strict = W-A1 + W-A4 + W-A3 acceptance (= new default for wsum/wmean).
|
|
573
595
|
raise "#{name}: array_arg: shape must be :match_source or :rev5_strict" \
|
|
574
596
|
unless [:match_source, :rev5_strict].include?(array_arg[:shape])
|
|
575
597
|
raise "#{name}: array_arg: mask must be :overlay (W.1 MVP)" \
|
|
@@ -649,6 +671,8 @@ module MkKernel
|
|
|
649
671
|
object_escape: object_escape,
|
|
650
672
|
identity_on_empty: identity_on_empty,
|
|
651
673
|
all_nan_result: all_nan_result,
|
|
674
|
+
undefined_if: undefined_if,
|
|
675
|
+
settled: settled,
|
|
652
676
|
outputs: outputs,
|
|
653
677
|
algorithm: algorithm,
|
|
654
678
|
divisor: divisor,
|
|
@@ -1054,11 +1078,14 @@ module MkKernel
|
|
|
1054
1078
|
# => ...` form).
|
|
1055
1079
|
#
|
|
1056
1080
|
# `bind:` defaults to true (= register CArray#<name> + CArray#<name>!).
|
|
1081
|
+
# `bang: false` leaves CArray#<name>! unregistered too, for a method whose
|
|
1082
|
+
# Ruby surface is defined by hand on top of the generated rb_ca_<name> and
|
|
1083
|
+
# rb_ca_<name>_bang.
|
|
1057
1084
|
# `op:` defaults to name (= the public method name; some mkmath ops
|
|
1058
1085
|
# use a different operator name from the kernel suffix, e.g. operator
|
|
1059
1086
|
# methods).
|
|
1060
|
-
def self.monop(name, source:, expr:, op: nil, bind: true,
|
|
1061
|
-
widening: nil, output: :preserve)
|
|
1087
|
+
def self.monop(name, source:, expr:, op: nil, bind: true, bang: true,
|
|
1088
|
+
cmath: true, widening: nil, output: :preserve)
|
|
1062
1089
|
op ||= name
|
|
1063
1090
|
# widening = true => emit wrapper that auto-casts integer input to
|
|
1064
1091
|
# CA_FLOAT64 (= mkmath monfunc behavior). Default: auto-detect based
|
|
@@ -1081,6 +1108,7 @@ module MkKernel
|
|
|
1081
1108
|
source: source,
|
|
1082
1109
|
expr: expr,
|
|
1083
1110
|
bind: bind,
|
|
1111
|
+
bang: bang,
|
|
1084
1112
|
cmath: cmath,
|
|
1085
1113
|
widening: widening,
|
|
1086
1114
|
output: output,
|
|
@@ -1092,8 +1120,10 @@ module MkKernel
|
|
|
1092
1120
|
# of integer input to f64 is decided by `monop`'s widening auto-detect
|
|
1093
1121
|
# based on whether `source:` includes integer data_types — matching mkmath's
|
|
1094
1122
|
# rule "if no INT_TYPES key, auto-cast; otherwise rely on integer kernel".
|
|
1095
|
-
def self.monfunc(name, source:, expr:, op: nil, bind: true,
|
|
1096
|
-
|
|
1123
|
+
def self.monfunc(name, source:, expr:, op: nil, bind: true, bang: true,
|
|
1124
|
+
cmath: true)
|
|
1125
|
+
monop(name, source: source, expr: expr, op: op, bind: bind, bang: bang,
|
|
1126
|
+
cmath: cmath)
|
|
1097
1127
|
end
|
|
1098
1128
|
|
|
1099
1129
|
# Register an alias of an existing monop method under a different
|
|
@@ -1355,6 +1385,12 @@ module MkKernel
|
|
|
1355
1385
|
when "_STAR" then "CA_SLAB_REDUCE_STAR8_EX"
|
|
1356
1386
|
end
|
|
1357
1387
|
acc_c = resolve_state_type(k[:state][acc_var], oi, si, src)
|
|
1388
|
+
# An 8- or 16-bit integer cell converted to double does not vectorize;
|
|
1389
|
+
# the narrow form sums blocks in int32 first.
|
|
1390
|
+
if macro8 == "CA_SLAB_REDUCE_SUM8_EX" && %i[i8 u8 i16 u16].include?(src) &&
|
|
1391
|
+
acc_c == DTYPES[:f64][:c]
|
|
1392
|
+
macro8 = "CA_SLAB_REDUCE_SUM8_NARROW_EX"
|
|
1393
|
+
end
|
|
1358
1394
|
expr_macro = "__#{k[:name]}_#{src}_EXPR"
|
|
1359
1395
|
io.puts "#define #{expr_macro}(__x) ((#{acc_c})(__x))"
|
|
1360
1396
|
io.puts "#{indent}#{macro8}(#{si[:c]}, #{acc_c}, st, p, m, #{acc_var}, #{acc_init}, #{expr_macro}, #{masked_cnt_var});"
|
|
@@ -1775,13 +1811,15 @@ module MkKernel
|
|
|
1775
1811
|
|
|
1776
1812
|
# Emits the native function of one source data type. The object lane
|
|
1777
1813
|
# calls Ruby for every cell, so any cell can raise in the middle of a walk;
|
|
1778
|
-
# its function is rebuilt around ca_iter_ensure (see
|
|
1779
|
-
#
|
|
1780
|
-
|
|
1781
|
-
|
|
1814
|
+
# its function is rebuilt around ca_iter_ensure (see guard_native). So is
|
|
1815
|
+
# every lane of a family that passes guard: true, whose walks allocate
|
|
1816
|
+
# (an output mask made on the first empty slab, a scratch buffer), since a
|
|
1817
|
+
# failed allocation raises too. Other lanes are emitted as written.
|
|
1818
|
+
def self.emit_native_for(io, k, src, guard: false)
|
|
1819
|
+
if src == :object || guard
|
|
1782
1820
|
buf = StringIO.new
|
|
1783
1821
|
yield buf
|
|
1784
|
-
io.print
|
|
1822
|
+
io.print guard_native(buf.string, "#{k[:name]}_ki_native_#{src}")
|
|
1785
1823
|
else
|
|
1786
1824
|
yield io
|
|
1787
1825
|
end
|
|
@@ -1822,7 +1860,7 @@ module MkKernel
|
|
|
1822
1860
|
# frame is gone once the body raises. The body text is kept as written:
|
|
1823
1861
|
# the parameters are unpacked from the frame under their own names, and
|
|
1824
1862
|
# each state name is defined to its slot for the length of the body.
|
|
1825
|
-
def self.
|
|
1863
|
+
def self.guard_native(text, fn)
|
|
1826
1864
|
header = /^static VALUE\n#{fn} \(([^)]*)\)\n\{\n/
|
|
1827
1865
|
m = header.match(text) or raise "mkkernel: #{fn} not found"
|
|
1828
1866
|
close = matching_brace(text, m.end(0) - 2)
|
|
@@ -1845,9 +1883,9 @@ module MkKernel
|
|
|
1845
1883
|
|
|
1846
1884
|
out = StringIO.new
|
|
1847
1885
|
out.print m.pre_match
|
|
1848
|
-
out.puts "/* The
|
|
1849
|
-
out.puts "
|
|
1850
|
-
out.puts " finishes them however the body leaves. */"
|
|
1886
|
+
out.puts "/* The walks can raise part way (a Ruby call per cell in the object"
|
|
1887
|
+
out.puts " lane, an allocation in any lane). Their states live in the caller's"
|
|
1888
|
+
out.puts " frame, and ca_iter_ensure finishes them however the body leaves. */"
|
|
1851
1889
|
out.puts "typedef struct {"
|
|
1852
1890
|
out.puts " ca_iter_state states[#{states.size}];"
|
|
1853
1891
|
params.each { |type, name| out.puts " #{type} #{name};".sub(/\* /, "*") }
|
|
@@ -1872,7 +1910,11 @@ module MkKernel
|
|
|
1872
1910
|
out.puts "{"
|
|
1873
1911
|
out.puts " #{frame} fr;"
|
|
1874
1912
|
params.each { |_, name| out.puts " fr.#{name} = #{name};" }
|
|
1875
|
-
|
|
1913
|
+
if params.any? { |type, name| name == "ca" && type.include?("CArray") } && fn.end_with?("_object")
|
|
1914
|
+
out.puts " return ca_iter_ensure_computing(ca, #{states.size}, fr.states, #{fn}_body, (VALUE) &fr);"
|
|
1915
|
+
else
|
|
1916
|
+
out.puts " return ca_iter_ensure(#{states.size}, fr.states, #{fn}_body, (VALUE) &fr);"
|
|
1917
|
+
end
|
|
1876
1918
|
out.puts "}"
|
|
1877
1919
|
out.print rest
|
|
1878
1920
|
out.string
|
|
@@ -1882,7 +1924,7 @@ module MkKernel
|
|
|
1882
1924
|
io.puts
|
|
1883
1925
|
io.puts "/* ===== #{k[:name]}_ki ============================================ */"
|
|
1884
1926
|
k[:source].each do |src|
|
|
1885
|
-
emit_native_for(io, k, src) { |o| emit_reduce_native(o, k, src) }
|
|
1927
|
+
emit_native_for(io, k, src, guard: true) { |o| emit_reduce_native(o, k, src) }
|
|
1886
1928
|
end
|
|
1887
1929
|
emit_reduce_dispatch(io, k)
|
|
1888
1930
|
end
|
|
@@ -2030,6 +2072,7 @@ module MkKernel
|
|
|
2030
2072
|
io.puts " if ( naxes != 1 || ! ca_too_few_present(ca->dim[slab_axes[0]], min_count, 0) ) {" if li_gate
|
|
2031
2073
|
emit_reduce_loop_interchange(io, k, si, oi, ruby_wrap, src,
|
|
2032
2074
|
acc_var, acc_init, reduce_stmt, finish_expr)
|
|
2075
|
+
emit_reduce_short_fiber(io, k, si, oi, src, reduce_stmt, finish_expr)
|
|
2033
2076
|
io.puts " }" if li_gate
|
|
2034
2077
|
end
|
|
2035
2078
|
|
|
@@ -2163,6 +2206,7 @@ module MkKernel
|
|
|
2163
2206
|
"#{k[:identity_on_empty] ? 0 : 1})"
|
|
2164
2207
|
end
|
|
2165
2208
|
trigger = "(#{trigger}) || #{all_nan_flag}" if all_nan_flag
|
|
2209
|
+
trigger = "(#{trigger}) || (#{k[:undefined_if]})" if k[:undefined_if]
|
|
2166
2210
|
finish_emit = view_flat \
|
|
2167
2211
|
? "(transform_active ? (outer_off + ((ca_size_t)(#{finish_expr})) * axis_vstride) : ((ca_size_t)(#{finish_expr})))" \
|
|
2168
2212
|
: "(#{finish_expr})"
|
|
@@ -2187,7 +2231,9 @@ module MkKernel
|
|
|
2187
2231
|
# Non-mask_policy path: the slab macros always take a masked_cnt,
|
|
2188
2232
|
# so hand them a throwaway. The generic form (suffix "", the
|
|
2189
2233
|
# :object lane) has a wrapper that hides it.
|
|
2190
|
-
if
|
|
2234
|
+
if k[:settled]
|
|
2235
|
+
io.puts " CA_SLAB_REDUCE_T_SETTLE(#{si[:c]}, st, p, m, #{acc_var}, #{acc_init}, #{reduce_stmt}, #{k[:settled]});"
|
|
2236
|
+
elsif suffix == ""
|
|
2191
2237
|
io.puts " CA_SLAB_REDUCE_T(#{si[:c]}, st, p, m, #{acc_var}, #{acc_init}, #{reduce_stmt});"
|
|
2192
2238
|
else
|
|
2193
2239
|
io.puts " ca_size_t __sr_throwaway_mc = 0;"
|
|
@@ -2535,8 +2581,16 @@ module MkKernel
|
|
|
2535
2581
|
expr_macro_defs = "#define #{p1_macro}(__x) #{p1_expr}\n" \
|
|
2536
2582
|
"#define #{p2_macro}(__x) #{p2_expr}\n"
|
|
2537
2583
|
expr_macro_undefs = "#undef #{p1_macro}\n#undef #{p2_macro}\n"
|
|
2538
|
-
|
|
2539
|
-
|
|
2584
|
+
if %i[i8 u8 i16 u16].include?(src)
|
|
2585
|
+
# Per-cell conversion to double does not vectorize for these widths
|
|
2586
|
+
# (see the CA_SLAB_REDUCE_*_NARROW_EX header comments).
|
|
2587
|
+
q_c = %i[i8 u8].include?(src) ? "int32_t" : "int64_t"
|
|
2588
|
+
p1_reduce_call = "CA_SLAB_REDUCE_SUM8_NARROW_EX(#{si[:c]}, #{sum_c}, st, p, m, sum, #{sum_init}, #{p1_macro}, masked_cnt);"
|
|
2589
|
+
p2_reduce_call = "CA_SLAB_REDUCE_M2_NARROW_EX(#{si[:c]}, #{q_c}, st, p, m, M2, #{m2_init}, mean, #{p2_macro}, __unused_mc);"
|
|
2590
|
+
else
|
|
2591
|
+
p1_reduce_call = "CA_SLAB_REDUCE_SUM8_EX(#{si[:c]}, #{sum_c}, st, p, m, sum, #{sum_init}, #{p1_macro}, masked_cnt);"
|
|
2592
|
+
p2_reduce_call = "CA_SLAB_REDUCE_SUM8_EX(#{si[:c]}, #{m2_c}, st, p, m, M2, #{m2_init}, #{p2_macro}, __unused_mc);"
|
|
2593
|
+
end
|
|
2540
2594
|
end
|
|
2541
2595
|
|
|
2542
2596
|
io.puts
|
|
@@ -2598,6 +2652,101 @@ module MkKernel
|
|
|
2598
2652
|
io.print expr_macro_undefs
|
|
2599
2653
|
end
|
|
2600
2654
|
|
|
2655
|
+
# Short-fiber fast path (shortcut short_fiber_reduce): a single-axis
|
|
2656
|
+
# reduction over the innermost axis of a row-major buffer whose fibers are
|
|
2657
|
+
# short. The general walk asks the kernel iterator for each fiber, and for
|
|
2658
|
+
# fibers of a few cells that per-fiber step costs more than the cells
|
|
2659
|
+
# (1M fibers of 4 float64 cells: 12.6 ms against 1.0 ms for a plain loop,
|
|
2660
|
+
# M2 Max). Here every fiber is folded in place from the buffer, in the
|
|
2661
|
+
# order the general walk folds it, into scalar accumulators. Eligibility
|
|
2662
|
+
# is the loop interchange's (same reduction kinds, no mask, an alias
|
|
2663
|
+
# attach); a fiber longer than 32 cells (16 for min / max) goes to the
|
|
2664
|
+
# general walk, which is already faster than this loop there.
|
|
2665
|
+
def self.emit_reduce_short_fiber(io, k, si, oi, src, reduce_stmt, finish_expr)
|
|
2666
|
+
buf_kinds = %i[plus star min max]
|
|
2667
|
+
rk = k[:reduction_kind]
|
|
2668
|
+
if buf_kinds.include?(rk)
|
|
2669
|
+
acc_vars = [k[:state].keys.first]
|
|
2670
|
+
induction_var = nil
|
|
2671
|
+
else
|
|
2672
|
+
acc_vars = k[:state].keys.select { |v| buf_kinds.include?(rk[v]) }
|
|
2673
|
+
ind_pair = rk.find { |_, kind| kind == :induction }
|
|
2674
|
+
induction_var = ind_pair && ind_pair[0]
|
|
2675
|
+
end
|
|
2676
|
+
|
|
2677
|
+
info = acc_vars.map do |v|
|
|
2678
|
+
{ var: v,
|
|
2679
|
+
c_type: resolve_state_type(k[:state][v], oi, si, src),
|
|
2680
|
+
init: resolve_init_expr(k[:init][v], oi, si, src) }
|
|
2681
|
+
end
|
|
2682
|
+
|
|
2683
|
+
reduce_sf = reduce_stmt.dup
|
|
2684
|
+
finish_sf = finish_expr.dup
|
|
2685
|
+
if induction_var
|
|
2686
|
+
ind_pat = Regexp.escape(induction_var.to_s)
|
|
2687
|
+
reduce_sf = reduce_sf.gsub(/\b#{ind_pat}\+\+/, "0")
|
|
2688
|
+
.gsub(/\+\+\b#{ind_pat}\b/, "0")
|
|
2689
|
+
finish_sf = finish_sf.gsub(/\b#{ind_pat}\b/, "((int64_t)__sf_M)")
|
|
2690
|
+
end
|
|
2691
|
+
|
|
2692
|
+
decls = info.map { |i| "#{i[:c_type]} #{i[:var]} = (#{i[:c_type]}) (#{i[:init]});" }
|
|
2693
|
+
.join("\n ")
|
|
2694
|
+
|
|
2695
|
+
# A float min / max whose accumulator is still at the init saw only NaN
|
|
2696
|
+
# or only +/-INFINITY; the fiber is re-read to tell which, as the
|
|
2697
|
+
# general walk's all-NaN rule answers NaN for the first.
|
|
2698
|
+
nan_fixup =
|
|
2699
|
+
if k[:all_nan_result] == :nan && FLOAT_DTYPES.include?(src) && info.size == 1
|
|
2700
|
+
i = info.first
|
|
2701
|
+
<<~C.rstrip
|
|
2702
|
+
if ( #{i[:var]} == (#{i[:c_type]}) (#{i[:init]}) ) {
|
|
2703
|
+
int __sf_seen = 0;
|
|
2704
|
+
for ( ca_size_t __j = 0; __j < __sf_M; __j++ ) __sf_seen |= (__sf_row[__j] == __sf_row[__j]);
|
|
2705
|
+
if ( ! __sf_seen ) #{i[:var]} = (#{i[:c_type]}) NAN;
|
|
2706
|
+
}
|
|
2707
|
+
C
|
|
2708
|
+
else
|
|
2709
|
+
""
|
|
2710
|
+
end
|
|
2711
|
+
|
|
2712
|
+
# A min / max fold is a compare per cell that the general walk splits
|
|
2713
|
+
# eight ways; past 16 cells that split is faster than this loop.
|
|
2714
|
+
max_m = info.any? { |i| %i[min max].include?(buf_kinds.include?(rk) ? rk : rk[i[:var]]) } ? 16 : 32
|
|
2715
|
+
|
|
2716
|
+
io.puts <<~C
|
|
2717
|
+
if ( naxes == 1
|
|
2718
|
+
&& ca->ndim >= 2
|
|
2719
|
+
&& slab_axes[0] == (int8_t)(ca->ndim - 1)
|
|
2720
|
+
&& ca->dim[ca->ndim - 1] >= 1
|
|
2721
|
+
&& ca->dim[ca->ndim - 1] <= #{max_m}
|
|
2722
|
+
&& ! ca_has_mask(ca)
|
|
2723
|
+
&& ca_attach_is_alias(ca)
|
|
2724
|
+
&& ca_fast_path(CA_SC_SHORT_FIBER_REDUCE) ) {
|
|
2725
|
+
ca_size_t __sf_M = ca->dim[ca->ndim - 1];
|
|
2726
|
+
ca_size_t __sf_OUTER = ca->elements / __sf_M;
|
|
2727
|
+
VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, #{oi[:ca]}, keep_axis);
|
|
2728
|
+
CArray *co;
|
|
2729
|
+
GetCArray(vout, co);
|
|
2730
|
+
#{oi[:c]} *op = (#{oi[:c]} *) co->ptr;
|
|
2731
|
+
ca_attach(ca); /* window: numeric lanes only, nothing raises inside; O(1) alias attach */
|
|
2732
|
+
const #{si[:c]} *p_root = (const #{si[:c]} *) ca->ptr;
|
|
2733
|
+
for ( ca_size_t __sf_o = 0; __sf_o < __sf_OUTER; __sf_o++ ) {
|
|
2734
|
+
const #{si[:c]} *__sf_row = p_root + __sf_o * __sf_M;
|
|
2735
|
+
#{decls}
|
|
2736
|
+
for ( ca_size_t __j = 0; __j < __sf_M; __j++ ) {
|
|
2737
|
+
#{si[:c]} v = __sf_row[__j];
|
|
2738
|
+
#{reduce_sf};
|
|
2739
|
+
(void) v;
|
|
2740
|
+
}
|
|
2741
|
+
#{nan_fixup}
|
|
2742
|
+
op[__sf_o] = (#{oi[:c]}) (#{finish_sf});
|
|
2743
|
+
}
|
|
2744
|
+
ca_detach(ca);
|
|
2745
|
+
return vout;
|
|
2746
|
+
}
|
|
2747
|
+
C
|
|
2748
|
+
end
|
|
2749
|
+
|
|
2601
2750
|
def self.emit_reduce_loop_interchange(io, k, si, oi, ruby_wrap, src,
|
|
2602
2751
|
acc_var, acc_init, reduce_stmt, finish_expr)
|
|
2603
2752
|
# Classify state vars by reduction_kind:
|
|
@@ -2747,7 +2896,7 @@ module MkKernel
|
|
|
2747
2896
|
&& slab_axes[0] != (int8_t)(ca->ndim - 1)
|
|
2748
2897
|
&& ! ca_has_mask(ca)
|
|
2749
2898
|
&& ca_attach_is_alias(ca)
|
|
2750
|
-
&&
|
|
2899
|
+
&& ca_fast_path(CA_SC_LOOP_INTERCHANGE) ) {
|
|
2751
2900
|
int8_t __li_ax = slab_axes[0];
|
|
2752
2901
|
ca_size_t __li_M = ca->dim[__li_ax];
|
|
2753
2902
|
ca_size_t __li_INNER = 1;
|
|
@@ -2798,7 +2947,7 @@ module MkKernel
|
|
|
2798
2947
|
&& ! ca_has_mask(ca)
|
|
2799
2948
|
&& ca_func[ca->obj_type].attach == ca_stack_func.attach
|
|
2800
2949
|
&& ((CAStack *) ca)->k_axis == 0
|
|
2801
|
-
&&
|
|
2950
|
+
&& ca_fast_path(CA_SC_STACK_LOOP_INTERCHANGE) ) {
|
|
2802
2951
|
CAStack *__li_st = (CAStack *) ca;
|
|
2803
2952
|
int8_t __li_ax = slab_axes[0];
|
|
2804
2953
|
ca_size_t __li_M = ca->dim[__li_ax];
|
|
@@ -2826,7 +2975,7 @@ module MkKernel
|
|
|
2826
2975
|
ca_size_t __li_parent_out = __li_OUTER * __li_INNER;
|
|
2827
2976
|
const ca_size_t __li_TILE = 512;
|
|
2828
2977
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2829
|
-
ca_attach(__li_st->parents[__kk]); /* window:
|
|
2978
|
+
ca_attach(__li_st->parents[__kk]); /* window: a parent may run Ruby to fill itself (a CAObject's copy_data); a raising attach holds nothing and the earlier parents are already detached, and the numeric loop after it does not raise */
|
|
2830
2979
|
const #{si[:c]} *p_root = (const #{si[:c]} *) __li_st->parents[__kk]->ptr;
|
|
2831
2980
|
#{oi[:c]} *__li_op_k = op + (ca_size_t) __kk * __li_parent_out;
|
|
2832
2981
|
#{core.call("p_root", "__li_op_k", "__li_OUTER")}
|
|
@@ -2864,7 +3013,7 @@ module MkKernel
|
|
|
2864
3013
|
&& ((CAStack *) ca)->k_axis > 0
|
|
2865
3014
|
&& ( slab_axes[0] > ((CAStack *) ca)->k_axis
|
|
2866
3015
|
|| slab_axes[0] + 1 == ((CAStack *) ca)->k_axis )
|
|
2867
|
-
&&
|
|
3016
|
+
&& ca_fast_path(CA_SC_STACK_LOOP_INTERCHANGE) ) {
|
|
2868
3017
|
CAStack *__li_st = (CAStack *) ca;
|
|
2869
3018
|
int8_t __li_ax = slab_axes[0];
|
|
2870
3019
|
int8_t __li_kax = (int8_t) __li_st->k_axis;
|
|
@@ -2921,7 +3070,7 @@ module MkKernel
|
|
|
2921
3070
|
ca_size_t __li_plane_elems = __li_M * __li_INNER;
|
|
2922
3071
|
const ca_size_t __li_TILE = 512;
|
|
2923
3072
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
2924
|
-
ca_attach(__li_st->parents[__kk]); /* window:
|
|
3073
|
+
ca_attach(__li_st->parents[__kk]); /* window: a parent may run Ruby to fill itself (a CAObject's copy_data); a raising attach holds nothing and the earlier parents are already detached, and the numeric loop after it does not raise */
|
|
2925
3074
|
const #{si[:c]} *p_root = (const #{si[:c]} *) __li_st->parents[__kk]->ptr;
|
|
2926
3075
|
ca_size_t __li_k_base = (ca_size_t) __kk * __li_k_stride;
|
|
2927
3076
|
|
|
@@ -3007,7 +3156,7 @@ module MkKernel
|
|
|
3007
3156
|
&& ca_func[ca->obj_type].attach == ca_stack_func.attach
|
|
3008
3157
|
&& ((CAStack *) ca)->k_axis > 0
|
|
3009
3158
|
&& slab_axes[0] + 1 < ((CAStack *) ca)->k_axis
|
|
3010
|
-
&&
|
|
3159
|
+
&& ca_fast_path(CA_SC_STACK_LOOP_INTERCHANGE) ) {
|
|
3011
3160
|
CAStack *__li_st = (CAStack *) ca;
|
|
3012
3161
|
int8_t __li_ax = slab_axes[0];
|
|
3013
3162
|
int8_t __li_kax = (int8_t) __li_st->k_axis;
|
|
@@ -3079,7 +3228,7 @@ module MkKernel
|
|
|
3079
3228
|
|
|
3080
3229
|
const ca_size_t __li_TILE = 512;
|
|
3081
3230
|
for ( int32_t __kk = 0; __kk < __li_st->n_parents; __kk++ ) {
|
|
3082
|
-
ca_attach(__li_st->parents[__kk]); /* window:
|
|
3231
|
+
ca_attach(__li_st->parents[__kk]); /* window: a parent may run Ruby to fill itself (a CAObject's copy_data); a raising attach holds nothing and the earlier parents are already detached, and the numeric loop after it does not raise */
|
|
3083
3232
|
const #{si[:c]} *p_root = (const #{si[:c]} *) __li_st->parents[__kk]->ptr;
|
|
3084
3233
|
ca_size_t __li_k_base = (ca_size_t) __kk * __li_k_stride;
|
|
3085
3234
|
|
|
@@ -3389,7 +3538,7 @@ module MkKernel
|
|
|
3389
3538
|
io.puts " ca->elements > 0 &&"
|
|
3390
3539
|
io.puts " ! ca_too_few_present(ca->elements, min_count, 0) &&" if k[:mask_policy] == :min_count
|
|
3391
3540
|
io.puts " ca_is_lazy_view(ca) && ! ca_has_mask(ca) &&"
|
|
3392
|
-
io.puts "
|
|
3541
|
+
io.puts " ca_fast_path(CA_SC_LAZY_STREAMING_REDUCE) ) {"
|
|
3393
3542
|
io.puts " #{fn}_t __ctx;"
|
|
3394
3543
|
io.puts " __ctx.chunk = NULL;"
|
|
3395
3544
|
io.puts " __ctx.ca = ca;"
|
|
@@ -3508,12 +3657,12 @@ module MkKernel
|
|
|
3508
3657
|
# computation type (so a float weight against an int source is not
|
|
3509
3658
|
# truncated -- the body reads it as `double`); :match_source keeps the
|
|
3510
3659
|
# legacy source-type storage. waca is the CA_* constant the three
|
|
3511
|
-
# materialization sites (W-A1 / W-
|
|
3660
|
+
# materialization sites (W-A1 / W-A4 / W-A3) allocate/wrap against, and
|
|
3512
3661
|
# must agree with the T_W the reduce macro reads (double for :promote).
|
|
3513
3662
|
waca = (k[:array_arg][:data_type] == :promote) ? "CA_FLOAT64" : "src->data_type"
|
|
3514
3663
|
# PROPOSAL_REDUCTION_PER_FIBER_AUX_OPERAND rev1: weights acceptance
|
|
3515
|
-
# expanded to 3 forms (W-A1 scalar / W-
|
|
3516
|
-
# full per-element). W-A1 / W-
|
|
3664
|
+
# expanded to 3 forms (W-A1 scalar / W-A4 shape of the reduced axes / W-A3
|
|
3665
|
+
# full per-element). W-A1 / W-A4 are materialized to W-A3 form
|
|
3517
3666
|
# at dispatch (= simple, uniform kernel path). rev5 strict acceptance
|
|
3518
3667
|
# mirrors PROPOSAL_LINEAR_INTERP_PER_FIBER_MATCHED rev5 in spirit.
|
|
3519
3668
|
io.puts " if ( argc < 1 ) {"
|
|
@@ -3521,13 +3670,18 @@ module MkKernel
|
|
|
3521
3670
|
io.puts " }"
|
|
3522
3671
|
io.puts " volatile VALUE rweights = argv[0];"
|
|
3523
3672
|
io.puts " argc--; argv++;"
|
|
3524
|
-
#
|
|
3525
|
-
#
|
|
3673
|
+
# A CScalar is a scalar weight. Any other array, one with a single
|
|
3674
|
+
# element included, is held to the shape rules below.
|
|
3526
3675
|
io.puts " if ( rb_obj_is_carray(rweights) ) {"
|
|
3527
3676
|
io.puts " CArray *cv_pre_;"
|
|
3528
3677
|
io.puts " GetCArray(rweights, cv_pre_);"
|
|
3529
|
-
io.puts " if ( cv_pre_
|
|
3678
|
+
io.puts " if ( ca_is_scalar(cv_pre_) ) {"
|
|
3679
|
+
io.puts " int8_t wtype_ = cv_pre_->data_type;"
|
|
3530
3680
|
io.puts " rweights = rb_funcall(rweights, rb_intern(\"[]\"), 1, INT2NUM(0));"
|
|
3681
|
+
# A boolean cell reads as true / false; as a weight it is 1 / 0.
|
|
3682
|
+
io.puts " if ( wtype_ == CA_BOOLEAN && rweights != CA_UNDEF ) {"
|
|
3683
|
+
io.puts " rweights = INT2FIX(RTEST(rweights) ? 1 : 0);"
|
|
3684
|
+
io.puts " }"
|
|
3531
3685
|
io.puts " }"
|
|
3532
3686
|
io.puts " }"
|
|
3533
3687
|
# The weights are attached from here to the end of the call, so they
|
|
@@ -3540,7 +3694,16 @@ module MkKernel
|
|
|
3540
3694
|
wio.puts " /* W-A1: Ruby Numeric scalar -> full-shape constant weight CArray */"
|
|
3541
3695
|
wio.puts " vcw = rb_carray_new(#{waca}, src->ndim, src->dim, 0, NULL);"
|
|
3542
3696
|
wio.puts " GetCArray(vcw, cw); /* a new entity: its ptr needs no attach */"
|
|
3543
|
-
wio.puts "
|
|
3697
|
+
wio.puts " if ( rweights == CA_UNDEF ) {"
|
|
3698
|
+
wio.puts " /* A masked weight (UNDEF, or a one-element array masked): every"
|
|
3699
|
+
wio.puts " cell's weight is masked, so no cell contributes. */"
|
|
3700
|
+
wio.puts " memset(cw->ptr, 0, (size_t) cw->elements * cw->bytes);"
|
|
3701
|
+
wio.puts " ca_create_mask(cw);"
|
|
3702
|
+
wio.puts " memset(cw->mask->ptr, 1, (size_t) cw->elements);"
|
|
3703
|
+
wio.puts " }"
|
|
3704
|
+
wio.puts " else {"
|
|
3705
|
+
wio.puts " rb_ca_obj2ptr(vcw, rweights, cw->ptr); /* fill first cell */"
|
|
3706
|
+
wio.puts " }"
|
|
3544
3707
|
wio.puts " /* broadcast first cell to all elements (= constant fill) */"
|
|
3545
3708
|
wio.puts " {"
|
|
3546
3709
|
wio.puts " char *p0 = cw->ptr;"
|
|
@@ -3552,69 +3715,75 @@ module MkKernel
|
|
|
3552
3715
|
wio.puts " else {"
|
|
3553
3716
|
wio.puts " /* CArray val: coerce to the weight storage type (f64 for :promote, so a"
|
|
3554
3717
|
wio.puts " float weight against an int source is not truncated) */"
|
|
3555
|
-
wio.puts " /* vcv holds the view while cv is read: W-
|
|
3718
|
+
wio.puts " /* vcv holds the view while cv is read: W-A4 below moves vcw on to"
|
|
3556
3719
|
wio.puts " the materialised copy before it is done with cv. */"
|
|
3557
3720
|
wio.puts " volatile VALUE vcv = rb_ca_wrap_readonly(rweights, INT2NUM(#{waca}));"
|
|
3558
3721
|
wio.puts " CArray *cv;"
|
|
3559
3722
|
wio.puts " GetCArray(vcv, cv);"
|
|
3560
3723
|
wio.puts " vcw = vcv;"
|
|
3561
3724
|
wio.puts ""
|
|
3562
|
-
wio.puts " /*
|
|
3563
|
-
wio.puts "
|
|
3564
|
-
wio.puts "
|
|
3565
|
-
wio.puts "
|
|
3566
|
-
wio.puts "
|
|
3567
|
-
wio.puts
|
|
3568
|
-
wio.puts "
|
|
3569
|
-
wio.puts "
|
|
3570
|
-
wio.puts "
|
|
3725
|
+
wio.puts " /* form detection: W-A3 (self.shape) -> W-A4 (shape of the reduced axes,"
|
|
3726
|
+
wio.puts " taken in ascending axis order whatever order axis: lists them) -> raise */"
|
|
3727
|
+
wio.puts " int8_t wax[CA_RANK_MAX];"
|
|
3728
|
+
wio.puts " for ( int8_t i = 0; i < naxes; i++ ) {"
|
|
3729
|
+
wio.puts " int8_t a = slab_axes[i], j = i;"
|
|
3730
|
+
wio.puts " while ( j > 0 && wax[j-1] > a ) { wax[j] = wax[j-1]; j--; }"
|
|
3731
|
+
wio.puts " wax[j] = a;"
|
|
3732
|
+
wio.puts " }"
|
|
3733
|
+
wio.puts " int is_full_shape = ( cv->ndim == src->ndim );"
|
|
3734
|
+
wio.puts " for ( int8_t i = 0; is_full_shape && i < src->ndim; i++ ) {"
|
|
3735
|
+
wio.puts " if ( cv->dim[i] != src->dim[i] ) is_full_shape = 0;"
|
|
3736
|
+
wio.puts " }"
|
|
3737
|
+
wio.puts " int is_reduced_shape = ( ! is_full_shape && naxes >= 1 && cv->ndim == naxes );"
|
|
3738
|
+
wio.puts " for ( int8_t i = 0; is_reduced_shape && i < naxes; i++ ) {"
|
|
3739
|
+
wio.puts " if ( cv->dim[i] != src->dim[wax[i]] ) is_reduced_shape = 0;"
|
|
3740
|
+
wio.puts " }"
|
|
3741
|
+
wio.puts " if ( is_full_shape ) {"
|
|
3742
|
+
wio.puts " /* W-A3: per-element weights, used as they are */"
|
|
3571
3743
|
wio.puts " cw = cv;"
|
|
3572
3744
|
wio.puts " }"
|
|
3573
|
-
wio.puts " else if (
|
|
3574
|
-
wio.puts " /* W-
|
|
3575
|
-
wio.puts "
|
|
3745
|
+
wio.puts " else if ( is_reduced_shape ) {"
|
|
3746
|
+
wio.puts " /* W-A4: one weight per cell of the reduced axes, repeated along the"
|
|
3747
|
+
wio.puts " kept axes; materialized to self.shape. cw and its mask are made"
|
|
3748
|
+
wio.puts " before cv is attached: making them allocates. */"
|
|
3576
3749
|
wio.puts " vcw = rb_carray_new(#{waca}, src->ndim, src->dim, 0, NULL);"
|
|
3577
3750
|
wio.puts " GetCArray(vcw, cw);"
|
|
3578
|
-
wio.puts " /* broadcast cv (1-D, length src->dim[slab_axes[0]]) along reduce axis */"
|
|
3579
|
-
wio.puts " {"
|
|
3580
|
-
wio.puts " int8_t baxis = slab_axes[0];"
|
|
3581
|
-
wio.puts " ca_size_t inner_stride = 1;"
|
|
3582
|
-
wio.puts " for ( int8_t ii = baxis + 1; ii < src->ndim; ii++ ) inner_stride *= src->dim[ii];"
|
|
3583
|
-
wio.puts " ca_size_t axis_n = src->dim[baxis];"
|
|
3584
|
-
wio.puts " ca_size_t outer = src->elements / (axis_n * inner_stride);"
|
|
3585
|
-
wio.puts " for ( ca_size_t o = 0; o < outer; o++ ) {"
|
|
3586
|
-
wio.puts " for ( ca_size_t a = 0; a < axis_n; a++ ) {"
|
|
3587
|
-
wio.puts " for ( ca_size_t in = 0; in < inner_stride; in++ ) {"
|
|
3588
|
-
wio.puts " ca_size_t flat = (o * axis_n + a) * inner_stride + in;"
|
|
3589
|
-
wio.puts " memcpy(cw->ptr + flat * cw->bytes, cv->ptr + a * cv->bytes, cw->bytes);"
|
|
3590
|
-
wio.puts " }"
|
|
3591
|
-
wio.puts " }"
|
|
3592
|
-
wio.puts " }"
|
|
3593
|
-
wio.puts " }"
|
|
3594
|
-
wio.puts " /* Propagate cv mask via W-A3 overlay path below (= mask materialize) */"
|
|
3595
3751
|
wio.puts " if ( ca_has_mask(cv) ) {"
|
|
3596
|
-
wio.puts " /* Allocate cw mask + broadcast cv mask along axis */"
|
|
3597
3752
|
wio.puts " ca_create_mask(cw);"
|
|
3598
|
-
wio.puts "
|
|
3599
|
-
wio.puts "
|
|
3600
|
-
wio.puts "
|
|
3601
|
-
wio.puts " ca_size_t
|
|
3602
|
-
wio.puts "
|
|
3603
|
-
wio.puts "
|
|
3604
|
-
wio.puts "
|
|
3605
|
-
wio.puts " for (
|
|
3606
|
-
wio.puts "
|
|
3607
|
-
wio.puts "
|
|
3608
|
-
wio.puts "
|
|
3609
|
-
wio.puts "
|
|
3610
|
-
wio.puts "
|
|
3753
|
+
wio.puts " }"
|
|
3754
|
+
wio.puts " ca_attach(cv); /* window: nothing raises inside */"
|
|
3755
|
+
wio.puts " {"
|
|
3756
|
+
wio.puts " ca_size_t idx[CA_RANK_MAX], wstep[CA_RANK_MAX];"
|
|
3757
|
+
wio.puts " ca_size_t step = 1;"
|
|
3758
|
+
wio.puts " boolean8_t *cw_m = ca_has_mask(cv) ? (boolean8_t *) cw->mask->ptr : NULL;"
|
|
3759
|
+
wio.puts " boolean8_t *cv_m = ca_has_mask(cv) ? (boolean8_t *) cv->mask->ptr : NULL;"
|
|
3760
|
+
wio.puts " for ( int8_t i = 0; i < src->ndim; i++ ) {"
|
|
3761
|
+
wio.puts " idx[i] = 0;"
|
|
3762
|
+
wio.puts " wstep[i] = 0; /* kept axes: the weight repeats */"
|
|
3763
|
+
wio.puts " }"
|
|
3764
|
+
wio.puts " for ( int8_t i = naxes - 1; i >= 0; i-- ) {"
|
|
3765
|
+
wio.puts " wstep[wax[i]] = step;"
|
|
3766
|
+
wio.puts " step *= cv->dim[i];"
|
|
3767
|
+
wio.puts " }"
|
|
3768
|
+
wio.puts " for ( ca_size_t flat = 0; flat < src->elements; flat++ ) {"
|
|
3769
|
+
wio.puts " ca_size_t w = 0;"
|
|
3770
|
+
wio.puts " for ( int8_t i = 0; i < src->ndim; i++ ) w += idx[i] * wstep[i];"
|
|
3771
|
+
wio.puts " memcpy(cw->ptr + flat * cw->bytes, cv->ptr + w * cv->bytes, cw->bytes);"
|
|
3772
|
+
wio.puts " if ( cw_m ) cw_m[flat] = cv_m[w];"
|
|
3773
|
+
wio.puts " for ( int8_t i = src->ndim - 1; i >= 0; i-- ) {"
|
|
3774
|
+
wio.puts " if ( ++idx[i] < src->dim[i] ) break;"
|
|
3775
|
+
wio.puts " idx[i] = 0;"
|
|
3611
3776
|
wio.puts " }"
|
|
3612
3777
|
wio.puts " }"
|
|
3613
3778
|
wio.puts " }"
|
|
3614
3779
|
wio.puts " ca_detach(cv);"
|
|
3615
3780
|
wio.puts " }"
|
|
3616
3781
|
wio.puts " else {"
|
|
3617
|
-
wio.puts
|
|
3782
|
+
wio.puts " volatile VALUE rexpect = rb_ary_new();"
|
|
3783
|
+
wio.puts " for ( int8_t i = 0; i < naxes; i++ ) {"
|
|
3784
|
+
wio.puts " rb_ary_push(rexpect, SIZE2NUM(src->dim[wax[i]]));"
|
|
3785
|
+
wio.puts " }"
|
|
3786
|
+
wio.puts %Q[ rb_raise(rb_eArgError, "%s: weights of shape %"PRIsVALUE" not accepted (expected a scalar, the shape of the reduced axes %"PRIsVALUE", or self.shape %"PRIsVALUE")", ca_calling_method_name(), rb_funcall(vcv, rb_intern("shape"), 0), rexpect, rb_funcall(self, rb_intern("shape"), 0));]
|
|
3618
3787
|
wio.puts " }"
|
|
3619
3788
|
wio.puts " }"
|
|
3620
3789
|
# Mask overlay (= legacy W-A3 path; if the materialized cw carries a mask, overlay it)
|
|
@@ -3827,6 +3996,8 @@ module MkKernel
|
|
|
3827
3996
|
# fiber's mask base.
|
|
3828
3997
|
mask_setup =
|
|
3829
3998
|
if gated
|
|
3999
|
+
# Before the walks open: creating the mask allocates, and a raise
|
|
4000
|
+
# inside them would jump past their finish.
|
|
3830
4001
|
"\n" \
|
|
3831
4002
|
" boolean8_t *op_mask = NULL;\n" \
|
|
3832
4003
|
" if ( ca_has_mask(ca) ) {\n" \
|
|
@@ -3867,6 +4038,7 @@ module MkKernel
|
|
|
3867
4038
|
INIT inside the macro. */
|
|
3868
4039
|
int8_t slab_axes[CA_RANK_MAX];
|
|
3869
4040
|
slab_axes[0] = (int8_t) axis;
|
|
4041
|
+
#{mask_setup}
|
|
3870
4042
|
ca_iter_state st_in, st_out;
|
|
3871
4043
|
int rc;
|
|
3872
4044
|
rc = ca_iter_state_init_l2(&st_in, ca, CA_SLAB_AXES,
|
|
@@ -3882,7 +4054,6 @@ module MkKernel
|
|
|
3882
4054
|
rb_raise(rb_eRuntimeError,
|
|
3883
4055
|
"%s: output init failed rc=%d", ca_calling_method_name(), rc);
|
|
3884
4056
|
}
|
|
3885
|
-
#{mask_setup}
|
|
3886
4057
|
char *pi, *po;
|
|
3887
4058
|
boolean8_t *mi, *mo;
|
|
3888
4059
|
while ( ca_iter_state_next_slab_axes(&st_in, &pi, &mi) &&
|
|
@@ -4167,7 +4338,7 @@ module MkKernel
|
|
|
4167
4338
|
if is_fixlen
|
|
4168
4339
|
io.puts " buf[#{dst}].vp = pi + #{kexpr} * istride; buf[#{dst}].nb = (ca_size_t) ca->bytes;"
|
|
4169
4340
|
else
|
|
4170
|
-
io.puts " buf[#{dst}].v
|
|
4341
|
+
io.puts " memcpy(&buf[#{dst}].v, pi + #{kexpr} * istride, sizeof(buf[#{dst}].v));"
|
|
4171
4342
|
end
|
|
4172
4343
|
end
|
|
4173
4344
|
|
|
@@ -4263,21 +4434,10 @@ module MkKernel
|
|
|
4263
4434
|
io.puts " when input has any mask. */"
|
|
4264
4435
|
io.puts " if ( ca_has_mask(ca) ) ca_create_mask(co);"
|
|
4265
4436
|
end
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
io.puts "
|
|
4270
|
-
io.puts " if ( rc != CA_ITER_OK ) {"
|
|
4271
|
-
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: input init failed rc=%d", ca_calling_method_name(), rc);]
|
|
4272
|
-
io.puts " }"
|
|
4273
|
-
io.puts " rc = ca_iter_state_init_l2_paired(&st_out, &st_in, co, CA_SLAB_AXES,"
|
|
4274
|
-
io.puts " slab_axes, 1, CA_KERNEL_WRITE);"
|
|
4275
|
-
io.puts " if ( rc != CA_ITER_OK ) {"
|
|
4276
|
-
io.puts " ca_iter_state_finish(&st_in);"
|
|
4277
|
-
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: output init failed rc=%d", ca_calling_method_name(), rc);]
|
|
4278
|
-
io.puts " }"
|
|
4279
|
-
io.puts
|
|
4280
|
-
io.puts " ca_size_t fiber_n = st_in.slab_elements;"
|
|
4437
|
+
# The buffers are taken before the walks open: an allocation that
|
|
4438
|
+
# raises inside them would jump past their finish. The slab is the
|
|
4439
|
+
# sort axis alone, so a fiber holds ca->dim[axis] cells.
|
|
4440
|
+
io.puts " ca_size_t fiber_n = ca->dim[axis];"
|
|
4281
4441
|
if src == :object
|
|
4282
4442
|
# The object comparator calls <=>, so the sort can raise part way.
|
|
4283
4443
|
# ALLOCV frees these buffers whether the function returns or unwinds.
|
|
@@ -4290,15 +4450,17 @@ module MkKernel
|
|
|
4290
4450
|
io.puts " #{name}_pair_#{src} *aux = ALLOCV_N(#{name}_pair_#{src}, haux, fiber_n);"
|
|
4291
4451
|
end
|
|
4292
4452
|
else
|
|
4293
|
-
# ALLOCV as well: the walk that fills buf is the only thing that
|
|
4294
|
-
# could raise, and it does not today, but the buffers need not rely
|
|
4295
|
-
# on that.
|
|
4296
4453
|
io.puts " volatile VALUE hbuf = 0, haux = 0;"
|
|
4297
4454
|
io.puts " #{name}_pair_#{src} *buf = ALLOCV_N(#{name}_pair_#{src}, hbuf, fiber_n);"
|
|
4298
4455
|
end
|
|
4299
|
-
if has_kind && src
|
|
4456
|
+
if has_kind && %i[f32 f64].include?(src)
|
|
4457
|
+
# aux is the mergesort's ping-pong buffer and, before that, the
|
|
4458
|
+
# scratch of the NaN pre-partition, so a float kernel always takes it.
|
|
4459
|
+
io.puts " #{name}_pair_#{src} *aux = ALLOCV_N(#{name}_pair_#{src}, haux, fiber_n);"
|
|
4460
|
+
elsif has_kind && src != :object && src != :fixlen
|
|
4300
4461
|
# The aux pair buffer for ca_sort_merge_pair_<src> (a ping-pong
|
|
4301
|
-
# bottom-up mergesort), taken once outside the fiber loop.
|
|
4462
|
+
# bottom-up mergesort), taken once outside the fiber loop. The fixlen
|
|
4463
|
+
# lane sorts with qsort and needs none.
|
|
4302
4464
|
io.puts " #{name}_pair_#{src} *aux = NULL;"
|
|
4303
4465
|
io.puts " if ( do_stable ) {"
|
|
4304
4466
|
io.puts " aux = ALLOCV_N(#{name}_pair_#{src}, haux, fiber_n);"
|
|
@@ -4307,6 +4469,20 @@ module MkKernel
|
|
|
4307
4469
|
io.puts " (void) haux;"
|
|
4308
4470
|
end
|
|
4309
4471
|
|
|
4472
|
+
io.puts " ca_iter_state st_in, st_out;"
|
|
4473
|
+
io.puts " int rc;"
|
|
4474
|
+
io.puts " rc = ca_iter_state_init_l2(&st_in, ca, CA_SLAB_AXES,"
|
|
4475
|
+
io.puts " slab_axes, 1, 0);"
|
|
4476
|
+
io.puts " if ( rc != CA_ITER_OK ) {"
|
|
4477
|
+
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: input init failed rc=%d", ca_calling_method_name(), rc);]
|
|
4478
|
+
io.puts " }"
|
|
4479
|
+
io.puts " rc = ca_iter_state_init_l2_paired(&st_out, &st_in, co, CA_SLAB_AXES,"
|
|
4480
|
+
io.puts " slab_axes, 1, CA_KERNEL_WRITE);"
|
|
4481
|
+
io.puts " if ( rc != CA_ITER_OK ) {"
|
|
4482
|
+
io.puts " ca_iter_state_finish(&st_in);"
|
|
4483
|
+
io.puts %Q[ rb_raise(rb_eRuntimeError, "%s: output init failed rc=%d", ca_calling_method_name(), rc);]
|
|
4484
|
+
io.puts " }"
|
|
4485
|
+
|
|
4310
4486
|
if view_flat
|
|
4311
4487
|
io.puts
|
|
4312
4488
|
io.puts " /* :view_flat semantics: kernel computes view-flat address"
|
|
@@ -4358,7 +4534,7 @@ module MkKernel
|
|
|
4358
4534
|
if is_fixlen
|
|
4359
4535
|
io.puts " buf[k].vp = pi + k * istride; buf[k].nb = (ca_size_t) ca->bytes;"
|
|
4360
4536
|
else
|
|
4361
|
-
io.puts " buf[k].v
|
|
4537
|
+
io.puts " memcpy(&buf[k].v, pi + k * istride, sizeof(buf[k].v));"
|
|
4362
4538
|
end
|
|
4363
4539
|
io.puts " buf[k].i = #{payload.call("k")};"
|
|
4364
4540
|
io.puts " }"
|
|
@@ -4373,7 +4549,7 @@ module MkKernel
|
|
|
4373
4549
|
if is_fixlen
|
|
4374
4550
|
io.puts " buf[k_unmasked].vp = pi + k * istride; buf[k_unmasked].nb = (ca_size_t) ca->bytes;"
|
|
4375
4551
|
else
|
|
4376
|
-
io.puts " buf[k_unmasked].v
|
|
4552
|
+
io.puts " memcpy(&buf[k_unmasked].v, pi + k * istride, sizeof(buf[k_unmasked].v));"
|
|
4377
4553
|
end
|
|
4378
4554
|
io.puts " buf[k_unmasked].i = k;"
|
|
4379
4555
|
io.puts " k_unmasked++;"
|
|
@@ -4389,7 +4565,7 @@ module MkKernel
|
|
|
4389
4565
|
if is_fixlen
|
|
4390
4566
|
io.puts " buf[k].vp = pi + k * istride; buf[k].nb = (ca_size_t) ca->bytes;"
|
|
4391
4567
|
else
|
|
4392
|
-
io.puts " buf[k].v
|
|
4568
|
+
io.puts " memcpy(&buf[k].v, pi + k * istride, sizeof(buf[k].v));"
|
|
4393
4569
|
end
|
|
4394
4570
|
io.puts " buf[k].i = k;"
|
|
4395
4571
|
io.puts " }"
|
|
@@ -4462,7 +4638,7 @@ module MkKernel
|
|
|
4462
4638
|
io.puts " /* P.3/P.4: NaN pre-partition for float argsort -- NaN pairs are"
|
|
4463
4639
|
io.puts " pushed to the tail (= NaN-at-end policy, matches sort_copy"
|
|
4464
4640
|
io.puts " AC8). Only the finite slice is sorted. */"
|
|
4465
|
-
io.puts " ca_size_t finite_n = ca_partition_nan_pair_#{src}((ca_pair_#{src} *) #{buf_expr}, (ca_size_t) #{sort_n_expr});"
|
|
4641
|
+
io.puts " ca_size_t finite_n = ca_partition_nan_pair_#{src}((ca_pair_#{src} *) #{buf_expr}, (ca_size_t) #{sort_n_expr}, (ca_pair_#{src} *) aux);"
|
|
4466
4642
|
io.puts " if ( do_stable ) {"
|
|
4467
4643
|
io.puts " ca_sort_merge_pair_#{src}((ca_pair_#{src} *) #{buf_expr}, (ca_pair_#{src} *) aux, finite_n);"
|
|
4468
4644
|
io.puts " } else {"
|
|
@@ -5004,7 +5180,7 @@ module MkKernel
|
|
|
5004
5180
|
io.puts
|
|
5005
5181
|
io.puts "/* ===== #{k[:name]}_ki ============================================ */"
|
|
5006
5182
|
k[:source].each do |src|
|
|
5007
|
-
emit_native_for(io, k, src) { |o| emit_search_native(o, k, src) }
|
|
5183
|
+
emit_native_for(io, k, src, guard: true) { |o| emit_search_native(o, k, src) }
|
|
5008
5184
|
end
|
|
5009
5185
|
emit_search_dispatch(io, k)
|
|
5010
5186
|
end
|
|
@@ -5101,15 +5277,16 @@ module MkKernel
|
|
|
5101
5277
|
# The object lane calls Ruby for every cell of the case B/C walk, which
|
|
5102
5278
|
# runs between a direct attach and detach of self and val; a raise there
|
|
5103
5279
|
# would leave both attached. An entity holds no attach, so the lane
|
|
5104
|
-
# reads an entity copy of whichever is a view
|
|
5280
|
+
# reads an entity copy of whichever is a view, or an entity that leaves
|
|
5281
|
+
# ptr NULL at rest (a CASource).
|
|
5105
5282
|
entity_bc = if is_object
|
|
5106
5283
|
<<~C.chomp
|
|
5107
5284
|
volatile VALUE vca_entity = self;
|
|
5108
|
-
if ( ! ca_is_entity(ca) ) {
|
|
5285
|
+
if ( ! ca_is_entity(ca) || ! ca->ptr ) {
|
|
5109
5286
|
vca_entity = rb_ca_copy(self);
|
|
5110
5287
|
GetCArray(vca_entity, ca);
|
|
5111
5288
|
}
|
|
5112
|
-
if ( ! ca_is_entity(cv) ) {
|
|
5289
|
+
if ( ! ca_is_entity(cv) || ! cv->ptr ) {
|
|
5113
5290
|
vsrc_val = rb_ca_copy(vsrc_val);
|
|
5114
5291
|
GetCArray(vsrc_val, cv);
|
|
5115
5292
|
}
|
|
@@ -5264,7 +5441,7 @@ module MkKernel
|
|
|
5264
5441
|
CArray *co;
|
|
5265
5442
|
GetCArray(vout, co);
|
|
5266
5443
|
#{oi[:c]} *op = (#{oi[:c]} *) co->ptr;
|
|
5267
|
-
#{op_mask_decl}
|
|
5444
|
+
#{undef_nm ? op_mask_decl : ""}
|
|
5268
5445
|
|
|
5269
5446
|
ca_iter_state st;
|
|
5270
5447
|
int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
|
|
@@ -5486,6 +5663,12 @@ module MkKernel
|
|
|
5486
5663
|
#{op_mask_decl}
|
|
5487
5664
|
|
|
5488
5665
|
#{entity_bc}
|
|
5666
|
+
/* The output mask is made before the window opens, since making it
|
|
5667
|
+
allocates; it comes off again below when no cell needed it. */
|
|
5668
|
+
if ( #{undef_nm ? "1" : "ca_has_mask(cv)"} ) {
|
|
5669
|
+
ca_create_mask(co);
|
|
5670
|
+
op_mask = (boolean8_t *) co->mask->ptr;
|
|
5671
|
+
}
|
|
5489
5672
|
/* Attach self + val; rev4 uses raw row-major byte addressing. */
|
|
5490
5673
|
ca_attach_n(2, ca, cv); /* window: numeric bodies do not raise; the object lane reads entities */
|
|
5491
5674
|
|
|
@@ -5592,6 +5775,11 @@ module MkKernel
|
|
|
5592
5775
|
|
|
5593
5776
|
ca_detach(ca);
|
|
5594
5777
|
ca_detach(cv);
|
|
5778
|
+
if ( op_mask && ! memchr(op_mask, 1, (size_t) co->elements) ) {
|
|
5779
|
+
CArray *unused = co->mask;
|
|
5780
|
+
co->mask = NULL;
|
|
5781
|
+
ca_free(unused);
|
|
5782
|
+
}
|
|
5595
5783
|
return vout;
|
|
5596
5784
|
}
|
|
5597
5785
|
C
|
|
@@ -5633,6 +5821,13 @@ module MkKernel
|
|
|
5633
5821
|
io.puts " VALUE rval = argv[0];"
|
|
5634
5822
|
io.puts " VALUE raxis = argv[1];"
|
|
5635
5823
|
io.puts " VALUE reps = (argc >= 3) ? argv[2] : Qnil;"
|
|
5824
|
+
io.puts " /* A tolerance is a distance: a negative one (or NaN) means nothing. */"
|
|
5825
|
+
io.puts " if ( ! NIL_P(reps) ) {"
|
|
5826
|
+
io.puts " double eps_given = NUM2DBL(reps);"
|
|
5827
|
+
io.puts " if ( ! ( eps_given >= 0.0 ) ) {"
|
|
5828
|
+
io.puts %Q[ rb_raise(rb_eArgError, "%s: eps must be zero or positive (got %"PRIsVALUE")", ca_calling_method_name(), rb_inspect(reps));]
|
|
5829
|
+
io.puts " }"
|
|
5830
|
+
io.puts " }"
|
|
5636
5831
|
end
|
|
5637
5832
|
io.puts " /* Query gate: the reference Face and the query are brought into one"
|
|
5638
5833
|
io.puts " space (ca_face_reconcile_pair), then the reference descends to"
|
|
@@ -5656,6 +5851,8 @@ module MkKernel
|
|
|
5656
5851
|
io.puts " GetCArray(self, src);"
|
|
5657
5852
|
io.puts " }"
|
|
5658
5853
|
io.puts " }"
|
|
5854
|
+
io.puts " self = ca_fixlen_widen_for_query(self, rval);"
|
|
5855
|
+
io.puts " GetCArray(self, src);"
|
|
5659
5856
|
|
|
5660
5857
|
case k[:mask_self]
|
|
5661
5858
|
when :raise
|
|
@@ -5687,13 +5884,14 @@ module MkKernel
|
|
|
5687
5884
|
# (`ca_monop_<name>_<suffix>`). mkmath uses typedef names
|
|
5688
5885
|
# `float32_t` / `float64_t` here, not the raw C types `float` / `double`,
|
|
5689
5886
|
# so that `ca_monop_dispatch.c`'s extern references stay ABI-stable.
|
|
5690
|
-
# For all other data_types, the suffix
|
|
5887
|
+
# For all other data_types, the suffix is `DTYPES[src][:c]` without the
|
|
5888
|
+
# `ca_u_` of its unaligned cell typedef (ca_u_int32_t -> int32_t).
|
|
5691
5889
|
def self.math_suffix(src)
|
|
5692
5890
|
case src
|
|
5693
5891
|
when :f32 then "float32_t"
|
|
5694
5892
|
when :f64 then "float64_t"
|
|
5695
5893
|
when :fixlen then "fixlen" # symbol suffix differs from c-type "char"
|
|
5696
|
-
else DTYPES[src][:c]
|
|
5894
|
+
else DTYPES[src][:c].sub(/\Aca_u_/, "")
|
|
5697
5895
|
end
|
|
5698
5896
|
end
|
|
5699
5897
|
|
|
@@ -5772,7 +5970,8 @@ module MkKernel
|
|
|
5772
5970
|
uses_input = raw.include?("#1")
|
|
5773
5971
|
# SL.2.x phase 2 (monop emit substrate hardening, MEMO_MONOP_STRIDED_AUDIT.md):
|
|
5774
5972
|
# 3-way split (mask / contig / strided) symmetric with binop SL.2.x phase 1.
|
|
5775
|
-
# contig branch (i1==1 && i2==1) uses index form + `
|
|
5973
|
+
# contig branch (i1==1 && i2==1) uses index form + `omp simd` (`__restrict__`
|
|
5974
|
+
# only when input and output are apart).
|
|
5776
5975
|
# strided branch uses `p += i` increment form (per-iter multiply removed).
|
|
5777
5976
|
# mask branch unchanged (per-cell predicate, not SIMD-amenable).
|
|
5778
5977
|
if uses_input
|
|
@@ -5780,11 +5979,11 @@ module MkKernel
|
|
|
5780
5979
|
|
|
5781
5980
|
static void
|
|
5782
5981
|
ca_monop_#{k[:name]}_#{suffix} (ca_size_t n, boolean8_t *m,
|
|
5783
|
-
char *
|
|
5784
|
-
char *
|
|
5982
|
+
char *ptr1, ca_size_t i1,
|
|
5983
|
+
char *ptr2, ca_size_t i2)
|
|
5785
5984
|
{
|
|
5786
|
-
#{c_type_in} *
|
|
5787
|
-
#{c_type_out} *
|
|
5985
|
+
#{c_type_in} *q1 = (#{c_type_in} *) ptr1;
|
|
5986
|
+
#{c_type_out} *q2 = (#{c_type_out} *) ptr2;
|
|
5788
5987
|
ca_size_t k;
|
|
5789
5988
|
if ( m ) {
|
|
5790
5989
|
#{c_type_in} *p1 = q1;
|
|
@@ -5800,17 +5999,38 @@ module MkKernel
|
|
|
5800
5999
|
}
|
|
5801
6000
|
} else if ( i1 == 1 && i2 == 1 ) {
|
|
5802
6001
|
/* contig branch: SIMD-friendly form. Index-form `q[k]` exposes
|
|
5803
|
-
the unit-stride access to the vectorizer
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
6002
|
+
the unit-stride access to the vectorizer. Element-wise monop
|
|
6003
|
+
has no reassoc, so ε-close numeric contract is unaffected.
|
|
6004
|
+
The input and output are either apart, the same cells (a
|
|
6005
|
+
bang, or the lazy scratch applied in place), or overlapping
|
|
6006
|
+
otherwise; `__restrict__` is promised only when apart, since
|
|
6007
|
+
the body may store part of a cell before it has read it. */
|
|
5807
6008
|
#{c_type_in} *p1;
|
|
5808
6009
|
#{c_type_out} *p2;
|
|
5809
6010
|
(void)p1; (void)p2;
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
6011
|
+
uintptr_t a1 = (uintptr_t) q1, a2 = (uintptr_t) q2;
|
|
6012
|
+
if ( a2 + n * sizeof(*q2) <= a1 || a1 + n * sizeof(*q1) <= a2 ) {
|
|
6013
|
+
#{c_type_in} * __restrict__ r1 = q1;
|
|
6014
|
+
#{c_type_out} * __restrict__ r2 = q2;
|
|
6015
|
+
#pragma omp simd
|
|
6016
|
+
for (k=0; k<n; k++) {
|
|
6017
|
+
p1 = r1 + k; p2 = r2 + k;
|
|
6018
|
+
{ #{expr_body} }
|
|
6019
|
+
}
|
|
6020
|
+
}
|
|
6021
|
+
else if ( a1 == a2 && sizeof(*q1) == sizeof(*q2) ) {
|
|
6022
|
+
/* in place: an iteration reads and writes only its own cell */
|
|
6023
|
+
#pragma omp simd
|
|
6024
|
+
for (k=0; k<n; k++) {
|
|
6025
|
+
p1 = q1 + k; p2 = q2 + k;
|
|
6026
|
+
{ #{expr_body} }
|
|
6027
|
+
}
|
|
6028
|
+
}
|
|
6029
|
+
else {
|
|
6030
|
+
for (k=0; k<n; k++) {
|
|
6031
|
+
p1 = q1 + k; p2 = q2 + k;
|
|
6032
|
+
{ #{expr_body} }
|
|
6033
|
+
}
|
|
5814
6034
|
}
|
|
5815
6035
|
} else {
|
|
5816
6036
|
/* strided branch: numo-shape `p += s` increment form. */
|
|
@@ -5993,10 +6213,14 @@ module MkKernel
|
|
|
5993
6213
|
# `op_powi_<type>` resolves to `op_powi_int8_t` for int8 source).
|
|
5994
6214
|
# Substitution uses math_suffix (= typedef name, matches the suffix
|
|
5995
6215
|
# used in op_powi macro instantiations in ca_op_powi.h).
|
|
6216
|
+
# An object body calls Ruby on every cell and does not vectorize; the
|
|
6217
|
+
# pragma there only adds a "loop not vectorized" warning.
|
|
6218
|
+
simd = ( src == :object ) ? "" : "#pragma omp simd"
|
|
5996
6219
|
expr_body = raw.gsub(/#1/, "*p1").gsub(/#2/, "*p2").gsub(/#3/, "*p3").gsub(/<type>/, suffix)
|
|
5997
6220
|
# Option B+C pilot (binop emit substrate hardening, MEMO_BINOP_STRIDED_AUDIT.md):
|
|
5998
6221
|
# The no-mask body is split into a contig branch (i1==i2==i3==1) using the
|
|
5999
|
-
# `q[k]` index form with
|
|
6222
|
+
# `q[k]` index form with `#pragma omp simd` (`__restrict__`-qualified only
|
|
6223
|
+
# when the output is apart from both inputs)
|
|
6000
6224
|
# (SL.2.x-style insurance, element-wise has no reassoc -> ε-close unaffected),
|
|
6001
6225
|
# and a strided branch using `p += i` increment form (numo-shape inner loop).
|
|
6002
6226
|
# `i1`/`i2`/`i3` remain element units (NOT byte) -- byte-flip is Option A scope.
|
|
@@ -6006,13 +6230,13 @@ module MkKernel
|
|
|
6006
6230
|
|
|
6007
6231
|
static void
|
|
6008
6232
|
ca_binop_#{k[:name]}_#{suffix} (ca_size_t n, boolean8_t *m,
|
|
6009
|
-
char *
|
|
6010
|
-
char *
|
|
6011
|
-
char *
|
|
6233
|
+
char *ptr1, ca_size_t i1,
|
|
6234
|
+
char *ptr2, ca_size_t i2,
|
|
6235
|
+
char *ptr3, ca_size_t i3)
|
|
6012
6236
|
{
|
|
6013
|
-
#{c_type} *
|
|
6014
|
-
#{c_type} *
|
|
6015
|
-
#{c_type} *
|
|
6237
|
+
#{c_type} *q1 = (#{c_type} *) ptr1;
|
|
6238
|
+
#{c_type} *q2 = (#{c_type} *) ptr2;
|
|
6239
|
+
#{c_type} *q3 = (#{c_type} *) ptr3;
|
|
6016
6240
|
ca_size_t k;
|
|
6017
6241
|
if ( m ) {
|
|
6018
6242
|
#{c_type} *p1 = q1, *p2 = q2, *p3 = q3;
|
|
@@ -6028,16 +6252,101 @@ module MkKernel
|
|
|
6028
6252
|
}
|
|
6029
6253
|
} else if ( i1 == 1 && i2 == 1 && i3 == 1 ) {
|
|
6030
6254
|
/* contig branch: SIMD-friendly form. Index-form `q[k]` exposes
|
|
6031
|
-
the unit-stride access to the vectorizer
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6255
|
+
the unit-stride access to the vectorizer. Element-wise binop
|
|
6256
|
+
has no reassoc, so ε-close numeric contract is unaffected.
|
|
6257
|
+
The output is apart from each input, the same cells as it (a
|
|
6258
|
+
bang), or overlaps it otherwise; `__restrict__` is promised
|
|
6259
|
+
only when apart from both, since the body may store part of a
|
|
6260
|
+
cell before it has read it. */
|
|
6035
6261
|
#{c_type} *p1, *p2, *p3;
|
|
6262
|
+
uintptr_t a1 = (uintptr_t) q1, a2 = (uintptr_t) q2, a3 = (uintptr_t) q3;
|
|
6263
|
+
uintptr_t nb = (uintptr_t) n * sizeof(*q3);
|
|
6264
|
+
int apart1 = ( a3 + nb <= a1 || a1 + nb <= a3 );
|
|
6265
|
+
int apart2 = ( a3 + nb <= a2 || a2 + nb <= a3 );
|
|
6036
6266
|
(void)p1; (void)p2; (void)p3;
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6267
|
+
if ( apart1 && apart2 ) {
|
|
6268
|
+
#{c_type} * __restrict__ r1 = q1;
|
|
6269
|
+
#{c_type} * __restrict__ r2 = q2;
|
|
6270
|
+
#{c_type} * __restrict__ r3 = q3;
|
|
6271
|
+
#{simd}
|
|
6272
|
+
for (k=0; k<n; k++) {
|
|
6273
|
+
p1 = r1 + k; p2 = r2 + k; p3 = r3 + k;
|
|
6274
|
+
{ #{expr_body} }
|
|
6275
|
+
}
|
|
6276
|
+
}
|
|
6277
|
+
else if ( ( apart1 || a1 == a3 ) && ( apart2 || a2 == a3 ) ) {
|
|
6278
|
+
/* in place: an iteration reads and writes only its own cell */
|
|
6279
|
+
#{simd}
|
|
6280
|
+
for (k=0; k<n; k++) {
|
|
6281
|
+
p1 = q1 + k; p2 = q2 + k; p3 = q3 + k;
|
|
6282
|
+
{ #{expr_body} }
|
|
6283
|
+
}
|
|
6284
|
+
}
|
|
6285
|
+
else {
|
|
6286
|
+
for (k=0; k<n; k++) {
|
|
6287
|
+
p1 = q1 + k; p2 = q2 + k; p3 = q3 + k;
|
|
6288
|
+
{ #{expr_body} }
|
|
6289
|
+
}
|
|
6290
|
+
}
|
|
6291
|
+
} else if ( i3 == 1 && ( ( i1 == 1 && i2 == 0 ) || ( i1 == 0 && i2 == 1 ) ) ) {
|
|
6292
|
+
/* scalar branch: one operand is a scalar (stride 0). It is read
|
|
6293
|
+
into a local before the loop, so a store to the output cannot
|
|
6294
|
+
change it and the loop vectorizes like the contig branch. */
|
|
6295
|
+
#{c_type} sv = ( i1 == 0 ) ? *q1 : *q2;
|
|
6296
|
+
#{c_type} *qa = ( i1 == 0 ) ? q2 : q1;
|
|
6297
|
+
#{c_type} *p1, *p2, *p3;
|
|
6298
|
+
uintptr_t aa = (uintptr_t) qa, a3 = (uintptr_t) q3;
|
|
6299
|
+
uintptr_t nb = (uintptr_t) n * sizeof(*q3);
|
|
6300
|
+
(void)p1; (void)p2; (void)p3;
|
|
6301
|
+
if ( i1 == 0 ) {
|
|
6302
|
+
p1 = &sv;
|
|
6303
|
+
if ( a3 + nb <= aa || aa + nb <= a3 ) {
|
|
6304
|
+
#{c_type} * __restrict__ ra = qa;
|
|
6305
|
+
#{c_type} * __restrict__ r3 = q3;
|
|
6306
|
+
#{simd}
|
|
6307
|
+
for (k=0; k<n; k++) {
|
|
6308
|
+
p2 = ra + k; p3 = r3 + k;
|
|
6309
|
+
{ #{expr_body} }
|
|
6310
|
+
}
|
|
6311
|
+
}
|
|
6312
|
+
else if ( aa == a3 ) {
|
|
6313
|
+
#{simd}
|
|
6314
|
+
for (k=0; k<n; k++) {
|
|
6315
|
+
p2 = qa + k; p3 = q3 + k;
|
|
6316
|
+
{ #{expr_body} }
|
|
6317
|
+
}
|
|
6318
|
+
}
|
|
6319
|
+
else {
|
|
6320
|
+
for (k=0; k<n; k++) {
|
|
6321
|
+
p2 = qa + k; p3 = q3 + k;
|
|
6322
|
+
{ #{expr_body} }
|
|
6323
|
+
}
|
|
6324
|
+
}
|
|
6325
|
+
}
|
|
6326
|
+
else {
|
|
6327
|
+
p2 = &sv;
|
|
6328
|
+
if ( a3 + nb <= aa || aa + nb <= a3 ) {
|
|
6329
|
+
#{c_type} * __restrict__ ra = qa;
|
|
6330
|
+
#{c_type} * __restrict__ r3 = q3;
|
|
6331
|
+
#{simd}
|
|
6332
|
+
for (k=0; k<n; k++) {
|
|
6333
|
+
p1 = ra + k; p3 = r3 + k;
|
|
6334
|
+
{ #{expr_body} }
|
|
6335
|
+
}
|
|
6336
|
+
}
|
|
6337
|
+
else if ( aa == a3 ) {
|
|
6338
|
+
#{simd}
|
|
6339
|
+
for (k=0; k<n; k++) {
|
|
6340
|
+
p1 = qa + k; p3 = q3 + k;
|
|
6341
|
+
{ #{expr_body} }
|
|
6342
|
+
}
|
|
6343
|
+
}
|
|
6344
|
+
else {
|
|
6345
|
+
for (k=0; k<n; k++) {
|
|
6346
|
+
p1 = qa + k; p3 = q3 + k;
|
|
6347
|
+
{ #{expr_body} }
|
|
6348
|
+
}
|
|
6349
|
+
}
|
|
6041
6350
|
}
|
|
6042
6351
|
} else {
|
|
6043
6352
|
/* strided branch: numo-shape `p += s` increment form. */
|
|
@@ -6371,6 +6680,24 @@ module MkKernel
|
|
|
6371
6680
|
{ #{expr_body} }
|
|
6372
6681
|
}
|
|
6373
6682
|
}
|
|
6683
|
+
} else if ( i3 == 1 && i1 == 1 && i2 == 0 ) {
|
|
6684
|
+
/* scalar on the right: read it once, so the boolean store
|
|
6685
|
+
cannot be taken to change it and the loop vectorizes */
|
|
6686
|
+
#{c_type} sv = *q2;
|
|
6687
|
+
p2 = &sv;
|
|
6688
|
+
for (k=0; k<n; k++) {
|
|
6689
|
+
p1 = q1 + k;
|
|
6690
|
+
p3 = q3 + k;
|
|
6691
|
+
{ #{expr_body} }
|
|
6692
|
+
}
|
|
6693
|
+
} else if ( i3 == 1 && i1 == 0 && i2 == 1 ) {
|
|
6694
|
+
#{c_type} sv = *q1;
|
|
6695
|
+
p1 = &sv;
|
|
6696
|
+
for (k=0; k<n; k++) {
|
|
6697
|
+
p2 = q2 + k;
|
|
6698
|
+
p3 = q3 + k;
|
|
6699
|
+
{ #{expr_body} }
|
|
6700
|
+
}
|
|
6374
6701
|
} else {
|
|
6375
6702
|
for (k=0; k<n; k++) {
|
|
6376
6703
|
p1 = q1 + k*i1;
|
|
@@ -6500,13 +6827,21 @@ module MkKernel
|
|
|
6500
6827
|
cmplx64 cmplx128].freeze
|
|
6501
6828
|
CAST_INEXACT = %i[f32 f64 cmplx64 cmplx128].freeze
|
|
6502
6829
|
|
|
6830
|
+
# The plain C type of a data type, for a body that leaves this file
|
|
6831
|
+
# (CArray.__kernel_body__, a fusion plan): the unaligned typedef in :c is
|
|
6832
|
+
# about how a kernel reaches a cell, not about the value.
|
|
6833
|
+
def self.value_c(src)
|
|
6834
|
+
{ "ca_u_float32_t" => "float", "ca_u_float64_t" => "double" }
|
|
6835
|
+
.fetch(DTYPES[src][:c]) { |c| c.sub(/\Aca_u_/, "") }
|
|
6836
|
+
end
|
|
6837
|
+
|
|
6503
6838
|
def self.cast_body_rows
|
|
6504
6839
|
CAST_SOURCES.product(CAST_TARGETS).filter_map { |src, dst|
|
|
6505
6840
|
next if CAST_INEXACT.include?(src) && !CAST_INEXACT.include?(dst)
|
|
6506
6841
|
name = DTYPES[dst][:ca].sub(/\ACA_/, "").downcase
|
|
6507
6842
|
via = ( %i[i64 u64].include?(src) && %i[f32 cmplx64].include?(dst) ) ? "(double)" : ""
|
|
6508
6843
|
["monop", "cast_#{name}", DTYPES[src][:ca].sub(/\ACA_/, "").downcase,
|
|
6509
|
-
"(#2) = (#{
|
|
6844
|
+
"(#2) = (#{value_c(dst)})#{via}(#1);"]
|
|
6510
6845
|
}
|
|
6511
6846
|
end
|
|
6512
6847
|
|
|
@@ -6638,7 +6973,7 @@ module MkKernel
|
|
|
6638
6973
|
# surface) + optional CAMath module function, NOT as `<name>_ki`.
|
|
6639
6974
|
if k[:kind] == :monop
|
|
6640
6975
|
io.puts %Q[ rb_define_method(rb_cCArray, "#{k[:op]}", rb_ca_#{k[:name]}, 0);] if k[:bind]
|
|
6641
|
-
io.puts %Q[ rb_define_method(rb_cCArray, "#{k[:name]}!", rb_ca_#{k[:name]}_bang, 0);]
|
|
6976
|
+
io.puts %Q[ rb_define_method(rb_cCArray, "#{k[:name]}!", rb_ca_#{k[:name]}_bang, 0);] if k[:bang]
|
|
6642
6977
|
io.puts %Q[ rb_define_module_function(rb_mCAMath, "#{k[:name]}", rb_cmath_#{k[:name]}, 1);] if k[:cmath]
|
|
6643
6978
|
return
|
|
6644
6979
|
end
|
|
@@ -6866,11 +7201,10 @@ MkKernel.reduce :max,
|
|
|
6866
7201
|
# its identity value (1 for all, 0 for any) for those cells. An all-
|
|
6867
7202
|
# masked slab returns the identity (all([]) == true, any([]) == false).
|
|
6868
7203
|
#
|
|
6869
|
-
#
|
|
6870
|
-
#
|
|
6871
|
-
#
|
|
6872
|
-
#
|
|
6873
|
-
# added later as a separate kernel.
|
|
7204
|
+
# The walk stops once the answer is settled (settled:): any at the first
|
|
7205
|
+
# true, all and none at the first false / true. The test runs between
|
|
7206
|
+
# blocks of branch-free `acc |= v` / `acc &= v`, so a slab that never
|
|
7207
|
+
# settles keeps the speed of the plain fold.
|
|
6874
7208
|
MkKernel.header_block <<~C
|
|
6875
7209
|
/* The square root of a variance: an ε-negative from rounding becomes 0,
|
|
6876
7210
|
a NaN stays NaN (fmax(NaN, 0.0) is 0.0). */
|
|
@@ -6894,6 +7228,8 @@ C
|
|
|
6894
7228
|
MkKernel.reduce :all,
|
|
6895
7229
|
init: "1",
|
|
6896
7230
|
reduce: "acc &= v",
|
|
7231
|
+
settled: "!acc",
|
|
7232
|
+
reduction_kind: :min,
|
|
6897
7233
|
source: [:bool],
|
|
6898
7234
|
output: :bool,
|
|
6899
7235
|
ruby_scalar: :BOOL2VAL,
|
|
@@ -6903,6 +7239,8 @@ MkKernel.reduce :all,
|
|
|
6903
7239
|
MkKernel.reduce :any,
|
|
6904
7240
|
init: "0",
|
|
6905
7241
|
reduce: "acc |= v",
|
|
7242
|
+
settled: "acc",
|
|
7243
|
+
reduction_kind: :max,
|
|
6906
7244
|
source: [:bool],
|
|
6907
7245
|
output: :bool,
|
|
6908
7246
|
ruby_scalar: :BOOL2VAL,
|
|
@@ -6917,6 +7255,8 @@ MkKernel.reduce :any,
|
|
|
6917
7255
|
MkKernel.reduce :none,
|
|
6918
7256
|
init: "1",
|
|
6919
7257
|
reduce: "acc &= !v",
|
|
7258
|
+
settled: "!acc",
|
|
7259
|
+
reduction_kind: :min,
|
|
6920
7260
|
source: [:bool],
|
|
6921
7261
|
output: :bool,
|
|
6922
7262
|
ruby_scalar: :BOOL2VAL,
|
|
@@ -7365,9 +7705,9 @@ MkKernel.reduce :wsum,
|
|
|
7365
7705
|
# framework as wsum, but uses state+finish form (= sum + den) for the
|
|
7366
7706
|
# normalisation `mean = Σv*w / Σw` semantic.
|
|
7367
7707
|
#
|
|
7368
|
-
#
|
|
7369
|
-
#
|
|
7370
|
-
#
|
|
7708
|
+
# Weights summing to zero (den == 0 with cells present, weights of 0 or of
|
|
7709
|
+
# both signs) leave the mean undefined: the cell is UNDEF, as for a slab
|
|
7710
|
+
# with no valid cell, rather than 0/0 = NaN or x/0 = +-Infinity.
|
|
7371
7711
|
#
|
|
7372
7712
|
# 3.0 breaking semantic changes vs legacy (= identical to wsum W.2):
|
|
7373
7713
|
# - calling convention positional -> kwargs (axes positional, opts kw)
|
|
@@ -7384,6 +7724,7 @@ MkKernel.reduce :wmean,
|
|
|
7384
7724
|
reduce: "(sum += (double) v * (double) w, den += (double) w)",
|
|
7385
7725
|
reduction_kind: { sum: :plus, den: :plus }, # SL.1.4b
|
|
7386
7726
|
finish: "sum / den",
|
|
7727
|
+
undefined_if: "den == 0",
|
|
7387
7728
|
source: MkKernel::ALL_NUMERIC,
|
|
7388
7729
|
output: :f64,
|
|
7389
7730
|
ruby_scalar: :rb_float_new,
|
|
@@ -7678,8 +8019,8 @@ MkKernel.search :find_value_index,
|
|
|
7678
8019
|
mask_self: :raise,
|
|
7679
8020
|
fallback: :raise
|
|
7680
8021
|
|
|
7681
|
-
# bsearch_ki: per-slice binary search returning the matching position
|
|
7682
|
-
# (assumes a sorted slab). NaN handling: NaN sorts to end; query == NaN
|
|
8022
|
+
# bsearch_ki: per-slice binary search returning the first matching position
|
|
8023
|
+
# (assumes a sorted slab; of equal cells, the first). NaN handling: NaN sorts to end; query == NaN
|
|
7683
8024
|
# never matches. Hand-rolled strided binary search (= libc bsearch()
|
|
7684
8025
|
# requires contig memory; slab_stride may be > sizeof(T_LOAD) for a
|
|
7685
8026
|
# non-innermost search axis). NaN guards are dead code for integer
|
|
@@ -7705,7 +8046,7 @@ MkKernel.search :bsearch,
|
|
|
7705
8046
|
T_LOAD v = *(T_LOAD *)(slab_ptr + mid * slab_stride);
|
|
7706
8047
|
if ( v < query_val ) lo = mid + 1;
|
|
7707
8048
|
else if ( v > query_val ) hi = mid;
|
|
7708
|
-
else { result = mid;
|
|
8049
|
+
else { result = mid; hi = mid; } /* keep looking left: the first equal cell */
|
|
7709
8050
|
}
|
|
7710
8051
|
}
|
|
7711
8052
|
C
|
|
@@ -7720,7 +8061,7 @@ MkKernel.search :bsearch,
|
|
|
7720
8061
|
int c = CA_FLOAT_ORDER_CMP(v, query_val);
|
|
7721
8062
|
if ( c < 0 ) lo = mid + 1;
|
|
7722
8063
|
else if ( c > 0 ) hi = mid;
|
|
7723
|
-
else { result = mid;
|
|
8064
|
+
else { result = mid; hi = mid; } /* keep looking left: the first equal cell */
|
|
7724
8065
|
}
|
|
7725
8066
|
}
|
|
7726
8067
|
C
|
|
@@ -7737,7 +8078,7 @@ MkKernel.search :bsearch,
|
|
|
7737
8078
|
int c = ca_obj_order_cmp(v, query_val);
|
|
7738
8079
|
if ( c < 0 ) lo = mid + 1;
|
|
7739
8080
|
else if ( c > 0 ) hi = mid;
|
|
7740
|
-
else { result = mid;
|
|
8081
|
+
else { result = mid; hi = mid; } /* keep looking left: the first equal cell */
|
|
7741
8082
|
}
|
|
7742
8083
|
C
|
|
7743
8084
|
fixlen: <<~C,
|
|
@@ -7753,7 +8094,7 @@ MkKernel.search :bsearch,
|
|
|
7753
8094
|
int c = memcmp(v, query_val, (size_t) ca->bytes);
|
|
7754
8095
|
if ( c < 0 ) lo = mid + 1;
|
|
7755
8096
|
else if ( c > 0 ) hi = mid;
|
|
7756
|
-
else { result = mid;
|
|
8097
|
+
else { result = mid; hi = mid; } /* keep looking left: the first equal cell */
|
|
7757
8098
|
}
|
|
7758
8099
|
C
|
|
7759
8100
|
},
|
|
@@ -7938,7 +8279,7 @@ MkKernel.search :bsearch_addr,
|
|
|
7938
8279
|
T_LOAD v = *(T_LOAD *)(slab_ptr + mid * slab_stride);
|
|
7939
8280
|
if ( v < query_val ) lo = mid + 1;
|
|
7940
8281
|
else if ( v > query_val ) hi = mid;
|
|
7941
|
-
else { result = mid;
|
|
8282
|
+
else { result = mid; hi = mid; } /* keep looking left: the first equal cell */
|
|
7942
8283
|
}
|
|
7943
8284
|
}
|
|
7944
8285
|
C
|
|
@@ -7953,7 +8294,7 @@ MkKernel.search :bsearch_addr,
|
|
|
7953
8294
|
int c = CA_FLOAT_ORDER_CMP(v, query_val);
|
|
7954
8295
|
if ( c < 0 ) lo = mid + 1;
|
|
7955
8296
|
else if ( c > 0 ) hi = mid;
|
|
7956
|
-
else { result = mid;
|
|
8297
|
+
else { result = mid; hi = mid; } /* keep looking left: the first equal cell */
|
|
7957
8298
|
}
|
|
7958
8299
|
}
|
|
7959
8300
|
C
|
|
@@ -7967,7 +8308,7 @@ MkKernel.search :bsearch_addr,
|
|
|
7967
8308
|
int c = ca_obj_order_cmp(v, query_val);
|
|
7968
8309
|
if ( c < 0 ) lo = mid + 1;
|
|
7969
8310
|
else if ( c > 0 ) hi = mid;
|
|
7970
|
-
else { result = mid;
|
|
8311
|
+
else { result = mid; hi = mid; } /* keep looking left: the first equal cell */
|
|
7971
8312
|
}
|
|
7972
8313
|
C
|
|
7973
8314
|
fixlen: <<~C,
|
|
@@ -7981,7 +8322,7 @@ MkKernel.search :bsearch_addr,
|
|
|
7981
8322
|
int c = memcmp(v, query_val, (size_t) ca->bytes);
|
|
7982
8323
|
if ( c < 0 ) lo = mid + 1;
|
|
7983
8324
|
else if ( c > 0 ) hi = mid;
|
|
7984
|
-
else { result = mid;
|
|
8325
|
+
else { result = mid; hi = mid; } /* keep looking left: the first equal cell */
|
|
7985
8326
|
}
|
|
7986
8327
|
C
|
|
7987
8328
|
},
|
|
@@ -8152,13 +8493,13 @@ MkKernel.search :linear_section_binary,
|
|
|
8152
8493
|
if ( slab_n <= 1 ) {
|
|
8153
8494
|
/* A one-point axis has no segment: only the point itself is in
|
|
8154
8495
|
* range, at 0. An empty one has nothing in range. */
|
|
8155
|
-
if ( slab_n == 1 && query_val == *(
|
|
8496
|
+
if ( slab_n == 1 && query_val == *(ca_u_float64_t *)slab_ptr ) {
|
|
8156
8497
|
result = 0.0;
|
|
8157
8498
|
}
|
|
8158
8499
|
}
|
|
8159
8500
|
else {
|
|
8160
|
-
double y0 = *(
|
|
8161
|
-
double yn = *(
|
|
8501
|
+
double y0 = *(ca_u_float64_t *)(slab_ptr + 0 * slab_stride);
|
|
8502
|
+
double yn = *(ca_u_float64_t *)(slab_ptr + (slab_n - 1) * slab_stride);
|
|
8162
8503
|
int ascending = ( yn >= y0 ); /* grid direction (monotonic assumed) */
|
|
8163
8504
|
ca_size_t x1 = 0;
|
|
8164
8505
|
int found = 0;
|
|
@@ -8181,8 +8522,8 @@ MkKernel.search :linear_section_binary,
|
|
|
8181
8522
|
double gd = (query_val - y0) / (yn - y0) * (double)(slab_n - 1);
|
|
8182
8523
|
if ( gd >= 0.0 && gd < (double)(slab_n - 1) ) {
|
|
8183
8524
|
ca_size_t ag = (ca_size_t) gd;
|
|
8184
|
-
double yga = *(
|
|
8185
|
-
double ygb = *(
|
|
8525
|
+
double yga = *(ca_u_float64_t *)(slab_ptr + ag * slab_stride);
|
|
8526
|
+
double ygb = *(ca_u_float64_t *)(slab_ptr + (ag + 1) * slab_stride);
|
|
8186
8527
|
if ( (yga - query_val) * (ygb - query_val) <= 0 ) {
|
|
8187
8528
|
x1 = ag; found = 1;
|
|
8188
8529
|
}
|
|
@@ -8197,7 +8538,7 @@ MkKernel.search :linear_section_binary,
|
|
|
8197
8538
|
double ya = y0;
|
|
8198
8539
|
while ( (b - a) > 1 ) {
|
|
8199
8540
|
ca_size_t c = (a + b) / 2;
|
|
8200
|
-
double yc = *(
|
|
8541
|
+
double yc = *(ca_u_float64_t *)(slab_ptr + c * slab_stride);
|
|
8201
8542
|
if ( (ya - query_val) * (yc - query_val) <= 0 ) { b = c; }
|
|
8202
8543
|
else { a = c; ya = yc; }
|
|
8203
8544
|
}
|
|
@@ -8206,8 +8547,8 @@ MkKernel.search :linear_section_binary,
|
|
|
8206
8547
|
}
|
|
8207
8548
|
|
|
8208
8549
|
if ( found ) {
|
|
8209
|
-
double y1 = *(
|
|
8210
|
-
double y2 = *(
|
|
8550
|
+
double y1 = *(ca_u_float64_t *)(slab_ptr + x1 * slab_stride);
|
|
8551
|
+
double y2 = *(ca_u_float64_t *)(slab_ptr + (x1 + 1) * slab_stride);
|
|
8211
8552
|
double rest = (query_val - y1) / (y2 - y1);
|
|
8212
8553
|
if ( y2 != 0.0 && fabs(y2 - query_val) / fabs(y2) < DBL_EPSILON * 100 ) {
|
|
8213
8554
|
result = (double)(x1 + 1);
|
|
@@ -8243,7 +8584,7 @@ MkKernel.search :linear_section_linear,
|
|
|
8243
8584
|
if ( slab_n <= 1 ) {
|
|
8244
8585
|
/* A one-point axis has no segment: only the point itself is in
|
|
8245
8586
|
* range, at 0. An empty one has nothing in range. */
|
|
8246
|
-
if ( slab_n == 1 && query_val == *(
|
|
8587
|
+
if ( slab_n == 1 && query_val == *(ca_u_float64_t *)slab_ptr ) {
|
|
8247
8588
|
result = 0.0;
|
|
8248
8589
|
}
|
|
8249
8590
|
}
|
|
@@ -8251,15 +8592,15 @@ MkKernel.search :linear_section_linear,
|
|
|
8251
8592
|
ca_size_t x1 = 0;
|
|
8252
8593
|
int found = 0;
|
|
8253
8594
|
for ( ca_size_t k = 0; k < slab_n - 1; k++ ) {
|
|
8254
|
-
double yk = *(
|
|
8255
|
-
double yk1 = *(
|
|
8595
|
+
double yk = *(ca_u_float64_t *)(slab_ptr + k * slab_stride);
|
|
8596
|
+
double yk1 = *(ca_u_float64_t *)(slab_ptr + (k + 1) * slab_stride);
|
|
8256
8597
|
if ( (query_val - yk) * (query_val - yk1) <= 0 ) {
|
|
8257
8598
|
x1 = k; found = 1; break;
|
|
8258
8599
|
}
|
|
8259
8600
|
}
|
|
8260
8601
|
if ( found ) {
|
|
8261
|
-
double y1 = *(
|
|
8262
|
-
double y2 = *(
|
|
8602
|
+
double y1 = *(ca_u_float64_t *)(slab_ptr + x1 * slab_stride);
|
|
8603
|
+
double y2 = *(ca_u_float64_t *)(slab_ptr + (x1 + 1) * slab_stride);
|
|
8263
8604
|
result = (query_val - y1) / (y2 - y1) + (double) x1;
|
|
8264
8605
|
}
|
|
8265
8606
|
|
|
@@ -8286,8 +8627,8 @@ MkKernel.search :linear_fetch,
|
|
|
8286
8627
|
ca_size_t il = (ca_size_t) floor(query_val);
|
|
8287
8628
|
ca_size_t iu = (ca_size_t) ceil(query_val);
|
|
8288
8629
|
double w = query_val - floor(query_val);
|
|
8289
|
-
double yl = *(
|
|
8290
|
-
double yu = *(
|
|
8630
|
+
double yl = *(ca_u_float64_t *)(slab_ptr + il * slab_stride);
|
|
8631
|
+
double yu = *(ca_u_float64_t *)(slab_ptr + iu * slab_stride);
|
|
8291
8632
|
result = yu * w + yl * (1.0 - w);
|
|
8292
8633
|
}
|
|
8293
8634
|
C
|
|
@@ -8540,7 +8881,11 @@ MkKernel.monfunc :floor,
|
|
|
8540
8881
|
object: '(#2) = rb_funcall((#1), rb_intern("floor"), 0);',
|
|
8541
8882
|
}
|
|
8542
8883
|
|
|
8884
|
+
# round: the Ruby methods `round(ndigits)` / `round!(ndigits)` are defined in
|
|
8885
|
+
# carray_round.c on top of this kernel, so none is registered here.
|
|
8543
8886
|
MkKernel.monfunc :round,
|
|
8887
|
+
bind: false,
|
|
8888
|
+
bang: false,
|
|
8544
8889
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8545
8890
|
expr: {
|
|
8546
8891
|
int: "(#2) = (#1);",
|
|
@@ -8847,7 +9192,7 @@ MkKernel.monfunc :rad_pi,
|
|
|
8847
9192
|
# Pair-wise max / min — three flavours:
|
|
8848
9193
|
#
|
|
8849
9194
|
# pmax / pmin CArray legacy names (kept for backwards compat),
|
|
8850
|
-
# NaN-skip semantics
|
|
9195
|
+
# NaN-skip semantics, as C99 fmax / fmin.
|
|
8851
9196
|
# fmax / fmin Ruby-level aliases of pmax / pmin (= same kernel,
|
|
8852
9197
|
# same NaN-skip behaviour). Matches the C99 library
|
|
8853
9198
|
# function of the same name.
|
|
@@ -8857,22 +9202,26 @@ MkKernel.monfunc :rad_pi,
|
|
|
8857
9202
|
#
|
|
8858
9203
|
# Integer branches are identical across all three (no NaN). The float
|
|
8859
9204
|
# and object branches differ: pmax / pmin let a NaN lose (an object cell's
|
|
8860
|
-
# Float NaN included), maximum / minimum let it win.
|
|
9205
|
+
# Float NaN included), maximum / minimum let it win. In both flavours, of
|
|
9206
|
+
# two zeros the max is +0 and the min is -0 in either order. C's fmax /
|
|
9207
|
+
# fmin leave that choice to the library (glibc and macOS answer
|
|
9208
|
+
# differently), so the float branch does not call them. The sign of a
|
|
9209
|
+
# zero is read with copysign(1, x) rather than signbit(x): GCC does not
|
|
9210
|
+
# vectorize signbit on a double, which left the float64 kernels 17x
|
|
9211
|
+
# slower than float32. NaN is handled before, so copysign never sees one.
|
|
8861
9212
|
MkKernel.binop :pmax,
|
|
8862
9213
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8863
9214
|
expr: {
|
|
8864
9215
|
int: "(#3) = (#1) > (#2) ? (#1) : (#2);",
|
|
8865
|
-
float: "(#3) = fmax(#1, #2);",
|
|
8866
9216
|
object: '(#3) = ca_obj_extremum_replaces((#2), (#1), rb_intern(">")) ? (#2) : (#1);',
|
|
8867
|
-
}
|
|
9217
|
+
}.merge(MkKernel.float_widths("(#3) = isnan(#1) ? (#2) : isnan(#2) ? (#1) : ((#1) > (#2) || ((#1) == (#2) && copysign<f>(1, (#1)) > 0)) ? (#1) : (#2);"))
|
|
8868
9218
|
|
|
8869
9219
|
MkKernel.binop :pmin,
|
|
8870
9220
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8871
9221
|
expr: {
|
|
8872
9222
|
int: "(#3) = (#1) < (#2) ? (#1) : (#2);",
|
|
8873
|
-
float: "(#3) = fmin(#1, #2);",
|
|
8874
9223
|
object: '(#3) = ca_obj_extremum_replaces((#2), (#1), rb_intern("<")) ? (#2) : (#1);',
|
|
8875
|
-
}
|
|
9224
|
+
}.merge(MkKernel.float_widths("(#3) = isnan(#1) ? (#2) : isnan(#2) ? (#1) : ((#1) < (#2) || ((#1) == (#2) && copysign<f>(1, (#1)) < 0)) ? (#1) : (#2);"))
|
|
8876
9225
|
|
|
8877
9226
|
# NaN-propagate variants. Float
|
|
8878
9227
|
# branch tests both operands for NaN and short-circuits to NaN; non-
|
|
@@ -8883,17 +9232,15 @@ MkKernel.binop :maximum,
|
|
|
8883
9232
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8884
9233
|
expr: {
|
|
8885
9234
|
int: "(#3) = (#1) > (#2) ? (#1) : (#2);",
|
|
8886
|
-
float: "(#3) = isnan(#1) ? (#1) : isnan(#2) ? (#2) : ((#1) > (#2) || ((#1) == (#2) && !signbit(#1))) ? (#1) : (#2);",
|
|
8887
9235
|
object: '(#3) = CA_OBJ_ISNAN(#1) ? (#1) : CA_OBJ_ISNAN(#2) ? (#2) : ca_obj_cmp((#2), (#1)) > 0 ? (#2) : (#1);',
|
|
8888
|
-
}
|
|
9236
|
+
}.merge(MkKernel.float_widths("(#3) = isnan(#1) ? (#1) : isnan(#2) ? (#2) : ((#1) > (#2) || ((#1) == (#2) && copysign<f>(1, (#1)) > 0)) ? (#1) : (#2);"))
|
|
8889
9237
|
|
|
8890
9238
|
MkKernel.binop :minimum,
|
|
8891
9239
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8892
9240
|
expr: {
|
|
8893
9241
|
int: "(#3) = (#1) < (#2) ? (#1) : (#2);",
|
|
8894
|
-
float: "(#3) = isnan(#1) ? (#1) : isnan(#2) ? (#2) : ((#1) < (#2) || ((#1) == (#2) && signbit(#1))) ? (#1) : (#2);",
|
|
8895
9242
|
object: '(#3) = CA_OBJ_ISNAN(#1) ? (#1) : CA_OBJ_ISNAN(#2) ? (#2) : ca_obj_cmp((#2), (#1)) < 0 ? (#2) : (#1);',
|
|
8896
|
-
}
|
|
9243
|
+
}.merge(MkKernel.float_widths("(#3) = isnan(#1) ? (#1) : isnan(#2) ? (#2) : ((#1) < (#2) || ((#1) == (#2) && copysign<f>(1, (#1)) < 0)) ? (#1) : (#2);"))
|
|
8897
9244
|
|
|
8898
9245
|
# + and - work on the parts independently, so one generic expression
|
|
8899
9246
|
# covers every numeric data_type at any width.
|