carray 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +5 -25
- data/CHANGELOG.md +16 -0
- data/LICENSE +1 -1
- data/NEWS.md +3 -0
- data/README.md +128 -44
- data/carray.gemspec +22 -24
- data/ext/ca_array_pool.c +91 -0
- data/ext/ca_axis_descriptor.h +186 -0
- data/ext/ca_axis_dispatch.c +924 -0
- data/ext/ca_axis_group.c +1208 -0
- data/ext/ca_bincmp_dispatch.c +76 -0
- data/ext/ca_bincmp_dispatch.h +85 -0
- data/ext/ca_binop_dispatch.c +125 -0
- data/ext/ca_binop_dispatch.h +159 -0
- data/ext/ca_categorical_iterator.c +1375 -0
- data/ext/ca_compare.c +94 -0
- data/ext/ca_compare.h +26 -0
- data/ext/ca_composite_dispatch.c +414 -0
- data/ext/ca_composite_dispatch.h +116 -0
- data/ext/ca_for_buffer.h +96 -0
- data/ext/ca_for_each_element.h +241 -0
- data/ext/ca_group_iter.c +304 -0
- data/ext/ca_iter_substrate.h +325 -0
- data/ext/ca_kernel_iterator.c +4321 -0
- data/ext/ca_kernel_iterator.h +2603 -0
- data/ext/ca_moncmp_dispatch.c +37 -0
- data/ext/ca_moncmp_dispatch.h +62 -0
- data/ext/ca_monop_dispatch.c +200 -0
- data/ext/ca_monop_dispatch.h +235 -0
- data/ext/ca_obj_array.c +355 -359
- data/ext/ca_obj_bincmp.c +809 -0
- data/ext/ca_obj_binop.c +892 -0
- data/ext/ca_obj_bitarray.c +369 -164
- data/ext/ca_obj_bitfield.c +294 -234
- data/ext/ca_obj_block.c +189 -711
- data/ext/ca_obj_byte_swap.c +766 -0
- data/ext/ca_obj_const_string.c +965 -0
- data/ext/ca_obj_face.c +670 -0
- data/ext/ca_obj_face.h +247 -0
- data/ext/ca_obj_fake.c +228 -100
- data/ext/ca_obj_farray.c +54 -441
- data/ext/ca_obj_field.c +82 -529
- data/ext/ca_obj_fixlen_string.c +306 -0
- data/ext/ca_obj_grid.c +858 -440
- data/ext/ca_obj_meld.c +1034 -0
- data/ext/ca_obj_moncmp.c +569 -0
- data/ext/ca_obj_monop.c +1111 -0
- data/ext/ca_obj_object.c +774 -298
- data/ext/ca_obj_record.c +468 -0
- data/ext/ca_obj_reduce.c +97 -82
- data/ext/ca_obj_refer.c +569 -459
- data/ext/ca_obj_remap.c +475 -0
- data/ext/ca_obj_repeat.c +92 -477
- data/ext/ca_obj_roll.c +616 -0
- data/ext/ca_obj_select.c +344 -296
- data/ext/ca_obj_select_axis.c +1296 -0
- data/ext/ca_obj_shift.c +230 -792
- data/ext/ca_obj_source.c +78 -0
- data/ext/ca_obj_stack.c +1173 -0
- data/ext/ca_obj_stride.c +2501 -0
- data/ext/ca_obj_string.c +268 -0
- data/ext/ca_obj_tile.c +614 -0
- data/ext/ca_obj_time.c +546 -0
- data/ext/ca_obj_timedelta.c +435 -0
- data/ext/ca_obj_transpose.c +62 -516
- data/ext/ca_obj_triop.c +746 -0
- data/ext/ca_obj_unbound_repeat.c +208 -241
- data/ext/ca_obj_window.c +1131 -563
- data/ext/ca_op_byte_swap.c +175 -0
- data/ext/ca_op_ipower.c +319 -0
- data/ext/ca_op_powi.h +88 -0
- data/ext/ca_sort_kernels.h +132 -0
- data/ext/ca_sweep_engine.c +430 -0
- data/ext/ca_sweep_engine.h +157 -0
- data/ext/ca_transform_common.c +228 -0
- data/ext/ca_triop_dispatch.c +55 -0
- data/ext/ca_triop_dispatch.h +62 -0
- data/ext/carray.h +795 -402
- data/ext/carray_access.c +831 -711
- data/ext/carray_attribute.c +98 -330
- data/ext/carray_bincount.c +255 -0
- data/ext/carray_broadcast.c +283 -0
- data/ext/carray_call_cfunc.c +1360 -828
- data/ext/carray_call_cfunc.h +160 -0
- data/ext/carray_cast.c +1212 -301
- data/ext/carray_cast_func.rb +81 -40
- data/ext/carray_class.c +53 -63
- data/ext/carray_config.h +28 -0
- data/ext/carray_conversion.c +350 -346
- data/ext/carray_copy.c +156 -268
- data/ext/carray_core.c +1342 -199
- data/ext/carray_count.c +312 -0
- data/ext/carray_data_type.c +43 -19
- data/ext/carray_element.c +585 -213
- data/ext/carray_factorize.c +2542 -0
- data/ext/carray_generate.c +230 -559
- data/ext/carray_histogram.c +490 -0
- data/ext/carray_hold.c +228 -0
- data/ext/carray_index_classifier.c +1035 -0
- data/ext/carray_index_classifier.h +27 -0
- data/ext/carray_internal.h +120 -0
- data/ext/carray_kernels_bincmp.c +4445 -0
- data/ext/carray_kernels_binop.c +10979 -0
- data/ext/carray_kernels_init.c +36 -0
- data/ext/carray_kernels_map.c +3466 -0
- data/ext/carray_kernels_moncmp.c +2096 -0
- data/ext/carray_kernels_monop.c +18312 -0
- data/ext/carray_kernels_reduce_aggregate.c +25836 -0
- data/ext/carray_kernels_reduce_boolean.c +329 -0
- data/ext/carray_kernels_reduce_cumulative.c +14592 -0
- data/ext/carray_kernels_reduce_extreme.c +16947 -0
- data/ext/carray_kernels_reduce_variance.c +3909 -0
- data/ext/carray_kernels_scan.c +3692 -0
- data/ext/carray_kernels_search.c +32137 -0
- data/ext/carray_kernels_sort.c +10625 -0
- data/ext/carray_kernels_triop.c +1391 -0
- data/ext/carray_lazy.c +567 -0
- data/ext/carray_loop.c +88 -200
- data/ext/carray_mask.c +848 -154
- data/ext/carray_math_kernel.h +120 -0
- data/ext/carray_mathfunc.c +10 -241
- data/ext/carray_median_percentile.c +1257 -0
- data/ext/carray_memory_view.c +1625 -0
- data/ext/carray_operator.c +1526 -318
- data/ext/carray_order.c +664 -1394
- data/ext/carray_partition.c +416 -0
- data/ext/carray_random.c +518 -0
- data/ext/carray_scatter.c +357 -0
- data/ext/carray_slab.c +1219 -0
- data/ext/carray_slab.h +84 -0
- data/ext/carray_sort.c +829 -0
- data/ext/carray_sort_kernel.c +620 -0
- data/ext/carray_struct.c +695 -0
- data/ext/carray_test.c +343 -229
- data/ext/carray_undef.c +34 -17
- data/ext/carray_utils.c +175 -74
- data/ext/extconf.rb +216 -55
- data/ext/mk_call_cfunc.rb +480 -0
- data/ext/mkkernel.rb +8842 -0
- data/ext/ruby_carray.c +202 -101
- data/ext/version.h +4 -14
- data/ext/version.rb +5 -13
- data/lib/carray/arrow_tensor.rb +401 -0
- data/lib/carray/attribute.rb +166 -0
- data/lib/carray/autoload_carray.rb +220 -0
- data/lib/carray/autoload_method_extension.rb +44 -0
- data/lib/carray/axis_group.rb +711 -0
- data/lib/carray/basics.rb +481 -0
- data/lib/carray/bincount_nd.rb +358 -0
- data/lib/carray/block_iterator.rb +604 -0
- data/lib/carray/boolean_reduce.rb +109 -0
- data/lib/carray/categorical.rb +561 -0
- data/lib/carray/categorical_iterator.rb +1062 -0
- data/lib/carray/complex.rb +150 -0
- data/lib/carray/conditional.rb +216 -0
- data/lib/carray/const_string.rb +228 -0
- data/lib/carray/construct.rb +139 -328
- data/lib/carray/core_extensions.rb +240 -0
- data/lib/carray/data_type_extension.rb +233 -0
- data/lib/carray/fixlen_string.rb +95 -0
- data/lib/carray/frame/concat.rb +132 -0
- data/lib/carray/frame/convert.rb +95 -0
- data/lib/carray/frame/csv_parser.rb +211 -0
- data/lib/carray/frame/frame.rb +649 -0
- data/lib/carray/frame/group.rb +186 -0
- data/lib/carray/frame/io.rb +164 -0
- data/lib/carray/frame/join.rb +248 -0
- data/lib/carray/frame/records.rb +99 -0
- data/lib/carray/frame/sort.rb +113 -0
- data/lib/carray/frame/verbs.rb +299 -0
- data/lib/carray/frame.rb +16 -0
- data/lib/carray/histogram.rb +512 -0
- data/lib/carray/inspect.rb +37 -20
- data/lib/carray/iterator.rb +57 -349
- data/lib/carray/lazy.rb +889 -0
- data/lib/carray/mask_gap_fill.rb +200 -0
- data/lib/carray/math.rb +78 -342
- data/lib/carray/meld_reduce.rb +289 -0
- data/lib/carray/methods/align_addr.rb +116 -0
- data/lib/carray/methods/bin.rb +128 -0
- data/lib/carray/methods/bincount.rb +87 -0
- data/lib/carray/methods/bit_string.rb +92 -0
- data/lib/carray/methods/broadcast.rb +63 -0
- data/lib/carray/methods/choose.rb +39 -0
- data/lib/carray/methods/composition.rb +280 -0
- data/lib/carray/methods/gather_nd.rb +206 -0
- data/lib/carray/methods/index.rb +39 -0
- data/lib/carray/methods/insert_block.rb +99 -0
- data/lib/carray/methods/is_in.rb +141 -0
- data/lib/carray/methods/join.rb +90 -0
- data/lib/carray/methods/locate_addr.rb +47 -0
- data/lib/carray/methods/mask_duplicates.rb +41 -0
- data/lib/carray/methods/meshgrid.rb +91 -0
- data/lib/carray/methods/mode.rb +126 -0
- data/lib/carray/methods/nunique.rb +46 -0
- data/lib/carray/methods/resize.rb +56 -0
- data/lib/carray/methods/snap.rb +156 -0
- data/lib/carray/methods/string_format.rb +57 -0
- data/lib/carray/methods/unique.rb +47 -0
- data/lib/carray/methods/value_counts.rb +71 -0
- data/lib/carray/mkmf.rb +124 -101
- data/lib/carray/runtime.rb +108 -0
- data/lib/carray/serialize.rb +478 -167
- data/lib/carray/slab_iterator.rb +292 -0
- data/lib/carray/stack.rb +291 -0
- data/lib/carray/string.rb +56 -180
- data/lib/carray/string_operation_extension.rb +289 -0
- data/lib/carray/struct.rb +335 -323
- data/lib/carray/struct_builder.rb +697 -0
- data/lib/carray/table.rb +41 -2
- data/lib/carray/time.rb +2255 -38
- data/lib/carray/window_iterator.rb +655 -0
- data/lib/carray.rb +55 -57
- metadata +163 -130
- data/Rakefile +0 -51
- data/TODO.md +0 -18
- data/ext/ca_iter_block.c +0 -257
- data/ext/ca_iter_dimension.c +0 -299
- data/ext/ca_iter_window.c +0 -214
- data/ext/ca_obj_mapping.c +0 -644
- data/ext/carray_iterator.c +0 -641
- data/ext/carray_math.rb +0 -850
- data/ext/carray_numeric.c +0 -259
- data/ext/carray_sort_addr.c +0 -254
- data/ext/carray_stat.c +0 -2100
- data/ext/carray_stat_proc.rb +0 -1999
- data/ext/mkmath.rb +0 -741
- data/ext/ruby_ccomplex.c +0 -509
- data/ext/ruby_float_func.c +0 -86
- data/lib/carray/array.rb +0 -8
- data/lib/carray/autoload/autoload_base.rb +0 -19
- data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
- data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
- data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
- data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
- data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
- data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
- data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
- data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
- data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
- data/lib/carray/autoload/autoload_gem_random.rb +0 -8
- data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
- data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
- data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
- data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
- data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
- data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
- data/lib/carray/autoload/autoload_object_link.rb +0 -1
- data/lib/carray/autoload/autoload_object_pack.rb +0 -2
- data/lib/carray/autoload.rb +0 -141
- data/lib/carray/basic.rb +0 -191
- data/lib/carray/broadcast.rb +0 -101
- data/lib/carray/compose.rb +0 -315
- data/lib/carray/convert.rb +0 -115
- data/lib/carray/info.rb +0 -110
- data/lib/carray/io/imagemagick.rb +0 -235
- data/lib/carray/mask.rb +0 -102
- data/lib/carray/math/histogram.rb +0 -177
- data/lib/carray/math/recurrence.rb +0 -93
- data/lib/carray/object/ca_obj_iterator.rb +0 -50
- data/lib/carray/object/ca_obj_link.rb +0 -50
- data/lib/carray/object/ca_obj_pack.rb +0 -99
- data/lib/carray/obsolete.rb +0 -256
- data/lib/carray/ordering.rb +0 -181
- data/lib/carray/testing.rb +0 -51
- data/lib/carray/transform.rb +0 -109
- data/misc/Methods.ja.md +0 -182
- data/misc/NOTE +0 -51
- data/spec/Classes/CABitfield_spec.rb +0 -58
- data/spec/Classes/CABlockIterator_spec.rb +0 -114
- data/spec/Classes/CABlock_spec.rb +0 -205
- data/spec/Classes/CAField_spec.rb +0 -39
- data/spec/Classes/CAGrid_spec.rb +0 -75
- data/spec/Classes/CAMap_spec.rb +0 -0
- data/spec/Classes/CAMapping_spec.rb +0 -105
- data/spec/Classes/CAObject_attribute_spec.rb +0 -33
- data/spec/Classes/CAObject_spec.rb +0 -33
- data/spec/Classes/CARefer_spec.rb +0 -93
- data/spec/Classes/CARepeat_spec.rb +0 -65
- data/spec/Classes/CASelect_spec.rb +0 -22
- data/spec/Classes/CAShift_spec.rb +0 -16
- data/spec/Classes/CAStruct_spec.rb +0 -71
- data/spec/Classes/CATranspose_spec.rb +0 -60
- data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
- data/spec/Classes/CAWindow_spec.rb +0 -54
- data/spec/Classes/CAWrap_spec.rb +0 -8
- data/spec/Classes/CArray_spec.rb +0 -184
- data/spec/Classes/CScalar_spec.rb +0 -55
- data/spec/Classes/ex1.rb +0 -46
- data/spec/Features/feature_130_spec.rb +0 -19
- data/spec/Features/feature_attributes_spec.rb +0 -280
- data/spec/Features/feature_boolean_spec.rb +0 -98
- data/spec/Features/feature_broadcast.rb +0 -116
- data/spec/Features/feature_cast_function.rb +0 -19
- data/spec/Features/feature_cast_spec.rb +0 -33
- data/spec/Features/feature_class_spec.rb +0 -84
- data/spec/Features/feature_complex_spec.rb +0 -42
- data/spec/Features/feature_composite_spec.rb +0 -124
- data/spec/Features/feature_convert_spec.rb +0 -46
- data/spec/Features/feature_copy_spec.rb +0 -123
- data/spec/Features/feature_creation_spec.rb +0 -84
- data/spec/Features/feature_element_spec.rb +0 -144
- data/spec/Features/feature_extream_spec.rb +0 -54
- data/spec/Features/feature_generate_spec.rb +0 -74
- data/spec/Features/feature_index_spec.rb +0 -69
- data/spec/Features/feature_mask_spec.rb +0 -580
- data/spec/Features/feature_math_spec.rb +0 -97
- data/spec/Features/feature_order_spec.rb +0 -146
- data/spec/Features/feature_ref_store_spec.rb +0 -209
- data/spec/Features/feature_serialization_spec.rb +0 -125
- data/spec/Features/feature_stat_spec.rb +0 -397
- data/spec/Features/feature_virtual_spec.rb +0 -48
- data/spec/Features/method_eq_spec.rb +0 -81
- data/spec/Features/method_is_nan_spec.rb +0 -12
- data/spec/Features/method_map_spec.rb +0 -54
- data/spec/Features/method_max_with.rb +0 -20
- data/spec/Features/method_min_with.rb +0 -19
- data/spec/Features/method_ne_spec.rb +0 -18
- data/spec/Features/method_project_spec.rb +0 -188
- data/spec/Features/method_ref_spec.rb +0 -27
- data/spec/Features/method_round_spec.rb +0 -11
- data/spec/Features/method_s_linspace_spec.rb +0 -48
- data/spec/Features/method_s_span_spec.rb +0 -14
- data/spec/Features/method_seq_spec.rb +0 -47
- data/spec/Features/method_sort_with.rb +0 -43
- data/spec/Features/method_sorted_with.rb +0 -29
- data/spec/Features/method_span_spec.rb +0 -42
- data/spec/Features/method_wrap_readonly_spec.rb +0 -43
- data/spec/UnitTest/test_CAVirtual.rb +0 -214
- data/spec/spec_all.rb +0 -10
- data/utils/ca_ase.rb +0 -21
- data/utils/ca_methods.rb +0 -15
- data/utils/cast_checker.rb +0 -30
- data/utils/convert_test.rb +0 -73
- data/utils/extract_yard.rb +0 -22
- data/utils/guess_shape.rb +0 -76
- data/utils/monkey_patch_methods.rb +0 -62
- data/utils/remove_resource_fork.sh +0 -5
|
@@ -0,0 +1,2603 @@
|
|
|
1
|
+
/* ---------------------------------------------------------------------------
|
|
2
|
+
|
|
3
|
+
ca_kernel_iterator.h
|
|
4
|
+
|
|
5
|
+
T1 kernel_iterator — C extension author surface.
|
|
6
|
+
|
|
7
|
+
Status: TWO-TIER FREEZE CONTRACT (3.0 onward). The surface is split
|
|
8
|
+
so the engine can be re-implemented across 3.x (the surface stays fixed)
|
|
9
|
+
without breaking ext-gem kernels. See docs/authoring/HOW_TO_WRITE_KERNEL.md
|
|
10
|
+
§0/§13 for the prose contract; utils/check_kernel_surface_freeze.rb
|
|
11
|
+
(rake kernel_surface_check) is the mechanical pin.
|
|
12
|
+
|
|
13
|
+
FROZEN (do NOT rename / re-arity / change semantics; additions only):
|
|
14
|
+
- the author macros (CA_FOR_EACH_SLAB / _FIBER families,
|
|
15
|
+
CA_SLAB_REDUCE_* / _MAP_* / _SCAN_* suites, CA_L2_FOR_EACH,
|
|
16
|
+
CA_*_UNMASKED helpers)
|
|
17
|
+
- the raw-API entry points the macros expand to: ca_iter_state_
|
|
18
|
+
init_l2 / next_slab_axes / sync_slab / finish
|
|
19
|
+
- the enum/status tokens authors write literally: CA_SLAB_AXES,
|
|
20
|
+
CA_KERNEL_WRITE, CA_KERNEL_NO_MASK, CA_ITER_OK, CA_ITER_ERR_*
|
|
21
|
+
- the slab-delivery representation = the ca_iter_state fields a
|
|
22
|
+
kernel reads (marked "FROZEN author contract" at the struct):
|
|
23
|
+
slab_ndim / slab_dims / slab_strides / slab_mask_strides /
|
|
24
|
+
slab_elements / outer_ndim / outer_axes / outer_dims
|
|
25
|
+
- the identifiers injected into REDUCE / MAP / STEP expressions:
|
|
26
|
+
v, r, w, acc, idx, first
|
|
27
|
+
|
|
28
|
+
INTERNAL (free to refactor across 3.x — engine implementation):
|
|
29
|
+
- the state-machine functions' bodies + init_l1 / next_slab /
|
|
30
|
+
next_slab_strided / ca_iter_can_alias
|
|
31
|
+
- alias_mode / src_kind routing (CA_ITER_ALIAS_* / CA_ITER_SRC_* /
|
|
32
|
+
CA_KERNEL_FIBER_CONTIG / CA_SLAB_FREE / CA_SLAB_WHOLE)
|
|
33
|
+
- every ca_iter_state field NOT marked frozen (scratch / stack /
|
|
34
|
+
fiber / composed / descriptor bookkeeping) + physical layout
|
|
35
|
+
|
|
36
|
+
Adding new author surface is fine and expected (new flag bit, new
|
|
37
|
+
_EX macro variant + thin wrapper, new catalog macro) — that is how
|
|
38
|
+
the surface evolves within 3.x. Changing or removing a FROZEN name
|
|
39
|
+
is a 3.x breaking change: update the doc contract AND the guard in
|
|
40
|
+
the same commit. (PROPOSAL_T1_KERNEL_ITERATOR.md;
|
|
41
|
+
devel/MEMO_KERNEL_ITERATOR_OOP_PATH.vev4.md for the planned engine
|
|
42
|
+
overhaul this contract insulates against.)
|
|
43
|
+
|
|
44
|
+
Step 1 scope (this commit):
|
|
45
|
+
- struct definition (subset used by step 1; chunk_pos / scratch /
|
|
46
|
+
descs / outer_idx fields are present but unused, reserved for
|
|
47
|
+
later steps per proposal §3.1)
|
|
48
|
+
- CA_SLAB_WHOLE policy only (single slab = whole array)
|
|
49
|
+
- alias path only: ca_attach_is_alias(src) must hold, else init
|
|
50
|
+
returns CA_ITER_ERR_NOT_CHEAP (renamed from ca_attach_is_cheap
|
|
51
|
+
in T1 step 9.4a)
|
|
52
|
+
- READ-only, no mask handling, no WRITE sync
|
|
53
|
+
|
|
54
|
+
Later steps will fill in scratch materialize (step 2), L2 strided
|
|
55
|
+
(step 3), WRITE + sync_slab (step 4), descriptor 6-view connection
|
|
56
|
+
via P3 ca_axis_dispatch_for_each_slab (step 5), mask + macros
|
|
57
|
+
(step 6), NO_MASK enforcement (step 7).
|
|
58
|
+
|
|
59
|
+
--------------------------------------------------------------------------- */
|
|
60
|
+
|
|
61
|
+
#ifndef CA_KERNEL_ITERATOR_H
|
|
62
|
+
#define CA_KERNEL_ITERATOR_H 1
|
|
63
|
+
|
|
64
|
+
#include "carray.h"
|
|
65
|
+
#include "ca_iter_substrate.h" /* ca_axis_desc_t, ca_op_prefix_axis_t */
|
|
66
|
+
|
|
67
|
+
/* ---- slab policy (proposal §2.2) ------------------------------------- */
|
|
68
|
+
typedef enum {
|
|
69
|
+
CA_SLAB_FREE = 0, /* engine-chosen chunk (S1 max merge). step 2+. */
|
|
70
|
+
CA_SLAB_AXES = 1, /* user-pinned axes inside the slab. step 5+. */
|
|
71
|
+
CA_SLAB_WHOLE = 2 /* whole array in one slab. step 1 default. */
|
|
72
|
+
} ca_slab_policy_t;
|
|
73
|
+
|
|
74
|
+
/* ---- kernel flags (proposal §2.2) ------------------------------------ */
|
|
75
|
+
#define CA_KERNEL_READ 0x0
|
|
76
|
+
#define CA_KERNEL_WRITE 0x1 /* step 4+ */
|
|
77
|
+
#define CA_KERNEL_NO_MASK 0x2 /* step 7 */
|
|
78
|
+
#define CA_KERNEL_CHUNK_HINT 0x4 /* T2 reserve, never set in MVP */
|
|
79
|
+
#define CA_KERNEL_FIBER_CONTIG 0x8 /* PROPOSAL_FIBER_DELIVERY F.1a:
|
|
80
|
+
when set with policy=CA_SLAB_AXES
|
|
81
|
+
and naxes==1, next_slab_axes
|
|
82
|
+
guarantees contig data delivery
|
|
83
|
+
(= gathers strided fibers into
|
|
84
|
+
fiber_data_scratch). Set by the
|
|
85
|
+
CA_FOR_EACH_FIBER macro family
|
|
86
|
+
(F.2). Direct callers that want
|
|
87
|
+
the original L2 strided semantic
|
|
88
|
+
(= *out_ptr is alias_ptr + offset,
|
|
89
|
+
caller walks via slab_strides[0])
|
|
90
|
+
leave this bit clear. */
|
|
91
|
+
|
|
92
|
+
/* ---- alias mode (proposal §3.1) -------------------------------------- */
|
|
93
|
+
#define CA_ITER_ALIAS_NONE 0 /* scratch materialize. step 2+. */
|
|
94
|
+
#define CA_ITER_ALIAS_CONTIG 1 /* parent.ptr+offset direct slab. */
|
|
95
|
+
#define CA_ITER_ALIAS_STRIDED 2 /* L2 stride-aware. step 3+. */
|
|
96
|
+
#define CA_ITER_ALIAS_ATTACH 3 /* SRC_ATTACH path: view's own
|
|
97
|
+
ca_attach materialised src->ptr,
|
|
98
|
+
sync via ca_sync(src). step 9+. */
|
|
99
|
+
#define CA_ITER_ALIAS_PER_SLAB 4 /* Phase C T3 fallback: per-slab
|
|
100
|
+
materialise via ca_axis_dispatch_gather
|
|
101
|
+
with subset descriptor (= caller-built
|
|
102
|
+
outer-pinned). scratch_ptr reused
|
|
103
|
+
across outer iters (D1.1 (B): max slab
|
|
104
|
+
size, refilled in next_slab_axes).
|
|
105
|
+
READ-only in C.1 (WRITE = future). */
|
|
106
|
+
#define CA_ITER_ALIAS_PER_FIBER_FUSED 6 /* PROPOSAL_FIBER_PER_SOURCE_PATH
|
|
107
|
+
F.6.1: per-fiber fused xfer
|
|
108
|
+
dispatch. Engine skips
|
|
109
|
+
whole-view materialise and
|
|
110
|
+
calls ca_xfer_stride GET/PUT
|
|
111
|
+
per fiber, routing into the
|
|
112
|
+
view's fused fast path (X.1
|
|
113
|
+
OOB-fused, X.4 transform-
|
|
114
|
+
fused). Selected by
|
|
115
|
+
ca_iter_should_per_fiber_fused
|
|
116
|
+
(= hybrid src_kind + view func
|
|
117
|
+
probe + fiber axis effective
|
|
118
|
+
stride predicate). Yields
|
|
119
|
+
from fiber_data_scratch with
|
|
120
|
+
mask in fiber_mask_scratch
|
|
121
|
+
(= rev2 §3.3, (data,mask)
|
|
122
|
+
pair). */
|
|
123
|
+
#define CA_ITER_ALIAS_STACK_OUTER_K 8 /* PROPOSAL_CASTACK_XFER_OPT_LAYERING
|
|
124
|
+
P.2 Case A (2026-06-18): CAStack
|
|
125
|
+
source + CA_SLAB_AXES with axis 0
|
|
126
|
+
NOT in slab (= K-axis in outer
|
|
127
|
+
iter, e.g. view.mean(axis: 1) /
|
|
128
|
+
view.mean(axis: 2)). Each slab
|
|
129
|
+
corresponds to a region inside
|
|
130
|
+
ONE parent selected by
|
|
131
|
+
outer_idx[K_outer_pos]. init_l2
|
|
132
|
+
attaches K parents (+ K parent
|
|
133
|
+
masks if present) and caches
|
|
134
|
+
their ptrs + uniform parent
|
|
135
|
+
native byte strides; next_slab_
|
|
136
|
+
axes aliases parents[k]->ptr +
|
|
137
|
+
parent_off directly (= zero
|
|
138
|
+
copy, zero scratch). parent
|
|
139
|
+
entity case = eager-equivalent
|
|
140
|
+
memory bandwidth. Mask aliases
|
|
141
|
+
parent->mask similarly. Scoped
|
|
142
|
+
to slab_axes that exclude axis 0
|
|
143
|
+
(= axis 0 must be an outer iter
|
|
144
|
+
axis). */
|
|
145
|
+
#define CA_ITER_ALIAS_STACK 7 /* PROPOSAL_CASTACK_LOOP_INTERCHANGE
|
|
146
|
+
Vector A rev2 (direct per-parent
|
|
147
|
+
ptr access): CAStack source +
|
|
148
|
+
CA_SLAB_AXES with slab_axes ==
|
|
149
|
+
[0] (= K-axis-only slab, e.g.
|
|
150
|
+
view.mean(axis: 0) / sum(axis: 0)).
|
|
151
|
+
init_l2 attaches K parents up
|
|
152
|
+
front (= O(1) per entity parent),
|
|
153
|
+
caches parent->ptr aliases +
|
|
154
|
+
uniform parent-native byte
|
|
155
|
+
strides. next_slab_axes does
|
|
156
|
+
K-fold direct memcpy gather from
|
|
157
|
+
parents[k]->ptr + parent_off
|
|
158
|
+
into a slab-sized scratch,
|
|
159
|
+
bypassing ca_xfer_stride
|
|
160
|
+
entirely (= no per-call dispatch
|
|
161
|
+
/ cyclic_check / strided_walk
|
|
162
|
+
function-boundary overhead).
|
|
163
|
+
Peak buffer = K * bytes
|
|
164
|
+
(= slab footprint), not
|
|
165
|
+
K * parent.elements * bytes.
|
|
166
|
+
Scope-narrow to slab_axes == [0]:
|
|
167
|
+
arbitrary slab shapes require
|
|
168
|
+
per-cell index decode which
|
|
169
|
+
loses the inner contig fast
|
|
170
|
+
path; covers the demand-driving
|
|
171
|
+
case (= reduce along the stacked
|
|
172
|
+
K-axis). Mask-bearing CAStack
|
|
173
|
+
falls back to SRC_ATTACH whole-
|
|
174
|
+
view path (per-slab mask gather
|
|
175
|
+
= future extension). Rev1
|
|
176
|
+
explored xfer_stride-based
|
|
177
|
+
delivery (see PROPOSAL
|
|
178
|
+
rev2 §11) and was rejected for
|
|
179
|
+
~16x wall-clock regression. */
|
|
180
|
+
#define CA_ITER_ALIAS_PER_SLAB_HOIST 5 /* Phase C T3 specialised (B-1b,
|
|
181
|
+
C.1b): innermost slab axis is
|
|
182
|
+
STRIDE and no SHIFT axes
|
|
183
|
+
anywhere. Manual gather:
|
|
184
|
+
outer + non-innermost-slab axes
|
|
185
|
+
hoisted (computed once per slab
|
|
186
|
+
row), inner = pure STRIDE
|
|
187
|
+
linear memcpy (= no engine
|
|
188
|
+
per-cell switch, SIMD-friendly
|
|
189
|
+
contig run). Target: 1.5-1.8x
|
|
190
|
+
win vs (A) fallback for INDEX
|
|
191
|
+
slab with innermost STRIDE. */
|
|
192
|
+
|
|
193
|
+
/* ---- error codes ----------------------------------------------------- */
|
|
194
|
+
#define CA_ITER_OK 0
|
|
195
|
+
#define CA_ITER_ERR_NOT_CHEAP 1 /* src needs materialize, step 2+ */
|
|
196
|
+
#define CA_ITER_ERR_POLICY 2 /* policy not implemented yet */
|
|
197
|
+
#define CA_ITER_ERR_FLAGS 3 /* flag combination unsupported */
|
|
198
|
+
#define CA_ITER_ERR_READONLY 4 /* WRITE on readonly view (CARepeat etc.) */
|
|
199
|
+
#define CA_ITER_ERR_MASK 5 /* masked source — step 4-5 only, lifted in step 6 */
|
|
200
|
+
#define CA_ITER_ERR_MASK_NOT_ALLOWED 6 /* NO_MASK flag set on a masked source (step 7) */
|
|
201
|
+
#define CA_ITER_ERR_UNBOUND_SHAPE 7 /* CAUnboundRepeat passed before bind() — reserved for
|
|
202
|
+
sub-step 9.3 (= used iff unbound CAUbrep smoke shows
|
|
203
|
+
unsafe behavior on the existing SRC_CASTRIDE path) */
|
|
204
|
+
|
|
205
|
+
/* ---- source kind (step 5+, internal routing) ------------------------- */
|
|
206
|
+
#define CA_ITER_SRC_NONE 0
|
|
207
|
+
#define CA_ITER_SRC_CASTRIDE 1 /* entity / CAStride family (step 1-4).
|
|
208
|
+
CAUnboundRepeat is also classified
|
|
209
|
+
here via ca_ubrep_func = ca_stride_func. */
|
|
210
|
+
#define CA_ITER_SRC_DESCRIPTOR 2 /* CSA / CAGrid / CASelect / CAMapping / CAWindow / CAShift (step 5+) */
|
|
211
|
+
#define CA_ITER_SRC_ATTACH 3 /* CAFake / CAByteSwap / CABitfield /
|
|
212
|
+
CABitarray / CAReduce — view's own
|
|
213
|
+
ca_attach materialises (step 9+). */
|
|
214
|
+
#define CA_ITER_SRC_DESCRIPTOR_L2_ALIASABLE 4
|
|
215
|
+
/* F-2 (PROPOSAL_F2_KERNEL_ITERATOR_ALIAS
|
|
216
|
+
rev6): descriptor view whose innermost
|
|
217
|
+
axis is STRIDE kind. init_l2 takes the
|
|
218
|
+
alias path (= no scratch alloc, parent.ptr
|
|
219
|
+
+ outer-prefix-offset + inner-byte-start
|
|
220
|
+
is yielded with inner_byte_stride). Only
|
|
221
|
+
emitted by ca_iter_route_source after
|
|
222
|
+
describe_axes inspection. */
|
|
223
|
+
|
|
224
|
+
/* ---- source-kind registration for externally installed obj_types -----
|
|
225
|
+
|
|
226
|
+
The classifier recognises the core's own view classes by comparing
|
|
227
|
+
their operation table against a list compiled into the engine. A view
|
|
228
|
+
class installed by a companion gem through ca_install_obj_type matches
|
|
229
|
+
nothing on that list, so without this hook it classifies as
|
|
230
|
+
CA_ITER_SRC_NONE and every kernel that goes through the iterator
|
|
231
|
+
refuses the array with CA_ITER_ERR_NOT_CHEAP. An external author
|
|
232
|
+
declares the routing here instead, once, in the class's Init:
|
|
233
|
+
|
|
234
|
+
ca_iter_register_source_kind(CA_OBJ_MY_VIEW, CA_ITER_SRC_ATTACH);
|
|
235
|
+
|
|
236
|
+
CA_ITER_SRC_ATTACH is the only kind that may be registered. It is the
|
|
237
|
+
one whose contract an external class can meet on its own: func_attach
|
|
238
|
+
materialises (or aliases) src->ptr and func_sync scatters back — the
|
|
239
|
+
CAFake contract, which every view already implements to be attachable
|
|
240
|
+
at all. The other kinds are not open to registration: SRC_CASTRIDE
|
|
241
|
+
asserts the struct *is* a CAStride (the engine reads its strides
|
|
242
|
+
directly, and a class that really is one is already classified by its
|
|
243
|
+
inherited operation table), and SRC_DESCRIPTOR requires a
|
|
244
|
+
describe_axes function the engine looks up in its own table, which an
|
|
245
|
+
external type has no way to supply. Passing anything else raises
|
|
246
|
+
rather than accepting a routing the iterator cannot honour.
|
|
247
|
+
|
|
248
|
+
Registering is additive and does not override the two structural
|
|
249
|
+
cases: the classifier still decides entity and CAStride-family sources
|
|
250
|
+
first (both are read directly and would only be made slower by a
|
|
251
|
+
materialising path), and consults this table before the built-in list. */
|
|
252
|
+
void ca_iter_register_source_kind (int obj_type, uint8_t kind);
|
|
253
|
+
|
|
254
|
+
/* ---- iter state (proposal §3.1, step 1 subset) ----------------------- */
|
|
255
|
+
/* Fields marked "[step N+]" are present for forward layout compat but
|
|
256
|
+
are zero-initialised and unused in step 1. Adding them now avoids a
|
|
257
|
+
struct-layout churn when later steps fill them in. */
|
|
258
|
+
typedef struct {
|
|
259
|
+
/* --- inputs (fixed at init) --- */
|
|
260
|
+
struct _CArray *src;
|
|
261
|
+
uint8_t src_kind; /* CA_ITER_SRC_* (step 5+) */
|
|
262
|
+
int8_t level; /* 1=L1 contig, 2=L2 strided (step 3+) */
|
|
263
|
+
ca_slab_policy_t policy;
|
|
264
|
+
int8_t ndim;
|
|
265
|
+
int8_t naxes; /* [step 5+] AXES policy axis count */
|
|
266
|
+
int8_t *axes; /* [step 5+] [naxes] */
|
|
267
|
+
uint32_t flags;
|
|
268
|
+
ca_size_t bytes; /* element size */
|
|
269
|
+
|
|
270
|
+
/* --- CAStride compose-fold cache (step 3+, L2 alias path) ---
|
|
271
|
+
For CAStride-family sources at L2, init_l2 runs
|
|
272
|
+
ca_stride_compose_to_root once to get the root entity + per-axis
|
|
273
|
+
byte strides + base offset. Cached here so next_slab_strided's
|
|
274
|
+
per-iter offset calc is just Σ outer_idx[k] * composed_strides[k].
|
|
275
|
+
Inline CA_RANK_MAX array (no heap alloc) since CA_DIM_MAX=16 keeps
|
|
276
|
+
the footprint at 128 bytes per state. root is NULL on L1 paths
|
|
277
|
+
and on L2 with an entity / contig source (use src as the base). */
|
|
278
|
+
struct _CArray *root; /* root entity, attached at init */
|
|
279
|
+
ca_size_t composed_strides[CA_RANK_MAX]; /* byte units */
|
|
280
|
+
ca_size_t composed_base; /* byte offset from root->ptr */
|
|
281
|
+
|
|
282
|
+
/* --- descriptor framework cache (step 5+, descriptor sources) ---
|
|
283
|
+
For CSA / CAGrid / CASelect / CAMapping / CAWindow / CAShift,
|
|
284
|
+
init_l1_descriptor runs the view's *_describe_axes once then
|
|
285
|
+
reuses the P3 substrate (ca_axis_dispatch_prepare / _layout /
|
|
286
|
+
_classify_prefix) to derive the slab layout. Cached inline so
|
|
287
|
+
next_slab walks the prefix axes without re-doing the analysis. */
|
|
288
|
+
ca_axis_desc_t descs[CA_RANK_MAX]; /* post-merge axes */
|
|
289
|
+
ca_size_t pstrides[CA_RANK_MAX]; /* parent byte strides */
|
|
290
|
+
ca_size_t mdim[CA_RANK_MAX]; /* effective parent dims */
|
|
291
|
+
ca_op_prefix_axis_t prefix[CA_RANK_MAX]; /* pre-classified prefix */
|
|
292
|
+
ca_size_t parent_axis_dims[CA_RANK_MAX]; /* from describe_axes */
|
|
293
|
+
int8_t desc_ndim;
|
|
294
|
+
int8_t slab_start; /* prefix axes [0..slab_start) */
|
|
295
|
+
ca_size_t slab_base; /* slab base byte offset */
|
|
296
|
+
ca_size_t slab_bytes_desc; /* descriptor slab span */
|
|
297
|
+
ca_size_t total_elements; /* view.elements snapshot */
|
|
298
|
+
|
|
299
|
+
/* --- iteration cursor --- */
|
|
300
|
+
ca_size_t *outer_idx; /* [step 3+] [outer_axes_n] */
|
|
301
|
+
ca_size_t slab_n; /* current slab element count */
|
|
302
|
+
ca_size_t total_slabs;
|
|
303
|
+
ca_size_t slabs_emitted;
|
|
304
|
+
|
|
305
|
+
/* --- chunk position (T2 forward compat, proposal §8) --- */
|
|
306
|
+
ca_size_t chunk_pos;
|
|
307
|
+
ca_size_t chunk_size;
|
|
308
|
+
|
|
309
|
+
/* --- scratch buffer [step 2+] --- */
|
|
310
|
+
char *scratch_ptr;
|
|
311
|
+
boolean8_t *scratch_mask;
|
|
312
|
+
ca_size_t scratch_cap;
|
|
313
|
+
|
|
314
|
+
/* --- alias path --- */
|
|
315
|
+
uint8_t alias_mode; /* CA_ITER_ALIAS_* */
|
|
316
|
+
char *alias_ptr; /* slab ptr when alias_mode != NONE */
|
|
317
|
+
boolean8_t *alias_mask; /* [step 6+] */
|
|
318
|
+
ca_size_t alias_stride; /* [step 3+] */
|
|
319
|
+
|
|
320
|
+
/* --- WRITE-path sync timing [step 4+] --- */
|
|
321
|
+
uint8_t write_dirty;
|
|
322
|
+
|
|
323
|
+
/* --- CA_SLAB_AXES policy fields (Phase A capstone, T1) ---
|
|
324
|
+
Populated when init_l2 is called with policy = CA_SLAB_AXES against
|
|
325
|
+
a SRC_CASTRIDE source. The kernel reads slab metadata directly
|
|
326
|
+
from these fields (= per-walk metadata, unchanged across slabs).
|
|
327
|
+
outer_axes / outer_dims / outer_strides drive the prefix walk;
|
|
328
|
+
slab_axes_buf / slab_dims / slab_strides describe the K-D slab
|
|
329
|
+
handed to the kernel. All strides are byte units. Zero-init for
|
|
330
|
+
other policies (CA_SLAB_WHOLE / FREE).
|
|
331
|
+
|
|
332
|
+
"slab_axes_buf" rather than "slab_axes" to avoid colliding with
|
|
333
|
+
the existing `int8_t *axes` user-input pointer field above.
|
|
334
|
+
|
|
335
|
+
>>> FROZEN author contract (see banner, two-tier freeze): the
|
|
336
|
+
slab-delivery representation a kernel reads is exactly
|
|
337
|
+
slab_ndim, slab_dims[], slab_strides[], slab_mask_strides[],
|
|
338
|
+
slab_elements, outer_ndim, outer_axes[], outer_dims[].
|
|
339
|
+
Do NOT rename / repurpose these — hand-written kernels and the
|
|
340
|
+
mkkernel-generated bodies read them by name. The other fields in
|
|
341
|
+
this block (slab_axes_buf, outer_strides, outer_mask_strides) are
|
|
342
|
+
INTERNAL bookkeeping and may be refactored. <<< */
|
|
343
|
+
int8_t slab_ndim;
|
|
344
|
+
int8_t slab_axes_buf[CA_RANK_MAX]; /* user axes (copied, sort-ascending) */
|
|
345
|
+
ca_size_t slab_dims[CA_RANK_MAX]; /* per-slab-axis dim */
|
|
346
|
+
ca_size_t slab_strides[CA_RANK_MAX]; /* per-slab-axis data byte stride */
|
|
347
|
+
ca_size_t slab_mask_strides[CA_RANK_MAX]; /* per-slab-axis mask element stride */
|
|
348
|
+
ca_size_t slab_elements; /* Π slab_dims */
|
|
349
|
+
int8_t outer_ndim; /* = src->ndim - slab_ndim */
|
|
350
|
+
int8_t outer_axes[CA_RANK_MAX]; /* complement of slab_axes_buf */
|
|
351
|
+
ca_size_t outer_dims[CA_RANK_MAX]; /* per-outer-axis dim */
|
|
352
|
+
ca_size_t outer_strides[CA_RANK_MAX]; /* per-outer-axis data byte stride */
|
|
353
|
+
ca_size_t outer_mask_strides[CA_RANK_MAX]; /* per-outer-axis mask element stride */
|
|
354
|
+
|
|
355
|
+
/* --- Per-fiber contig scratch (PROPOSAL_FIBER_DELIVERY F.1a) ---
|
|
356
|
+
For naxes==1 (= per-axis fiber, the catalog CA_FOR_EACH_FIBER target)
|
|
357
|
+
in the default fall-through path of next_slab_axes (= Phase A/B alias
|
|
358
|
+
+ SRC_ATTACH + Phase B.1.5), when slab_strides[0] != bytes the fiber
|
|
359
|
+
is yielded strided. To honor the catalog contract "data contig
|
|
360
|
+
delivery", next_slab_axes lazily allocates this scratch on first
|
|
361
|
+
gather and gathers each fiber here before yielding. Reused across
|
|
362
|
+
fibers; size grows to max slab_dims[0] * bytes.
|
|
363
|
+
|
|
364
|
+
last_data_off is captured by next_slab_axes(k) BEFORE the outer_idx
|
|
365
|
+
advance, then consumed by sync_slab(k) to compute the dst base for
|
|
366
|
+
WRITE scatter (= rebuilding from outer_idx in sync would duplicate
|
|
367
|
+
next_slab_axes logic; see PROPOSAL §4.3.2 hazard comment).
|
|
368
|
+
|
|
369
|
+
Phase C T3 paths (CA_ITER_ALIAS_PER_SLAB / _HOIST) yield from
|
|
370
|
+
scratch_ptr (= already contig per-slab materialise) and do NOT
|
|
371
|
+
touch these fields. */
|
|
372
|
+
char *fiber_data_scratch;
|
|
373
|
+
ca_size_t fiber_data_scratch_cap;
|
|
374
|
+
ca_size_t last_data_off;
|
|
375
|
+
|
|
376
|
+
/* PROPOSAL_FIBER_DELIVERY F.1b: per-fiber contig mask scratch.
|
|
377
|
+
Symmetric to fiber_data_scratch. When the source carries a mask
|
|
378
|
+
(= alias_mask != NULL) and slab_mask_strides[0] != 1, the engine
|
|
379
|
+
gathers the fiber's mask into contig boolean8_t order here so the
|
|
380
|
+
author can do `m[i]` without indirection. Read-only from kernel
|
|
381
|
+
POV (= L2 WRITE semantic does not propagate to mask state), so no
|
|
382
|
+
scatter is needed. See header field doc for sibling field
|
|
383
|
+
fiber_data_scratch. */
|
|
384
|
+
boolean8_t *fiber_mask_scratch;
|
|
385
|
+
ca_size_t fiber_mask_scratch_cap;
|
|
386
|
+
|
|
387
|
+
/* --- PROPOSAL_FIBER_PER_SOURCE_PATH F.6.1 substrate ---
|
|
388
|
+
When alias_mode == CA_ITER_ALIAS_PER_FIBER_FUSED, next_slab_axes
|
|
389
|
+
builds a fiber region from outer_idx + fiber_axis and calls
|
|
390
|
+
ca_xfer_stride(src, ..., GET) into fiber_data_scratch instead of
|
|
391
|
+
reading from a whole-view scratch buffer. fiber_axis is the
|
|
392
|
+
source-axis index (= same axis-space as src->dim[]) of the user-
|
|
393
|
+
passed slab axis. fiber_native_strides are row-major byte strides
|
|
394
|
+
over src->dim used in ca_xfer_stride strides[] argument. */
|
|
395
|
+
int8_t fiber_axis;
|
|
396
|
+
ca_size_t fiber_native_strides[CA_RANK_MAX];
|
|
397
|
+
/* fiber_region_starts[] cached by next_slab_axes BEFORE outer_idx
|
|
398
|
+
advance so sync_slab can reconstruct the same ca_xfer_stride
|
|
399
|
+
region for WRITE PUT. Same hazard pattern as last_data_off
|
|
400
|
+
(= F.1a). */
|
|
401
|
+
ca_size_t fiber_region_starts[CA_RANK_MAX];
|
|
402
|
+
|
|
403
|
+
/* --- PROPOSAL_CASTACK_LOOP_INTERCHANGE Vector A rev2 (direct per-
|
|
404
|
+
parent ptr access) --- */
|
|
405
|
+
/* When alias_mode == CA_ITER_ALIAS_STACK, init_l2 attaches all K
|
|
406
|
+
parents (= O(1) per entity parent) and caches their ptr aliases
|
|
407
|
+
here so next_slab_axes can do K-fold direct memcpy gather without
|
|
408
|
+
going through ca_xfer_stride. Owned by iter (xfree in finish).
|
|
409
|
+
stack_parent_strides[] are the uniform parent-native byte strides
|
|
410
|
+
(= all CAStack parents are uniform shape per MEMO §3.2). */
|
|
411
|
+
char **stack_parent_ptrs; /* [n_parents] */
|
|
412
|
+
int32_t stack_n_parents; /* = ((CAStack *)src)->n_parents */
|
|
413
|
+
ca_size_t stack_parent_strides[CA_RANK_MAX]; /* parent-space byte strides */
|
|
414
|
+
/* Parent-space element strides (= 1 byte per cell) for parent mask
|
|
415
|
+
addressing; only filled when stack_parent_mask_ptrs != NULL. Caching
|
|
416
|
+
here lets next_slab_axes compute mask_off without downcasting to
|
|
417
|
+
CAStack (= AC3 layering goal). */
|
|
418
|
+
ca_size_t stack_parent_mask_strides[CA_RANK_MAX];
|
|
419
|
+
/* --- PROPOSAL_CASTACK_XFER_OPT_LAYERING P.2 Case A (2026-06-18) --- */
|
|
420
|
+
/* When alias_mode == CA_ITER_ALIAS_STACK_OUTER_K and the CAStack
|
|
421
|
+
source carries a mask, init_l2 attaches K parent masks and caches
|
|
422
|
+
their ptrs here for parent->mask alias delivery alongside
|
|
423
|
+
parents[k]->ptr. NULL when source has no mask. Owned by iter
|
|
424
|
+
(xfree in finish). */
|
|
425
|
+
boolean8_t **stack_parent_mask_ptrs; /* [n_parents] or NULL */
|
|
426
|
+
/* K axis position within the outer iter axis list. Set by init_l2
|
|
427
|
+
when alias_mode == CA_ITER_ALIAS_STACK_OUTER_K; next_slab_axes uses
|
|
428
|
+
outer_idx[stack_k_outer_pos] to pick the active parent. */
|
|
429
|
+
int8_t stack_k_outer_pos;
|
|
430
|
+
/* --- pilot/castack-axis0-loop-interchange (2026-06-19) --- */
|
|
431
|
+
/* CA_ITER_ALIAS_STACK tile cache. Refills TILE fibers (= K cells each)
|
|
432
|
+
at once via K contig parent reads, then serves the next TILE next_slab
|
|
433
|
+
calls from L1d-resident buffer. Layout: cache[t][k] so a fiber at
|
|
434
|
+
tile_pos = cache + tile_pos * K * bytes (matches slab_strides[0] =
|
|
435
|
+
bytes). Tile capacity sized to fit ~32 KB L1d budget; current refill
|
|
436
|
+
length clamped to remaining slabs. Owned by iter (xfree in finish). */
|
|
437
|
+
char *stack_tile_cache; /* K * stack_tile_cap * bytes */
|
|
438
|
+
ca_size_t stack_tile_cap; /* TILE = fibers per refill (0 = disabled) */
|
|
439
|
+
ca_size_t stack_tile_pos; /* 0..stack_tile_have-1 (= ready); ==have triggers refill */
|
|
440
|
+
ca_size_t stack_tile_have; /* fibers actually present in current tile */
|
|
441
|
+
} ca_iter_state;
|
|
442
|
+
|
|
443
|
+
/* ---- alias eligibility predicate (proposal §11.3) ------------------- */
|
|
444
|
+
|
|
445
|
+
/* Generalises ca_attach_is_alias (carray_core.c:410, renamed from
|
|
446
|
+
ca_attach_is_cheap in T1 step 9.4a) so the alias decision can be
|
|
447
|
+
made level-aware. Level is the dispatch level the caller intends
|
|
448
|
+
to use:
|
|
449
|
+
|
|
450
|
+
level == 1 (L1, contig kernel)
|
|
451
|
+
Alias iff parent->ptr can be handed to the kernel as one contig
|
|
452
|
+
run with stride implicit = bytes. True for entity arrays and
|
|
453
|
+
CAStride-family views whose composed strides are row-major
|
|
454
|
+
contiguous — exactly ca_attach_is_alias's domain.
|
|
455
|
+
|
|
456
|
+
level == 2 (L2, strided kernel)
|
|
457
|
+
Alias iff the engine can yield per-outer-axis slabs as
|
|
458
|
+
parent->ptr + offset with a native stride_bytes argument, with
|
|
459
|
+
no scratch allocation. Broader than L1: any CAStride-family
|
|
460
|
+
view qualifies (the innermost run, even a stride-of-1 of count 1,
|
|
461
|
+
defines a valid strided slab). Entity arrays also qualify
|
|
462
|
+
trivially. Pathological all-strided-no-contig sources still
|
|
463
|
+
qualify here — the engine yields slab_n=1 with the native step;
|
|
464
|
+
L1 fallback is an engine-policy choice, not an eligibility one.
|
|
465
|
+
|
|
466
|
+
level == 3 (L3, multi-d kernel)
|
|
467
|
+
Not implemented in Phase 1 — falls back to L1 semantics so the predicate
|
|
468
|
+
stays well-defined for callers that probe ahead.
|
|
469
|
+
|
|
470
|
+
Descriptor framework views (CAGrid / CASelect / CAMapping / CAWindow /
|
|
471
|
+
CAShift / CSA) and overlay views (CAFake / CAByteSwap / CABitfield /
|
|
472
|
+
CABitarray) return 0 at every level; their alias story lands in
|
|
473
|
+
step 5 (descriptor connection via ca_axis_dispatch_for_each_slab).
|
|
474
|
+
|
|
475
|
+
ca_attach_is_alias is retained as the level=1 oracle for the Tier A
|
|
476
|
+
(PROPOSAL_DELEGATE_COPY_DATA) defer site; callers that already use
|
|
477
|
+
it keep working unchanged. New code in the kernel_iterator path
|
|
478
|
+
should call ca_iter_can_alias with an explicit level. */
|
|
479
|
+
int ca_iter_can_alias (void *ap, int level);
|
|
480
|
+
|
|
481
|
+
/* ---- state machine (proposal §3.2) ---------------------------------- */
|
|
482
|
+
|
|
483
|
+
/* Initialise `st` for an **L1 (contig kernel)** walk of `src`.
|
|
484
|
+
|
|
485
|
+
Source routing:
|
|
486
|
+
- entity / CAStride contig: alias path (single slab, alias_ptr =
|
|
487
|
+
src->ptr, stride implicit = bytes)
|
|
488
|
+
- CAStride family non-contig: scratch path (ca_copy_data
|
|
489
|
+
compose-fold gather into a malloc'd buffer)
|
|
490
|
+
- other sources: CA_ITER_ERR_NOT_CHEAP
|
|
491
|
+
|
|
492
|
+
policy: only CA_SLAB_WHOLE accepted in step 1-3.
|
|
493
|
+
flags: must be 0 (READ) until step 4 / 7.
|
|
494
|
+
|
|
495
|
+
On success returns CA_ITER_OK; on error returns CA_ITER_ERR_*
|
|
496
|
+
without claiming resources (finish need not be called).
|
|
497
|
+
|
|
498
|
+
Pair with ca_iter_state_next_slab. Step 3 split init into
|
|
499
|
+
level-specific entry points so each setup path stays focused
|
|
500
|
+
(proposal §3.2 rev: original single-init was relaxed when L2 setup
|
|
501
|
+
diverged enough to warrant its own state initialiser; see
|
|
502
|
+
ROADMAP/CHANGELOG rev). */
|
|
503
|
+
int ca_iter_state_init_l1 (ca_iter_state *st,
|
|
504
|
+
struct _CArray *src,
|
|
505
|
+
ca_slab_policy_t policy,
|
|
506
|
+
int8_t *axes,
|
|
507
|
+
int8_t naxes,
|
|
508
|
+
uint32_t flags);
|
|
509
|
+
|
|
510
|
+
/* Initialise `st` for an **L2 (strided kernel)** walk of `src`.
|
|
511
|
+
|
|
512
|
+
Source routing:
|
|
513
|
+
- entity / CAStride contig: alias_mode = CONTIG, single slab,
|
|
514
|
+
stride = bytes (kernel still receives the explicit stride arg)
|
|
515
|
+
- CAStride non-contig: alias_mode = STRIDED, multi-slab walk over
|
|
516
|
+
prefix axes, each yield carries native inner stride_bytes. No
|
|
517
|
+
scratch.
|
|
518
|
+
- other sources: CA_ITER_ERR_NOT_CHEAP
|
|
519
|
+
|
|
520
|
+
Other args match init_l1. Pair with ca_iter_state_next_slab_strided. */
|
|
521
|
+
int ca_iter_state_init_l2 (ca_iter_state *st,
|
|
522
|
+
struct _CArray *src,
|
|
523
|
+
ca_slab_policy_t policy,
|
|
524
|
+
int8_t *axes,
|
|
525
|
+
int8_t naxes,
|
|
526
|
+
uint32_t flags);
|
|
527
|
+
|
|
528
|
+
/* Pull the next L1 slab. Returns 1 and writes *out_ptr / *out_mask /
|
|
529
|
+
*out_n when a slab is yielded; returns 0 when the walk is complete.
|
|
530
|
+
After a 0 return, subsequent calls also return 0. Only valid when
|
|
531
|
+
init_l1 was used.
|
|
532
|
+
|
|
533
|
+
*out_mask is set to the per-slab boolean8_t mask pointer when the
|
|
534
|
+
source carries a mask (= ca_has_mask(src)), or NULL otherwise.
|
|
535
|
+
The mask layout matches the value layout (= same iteration order
|
|
536
|
+
and same n). Step 6+: kernels use the CA_FOR_EACH_UNMASKED macro
|
|
537
|
+
family (carray.h) to skip masked cells. */
|
|
538
|
+
int ca_iter_state_next_slab (ca_iter_state *st,
|
|
539
|
+
char **out_ptr,
|
|
540
|
+
boolean8_t **out_mask,
|
|
541
|
+
ca_size_t *out_n);
|
|
542
|
+
|
|
543
|
+
/* Pull the next L2 strided slab. Returns 1 with *out_ptr /
|
|
544
|
+
*out_mask / *out_n / *out_stride_bytes when a slab is yielded;
|
|
545
|
+
returns 0 when the walk is complete. The kernel walks `*out_n`
|
|
546
|
+
elements by stepping `*out_stride_bytes` between consecutive
|
|
547
|
+
elements starting at `*out_ptr`; the mask uses the **same stride
|
|
548
|
+
semantics** when non-NULL (= each mask byte at offset i * stride is
|
|
549
|
+
conceptually paired with the value at ptr + i * stride_bytes, but
|
|
550
|
+
since mask is boolean8_t == 1 byte, mask stride is 1 byte when the
|
|
551
|
+
value stride is bytes, and proportional otherwise). Only valid
|
|
552
|
+
when init_l2 was used. */
|
|
553
|
+
int ca_iter_state_next_slab_strided (ca_iter_state *st,
|
|
554
|
+
char **out_ptr,
|
|
555
|
+
boolean8_t **out_mask,
|
|
556
|
+
ca_size_t *out_n,
|
|
557
|
+
ca_size_t *out_stride_bytes);
|
|
558
|
+
|
|
559
|
+
/* Pull the next CA_SLAB_AXES slab (K-D block). Returns 1 with *out_ptr
|
|
560
|
+
/ *out_mask set to the slab base when a slab is yielded; returns 0
|
|
561
|
+
when the walk is complete. Only valid when init_l2 was called with
|
|
562
|
+
policy = CA_SLAB_AXES.
|
|
563
|
+
|
|
564
|
+
Slab shape and strides are constant across the walk (per-walk
|
|
565
|
+
metadata) — the kernel reads them directly from the state struct:
|
|
566
|
+
st->slab_ndim (number of slab axes)
|
|
567
|
+
st->slab_dims[k] (size along slab axis k, k in [0..slab_ndim))
|
|
568
|
+
st->slab_strides[k] (data byte stride along slab axis k)
|
|
569
|
+
st->slab_mask_strides[k] (mask element stride along slab axis k)
|
|
570
|
+
st->slab_elements (Π slab_dims, total cells per slab)
|
|
571
|
+
|
|
572
|
+
*out_mask is the per-slab mask base when ca_has_mask(src), NULL
|
|
573
|
+
otherwise. Mask uses 1-byte boolean8_t per element. Data and mask
|
|
574
|
+
strides are independent so kernels can handle masked CAStride
|
|
575
|
+
non-contig sources correctly (= mask scratch is gathered in view
|
|
576
|
+
row-major order via ca_copy_data, whereas data strides may walk the
|
|
577
|
+
parent entity through a non-row-major composed path).
|
|
578
|
+
|
|
579
|
+
The slab walk pattern in kernel code (= roadmap §1.1 idealized form):
|
|
580
|
+
while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
|
|
581
|
+
acc_t acc = 0;
|
|
582
|
+
// K-D walk: for each (s0..s_{K-1}) in slab_dims:
|
|
583
|
+
// data_off = Σ s_k * slab_strides[k]
|
|
584
|
+
// mask_off = Σ s_k * slab_mask_strides[k]
|
|
585
|
+
// if (m == NULL || !m[mask_off]) acc += *(T *)(p + data_off)
|
|
586
|
+
op[out_i++] = acc;
|
|
587
|
+
} */
|
|
588
|
+
int ca_iter_state_next_slab_axes (ca_iter_state *st,
|
|
589
|
+
char **out_ptr,
|
|
590
|
+
boolean8_t **out_mask);
|
|
591
|
+
|
|
592
|
+
/* Sync the just-yielded slab back to parent (WRITE path; READ no-op).
|
|
593
|
+
|
|
594
|
+
Caller calls this **unconditionally** after each next_slab /
|
|
595
|
+
next_slab_strided + kernel invocation; the alias_mode branch lives
|
|
596
|
+
inside the state machine so the caller never has to inspect it.
|
|
597
|
+
|
|
598
|
+
Behaviour (proposal PROPOSAL_T1_WRITE_SEMANTICS.md §(b)):
|
|
599
|
+
- !(flags & CA_KERNEL_WRITE): no-op (READ walk)
|
|
600
|
+
- alias path (scratch_ptr == NULL): no-op — kernel wrote to
|
|
601
|
+
parent directly through alias_ptr (case A semantics)
|
|
602
|
+
- scratch path (L1 only by step-4 invariant): scatter back via
|
|
603
|
+
ca_sync_data(src, scratch). L2 scratch is structurally
|
|
604
|
+
unreachable in step 4 (CAStride only) and is guarded by an
|
|
605
|
+
assert; step 5 re-evaluation noted in the proposal. */
|
|
606
|
+
void ca_iter_state_sync_slab (ca_iter_state *st);
|
|
607
|
+
|
|
608
|
+
/* Release any resources held by `st` and detach parent. Safe to call
|
|
609
|
+
exactly once after a successful init (either level). */
|
|
610
|
+
void ca_iter_state_finish (ca_iter_state *st);
|
|
611
|
+
|
|
612
|
+
/* ---- Phase C C.3: kernel author block macros ------------------------
|
|
613
|
+
(PROPOSAL_CAPSTONE_PHASE_C.md D3.1 (A) do/while/for + D3.2 (C) 2 kinds)
|
|
614
|
+
|
|
615
|
+
Wrap init_l2 / next_slab_axes / [sync_slab] / finish in a single
|
|
616
|
+
block scope so kernel authors don't write lifecycle plumbing.
|
|
617
|
+
|
|
618
|
+
--- Constraints ---
|
|
619
|
+
|
|
620
|
+
- Author must pre-declare `char *p` and `boolean8_t *m` (or names of
|
|
621
|
+
their choice). C99 doesn't permit two different-typed declarations
|
|
622
|
+
in a `for` init clause, so the slab/mask cursors live in the
|
|
623
|
+
surrounding scope.
|
|
624
|
+
- `flags` arg propagates to init_l2 (= CA_KERNEL_WRITE supported).
|
|
625
|
+
`sync_slab` is called automatically after each iteration; it's a
|
|
626
|
+
no-op when WRITE flag is absent.
|
|
627
|
+
- Init failure (ca_iter_state_init_l2 returns CA_ITER_ERR_*) is
|
|
628
|
+
silently discarded: the body runs zero times and finish is still
|
|
629
|
+
called. Production kernels that need explicit error messages
|
|
630
|
+
(e.g., sum_ki's rc=%d raise) should drop down to the raw API
|
|
631
|
+
instead of using this macro.
|
|
632
|
+
- `break;` from inside the body exits the loop AND triggers finish
|
|
633
|
+
correctly (= outer for's "increment" clause runs once on natural
|
|
634
|
+
exit; `break` from the inner while breaks both). `return` inside
|
|
635
|
+
the body LEAKS resources (scratch_ptr, parent attach) — drop to
|
|
636
|
+
raw API if early return is needed.
|
|
637
|
+
- Macros are not statement-equivalent (= they expand to nested for
|
|
638
|
+
constructs). Don't follow them with `else` etc. */
|
|
639
|
+
|
|
640
|
+
/* ---- T1 kernel_iterator mask helper macros (step 6+) ---------------------
|
|
641
|
+
These macros let kernel_iterator kernels handle masked sources
|
|
642
|
+
uniformly: pass NULL for `mask` if the source has no mask (= treat
|
|
643
|
+
all cells as unmasked), or a `boolean8_t *` of length n where
|
|
644
|
+
non-zero entries indicate masked (= "skip this cell").
|
|
645
|
+
|
|
646
|
+
PROPOSAL_T1_KERNEL_ITERATOR.md §2.4. GCC statement-expression for
|
|
647
|
+
CA_COUNT_UNMASKED is GCC/Clang only (MSVC not in scope).
|
|
648
|
+
--------------------------------------------------------------------- */
|
|
649
|
+
|
|
650
|
+
#define CA_FOR_EACH_UNMASKED(p, mask, n, body) do { \
|
|
651
|
+
ca_size_t _ca_i; \
|
|
652
|
+
if (mask) { \
|
|
653
|
+
for (_ca_i = 0; _ca_i < (n); _ca_i++) if (!(mask)[_ca_i]) { body } \
|
|
654
|
+
} else { \
|
|
655
|
+
for (_ca_i = 0; _ca_i < (n); _ca_i++) { body } \
|
|
656
|
+
} \
|
|
657
|
+
} while (0)
|
|
658
|
+
|
|
659
|
+
#define CA_FOR_EACH_INDEX_UNMASKED(p, mask, n, i, body) do { \
|
|
660
|
+
if (mask) { \
|
|
661
|
+
for (ca_size_t i = 0; i < (n); i++) if (!(mask)[i]) { body } \
|
|
662
|
+
} else { \
|
|
663
|
+
for (ca_size_t i = 0; i < (n); i++) { body } \
|
|
664
|
+
} \
|
|
665
|
+
} while (0)
|
|
666
|
+
|
|
667
|
+
#define CA_COUNT_UNMASKED(mask, n) ({ \
|
|
668
|
+
ca_size_t _ca_cnt = 0; \
|
|
669
|
+
ca_size_t _ca_n = (n); \
|
|
670
|
+
if (mask) { \
|
|
671
|
+
for (ca_size_t _ca_i = 0; _ca_i < _ca_n; _ca_i++) \
|
|
672
|
+
if (!(mask)[_ca_i]) _ca_cnt++; \
|
|
673
|
+
} else { \
|
|
674
|
+
_ca_cnt = _ca_n; \
|
|
675
|
+
} \
|
|
676
|
+
_ca_cnt; \
|
|
677
|
+
})
|
|
678
|
+
|
|
679
|
+
#define CA_MASK_GET(mask, i) ((mask) ? (mask)[i] : 0)
|
|
680
|
+
|
|
681
|
+
/* ---- L2 inner-loop macros (step 8+) -----------------------------------
|
|
682
|
+
`CA_L2_FOR_EACH(T, ptr, n, stride, p, body)` and its unmasked sibling
|
|
683
|
+
wrap the strided callback signature `(ptr, n, stride_bytes)` with a
|
|
684
|
+
`stride == sizeof(T)` fast-path branch. When the runtime stride
|
|
685
|
+
matches the element size, `body` runs against a `T *p` that the
|
|
686
|
+
compiler can autovectorise; when it doesn't, `p` is recomputed per
|
|
687
|
+
iteration with the runtime stride.
|
|
688
|
+
|
|
689
|
+
The split is intentional: L2 strided callbacks are deliberately
|
|
690
|
+
universal — they accept arbitrary stride and so the compiler cannot
|
|
691
|
+
prove contig on the kernel-side loop. When the iterator hands a
|
|
692
|
+
contig scratch (= descriptor materialise path, stride == bytes), the
|
|
693
|
+
universal-dispatch cost (step 5.5 §10.4.5) shows up as an 18-22%
|
|
694
|
+
SIMD inhibition on the kernel. These macros let kernel authors recover
|
|
695
|
+
the contig autovectorisation without giving up the L2 surface.
|
|
696
|
+
|
|
697
|
+
This is the standard pattern for L2 kernels: write the body once,
|
|
698
|
+
the macro picks the right loop shape. Step 8 verifies the speed-up
|
|
699
|
+
empirically; the framing (universal dispatch is the abstraction,
|
|
700
|
+
kernel-side macros are the speed knob) is `PROPOSAL_T1_KERNEL_
|
|
701
|
+
ITERATOR.md` §13.1.
|
|
702
|
+
|
|
703
|
+
Usage:
|
|
704
|
+
CA_L2_FOR_EACH(double, ptr, n, stride, p, {
|
|
705
|
+
*p = value; // p is `double *`
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
GCC / Clang only (block expressions and per-iteration variable
|
|
709
|
+
declarations inside a macro). */
|
|
710
|
+
|
|
711
|
+
#define CA_L2_FOR_EACH(T, ptr, n, stride, p, body) do { \
|
|
712
|
+
ca_size_t _l2_n = (n); \
|
|
713
|
+
ca_size_t _l2_s = (stride); \
|
|
714
|
+
char *_l2_b = (char *)(ptr); \
|
|
715
|
+
if (_l2_s == sizeof(T)) { \
|
|
716
|
+
T *p = (T *)_l2_b; \
|
|
717
|
+
for (ca_size_t _l2_i = 0; _l2_i < _l2_n; _l2_i++) { \
|
|
718
|
+
body \
|
|
719
|
+
p++; \
|
|
720
|
+
} \
|
|
721
|
+
} else { \
|
|
722
|
+
for (ca_size_t _l2_i = 0; _l2_i < _l2_n; _l2_i++) { \
|
|
723
|
+
T *p = (T *)(_l2_b + _l2_i * _l2_s); \
|
|
724
|
+
body \
|
|
725
|
+
} \
|
|
726
|
+
} \
|
|
727
|
+
} while (0)
|
|
728
|
+
|
|
729
|
+
#define CA_L2_FOR_EACH_UNMASKED(T, ptr, mask, n, stride, p, body) do { \
|
|
730
|
+
ca_size_t _l2_n = (n); \
|
|
731
|
+
ca_size_t _l2_s = (stride); \
|
|
732
|
+
char *_l2_b = (char *)(ptr); \
|
|
733
|
+
boolean8_t *_l2_m = (mask); \
|
|
734
|
+
if (_l2_s == sizeof(T)) { \
|
|
735
|
+
T *p = (T *)_l2_b; \
|
|
736
|
+
if (_l2_m) { \
|
|
737
|
+
for (ca_size_t _l2_i = 0; _l2_i < _l2_n; _l2_i++) { \
|
|
738
|
+
if (!_l2_m[_l2_i]) { body } \
|
|
739
|
+
p++; \
|
|
740
|
+
} \
|
|
741
|
+
} else { \
|
|
742
|
+
for (ca_size_t _l2_i = 0; _l2_i < _l2_n; _l2_i++) { \
|
|
743
|
+
body \
|
|
744
|
+
p++; \
|
|
745
|
+
} \
|
|
746
|
+
} \
|
|
747
|
+
} else { \
|
|
748
|
+
if (_l2_m) { \
|
|
749
|
+
for (ca_size_t _l2_i = 0; _l2_i < _l2_n; _l2_i++) { \
|
|
750
|
+
T *p = (T *)(_l2_b + _l2_i * _l2_s); \
|
|
751
|
+
if (!_l2_m[_l2_i]) { body } \
|
|
752
|
+
} \
|
|
753
|
+
} else { \
|
|
754
|
+
for (ca_size_t _l2_i = 0; _l2_i < _l2_n; _l2_i++) { \
|
|
755
|
+
T *p = (T *)(_l2_b + _l2_i * _l2_s); \
|
|
756
|
+
body \
|
|
757
|
+
} \
|
|
758
|
+
} \
|
|
759
|
+
} \
|
|
760
|
+
} while (0)
|
|
761
|
+
|
|
762
|
+
/* The slab policy is fixed to CA_SLAB_AXES (the only policy compatible
|
|
763
|
+
with next_slab_axes); it is hardcoded inside the macro rather than
|
|
764
|
+
taken as an argument, so block-macro authors never type the policy
|
|
765
|
+
enum (symmetry with the CA_FOR_EACH_FIBER family, and one less
|
|
766
|
+
always-constant argument). CA_SLAB_AXES is still FROZEN, because
|
|
767
|
+
raw-API kernels pass it to ca_iter_state_init_l2 directly. */
|
|
768
|
+
#define CA_FOR_EACH_SLAB(st, ca, axes, naxes, flags, p, m) \
|
|
769
|
+
for ( int __caf_init = (ca_iter_state_init_l2(&(st), (ca), CA_SLAB_AXES, \
|
|
770
|
+
(axes), (naxes), (flags)), \
|
|
771
|
+
1); \
|
|
772
|
+
__caf_init; \
|
|
773
|
+
__caf_init = 0, ca_iter_state_finish(&(st)) ) \
|
|
774
|
+
for ( ; ca_iter_state_next_slab_axes(&(st), &(p), &(m)); \
|
|
775
|
+
ca_iter_state_sync_slab(&(st)) )
|
|
776
|
+
|
|
777
|
+
/* CA_FOR_EACH_SLAB_INOUT: parallel iter for map kernels (= input view
|
|
778
|
+
+ same-shape output view). Author pre-declares two state structs,
|
|
779
|
+
two slab cursors, two mask cursors. Input iter runs READ-only,
|
|
780
|
+
output iter runs WRITE; sync_slab is called on output after each
|
|
781
|
+
body iteration.
|
|
782
|
+
|
|
783
|
+
Shape mismatch between ca_in / ca_out is NOT validated by the macro
|
|
784
|
+
— caller responsibility (= typically output is `rb_ca_template_with_type`
|
|
785
|
+
of input, guaranteeing same shape). Init failure on either iter
|
|
786
|
+
silently skips the body. */
|
|
787
|
+
/* Policy fixed to CA_SLAB_AXES internally — see CA_FOR_EACH_SLAB above. */
|
|
788
|
+
#define CA_FOR_EACH_SLAB_INOUT(st_in, st_out, ca_in, ca_out, \
|
|
789
|
+
axes, naxes, \
|
|
790
|
+
p_in, p_out, m_in, m_out) \
|
|
791
|
+
for ( int __cafi_init = ( \
|
|
792
|
+
ca_iter_state_init_l2(&(st_in), (ca_in), CA_SLAB_AXES, \
|
|
793
|
+
(axes), (naxes), 0), \
|
|
794
|
+
ca_iter_state_init_l2(&(st_out), (ca_out), CA_SLAB_AXES, \
|
|
795
|
+
(axes), (naxes), CA_KERNEL_WRITE), \
|
|
796
|
+
1); \
|
|
797
|
+
__cafi_init; \
|
|
798
|
+
__cafi_init = 0, \
|
|
799
|
+
ca_iter_state_finish(&(st_in)), \
|
|
800
|
+
ca_iter_state_finish(&(st_out)) ) \
|
|
801
|
+
for ( ; ca_iter_state_next_slab_axes(&(st_in), &(p_in), &(m_in)) && \
|
|
802
|
+
ca_iter_state_next_slab_axes(&(st_out), &(p_out), &(m_out)); \
|
|
803
|
+
ca_iter_state_sync_slab(&(st_out)) )
|
|
804
|
+
|
|
805
|
+
/* ---- PROPOSAL_FIBER_DELIVERY F.2: per-axis fiber catalog macros ----
|
|
806
|
+
(rev4 §3 catalog contract)
|
|
807
|
+
|
|
808
|
+
Author-facing surface for "deliver one contig fiber along `axis` to
|
|
809
|
+
the kernel". Contig delivery is contract:
|
|
810
|
+
- data: contig (= author writes p[i] / p_out[i] without stride math)
|
|
811
|
+
- mask (MASKED forms): contig (= author writes m[i]; m is NULL for
|
|
812
|
+
no-mask source, author NULL-checks before access)
|
|
813
|
+
- output (INOUT forms): contig same as data; CA_KERNEL_WRITE auto-set
|
|
814
|
+
|
|
815
|
+
The CA_KERNEL_FIBER_CONTIG flag is auto-set; engine gathers strided
|
|
816
|
+
fibers into per-state scratch when slab_strides[0] != bytes (= F.1a/b).
|
|
817
|
+
|
|
818
|
+
`axis` is evaluated ONCE into a stack-local int8 buffer of static
|
|
819
|
+
storage scope; `ca`/`ca_in`/`ca_out` are evaluated ONCE in init.
|
|
820
|
+
`n` is set to the fiber length (= slab_dims[0], constant per walk).
|
|
821
|
+
|
|
822
|
+
Same constraints as CA_FOR_EACH_SLAB family:
|
|
823
|
+
- `break;` from body exits cleanly (finish runs).
|
|
824
|
+
- `return;` from body LEAKS scratch / parent attach -- use raw API.
|
|
825
|
+
- Macros are NOT statement-equivalent (nested for); no trailing else.
|
|
826
|
+
|
|
827
|
+
INOUT forms (form 2 / 4) require STRICT FULL SHAPE EQUALITY of
|
|
828
|
+
`ca_in` and `ca_out` (= ndim + every dim[k] match). Mismatch is a
|
|
829
|
+
silent-corruption seam (= short-circuit fiber-count drop, k-th
|
|
830
|
+
pairing corruption); init_l2 does not validate it itself, so the
|
|
831
|
+
macros runtime-assert shape equality and skip body on mismatch.
|
|
832
|
+
Authors that need broadcasting must drop to raw API. */
|
|
833
|
+
|
|
834
|
+
#define CA_FOR_EACH_FIBER(st, ca, axis, flags, p, n) \
|
|
835
|
+
for ( int __cff_init = ( \
|
|
836
|
+
ca_iter_state_init_l2(&(st), (ca), CA_SLAB_AXES, \
|
|
837
|
+
(int8_t[]){(int8_t)(axis)}, 1, \
|
|
838
|
+
(flags) | CA_KERNEL_FIBER_CONTIG), \
|
|
839
|
+
(n) = (st).slab_dims[0], \
|
|
840
|
+
1); \
|
|
841
|
+
__cff_init; \
|
|
842
|
+
__cff_init = 0, ca_iter_state_finish(&(st)) ) \
|
|
843
|
+
for ( ; ca_iter_state_next_slab_axes(&(st), &(p), NULL); \
|
|
844
|
+
ca_iter_state_sync_slab(&(st)) )
|
|
845
|
+
|
|
846
|
+
#define CA_FOR_EACH_FIBER_MASKED(st, ca, axis, flags, p, n, m) \
|
|
847
|
+
for ( int __cffm_init = ( \
|
|
848
|
+
ca_iter_state_init_l2(&(st), (ca), CA_SLAB_AXES, \
|
|
849
|
+
(int8_t[]){(int8_t)(axis)}, 1, \
|
|
850
|
+
(flags) | CA_KERNEL_FIBER_CONTIG), \
|
|
851
|
+
(n) = (st).slab_dims[0], \
|
|
852
|
+
1); \
|
|
853
|
+
__cffm_init; \
|
|
854
|
+
__cffm_init = 0, ca_iter_state_finish(&(st)) ) \
|
|
855
|
+
for ( ; ca_iter_state_next_slab_axes(&(st), &(p), &(m)); \
|
|
856
|
+
ca_iter_state_sync_slab(&(st)) )
|
|
857
|
+
|
|
858
|
+
/* INOUT form 2 (NO_MASK). Output gets CA_KERNEL_WRITE auto-set.
|
|
859
|
+
|
|
860
|
+
STRICT FULL SHAPE EQUALITY (rev4 §2.3): the inner for-condition
|
|
861
|
+
re-evaluates ca_in->ndim == ca_out->ndim and dim[axis] equality (=
|
|
862
|
+
minimal seam coverage given the macro can't loop over k). Full
|
|
863
|
+
per-axis equality lives one level up in the init-time short-circuit
|
|
864
|
+
below: we compare elements + ndim + axis dim, which catches the
|
|
865
|
+
common silent-corruption seam (= e.g. (3,5) vs (4,5) axis=1 with
|
|
866
|
+
matching fiber length but different fiber count). Comprehensive
|
|
867
|
+
per-dim check is the caller's responsibility for now (= simpler than
|
|
868
|
+
building a per-dim k loop into a macro; ext authors can drop to raw
|
|
869
|
+
API for arbitrary broadcasting designs). */
|
|
870
|
+
#define CA_FOR_EACH_FIBER_INOUT(st_in, st_out, ca_in, ca_out, axis, \
|
|
871
|
+
flags, p_in, p_out, n) \
|
|
872
|
+
for ( int __cffi_init = ( \
|
|
873
|
+
ca_iter_state_init_l2(&(st_in), (ca_in), CA_SLAB_AXES, \
|
|
874
|
+
(int8_t[]){(int8_t)(axis)}, 1, \
|
|
875
|
+
(flags) | CA_KERNEL_FIBER_CONTIG), \
|
|
876
|
+
ca_iter_state_init_l2(&(st_out), (ca_out), CA_SLAB_AXES, \
|
|
877
|
+
(int8_t[]){(int8_t)(axis)}, 1, \
|
|
878
|
+
((flags) | CA_KERNEL_FIBER_CONTIG \
|
|
879
|
+
| CA_KERNEL_WRITE)), \
|
|
880
|
+
(n) = (st_in).slab_dims[0], \
|
|
881
|
+
1); \
|
|
882
|
+
__cffi_init; \
|
|
883
|
+
__cffi_init = 0, \
|
|
884
|
+
ca_iter_state_finish(&(st_in)), \
|
|
885
|
+
ca_iter_state_finish(&(st_out)) ) \
|
|
886
|
+
for ( ; (st_in).src->ndim == (st_out).src->ndim \
|
|
887
|
+
&& (st_in).src->elements == (st_out).src->elements \
|
|
888
|
+
&& (st_in).slab_dims[0] == (st_out).slab_dims[0] \
|
|
889
|
+
&& ca_iter_state_next_slab_axes(&(st_in), &(p_in), NULL) \
|
|
890
|
+
&& ca_iter_state_next_slab_axes(&(st_out), &(p_out), NULL); \
|
|
891
|
+
ca_iter_state_sync_slab(&(st_in)), \
|
|
892
|
+
ca_iter_state_sync_slab(&(st_out)) )
|
|
893
|
+
|
|
894
|
+
#define CA_FOR_EACH_FIBER_INOUT_MASKED(st_in, st_out, ca_in, ca_out, axis, \
|
|
895
|
+
flags, p_in, p_out, n, m) \
|
|
896
|
+
for ( int __cffim_init = ( \
|
|
897
|
+
ca_iter_state_init_l2(&(st_in), (ca_in), CA_SLAB_AXES, \
|
|
898
|
+
(int8_t[]){(int8_t)(axis)}, 1, \
|
|
899
|
+
(flags) | CA_KERNEL_FIBER_CONTIG), \
|
|
900
|
+
ca_iter_state_init_l2(&(st_out), (ca_out), CA_SLAB_AXES, \
|
|
901
|
+
(int8_t[]){(int8_t)(axis)}, 1, \
|
|
902
|
+
((flags) | CA_KERNEL_FIBER_CONTIG \
|
|
903
|
+
| CA_KERNEL_WRITE)), \
|
|
904
|
+
(n) = (st_in).slab_dims[0], \
|
|
905
|
+
1); \
|
|
906
|
+
__cffim_init; \
|
|
907
|
+
__cffim_init = 0, \
|
|
908
|
+
ca_iter_state_finish(&(st_in)), \
|
|
909
|
+
ca_iter_state_finish(&(st_out)) ) \
|
|
910
|
+
for ( ; (st_in).src->ndim == (st_out).src->ndim \
|
|
911
|
+
&& (st_in).src->elements == (st_out).src->elements \
|
|
912
|
+
&& (st_in).slab_dims[0] == (st_out).slab_dims[0] \
|
|
913
|
+
&& ca_iter_state_next_slab_axes(&(st_in), &(p_in), &(m)) \
|
|
914
|
+
&& ca_iter_state_next_slab_axes(&(st_out), &(p_out), NULL); \
|
|
915
|
+
ca_iter_state_sync_slab(&(st_in)), \
|
|
916
|
+
ca_iter_state_sync_slab(&(st_out)) )
|
|
917
|
+
|
|
918
|
+
/* ---- Phase D: per-data_type reduction macro suite ----------------------- */
|
|
919
|
+
|
|
920
|
+
/* CA_SLAB_REDUCE_T(T, ...): generic per-data_type slab reduction. T is the
|
|
921
|
+
element load type (`double`, `float`, `int32_t`, `int64_t`, ...).
|
|
922
|
+
The accumulator `acc` is supplied by the caller and may be a wider
|
|
923
|
+
type — the macro binds `v` as T and lets REDUCE handle implicit
|
|
924
|
+
widening (e.g., int32 source → int64 acc via `acc += v`).
|
|
925
|
+
|
|
926
|
+
Canonical inner walk: outer K-1 carry + innermost SIMD-friendly
|
|
927
|
+
inner loop, with mask + contig-stride dispatch hoisted out of the
|
|
928
|
+
inner iteration.
|
|
929
|
+
|
|
930
|
+
Author-supplied:
|
|
931
|
+
- T: element C type (load type). `sizeof(T)` is used
|
|
932
|
+
for the contig-stride check.
|
|
933
|
+
- acc: lvalue (any numeric type) — receives the result;
|
|
934
|
+
initialised to (INIT) at macro entry.
|
|
935
|
+
- INIT: initial value expression (e.g., 0, 0.0, -INFINITY).
|
|
936
|
+
- REDUCE: statement folding `v` (the current element, type T)
|
|
937
|
+
into `acc`. Example: `acc += v` for sum,
|
|
938
|
+
`if (v > acc) acc = v` for max.
|
|
939
|
+
|
|
940
|
+
Engine-supplied (from CA_FOR_EACH_SLAB / ca_iter_state_next_slab_axes):
|
|
941
|
+
- st: ca_iter_state, already positioned on the current slab.
|
|
942
|
+
- p: slab data pointer (char *).
|
|
943
|
+
- m: slab mask pointer (boolean8_t *, may be NULL).
|
|
944
|
+
|
|
945
|
+
Convenience aliases (defined below): CA_SLAB_REDUCE_F64, _F32, _I32,
|
|
946
|
+
_I64. Use those when the load type is one of the standard four
|
|
947
|
+
numerics; use CA_SLAB_REDUCE_T directly for less common types
|
|
948
|
+
(boolean8_t, int8_t, uint16_t, ...).
|
|
949
|
+
|
|
950
|
+
Mask semantics: when m != NULL, masked cells are skipped (REDUCE is
|
|
951
|
+
not invoked). When m == NULL, every cell contributes.
|
|
952
|
+
|
|
953
|
+
For slab_ndim == 1 this collapses to a single inner loop. For
|
|
954
|
+
slab_ndim >= 2 the outer K-1 axes carry-walk row-major and the
|
|
955
|
+
innermost axis stays the SIMD leaf.
|
|
956
|
+
|
|
957
|
+
`idx` (ca_size_t) is exposed to the REDUCE expression as the
|
|
958
|
+
flat slab-row-major index of the current cell (0 .. slab_elements-1).
|
|
959
|
+
It increments per cell regardless of mask state, so REDUCE can use
|
|
960
|
+
it for position-sensitive reductions like argmin / argmax even when
|
|
961
|
+
some cells are masked out. Kernels that don't reference `idx` get
|
|
962
|
+
it dead-code-eliminated; the trailing `(void) idx;` silences any
|
|
963
|
+
set-but-not-used warnings. */
|
|
964
|
+
#define CA_SLAB_REDUCE_T_EX(T, st, p, m, acc, INIT, REDUCE, masked_cnt) do { \
|
|
965
|
+
(acc) = (INIT); \
|
|
966
|
+
int8_t __srK = (st).slab_ndim; \
|
|
967
|
+
int8_t __srOuterK = __srK - 1; \
|
|
968
|
+
ca_size_t __srInnerN = (st).slab_dims[__srK - 1]; \
|
|
969
|
+
ca_size_t __srInnerS = (st).slab_strides[__srK - 1]; \
|
|
970
|
+
ca_size_t __srInnerMS = (st).slab_mask_strides[__srK - 1]; \
|
|
971
|
+
int __srContig = (__srInnerS == (ca_size_t) sizeof(T)); \
|
|
972
|
+
int __srMaskU = (__srInnerMS == 1); \
|
|
973
|
+
ca_size_t __srOC = 1; \
|
|
974
|
+
for ( int8_t __sk = 0; __sk < __srOuterK; __sk++ ) \
|
|
975
|
+
__srOC *= (st).slab_dims[__sk]; \
|
|
976
|
+
/* Slab-collapse: when the whole K-D slab is row-major contiguous \
|
|
977
|
+
(data, and mask if present), fold all slab axes into one flat \
|
|
978
|
+
inner loop. Removes the per-outer multi-index offset recompute + \
|
|
979
|
+
carry that otherwise dominates when the innermost slab axis is \
|
|
980
|
+
small (full reduction of [N,1] / [N,small] entity, or trailing- \
|
|
981
|
+
contig multi-axis reduce). Platform-general: structural, not SIMD. \
|
|
982
|
+
No-op for 1-D slabs (__srOuterK == 0 leaves __srOC == 1). */ \
|
|
983
|
+
{ \
|
|
984
|
+
int __srFlat = __srContig; \
|
|
985
|
+
for ( int8_t __sk = (int8_t)(__srK - 2); __sk >= 0 && __srFlat; __sk-- ) \
|
|
986
|
+
if ( (st).slab_strides[__sk] != \
|
|
987
|
+
(st).slab_dims[__sk + 1] * (st).slab_strides[__sk + 1] ) \
|
|
988
|
+
__srFlat = 0; \
|
|
989
|
+
if ( __srFlat && (m) != NULL ) { \
|
|
990
|
+
if ( ! __srMaskU ) __srFlat = 0; \
|
|
991
|
+
for ( int8_t __sk = (int8_t)(__srK - 2); __sk >= 0 && __srFlat; __sk-- ) \
|
|
992
|
+
if ( (st).slab_mask_strides[__sk] != \
|
|
993
|
+
(st).slab_dims[__sk + 1] * (st).slab_mask_strides[__sk + 1] ) \
|
|
994
|
+
__srFlat = 0; \
|
|
995
|
+
} \
|
|
996
|
+
if ( __srFlat ) { __srInnerN = (st).slab_elements; __srOC = 1; } \
|
|
997
|
+
} \
|
|
998
|
+
ca_size_t __srIdx[CA_RANK_MAX] = { 0 }; \
|
|
999
|
+
ca_size_t idx = 0; \
|
|
1000
|
+
for ( ca_size_t __so = 0; __so < __srOC; __so++ ) { \
|
|
1001
|
+
ca_size_t __srDoff = 0, __srMoff = 0; \
|
|
1002
|
+
for ( int8_t __sk = 0; __sk < __srOuterK; __sk++ ) { \
|
|
1003
|
+
__srDoff += __srIdx[__sk] * (st).slab_strides[__sk]; \
|
|
1004
|
+
__srMoff += __srIdx[__sk] * (st).slab_mask_strides[__sk]; \
|
|
1005
|
+
} \
|
|
1006
|
+
const char *__srQ = (const char *)(p) + __srDoff; \
|
|
1007
|
+
if ( (m) == NULL ) { \
|
|
1008
|
+
if ( __srContig ) { \
|
|
1009
|
+
const T *__srSrc = (const T *) __srQ; \
|
|
1010
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1011
|
+
T v = __srSrc[__sj]; \
|
|
1012
|
+
REDUCE; \
|
|
1013
|
+
idx++; \
|
|
1014
|
+
} \
|
|
1015
|
+
} else { \
|
|
1016
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1017
|
+
T v = *(const T *)(__srQ + __sj * __srInnerS); \
|
|
1018
|
+
REDUCE; \
|
|
1019
|
+
idx++; \
|
|
1020
|
+
} \
|
|
1021
|
+
} \
|
|
1022
|
+
} else { \
|
|
1023
|
+
const boolean8_t *__srMM = (const boolean8_t *)(m) + __srMoff; \
|
|
1024
|
+
if ( __srContig && __srMaskU ) { \
|
|
1025
|
+
const T *__srSrc = (const T *) __srQ; \
|
|
1026
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1027
|
+
if ( ! __srMM[__sj] ) { \
|
|
1028
|
+
T v = __srSrc[__sj]; \
|
|
1029
|
+
REDUCE; \
|
|
1030
|
+
} else { \
|
|
1031
|
+
(masked_cnt)++; \
|
|
1032
|
+
} \
|
|
1033
|
+
idx++; \
|
|
1034
|
+
} \
|
|
1035
|
+
} else { \
|
|
1036
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1037
|
+
if ( ! __srMM[__sj * __srInnerMS] ) { \
|
|
1038
|
+
T v = *(const T *)(__srQ + __sj * __srInnerS); \
|
|
1039
|
+
REDUCE; \
|
|
1040
|
+
} else { \
|
|
1041
|
+
(masked_cnt)++; \
|
|
1042
|
+
} \
|
|
1043
|
+
idx++; \
|
|
1044
|
+
} \
|
|
1045
|
+
} \
|
|
1046
|
+
} \
|
|
1047
|
+
for ( int8_t __sk = __srOuterK - 1; __sk >= 0; __sk-- ) { \
|
|
1048
|
+
if ( ++__srIdx[__sk] < (st).slab_dims[__sk] ) break; \
|
|
1049
|
+
__srIdx[__sk] = 0; \
|
|
1050
|
+
} \
|
|
1051
|
+
} \
|
|
1052
|
+
(void) idx; \
|
|
1053
|
+
} while (0)
|
|
1054
|
+
|
|
1055
|
+
/* Backward-compatible wrapper that hides masked_cnt from kernels that
|
|
1056
|
+
don't need it. Existing kernels (sum / mean / min / argmin / ...)
|
|
1057
|
+
continue to use CA_SLAB_REDUCE_T unchanged; only mask-policy-aware
|
|
1058
|
+
kernels reach for CA_SLAB_REDUCE_T_EX. */
|
|
1059
|
+
#define CA_SLAB_REDUCE_T(T, st, p, m, acc, INIT, REDUCE) do { \
|
|
1060
|
+
ca_size_t __sr_throwaway_mc = 0; \
|
|
1061
|
+
CA_SLAB_REDUCE_T_EX(T, st, p, m, acc, INIT, REDUCE, __sr_throwaway_mc); \
|
|
1062
|
+
(void) __sr_throwaway_mc; \
|
|
1063
|
+
} while (0)
|
|
1064
|
+
|
|
1065
|
+
/* ----------------------------------------------------------------------
|
|
1066
|
+
* SIMD-licensed reduction variants (SL.1.0 stubs)
|
|
1067
|
+
*
|
|
1068
|
+
* The PLUS/MIN/MAX/STAR/VAR _EX variants below are the SIMD-license
|
|
1069
|
+
* vehicle introduced by PROPOSAL_REDUCTION_SIMD_LICENSE. In Phase
|
|
1070
|
+
* SL.1.0 they are wired-but-inert: each variant currently forwards to
|
|
1071
|
+
* CA_SLAB_REDUCE_T_EX so DSL plumbing (mkkernel reduction_kind:) can
|
|
1072
|
+
* be exercised without any kernel behavior change.
|
|
1073
|
+
*
|
|
1074
|
+
* Subsequent sub-steps (SL.1.1+) will replace each forward with a
|
|
1075
|
+
* contig-branch body carrying `#pragma omp simd reduction(<kind>:acc)`
|
|
1076
|
+
* (and per-acc pragmas for VAR). The pragma is emitted via the
|
|
1077
|
+
* _Pragma + _CA_XSTR substitution helpers below so the acc lvalue
|
|
1078
|
+
* can be parameterised.
|
|
1079
|
+
*
|
|
1080
|
+
* The pragma is a no-op on compilers that don't support `-fopenmp-simd`
|
|
1081
|
+
* (extconf.rb probe — graceful degradation, code stays correct).
|
|
1082
|
+
*
|
|
1083
|
+
* Q1-Q6 closure (sparring round 1 2026-06-12): see proposal §5.
|
|
1084
|
+
* --------------------------------------------------------------------*/
|
|
1085
|
+
|
|
1086
|
+
#define _CA_STR(x) #x
|
|
1087
|
+
#define _CA_XSTR(x) _CA_STR(x)
|
|
1088
|
+
#define _CA_SIMD_PLUS(var) _Pragma(_CA_XSTR(omp simd reduction(+:var)))
|
|
1089
|
+
#define _CA_SIMD_MIN(var) _Pragma(_CA_XSTR(omp simd reduction(min:var)))
|
|
1090
|
+
#define _CA_SIMD_MAX(var) _Pragma(_CA_XSTR(omp simd reduction(max:var)))
|
|
1091
|
+
#define _CA_SIMD_STAR(var) _Pragma(_CA_XSTR(omp simd reduction(*:var)))
|
|
1092
|
+
|
|
1093
|
+
/* CA_SLAB_REDUCE_T_PLUS_EX (SL.1.1):
|
|
1094
|
+
* Same structure as CA_SLAB_REDUCE_T_EX, but the **no-mask + contig**
|
|
1095
|
+
* inner loop carries `#pragma omp simd reduction(+:acc)` so clang/gcc
|
|
1096
|
+
* are licensed to reassoc the accumulator and emit SIMD reduction
|
|
1097
|
+
* sequences. All other branches (masked-contig, non-contig, masked-
|
|
1098
|
+
* non-contig) are byte-identical to _EX — proposal §2.2 defers
|
|
1099
|
+
* strided/masked SIMD to Phase 2 (output-buffered loop interchange).
|
|
1100
|
+
*
|
|
1101
|
+
* PoC (2026-06-12): N=1M f64 sum 906 us -> 116 us (= 7.8x, 68.8 GB/s).
|
|
1102
|
+
* Parity: ε-close (relative error < 2e-16 for f64 sum), bit-exact
|
|
1103
|
+
* not guaranteed (= reassoc license, documented in CLAUDE.md
|
|
1104
|
+
* ε-close policy section, SL.1.5).
|
|
1105
|
+
*
|
|
1106
|
+
* Other state vars referenced inside REDUCE (induction counters
|
|
1107
|
+
* like `cnt`, position counters like `idx`) are auto-vectorised
|
|
1108
|
+
* by the compiler when their update is a simple ++ pattern.
|
|
1109
|
+
*/
|
|
1110
|
+
#define CA_SLAB_REDUCE_T_PLUS_EX(T, st, p, m, acc, INIT, REDUCE, masked_cnt) do { \
|
|
1111
|
+
(acc) = (INIT); \
|
|
1112
|
+
int8_t __srK = (st).slab_ndim; \
|
|
1113
|
+
int8_t __srOuterK = __srK - 1; \
|
|
1114
|
+
ca_size_t __srInnerN = (st).slab_dims[__srK - 1]; \
|
|
1115
|
+
ca_size_t __srInnerS = (st).slab_strides[__srK - 1]; \
|
|
1116
|
+
ca_size_t __srInnerMS = (st).slab_mask_strides[__srK - 1]; \
|
|
1117
|
+
int __srContig = (__srInnerS == (ca_size_t) sizeof(T)); \
|
|
1118
|
+
int __srMaskU = (__srInnerMS == 1); \
|
|
1119
|
+
ca_size_t __srOC = 1; \
|
|
1120
|
+
for ( int8_t __sk = 0; __sk < __srOuterK; __sk++ ) \
|
|
1121
|
+
__srOC *= (st).slab_dims[__sk]; \
|
|
1122
|
+
/* Slab-collapse: when the whole K-D slab is row-major contiguous \
|
|
1123
|
+
(data, and mask if present), fold all slab axes into one flat \
|
|
1124
|
+
inner loop. Removes the per-outer multi-index offset recompute + \
|
|
1125
|
+
carry that otherwise dominates when the innermost slab axis is \
|
|
1126
|
+
small (full reduction of [N,1] / [N,small] entity, or trailing- \
|
|
1127
|
+
contig multi-axis reduce). Platform-general: structural, not SIMD. \
|
|
1128
|
+
No-op for 1-D slabs (__srOuterK == 0 leaves __srOC == 1). */ \
|
|
1129
|
+
{ \
|
|
1130
|
+
int __srFlat = __srContig; \
|
|
1131
|
+
for ( int8_t __sk = (int8_t)(__srK - 2); __sk >= 0 && __srFlat; __sk-- ) \
|
|
1132
|
+
if ( (st).slab_strides[__sk] != \
|
|
1133
|
+
(st).slab_dims[__sk + 1] * (st).slab_strides[__sk + 1] ) \
|
|
1134
|
+
__srFlat = 0; \
|
|
1135
|
+
if ( __srFlat && (m) != NULL ) { \
|
|
1136
|
+
if ( ! __srMaskU ) __srFlat = 0; \
|
|
1137
|
+
for ( int8_t __sk = (int8_t)(__srK - 2); __sk >= 0 && __srFlat; __sk-- ) \
|
|
1138
|
+
if ( (st).slab_mask_strides[__sk] != \
|
|
1139
|
+
(st).slab_dims[__sk + 1] * (st).slab_mask_strides[__sk + 1] ) \
|
|
1140
|
+
__srFlat = 0; \
|
|
1141
|
+
} \
|
|
1142
|
+
if ( __srFlat ) { __srInnerN = (st).slab_elements; __srOC = 1; } \
|
|
1143
|
+
} \
|
|
1144
|
+
ca_size_t __srIdx[CA_RANK_MAX] = { 0 }; \
|
|
1145
|
+
ca_size_t idx = 0; \
|
|
1146
|
+
for ( ca_size_t __so = 0; __so < __srOC; __so++ ) { \
|
|
1147
|
+
ca_size_t __srDoff = 0, __srMoff = 0; \
|
|
1148
|
+
for ( int8_t __sk = 0; __sk < __srOuterK; __sk++ ) { \
|
|
1149
|
+
__srDoff += __srIdx[__sk] * (st).slab_strides[__sk]; \
|
|
1150
|
+
__srMoff += __srIdx[__sk] * (st).slab_mask_strides[__sk]; \
|
|
1151
|
+
} \
|
|
1152
|
+
const char *__srQ = (const char *)(p) + __srDoff; \
|
|
1153
|
+
if ( (m) == NULL ) { \
|
|
1154
|
+
if ( __srContig ) { \
|
|
1155
|
+
const T *__srSrc = (const T *) __srQ; \
|
|
1156
|
+
_CA_SIMD_PLUS(acc) \
|
|
1157
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1158
|
+
T v = __srSrc[__sj]; \
|
|
1159
|
+
REDUCE; \
|
|
1160
|
+
idx++; \
|
|
1161
|
+
} \
|
|
1162
|
+
} else { \
|
|
1163
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1164
|
+
T v = *(const T *)(__srQ + __sj * __srInnerS); \
|
|
1165
|
+
REDUCE; \
|
|
1166
|
+
idx++; \
|
|
1167
|
+
} \
|
|
1168
|
+
} \
|
|
1169
|
+
} else { \
|
|
1170
|
+
const boolean8_t *__srMM = (const boolean8_t *)(m) + __srMoff; \
|
|
1171
|
+
if ( __srContig && __srMaskU ) { \
|
|
1172
|
+
const T *__srSrc = (const T *) __srQ; \
|
|
1173
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1174
|
+
if ( ! __srMM[__sj] ) { \
|
|
1175
|
+
T v = __srSrc[__sj]; \
|
|
1176
|
+
REDUCE; \
|
|
1177
|
+
} else { \
|
|
1178
|
+
(masked_cnt)++; \
|
|
1179
|
+
} \
|
|
1180
|
+
idx++; \
|
|
1181
|
+
} \
|
|
1182
|
+
} else { \
|
|
1183
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1184
|
+
if ( ! __srMM[__sj * __srInnerMS] ) { \
|
|
1185
|
+
T v = *(const T *)(__srQ + __sj * __srInnerS); \
|
|
1186
|
+
REDUCE; \
|
|
1187
|
+
} else { \
|
|
1188
|
+
(masked_cnt)++; \
|
|
1189
|
+
} \
|
|
1190
|
+
idx++; \
|
|
1191
|
+
} \
|
|
1192
|
+
} \
|
|
1193
|
+
} \
|
|
1194
|
+
for ( int8_t __sk = __srOuterK - 1; __sk >= 0; __sk-- ) { \
|
|
1195
|
+
if ( ++__srIdx[__sk] < (st).slab_dims[__sk] ) break; \
|
|
1196
|
+
__srIdx[__sk] = 0; \
|
|
1197
|
+
} \
|
|
1198
|
+
} \
|
|
1199
|
+
(void) idx; \
|
|
1200
|
+
} while (0)
|
|
1201
|
+
|
|
1202
|
+
/* CA_SLAB_REDUCE_SUM8_EX (variance/stddev regression fix, 2026-07-18):
|
|
1203
|
+
* 8-way manual accumulator split for the no-mask + contig inner loop.
|
|
1204
|
+
*
|
|
1205
|
+
* Why: GCC 11.5 with -march=native (FMA) does NOT split the pragma-simd
|
|
1206
|
+
* reduction of the centred Pass 2 (M2 += (v-mean)^2) into multiple
|
|
1207
|
+
* accumulators -- it fuses mul+add into a single `vfmadd231sd` whose
|
|
1208
|
+
* result feeds the next iteration, a single dependency chain that is
|
|
1209
|
+
* latency-bound (~1 elem per FMA latency, 4-6 cycles). perf on
|
|
1210
|
+
* i7-14700K measured variance_flatten 100% scalar in both SSE2 and AVX2
|
|
1211
|
+
* builds, with the AVX2 FMA form 2.8x slower on Pass 2 -> 1.76x overall
|
|
1212
|
+
* regression vs SSE2 (mul+add, whose add-only recurrence overlaps
|
|
1213
|
+
* better). Writing 8 explicit accumulators gives the compiler 8
|
|
1214
|
+
* independent chains, hiding the latency on any FADD/FMA-latency arch
|
|
1215
|
+
* (Golden Cove / Zen4 FMA latency 4, headroom for 6).
|
|
1216
|
+
*
|
|
1217
|
+
* EXPR(x) is a function-like macro producing the per-element contribution
|
|
1218
|
+
* (Pass 1: (double)(x); Pass 2: ((double)(x)-mean)*((double)(x)-mean)).
|
|
1219
|
+
* Reassoc across the 8 lanes is the same ε-close license as _PLUS_EX
|
|
1220
|
+
* (bit-exact not guaranteed; CLAUDE.md ε-close policy, SL.1.5).
|
|
1221
|
+
*
|
|
1222
|
+
* Only the no-mask + contig branch is 8-way; masked / non-contig
|
|
1223
|
+
* branches stay single-accumulator (not the hot path). Position
|
|
1224
|
+
* counter `idx` is not tracked (variance REDUCE never uses it). ACC_T
|
|
1225
|
+
* is the accumulator C type (double for Pass 1 sum on numeric/bool and
|
|
1226
|
+
* Pass 2 M2; complex Pass 1 passes its complex type). Used only by the
|
|
1227
|
+
* two_pass_centred emitter in mkkernel.rb.
|
|
1228
|
+
*/
|
|
1229
|
+
#define CA_SLAB_REDUCE_SUM8_EX(T, ACC_T, st, p, m, acc, INIT, EXPR, masked_cnt) do { \
|
|
1230
|
+
(acc) = (INIT); \
|
|
1231
|
+
int8_t __srK = (st).slab_ndim; \
|
|
1232
|
+
int8_t __srOuterK = __srK - 1; \
|
|
1233
|
+
ca_size_t __srInnerN = (st).slab_dims[__srK - 1]; \
|
|
1234
|
+
ca_size_t __srInnerS = (st).slab_strides[__srK - 1]; \
|
|
1235
|
+
ca_size_t __srInnerMS = (st).slab_mask_strides[__srK - 1]; \
|
|
1236
|
+
int __srContig = (__srInnerS == (ca_size_t) sizeof(T)); \
|
|
1237
|
+
int __srMaskU = (__srInnerMS == 1); \
|
|
1238
|
+
ca_size_t __srOC = 1; \
|
|
1239
|
+
for ( int8_t __sk = 0; __sk < __srOuterK; __sk++ ) \
|
|
1240
|
+
__srOC *= (st).slab_dims[__sk]; \
|
|
1241
|
+
{ \
|
|
1242
|
+
int __srFlat = __srContig; \
|
|
1243
|
+
for ( int8_t __sk = (int8_t)(__srK - 2); __sk >= 0 && __srFlat; __sk-- ) \
|
|
1244
|
+
if ( (st).slab_strides[__sk] != \
|
|
1245
|
+
(st).slab_dims[__sk + 1] * (st).slab_strides[__sk + 1] ) \
|
|
1246
|
+
__srFlat = 0; \
|
|
1247
|
+
if ( __srFlat && (m) != NULL ) { \
|
|
1248
|
+
if ( ! __srMaskU ) __srFlat = 0; \
|
|
1249
|
+
for ( int8_t __sk = (int8_t)(__srK - 2); __sk >= 0 && __srFlat; __sk-- ) \
|
|
1250
|
+
if ( (st).slab_mask_strides[__sk] != \
|
|
1251
|
+
(st).slab_dims[__sk + 1] * (st).slab_mask_strides[__sk + 1] ) \
|
|
1252
|
+
__srFlat = 0; \
|
|
1253
|
+
} \
|
|
1254
|
+
if ( __srFlat ) { __srInnerN = (st).slab_elements; __srOC = 1; } \
|
|
1255
|
+
} \
|
|
1256
|
+
ca_size_t __srIdx[CA_RANK_MAX] = { 0 }; \
|
|
1257
|
+
for ( ca_size_t __so = 0; __so < __srOC; __so++ ) { \
|
|
1258
|
+
ca_size_t __srDoff = 0, __srMoff = 0; \
|
|
1259
|
+
for ( int8_t __sk = 0; __sk < __srOuterK; __sk++ ) { \
|
|
1260
|
+
__srDoff += __srIdx[__sk] * (st).slab_strides[__sk]; \
|
|
1261
|
+
__srMoff += __srIdx[__sk] * (st).slab_mask_strides[__sk]; \
|
|
1262
|
+
} \
|
|
1263
|
+
const char *__srQ = (const char *)(p) + __srDoff; \
|
|
1264
|
+
if ( (m) == NULL ) { \
|
|
1265
|
+
if ( __srContig ) { \
|
|
1266
|
+
const T *__srSrc = (const T *) __srQ; \
|
|
1267
|
+
ACC_T __a0=(ACC_T)0,__a1=(ACC_T)0,__a2=(ACC_T)0,__a3=(ACC_T)0; \
|
|
1268
|
+
ACC_T __a4=(ACC_T)0,__a5=(ACC_T)0,__a6=(ACC_T)0,__a7=(ACC_T)0; \
|
|
1269
|
+
ca_size_t __sj = 0, __sN = __srInnerN; \
|
|
1270
|
+
for ( ; __sj + 8 <= __sN; __sj += 8 ) { \
|
|
1271
|
+
__a0 += EXPR(__srSrc[__sj + 0]); \
|
|
1272
|
+
__a1 += EXPR(__srSrc[__sj + 1]); \
|
|
1273
|
+
__a2 += EXPR(__srSrc[__sj + 2]); \
|
|
1274
|
+
__a3 += EXPR(__srSrc[__sj + 3]); \
|
|
1275
|
+
__a4 += EXPR(__srSrc[__sj + 4]); \
|
|
1276
|
+
__a5 += EXPR(__srSrc[__sj + 5]); \
|
|
1277
|
+
__a6 += EXPR(__srSrc[__sj + 6]); \
|
|
1278
|
+
__a7 += EXPR(__srSrc[__sj + 7]); \
|
|
1279
|
+
} \
|
|
1280
|
+
for ( ; __sj < __sN; __sj++ ) __a0 += EXPR(__srSrc[__sj]); \
|
|
1281
|
+
(acc) += ((__a0 + __a1) + (__a2 + __a3)) \
|
|
1282
|
+
+ ((__a4 + __a5) + (__a6 + __a7)); \
|
|
1283
|
+
} else { \
|
|
1284
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1285
|
+
T v = *(const T *)(__srQ + __sj * __srInnerS); \
|
|
1286
|
+
(acc) += EXPR(v); \
|
|
1287
|
+
} \
|
|
1288
|
+
} \
|
|
1289
|
+
} else { \
|
|
1290
|
+
const boolean8_t *__srMM = (const boolean8_t *)(m) + __srMoff; \
|
|
1291
|
+
if ( __srContig && __srMaskU ) { \
|
|
1292
|
+
const T *__srSrc = (const T *) __srQ; \
|
|
1293
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1294
|
+
if ( ! __srMM[__sj] ) { T v = __srSrc[__sj]; (acc) += EXPR(v); } \
|
|
1295
|
+
else { (masked_cnt)++; } \
|
|
1296
|
+
} \
|
|
1297
|
+
} else { \
|
|
1298
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1299
|
+
if ( ! __srMM[__sj * __srInnerMS] ) { \
|
|
1300
|
+
T v = *(const T *)(__srQ + __sj * __srInnerS); \
|
|
1301
|
+
(acc) += EXPR(v); \
|
|
1302
|
+
} else { (masked_cnt)++; } \
|
|
1303
|
+
} \
|
|
1304
|
+
} \
|
|
1305
|
+
} \
|
|
1306
|
+
for ( int8_t __sk = __srOuterK - 1; __sk >= 0; __sk-- ) { \
|
|
1307
|
+
if ( ++__srIdx[__sk] < (st).slab_dims[__sk] ) break; \
|
|
1308
|
+
__srIdx[__sk] = 0; \
|
|
1309
|
+
} \
|
|
1310
|
+
} \
|
|
1311
|
+
} while (0)
|
|
1312
|
+
|
|
1313
|
+
/* CA_SLAB_REDUCE_MIN8_EX / _MAX8_EX / _STAR8_EX
|
|
1314
|
+
* (horizontal extension of the SUM8_EX pattern to standard reducers,
|
|
1315
|
+
* 2026-07-19):
|
|
1316
|
+
*
|
|
1317
|
+
* Same rationale as SUM8_EX — GCC does not split single-accumulator pragma
|
|
1318
|
+
* reductions into multiple independent chains on FMA/FADD-latency-bound
|
|
1319
|
+
* paths, so we write 8 explicit accumulators in source. On i7-14700K
|
|
1320
|
+
* the standalone sum kernel was measured at ~15.9 GB/s (scalar with
|
|
1321
|
+
* compiler-auto-unroll) while variance per-pass reached ~58.9 GB/s after
|
|
1322
|
+
* its SUM8_EX fix — the standalone reducers had the same headroom
|
|
1323
|
+
* available. Applying SUM8/MIN8/MAX8/STAR8 to sum/mean/min/max/prod
|
|
1324
|
+
* closes the gap. Only the no-mask+contig branch is 8-way; masked /
|
|
1325
|
+
* non-contig / object stay single-accumulator (not the hot path).
|
|
1326
|
+
*
|
|
1327
|
+
* Interface parallels SUM8_EX: EXPR(x) is a function-like macro
|
|
1328
|
+
* producing the value to reduce (typically `((ACC_T)(x))`), so the
|
|
1329
|
+
* caller controls the per-src cast without embedding it in a REDUCE
|
|
1330
|
+
* statement. Reassoc across the 8 lanes is the same ε-close license
|
|
1331
|
+
* as _MIN_EX / _MAX_EX / _STAR_EX (bit-exact not guaranteed; min/max
|
|
1332
|
+
* are strictly associative + commutative so the 8-way tree gives the
|
|
1333
|
+
* same result modulo NaN handling which stays identical to the
|
|
1334
|
+
* single-accumulator case).
|
|
1335
|
+
*/
|
|
1336
|
+
#define CA_SLAB_REDUCE_MIN8_EX(T, ACC_T, st, p, m, acc, INIT, EXPR, masked_cnt) do { \
|
|
1337
|
+
(acc) = (INIT); \
|
|
1338
|
+
int8_t __srK = (st).slab_ndim; \
|
|
1339
|
+
int8_t __srOuterK = __srK - 1; \
|
|
1340
|
+
ca_size_t __srInnerN = (st).slab_dims[__srK - 1]; \
|
|
1341
|
+
ca_size_t __srInnerS = (st).slab_strides[__srK - 1]; \
|
|
1342
|
+
ca_size_t __srInnerMS = (st).slab_mask_strides[__srK - 1]; \
|
|
1343
|
+
int __srContig = (__srInnerS == (ca_size_t) sizeof(T)); \
|
|
1344
|
+
int __srMaskU = (__srInnerMS == 1); \
|
|
1345
|
+
ca_size_t __srOC = 1; \
|
|
1346
|
+
for ( int8_t __sk = 0; __sk < __srOuterK; __sk++ ) \
|
|
1347
|
+
__srOC *= (st).slab_dims[__sk]; \
|
|
1348
|
+
{ \
|
|
1349
|
+
int __srFlat = __srContig; \
|
|
1350
|
+
for ( int8_t __sk = (int8_t)(__srK - 2); __sk >= 0 && __srFlat; __sk-- ) \
|
|
1351
|
+
if ( (st).slab_strides[__sk] != \
|
|
1352
|
+
(st).slab_dims[__sk + 1] * (st).slab_strides[__sk + 1] ) \
|
|
1353
|
+
__srFlat = 0; \
|
|
1354
|
+
if ( __srFlat && (m) != NULL ) { \
|
|
1355
|
+
if ( ! __srMaskU ) __srFlat = 0; \
|
|
1356
|
+
for ( int8_t __sk = (int8_t)(__srK - 2); __sk >= 0 && __srFlat; __sk-- ) \
|
|
1357
|
+
if ( (st).slab_mask_strides[__sk] != \
|
|
1358
|
+
(st).slab_dims[__sk + 1] * (st).slab_mask_strides[__sk + 1] ) \
|
|
1359
|
+
__srFlat = 0; \
|
|
1360
|
+
} \
|
|
1361
|
+
if ( __srFlat ) { __srInnerN = (st).slab_elements; __srOC = 1; } \
|
|
1362
|
+
} \
|
|
1363
|
+
ca_size_t __srIdx[CA_RANK_MAX] = { 0 }; \
|
|
1364
|
+
for ( ca_size_t __so = 0; __so < __srOC; __so++ ) { \
|
|
1365
|
+
ca_size_t __srDoff = 0, __srMoff = 0; \
|
|
1366
|
+
for ( int8_t __sk = 0; __sk < __srOuterK; __sk++ ) { \
|
|
1367
|
+
__srDoff += __srIdx[__sk] * (st).slab_strides[__sk]; \
|
|
1368
|
+
__srMoff += __srIdx[__sk] * (st).slab_mask_strides[__sk]; \
|
|
1369
|
+
} \
|
|
1370
|
+
const char *__srQ = (const char *)(p) + __srDoff; \
|
|
1371
|
+
if ( (m) == NULL ) { \
|
|
1372
|
+
if ( __srContig ) { \
|
|
1373
|
+
const T *__srSrc = (const T *) __srQ; \
|
|
1374
|
+
ACC_T __a0=(acc),__a1=(acc),__a2=(acc),__a3=(acc); \
|
|
1375
|
+
ACC_T __a4=(acc),__a5=(acc),__a6=(acc),__a7=(acc); \
|
|
1376
|
+
ca_size_t __sj = 0, __sN = __srInnerN; \
|
|
1377
|
+
for ( ; __sj + 8 <= __sN; __sj += 8 ) { \
|
|
1378
|
+
ACC_T __v0 = EXPR(__srSrc[__sj + 0]); __a0 = (__v0 < __a0) ? __v0 : __a0; \
|
|
1379
|
+
ACC_T __v1 = EXPR(__srSrc[__sj + 1]); __a1 = (__v1 < __a1) ? __v1 : __a1; \
|
|
1380
|
+
ACC_T __v2 = EXPR(__srSrc[__sj + 2]); __a2 = (__v2 < __a2) ? __v2 : __a2; \
|
|
1381
|
+
ACC_T __v3 = EXPR(__srSrc[__sj + 3]); __a3 = (__v3 < __a3) ? __v3 : __a3; \
|
|
1382
|
+
ACC_T __v4 = EXPR(__srSrc[__sj + 4]); __a4 = (__v4 < __a4) ? __v4 : __a4; \
|
|
1383
|
+
ACC_T __v5 = EXPR(__srSrc[__sj + 5]); __a5 = (__v5 < __a5) ? __v5 : __a5; \
|
|
1384
|
+
ACC_T __v6 = EXPR(__srSrc[__sj + 6]); __a6 = (__v6 < __a6) ? __v6 : __a6; \
|
|
1385
|
+
ACC_T __v7 = EXPR(__srSrc[__sj + 7]); __a7 = (__v7 < __a7) ? __v7 : __a7; \
|
|
1386
|
+
} \
|
|
1387
|
+
for ( ; __sj < __sN; __sj++ ) { \
|
|
1388
|
+
ACC_T __v = EXPR(__srSrc[__sj]); __a0 = (__v < __a0) ? __v : __a0; \
|
|
1389
|
+
} \
|
|
1390
|
+
ACC_T __b0 = (__a0 < __a1) ? __a0 : __a1; \
|
|
1391
|
+
ACC_T __b1 = (__a2 < __a3) ? __a2 : __a3; \
|
|
1392
|
+
ACC_T __b2 = (__a4 < __a5) ? __a4 : __a5; \
|
|
1393
|
+
ACC_T __b3 = (__a6 < __a7) ? __a6 : __a7; \
|
|
1394
|
+
ACC_T __c0 = (__b0 < __b1) ? __b0 : __b1; \
|
|
1395
|
+
ACC_T __c1 = (__b2 < __b3) ? __b2 : __b3; \
|
|
1396
|
+
(acc) = (__c0 < __c1) ? __c0 : __c1; \
|
|
1397
|
+
} else { \
|
|
1398
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1399
|
+
ACC_T __v = EXPR(*(const T *)(__srQ + __sj * __srInnerS)); \
|
|
1400
|
+
if (__v < (acc)) (acc) = __v; \
|
|
1401
|
+
} \
|
|
1402
|
+
} \
|
|
1403
|
+
} else { \
|
|
1404
|
+
const boolean8_t *__srMM = (const boolean8_t *)(m) + __srMoff; \
|
|
1405
|
+
if ( __srContig && __srMaskU ) { \
|
|
1406
|
+
const T *__srSrc = (const T *) __srQ; \
|
|
1407
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1408
|
+
if ( ! __srMM[__sj] ) { \
|
|
1409
|
+
ACC_T __v = EXPR(__srSrc[__sj]); \
|
|
1410
|
+
if (__v < (acc)) (acc) = __v; \
|
|
1411
|
+
} else { (masked_cnt)++; } \
|
|
1412
|
+
} \
|
|
1413
|
+
} else { \
|
|
1414
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1415
|
+
if ( ! __srMM[__sj * __srInnerMS] ) { \
|
|
1416
|
+
ACC_T __v = EXPR(*(const T *)(__srQ + __sj * __srInnerS)); \
|
|
1417
|
+
if (__v < (acc)) (acc) = __v; \
|
|
1418
|
+
} else { (masked_cnt)++; } \
|
|
1419
|
+
} \
|
|
1420
|
+
} \
|
|
1421
|
+
} \
|
|
1422
|
+
for ( int8_t __sk = __srOuterK - 1; __sk >= 0; __sk-- ) { \
|
|
1423
|
+
if ( ++__srIdx[__sk] < (st).slab_dims[__sk] ) break; \
|
|
1424
|
+
__srIdx[__sk] = 0; \
|
|
1425
|
+
} \
|
|
1426
|
+
} \
|
|
1427
|
+
} while (0)
|
|
1428
|
+
|
|
1429
|
+
#define CA_SLAB_REDUCE_MAX8_EX(T, ACC_T, st, p, m, acc, INIT, EXPR, masked_cnt) do { \
|
|
1430
|
+
(acc) = (INIT); \
|
|
1431
|
+
int8_t __srK = (st).slab_ndim; \
|
|
1432
|
+
int8_t __srOuterK = __srK - 1; \
|
|
1433
|
+
ca_size_t __srInnerN = (st).slab_dims[__srK - 1]; \
|
|
1434
|
+
ca_size_t __srInnerS = (st).slab_strides[__srK - 1]; \
|
|
1435
|
+
ca_size_t __srInnerMS = (st).slab_mask_strides[__srK - 1]; \
|
|
1436
|
+
int __srContig = (__srInnerS == (ca_size_t) sizeof(T)); \
|
|
1437
|
+
int __srMaskU = (__srInnerMS == 1); \
|
|
1438
|
+
ca_size_t __srOC = 1; \
|
|
1439
|
+
for ( int8_t __sk = 0; __sk < __srOuterK; __sk++ ) \
|
|
1440
|
+
__srOC *= (st).slab_dims[__sk]; \
|
|
1441
|
+
{ \
|
|
1442
|
+
int __srFlat = __srContig; \
|
|
1443
|
+
for ( int8_t __sk = (int8_t)(__srK - 2); __sk >= 0 && __srFlat; __sk-- ) \
|
|
1444
|
+
if ( (st).slab_strides[__sk] != \
|
|
1445
|
+
(st).slab_dims[__sk + 1] * (st).slab_strides[__sk + 1] ) \
|
|
1446
|
+
__srFlat = 0; \
|
|
1447
|
+
if ( __srFlat && (m) != NULL ) { \
|
|
1448
|
+
if ( ! __srMaskU ) __srFlat = 0; \
|
|
1449
|
+
for ( int8_t __sk = (int8_t)(__srK - 2); __sk >= 0 && __srFlat; __sk-- ) \
|
|
1450
|
+
if ( (st).slab_mask_strides[__sk] != \
|
|
1451
|
+
(st).slab_dims[__sk + 1] * (st).slab_mask_strides[__sk + 1] ) \
|
|
1452
|
+
__srFlat = 0; \
|
|
1453
|
+
} \
|
|
1454
|
+
if ( __srFlat ) { __srInnerN = (st).slab_elements; __srOC = 1; } \
|
|
1455
|
+
} \
|
|
1456
|
+
ca_size_t __srIdx[CA_RANK_MAX] = { 0 }; \
|
|
1457
|
+
for ( ca_size_t __so = 0; __so < __srOC; __so++ ) { \
|
|
1458
|
+
ca_size_t __srDoff = 0, __srMoff = 0; \
|
|
1459
|
+
for ( int8_t __sk = 0; __sk < __srOuterK; __sk++ ) { \
|
|
1460
|
+
__srDoff += __srIdx[__sk] * (st).slab_strides[__sk]; \
|
|
1461
|
+
__srMoff += __srIdx[__sk] * (st).slab_mask_strides[__sk]; \
|
|
1462
|
+
} \
|
|
1463
|
+
const char *__srQ = (const char *)(p) + __srDoff; \
|
|
1464
|
+
if ( (m) == NULL ) { \
|
|
1465
|
+
if ( __srContig ) { \
|
|
1466
|
+
const T *__srSrc = (const T *) __srQ; \
|
|
1467
|
+
ACC_T __a0=(acc),__a1=(acc),__a2=(acc),__a3=(acc); \
|
|
1468
|
+
ACC_T __a4=(acc),__a5=(acc),__a6=(acc),__a7=(acc); \
|
|
1469
|
+
ca_size_t __sj = 0, __sN = __srInnerN; \
|
|
1470
|
+
for ( ; __sj + 8 <= __sN; __sj += 8 ) { \
|
|
1471
|
+
ACC_T __v0 = EXPR(__srSrc[__sj + 0]); __a0 = (__v0 > __a0) ? __v0 : __a0; \
|
|
1472
|
+
ACC_T __v1 = EXPR(__srSrc[__sj + 1]); __a1 = (__v1 > __a1) ? __v1 : __a1; \
|
|
1473
|
+
ACC_T __v2 = EXPR(__srSrc[__sj + 2]); __a2 = (__v2 > __a2) ? __v2 : __a2; \
|
|
1474
|
+
ACC_T __v3 = EXPR(__srSrc[__sj + 3]); __a3 = (__v3 > __a3) ? __v3 : __a3; \
|
|
1475
|
+
ACC_T __v4 = EXPR(__srSrc[__sj + 4]); __a4 = (__v4 > __a4) ? __v4 : __a4; \
|
|
1476
|
+
ACC_T __v5 = EXPR(__srSrc[__sj + 5]); __a5 = (__v5 > __a5) ? __v5 : __a5; \
|
|
1477
|
+
ACC_T __v6 = EXPR(__srSrc[__sj + 6]); __a6 = (__v6 > __a6) ? __v6 : __a6; \
|
|
1478
|
+
ACC_T __v7 = EXPR(__srSrc[__sj + 7]); __a7 = (__v7 > __a7) ? __v7 : __a7; \
|
|
1479
|
+
} \
|
|
1480
|
+
for ( ; __sj < __sN; __sj++ ) { \
|
|
1481
|
+
ACC_T __v = EXPR(__srSrc[__sj]); __a0 = (__v > __a0) ? __v : __a0; \
|
|
1482
|
+
} \
|
|
1483
|
+
ACC_T __b0 = (__a0 > __a1) ? __a0 : __a1; \
|
|
1484
|
+
ACC_T __b1 = (__a2 > __a3) ? __a2 : __a3; \
|
|
1485
|
+
ACC_T __b2 = (__a4 > __a5) ? __a4 : __a5; \
|
|
1486
|
+
ACC_T __b3 = (__a6 > __a7) ? __a6 : __a7; \
|
|
1487
|
+
ACC_T __c0 = (__b0 > __b1) ? __b0 : __b1; \
|
|
1488
|
+
ACC_T __c1 = (__b2 > __b3) ? __b2 : __b3; \
|
|
1489
|
+
(acc) = (__c0 > __c1) ? __c0 : __c1; \
|
|
1490
|
+
} else { \
|
|
1491
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1492
|
+
ACC_T __v = EXPR(*(const T *)(__srQ + __sj * __srInnerS)); \
|
|
1493
|
+
if (__v > (acc)) (acc) = __v; \
|
|
1494
|
+
} \
|
|
1495
|
+
} \
|
|
1496
|
+
} else { \
|
|
1497
|
+
const boolean8_t *__srMM = (const boolean8_t *)(m) + __srMoff; \
|
|
1498
|
+
if ( __srContig && __srMaskU ) { \
|
|
1499
|
+
const T *__srSrc = (const T *) __srQ; \
|
|
1500
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1501
|
+
if ( ! __srMM[__sj] ) { \
|
|
1502
|
+
ACC_T __v = EXPR(__srSrc[__sj]); \
|
|
1503
|
+
if (__v > (acc)) (acc) = __v; \
|
|
1504
|
+
} else { (masked_cnt)++; } \
|
|
1505
|
+
} \
|
|
1506
|
+
} else { \
|
|
1507
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1508
|
+
if ( ! __srMM[__sj * __srInnerMS] ) { \
|
|
1509
|
+
ACC_T __v = EXPR(*(const T *)(__srQ + __sj * __srInnerS)); \
|
|
1510
|
+
if (__v > (acc)) (acc) = __v; \
|
|
1511
|
+
} else { (masked_cnt)++; } \
|
|
1512
|
+
} \
|
|
1513
|
+
} \
|
|
1514
|
+
} \
|
|
1515
|
+
for ( int8_t __sk = __srOuterK - 1; __sk >= 0; __sk-- ) { \
|
|
1516
|
+
if ( ++__srIdx[__sk] < (st).slab_dims[__sk] ) break; \
|
|
1517
|
+
__srIdx[__sk] = 0; \
|
|
1518
|
+
} \
|
|
1519
|
+
} \
|
|
1520
|
+
} while (0)
|
|
1521
|
+
|
|
1522
|
+
#define CA_SLAB_REDUCE_STAR8_EX(T, ACC_T, st, p, m, acc, INIT, EXPR, masked_cnt) do { \
|
|
1523
|
+
(acc) = (INIT); \
|
|
1524
|
+
int8_t __srK = (st).slab_ndim; \
|
|
1525
|
+
int8_t __srOuterK = __srK - 1; \
|
|
1526
|
+
ca_size_t __srInnerN = (st).slab_dims[__srK - 1]; \
|
|
1527
|
+
ca_size_t __srInnerS = (st).slab_strides[__srK - 1]; \
|
|
1528
|
+
ca_size_t __srInnerMS = (st).slab_mask_strides[__srK - 1]; \
|
|
1529
|
+
int __srContig = (__srInnerS == (ca_size_t) sizeof(T)); \
|
|
1530
|
+
int __srMaskU = (__srInnerMS == 1); \
|
|
1531
|
+
ca_size_t __srOC = 1; \
|
|
1532
|
+
for ( int8_t __sk = 0; __sk < __srOuterK; __sk++ ) \
|
|
1533
|
+
__srOC *= (st).slab_dims[__sk]; \
|
|
1534
|
+
{ \
|
|
1535
|
+
int __srFlat = __srContig; \
|
|
1536
|
+
for ( int8_t __sk = (int8_t)(__srK - 2); __sk >= 0 && __srFlat; __sk-- ) \
|
|
1537
|
+
if ( (st).slab_strides[__sk] != \
|
|
1538
|
+
(st).slab_dims[__sk + 1] * (st).slab_strides[__sk + 1] ) \
|
|
1539
|
+
__srFlat = 0; \
|
|
1540
|
+
if ( __srFlat && (m) != NULL ) { \
|
|
1541
|
+
if ( ! __srMaskU ) __srFlat = 0; \
|
|
1542
|
+
for ( int8_t __sk = (int8_t)(__srK - 2); __sk >= 0 && __srFlat; __sk-- ) \
|
|
1543
|
+
if ( (st).slab_mask_strides[__sk] != \
|
|
1544
|
+
(st).slab_dims[__sk + 1] * (st).slab_mask_strides[__sk + 1] ) \
|
|
1545
|
+
__srFlat = 0; \
|
|
1546
|
+
} \
|
|
1547
|
+
if ( __srFlat ) { __srInnerN = (st).slab_elements; __srOC = 1; } \
|
|
1548
|
+
} \
|
|
1549
|
+
ca_size_t __srIdx[CA_RANK_MAX] = { 0 }; \
|
|
1550
|
+
for ( ca_size_t __so = 0; __so < __srOC; __so++ ) { \
|
|
1551
|
+
ca_size_t __srDoff = 0, __srMoff = 0; \
|
|
1552
|
+
for ( int8_t __sk = 0; __sk < __srOuterK; __sk++ ) { \
|
|
1553
|
+
__srDoff += __srIdx[__sk] * (st).slab_strides[__sk]; \
|
|
1554
|
+
__srMoff += __srIdx[__sk] * (st).slab_mask_strides[__sk]; \
|
|
1555
|
+
} \
|
|
1556
|
+
const char *__srQ = (const char *)(p) + __srDoff; \
|
|
1557
|
+
if ( (m) == NULL ) { \
|
|
1558
|
+
if ( __srContig ) { \
|
|
1559
|
+
const T *__srSrc = (const T *) __srQ; \
|
|
1560
|
+
ACC_T __a0=(ACC_T)1,__a1=(ACC_T)1,__a2=(ACC_T)1,__a3=(ACC_T)1; \
|
|
1561
|
+
ACC_T __a4=(ACC_T)1,__a5=(ACC_T)1,__a6=(ACC_T)1,__a7=(ACC_T)1; \
|
|
1562
|
+
ca_size_t __sj = 0, __sN = __srInnerN; \
|
|
1563
|
+
for ( ; __sj + 8 <= __sN; __sj += 8 ) { \
|
|
1564
|
+
__a0 *= EXPR(__srSrc[__sj + 0]); \
|
|
1565
|
+
__a1 *= EXPR(__srSrc[__sj + 1]); \
|
|
1566
|
+
__a2 *= EXPR(__srSrc[__sj + 2]); \
|
|
1567
|
+
__a3 *= EXPR(__srSrc[__sj + 3]); \
|
|
1568
|
+
__a4 *= EXPR(__srSrc[__sj + 4]); \
|
|
1569
|
+
__a5 *= EXPR(__srSrc[__sj + 5]); \
|
|
1570
|
+
__a6 *= EXPR(__srSrc[__sj + 6]); \
|
|
1571
|
+
__a7 *= EXPR(__srSrc[__sj + 7]); \
|
|
1572
|
+
} \
|
|
1573
|
+
for ( ; __sj < __sN; __sj++ ) __a0 *= EXPR(__srSrc[__sj]); \
|
|
1574
|
+
(acc) *= ((__a0 * __a1) * (__a2 * __a3)) \
|
|
1575
|
+
* ((__a4 * __a5) * (__a6 * __a7)); \
|
|
1576
|
+
} else { \
|
|
1577
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1578
|
+
T v = *(const T *)(__srQ + __sj * __srInnerS); \
|
|
1579
|
+
(acc) *= EXPR(v); \
|
|
1580
|
+
} \
|
|
1581
|
+
} \
|
|
1582
|
+
} else { \
|
|
1583
|
+
const boolean8_t *__srMM = (const boolean8_t *)(m) + __srMoff; \
|
|
1584
|
+
if ( __srContig && __srMaskU ) { \
|
|
1585
|
+
const T *__srSrc = (const T *) __srQ; \
|
|
1586
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1587
|
+
if ( ! __srMM[__sj] ) { T v = __srSrc[__sj]; (acc) *= EXPR(v); } \
|
|
1588
|
+
else { (masked_cnt)++; } \
|
|
1589
|
+
} \
|
|
1590
|
+
} else { \
|
|
1591
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1592
|
+
if ( ! __srMM[__sj * __srInnerMS] ) { \
|
|
1593
|
+
T v = *(const T *)(__srQ + __sj * __srInnerS); \
|
|
1594
|
+
(acc) *= EXPR(v); \
|
|
1595
|
+
} else { (masked_cnt)++; } \
|
|
1596
|
+
} \
|
|
1597
|
+
} \
|
|
1598
|
+
} \
|
|
1599
|
+
for ( int8_t __sk = __srOuterK - 1; __sk >= 0; __sk-- ) { \
|
|
1600
|
+
if ( ++__srIdx[__sk] < (st).slab_dims[__sk] ) break; \
|
|
1601
|
+
__srIdx[__sk] = 0; \
|
|
1602
|
+
} \
|
|
1603
|
+
} \
|
|
1604
|
+
} while (0)
|
|
1605
|
+
|
|
1606
|
+
/* CA_SLAB_REDUCE_T_MIN_EX (SL.1.2):
|
|
1607
|
+
* Same structure as PLUS_EX; no-mask + contig inner loop carries
|
|
1608
|
+
* `#pragma omp simd reduction(min:acc)`. Author REDUCE expression
|
|
1609
|
+
* is `if (v < acc) acc = v` (see :min kernel in mkkernel.rb) which
|
|
1610
|
+
* matches OpenMP `min:` reduction semantics exactly — bit-exact
|
|
1611
|
+
* parity preserved (no reassoc license needed; min/max are
|
|
1612
|
+
* associative + commutative under `<` / `>`).
|
|
1613
|
+
*
|
|
1614
|
+
* NaN behaviour: `(NaN < x)` is false in C, so NaN never wins
|
|
1615
|
+
* the comparison. Final acc for all-NaN slab stays at INIT
|
|
1616
|
+
* (T_LIMIT_HI), matching the pre-SIMD path byte-identically.
|
|
1617
|
+
*/
|
|
1618
|
+
#define CA_SLAB_REDUCE_T_MIN_EX(T, st, p, m, acc, INIT, REDUCE, masked_cnt) do { \
|
|
1619
|
+
(acc) = (INIT); \
|
|
1620
|
+
int8_t __srK = (st).slab_ndim; \
|
|
1621
|
+
int8_t __srOuterK = __srK - 1; \
|
|
1622
|
+
ca_size_t __srInnerN = (st).slab_dims[__srK - 1]; \
|
|
1623
|
+
ca_size_t __srInnerS = (st).slab_strides[__srK - 1]; \
|
|
1624
|
+
ca_size_t __srInnerMS = (st).slab_mask_strides[__srK - 1]; \
|
|
1625
|
+
int __srContig = (__srInnerS == (ca_size_t) sizeof(T)); \
|
|
1626
|
+
int __srMaskU = (__srInnerMS == 1); \
|
|
1627
|
+
ca_size_t __srOC = 1; \
|
|
1628
|
+
for ( int8_t __sk = 0; __sk < __srOuterK; __sk++ ) \
|
|
1629
|
+
__srOC *= (st).slab_dims[__sk]; \
|
|
1630
|
+
/* Slab-collapse: when the whole K-D slab is row-major contiguous \
|
|
1631
|
+
(data, and mask if present), fold all slab axes into one flat \
|
|
1632
|
+
inner loop. Removes the per-outer multi-index offset recompute + \
|
|
1633
|
+
carry that otherwise dominates when the innermost slab axis is \
|
|
1634
|
+
small (full reduction of [N,1] / [N,small] entity, or trailing- \
|
|
1635
|
+
contig multi-axis reduce). Platform-general: structural, not SIMD. \
|
|
1636
|
+
No-op for 1-D slabs (__srOuterK == 0 leaves __srOC == 1). */ \
|
|
1637
|
+
{ \
|
|
1638
|
+
int __srFlat = __srContig; \
|
|
1639
|
+
for ( int8_t __sk = (int8_t)(__srK - 2); __sk >= 0 && __srFlat; __sk-- ) \
|
|
1640
|
+
if ( (st).slab_strides[__sk] != \
|
|
1641
|
+
(st).slab_dims[__sk + 1] * (st).slab_strides[__sk + 1] ) \
|
|
1642
|
+
__srFlat = 0; \
|
|
1643
|
+
if ( __srFlat && (m) != NULL ) { \
|
|
1644
|
+
if ( ! __srMaskU ) __srFlat = 0; \
|
|
1645
|
+
for ( int8_t __sk = (int8_t)(__srK - 2); __sk >= 0 && __srFlat; __sk-- ) \
|
|
1646
|
+
if ( (st).slab_mask_strides[__sk] != \
|
|
1647
|
+
(st).slab_dims[__sk + 1] * (st).slab_mask_strides[__sk + 1] ) \
|
|
1648
|
+
__srFlat = 0; \
|
|
1649
|
+
} \
|
|
1650
|
+
if ( __srFlat ) { __srInnerN = (st).slab_elements; __srOC = 1; } \
|
|
1651
|
+
} \
|
|
1652
|
+
ca_size_t __srIdx[CA_RANK_MAX] = { 0 }; \
|
|
1653
|
+
ca_size_t idx = 0; \
|
|
1654
|
+
for ( ca_size_t __so = 0; __so < __srOC; __so++ ) { \
|
|
1655
|
+
ca_size_t __srDoff = 0, __srMoff = 0; \
|
|
1656
|
+
for ( int8_t __sk = 0; __sk < __srOuterK; __sk++ ) { \
|
|
1657
|
+
__srDoff += __srIdx[__sk] * (st).slab_strides[__sk]; \
|
|
1658
|
+
__srMoff += __srIdx[__sk] * (st).slab_mask_strides[__sk]; \
|
|
1659
|
+
} \
|
|
1660
|
+
const char *__srQ = (const char *)(p) + __srDoff; \
|
|
1661
|
+
if ( (m) == NULL ) { \
|
|
1662
|
+
if ( __srContig ) { \
|
|
1663
|
+
const T *__srSrc = (const T *) __srQ; \
|
|
1664
|
+
_CA_SIMD_MIN(acc) \
|
|
1665
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1666
|
+
T v = __srSrc[__sj]; \
|
|
1667
|
+
REDUCE; \
|
|
1668
|
+
idx++; \
|
|
1669
|
+
} \
|
|
1670
|
+
} else { \
|
|
1671
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1672
|
+
T v = *(const T *)(__srQ + __sj * __srInnerS); \
|
|
1673
|
+
REDUCE; \
|
|
1674
|
+
idx++; \
|
|
1675
|
+
} \
|
|
1676
|
+
} \
|
|
1677
|
+
} else { \
|
|
1678
|
+
const boolean8_t *__srMM = (const boolean8_t *)(m) + __srMoff; \
|
|
1679
|
+
if ( __srContig && __srMaskU ) { \
|
|
1680
|
+
const T *__srSrc = (const T *) __srQ; \
|
|
1681
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1682
|
+
if ( ! __srMM[__sj] ) { \
|
|
1683
|
+
T v = __srSrc[__sj]; \
|
|
1684
|
+
REDUCE; \
|
|
1685
|
+
} else { \
|
|
1686
|
+
(masked_cnt)++; \
|
|
1687
|
+
} \
|
|
1688
|
+
idx++; \
|
|
1689
|
+
} \
|
|
1690
|
+
} else { \
|
|
1691
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1692
|
+
if ( ! __srMM[__sj * __srInnerMS] ) { \
|
|
1693
|
+
T v = *(const T *)(__srQ + __sj * __srInnerS); \
|
|
1694
|
+
REDUCE; \
|
|
1695
|
+
} else { \
|
|
1696
|
+
(masked_cnt)++; \
|
|
1697
|
+
} \
|
|
1698
|
+
idx++; \
|
|
1699
|
+
} \
|
|
1700
|
+
} \
|
|
1701
|
+
} \
|
|
1702
|
+
for ( int8_t __sk = __srOuterK - 1; __sk >= 0; __sk-- ) { \
|
|
1703
|
+
if ( ++__srIdx[__sk] < (st).slab_dims[__sk] ) break; \
|
|
1704
|
+
__srIdx[__sk] = 0; \
|
|
1705
|
+
} \
|
|
1706
|
+
} \
|
|
1707
|
+
(void) idx; \
|
|
1708
|
+
} while (0)
|
|
1709
|
+
|
|
1710
|
+
/* CA_SLAB_REDUCE_T_MAX_EX (SL.1.2): same as MIN_EX with `_CA_SIMD_MAX`. */
|
|
1711
|
+
#define CA_SLAB_REDUCE_T_MAX_EX(T, st, p, m, acc, INIT, REDUCE, masked_cnt) do { \
|
|
1712
|
+
(acc) = (INIT); \
|
|
1713
|
+
int8_t __srK = (st).slab_ndim; \
|
|
1714
|
+
int8_t __srOuterK = __srK - 1; \
|
|
1715
|
+
ca_size_t __srInnerN = (st).slab_dims[__srK - 1]; \
|
|
1716
|
+
ca_size_t __srInnerS = (st).slab_strides[__srK - 1]; \
|
|
1717
|
+
ca_size_t __srInnerMS = (st).slab_mask_strides[__srK - 1]; \
|
|
1718
|
+
int __srContig = (__srInnerS == (ca_size_t) sizeof(T)); \
|
|
1719
|
+
int __srMaskU = (__srInnerMS == 1); \
|
|
1720
|
+
ca_size_t __srOC = 1; \
|
|
1721
|
+
for ( int8_t __sk = 0; __sk < __srOuterK; __sk++ ) \
|
|
1722
|
+
__srOC *= (st).slab_dims[__sk]; \
|
|
1723
|
+
/* Slab-collapse: when the whole K-D slab is row-major contiguous \
|
|
1724
|
+
(data, and mask if present), fold all slab axes into one flat \
|
|
1725
|
+
inner loop. Removes the per-outer multi-index offset recompute + \
|
|
1726
|
+
carry that otherwise dominates when the innermost slab axis is \
|
|
1727
|
+
small (full reduction of [N,1] / [N,small] entity, or trailing- \
|
|
1728
|
+
contig multi-axis reduce). Platform-general: structural, not SIMD. \
|
|
1729
|
+
No-op for 1-D slabs (__srOuterK == 0 leaves __srOC == 1). */ \
|
|
1730
|
+
{ \
|
|
1731
|
+
int __srFlat = __srContig; \
|
|
1732
|
+
for ( int8_t __sk = (int8_t)(__srK - 2); __sk >= 0 && __srFlat; __sk-- ) \
|
|
1733
|
+
if ( (st).slab_strides[__sk] != \
|
|
1734
|
+
(st).slab_dims[__sk + 1] * (st).slab_strides[__sk + 1] ) \
|
|
1735
|
+
__srFlat = 0; \
|
|
1736
|
+
if ( __srFlat && (m) != NULL ) { \
|
|
1737
|
+
if ( ! __srMaskU ) __srFlat = 0; \
|
|
1738
|
+
for ( int8_t __sk = (int8_t)(__srK - 2); __sk >= 0 && __srFlat; __sk-- ) \
|
|
1739
|
+
if ( (st).slab_mask_strides[__sk] != \
|
|
1740
|
+
(st).slab_dims[__sk + 1] * (st).slab_mask_strides[__sk + 1] ) \
|
|
1741
|
+
__srFlat = 0; \
|
|
1742
|
+
} \
|
|
1743
|
+
if ( __srFlat ) { __srInnerN = (st).slab_elements; __srOC = 1; } \
|
|
1744
|
+
} \
|
|
1745
|
+
ca_size_t __srIdx[CA_RANK_MAX] = { 0 }; \
|
|
1746
|
+
ca_size_t idx = 0; \
|
|
1747
|
+
for ( ca_size_t __so = 0; __so < __srOC; __so++ ) { \
|
|
1748
|
+
ca_size_t __srDoff = 0, __srMoff = 0; \
|
|
1749
|
+
for ( int8_t __sk = 0; __sk < __srOuterK; __sk++ ) { \
|
|
1750
|
+
__srDoff += __srIdx[__sk] * (st).slab_strides[__sk]; \
|
|
1751
|
+
__srMoff += __srIdx[__sk] * (st).slab_mask_strides[__sk]; \
|
|
1752
|
+
} \
|
|
1753
|
+
const char *__srQ = (const char *)(p) + __srDoff; \
|
|
1754
|
+
if ( (m) == NULL ) { \
|
|
1755
|
+
if ( __srContig ) { \
|
|
1756
|
+
const T *__srSrc = (const T *) __srQ; \
|
|
1757
|
+
_CA_SIMD_MAX(acc) \
|
|
1758
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1759
|
+
T v = __srSrc[__sj]; \
|
|
1760
|
+
REDUCE; \
|
|
1761
|
+
idx++; \
|
|
1762
|
+
} \
|
|
1763
|
+
} else { \
|
|
1764
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1765
|
+
T v = *(const T *)(__srQ + __sj * __srInnerS); \
|
|
1766
|
+
REDUCE; \
|
|
1767
|
+
idx++; \
|
|
1768
|
+
} \
|
|
1769
|
+
} \
|
|
1770
|
+
} else { \
|
|
1771
|
+
const boolean8_t *__srMM = (const boolean8_t *)(m) + __srMoff; \
|
|
1772
|
+
if ( __srContig && __srMaskU ) { \
|
|
1773
|
+
const T *__srSrc = (const T *) __srQ; \
|
|
1774
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1775
|
+
if ( ! __srMM[__sj] ) { \
|
|
1776
|
+
T v = __srSrc[__sj]; \
|
|
1777
|
+
REDUCE; \
|
|
1778
|
+
} else { \
|
|
1779
|
+
(masked_cnt)++; \
|
|
1780
|
+
} \
|
|
1781
|
+
idx++; \
|
|
1782
|
+
} \
|
|
1783
|
+
} else { \
|
|
1784
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1785
|
+
if ( ! __srMM[__sj * __srInnerMS] ) { \
|
|
1786
|
+
T v = *(const T *)(__srQ + __sj * __srInnerS); \
|
|
1787
|
+
REDUCE; \
|
|
1788
|
+
} else { \
|
|
1789
|
+
(masked_cnt)++; \
|
|
1790
|
+
} \
|
|
1791
|
+
idx++; \
|
|
1792
|
+
} \
|
|
1793
|
+
} \
|
|
1794
|
+
} \
|
|
1795
|
+
for ( int8_t __sk = __srOuterK - 1; __sk >= 0; __sk-- ) { \
|
|
1796
|
+
if ( ++__srIdx[__sk] < (st).slab_dims[__sk] ) break; \
|
|
1797
|
+
__srIdx[__sk] = 0; \
|
|
1798
|
+
} \
|
|
1799
|
+
} \
|
|
1800
|
+
(void) idx; \
|
|
1801
|
+
} while (0)
|
|
1802
|
+
|
|
1803
|
+
/* CA_SLAB_REDUCE_T_STAR_EX (SL.1.4):
|
|
1804
|
+
* Same structure as PLUS_EX; no-mask + contig inner loop carries
|
|
1805
|
+
* `#pragma omp simd reduction(*:acc)`. Used by :prod kernel
|
|
1806
|
+
* (acc *= v). f64 / f32 multiplication parity is ε-close (same
|
|
1807
|
+
* reassoc license as PLUS_EX); integer multiplication is bit-exact
|
|
1808
|
+
* under reassoc (associative + commutative on bounded-precision
|
|
1809
|
+
* integers).
|
|
1810
|
+
*/
|
|
1811
|
+
#define CA_SLAB_REDUCE_T_STAR_EX(T, st, p, m, acc, INIT, REDUCE, masked_cnt) do { \
|
|
1812
|
+
(acc) = (INIT); \
|
|
1813
|
+
int8_t __srK = (st).slab_ndim; \
|
|
1814
|
+
int8_t __srOuterK = __srK - 1; \
|
|
1815
|
+
ca_size_t __srInnerN = (st).slab_dims[__srK - 1]; \
|
|
1816
|
+
ca_size_t __srInnerS = (st).slab_strides[__srK - 1]; \
|
|
1817
|
+
ca_size_t __srInnerMS = (st).slab_mask_strides[__srK - 1]; \
|
|
1818
|
+
int __srContig = (__srInnerS == (ca_size_t) sizeof(T)); \
|
|
1819
|
+
int __srMaskU = (__srInnerMS == 1); \
|
|
1820
|
+
ca_size_t __srOC = 1; \
|
|
1821
|
+
for ( int8_t __sk = 0; __sk < __srOuterK; __sk++ ) \
|
|
1822
|
+
__srOC *= (st).slab_dims[__sk]; \
|
|
1823
|
+
/* Slab-collapse: when the whole K-D slab is row-major contiguous \
|
|
1824
|
+
(data, and mask if present), fold all slab axes into one flat \
|
|
1825
|
+
inner loop. Removes the per-outer multi-index offset recompute + \
|
|
1826
|
+
carry that otherwise dominates when the innermost slab axis is \
|
|
1827
|
+
small (full reduction of [N,1] / [N,small] entity, or trailing- \
|
|
1828
|
+
contig multi-axis reduce). Platform-general: structural, not SIMD. \
|
|
1829
|
+
No-op for 1-D slabs (__srOuterK == 0 leaves __srOC == 1). */ \
|
|
1830
|
+
{ \
|
|
1831
|
+
int __srFlat = __srContig; \
|
|
1832
|
+
for ( int8_t __sk = (int8_t)(__srK - 2); __sk >= 0 && __srFlat; __sk-- ) \
|
|
1833
|
+
if ( (st).slab_strides[__sk] != \
|
|
1834
|
+
(st).slab_dims[__sk + 1] * (st).slab_strides[__sk + 1] ) \
|
|
1835
|
+
__srFlat = 0; \
|
|
1836
|
+
if ( __srFlat && (m) != NULL ) { \
|
|
1837
|
+
if ( ! __srMaskU ) __srFlat = 0; \
|
|
1838
|
+
for ( int8_t __sk = (int8_t)(__srK - 2); __sk >= 0 && __srFlat; __sk-- ) \
|
|
1839
|
+
if ( (st).slab_mask_strides[__sk] != \
|
|
1840
|
+
(st).slab_dims[__sk + 1] * (st).slab_mask_strides[__sk + 1] ) \
|
|
1841
|
+
__srFlat = 0; \
|
|
1842
|
+
} \
|
|
1843
|
+
if ( __srFlat ) { __srInnerN = (st).slab_elements; __srOC = 1; } \
|
|
1844
|
+
} \
|
|
1845
|
+
ca_size_t __srIdx[CA_RANK_MAX] = { 0 }; \
|
|
1846
|
+
ca_size_t idx = 0; \
|
|
1847
|
+
for ( ca_size_t __so = 0; __so < __srOC; __so++ ) { \
|
|
1848
|
+
ca_size_t __srDoff = 0, __srMoff = 0; \
|
|
1849
|
+
for ( int8_t __sk = 0; __sk < __srOuterK; __sk++ ) { \
|
|
1850
|
+
__srDoff += __srIdx[__sk] * (st).slab_strides[__sk]; \
|
|
1851
|
+
__srMoff += __srIdx[__sk] * (st).slab_mask_strides[__sk]; \
|
|
1852
|
+
} \
|
|
1853
|
+
const char *__srQ = (const char *)(p) + __srDoff; \
|
|
1854
|
+
if ( (m) == NULL ) { \
|
|
1855
|
+
if ( __srContig ) { \
|
|
1856
|
+
const T *__srSrc = (const T *) __srQ; \
|
|
1857
|
+
_CA_SIMD_STAR(acc) \
|
|
1858
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1859
|
+
T v = __srSrc[__sj]; \
|
|
1860
|
+
REDUCE; \
|
|
1861
|
+
idx++; \
|
|
1862
|
+
} \
|
|
1863
|
+
} else { \
|
|
1864
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1865
|
+
T v = *(const T *)(__srQ + __sj * __srInnerS); \
|
|
1866
|
+
REDUCE; \
|
|
1867
|
+
idx++; \
|
|
1868
|
+
} \
|
|
1869
|
+
} \
|
|
1870
|
+
} else { \
|
|
1871
|
+
const boolean8_t *__srMM = (const boolean8_t *)(m) + __srMoff; \
|
|
1872
|
+
if ( __srContig && __srMaskU ) { \
|
|
1873
|
+
const T *__srSrc = (const T *) __srQ; \
|
|
1874
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1875
|
+
if ( ! __srMM[__sj] ) { \
|
|
1876
|
+
T v = __srSrc[__sj]; \
|
|
1877
|
+
REDUCE; \
|
|
1878
|
+
} else { \
|
|
1879
|
+
(masked_cnt)++; \
|
|
1880
|
+
} \
|
|
1881
|
+
idx++; \
|
|
1882
|
+
} \
|
|
1883
|
+
} else { \
|
|
1884
|
+
for ( ca_size_t __sj = 0; __sj < __srInnerN; __sj++ ) { \
|
|
1885
|
+
if ( ! __srMM[__sj * __srInnerMS] ) { \
|
|
1886
|
+
T v = *(const T *)(__srQ + __sj * __srInnerS); \
|
|
1887
|
+
REDUCE; \
|
|
1888
|
+
} else { \
|
|
1889
|
+
(masked_cnt)++; \
|
|
1890
|
+
} \
|
|
1891
|
+
idx++; \
|
|
1892
|
+
} \
|
|
1893
|
+
} \
|
|
1894
|
+
} \
|
|
1895
|
+
for ( int8_t __sk = __srOuterK - 1; __sk >= 0; __sk-- ) { \
|
|
1896
|
+
if ( ++__srIdx[__sk] < (st).slab_dims[__sk] ) break; \
|
|
1897
|
+
__srIdx[__sk] = 0; \
|
|
1898
|
+
} \
|
|
1899
|
+
} \
|
|
1900
|
+
(void) idx; \
|
|
1901
|
+
} while (0)
|
|
1902
|
+
|
|
1903
|
+
/* No-mask wrappers (hide masked_cnt) — parallel to CA_SLAB_REDUCE_T. */
|
|
1904
|
+
#define CA_SLAB_REDUCE_T_PLUS(T, st, p, m, acc, INIT, REDUCE) do { \
|
|
1905
|
+
ca_size_t __sr_throwaway_mc = 0; \
|
|
1906
|
+
CA_SLAB_REDUCE_T_PLUS_EX(T, st, p, m, acc, INIT, REDUCE, \
|
|
1907
|
+
__sr_throwaway_mc); \
|
|
1908
|
+
(void) __sr_throwaway_mc; \
|
|
1909
|
+
} while (0)
|
|
1910
|
+
#define CA_SLAB_REDUCE_T_MIN(T, st, p, m, acc, INIT, REDUCE) do { \
|
|
1911
|
+
ca_size_t __sr_throwaway_mc = 0; \
|
|
1912
|
+
CA_SLAB_REDUCE_T_MIN_EX(T, st, p, m, acc, INIT, REDUCE, \
|
|
1913
|
+
__sr_throwaway_mc); \
|
|
1914
|
+
(void) __sr_throwaway_mc; \
|
|
1915
|
+
} while (0)
|
|
1916
|
+
#define CA_SLAB_REDUCE_T_MAX(T, st, p, m, acc, INIT, REDUCE) do { \
|
|
1917
|
+
ca_size_t __sr_throwaway_mc = 0; \
|
|
1918
|
+
CA_SLAB_REDUCE_T_MAX_EX(T, st, p, m, acc, INIT, REDUCE, \
|
|
1919
|
+
__sr_throwaway_mc); \
|
|
1920
|
+
(void) __sr_throwaway_mc; \
|
|
1921
|
+
} while (0)
|
|
1922
|
+
#define CA_SLAB_REDUCE_T_STAR(T, st, p, m, acc, INIT, REDUCE) do { \
|
|
1923
|
+
ca_size_t __sr_throwaway_mc = 0; \
|
|
1924
|
+
CA_SLAB_REDUCE_T_STAR_EX(T, st, p, m, acc, INIT, REDUCE, \
|
|
1925
|
+
__sr_throwaway_mc); \
|
|
1926
|
+
(void) __sr_throwaway_mc; \
|
|
1927
|
+
} while (0)
|
|
1928
|
+
|
|
1929
|
+
/* Multi-acc variant for variance/stddev (sum + sumsq + cnt).
|
|
1930
|
+
*
|
|
1931
|
+
* SL.1.3 measurement (2026-06-12) showed VAR_EX is **NOT NEEDED** for
|
|
1932
|
+
* variance/stddev: a single `#pragma omp simd reduction(+:acc)` on
|
|
1933
|
+
* the primary accumulator (via PLUS_EX dispatch) is sufficient — clang
|
|
1934
|
+
* extends SIMD treatment to the dependent `sumsq` updates via idiom
|
|
1935
|
+
* recognition (the secondary accumulator is recognised as a derived
|
|
1936
|
+
* reduction over `v*v`). variance entity 1-D f64 reached 53.4 GB/s
|
|
1937
|
+
* (gate > 40 PASS, 8.2x speedup vs 6.5 baseline) using PLUS_EX.
|
|
1938
|
+
*
|
|
1939
|
+
* VAR_EX is **retained as a stub** here for future SL.2.x output-
|
|
1940
|
+
* buffered work or other multi-acc reductions that don't share the
|
|
1941
|
+
* variance pattern (= primary + derived sumsq). The stub forwards
|
|
1942
|
+
* to PLUS_EX on acc1; acc2 is zero-initialised but otherwise left
|
|
1943
|
+
* to the enclosing REDUCE expression. This is safe because no
|
|
1944
|
+
* kernel currently dispatches to VAR_EX (= reduce_macro_suffix never
|
|
1945
|
+
* returns "_VAR"; SL.1.0/1.1 reduce DSL has no :var value).
|
|
1946
|
+
*
|
|
1947
|
+
* Removal candidate if no caller materialises by SL.2 close.
|
|
1948
|
+
*/
|
|
1949
|
+
#define CA_SLAB_REDUCE_T_VAR_EX(T, st, p, m, acc1, acc2, \
|
|
1950
|
+
INIT1, INIT2, REDUCE, masked_cnt) do { \
|
|
1951
|
+
(acc2) = (INIT2); \
|
|
1952
|
+
CA_SLAB_REDUCE_T_PLUS_EX(T, st, p, m, acc1, INIT1, REDUCE, masked_cnt); \
|
|
1953
|
+
} while (0)
|
|
1954
|
+
|
|
1955
|
+
/* CA_SLAB_REDUCE_ARRAY_T_EX(T, T_W, ...): reduction with a parallel
|
|
1956
|
+
second-array operand (the "weights" or "right-hand operand"). Used by
|
|
1957
|
+
weighted reduction kernels (wsum, wmean, future wvariance/wstddev) that
|
|
1958
|
+
take a same-shape second CArray argument.
|
|
1959
|
+
|
|
1960
|
+
The two operands are driven by **two parallel kernel_iterator state
|
|
1961
|
+
machines** (= st_src and st_w), each initialised on the same slab_axes.
|
|
1962
|
+
They yield slab pointers in lockstep — same logical slab, possibly
|
|
1963
|
+
different physical layout (e.g. self is a transpose view and weights
|
|
1964
|
+
is a fresh entity). Each side carries its own slab_strides /
|
|
1965
|
+
slab_dims; only slab_dims need to match (= same shape invariant
|
|
1966
|
+
enforced by ca_check_same_shape at dispatcher).
|
|
1967
|
+
|
|
1968
|
+
Author-supplied (additional to CA_SLAB_REDUCE_T_EX):
|
|
1969
|
+
- T_W: second-operand element C type (load type). Must match
|
|
1970
|
+
T in the W.1 framework (mkkernel array_arg: data_type:
|
|
1971
|
+
:match_source enforces this). Kept separate in the
|
|
1972
|
+
macro signature so future heterogeneous-data_type variants
|
|
1973
|
+
can plug in without macro change.
|
|
1974
|
+
- st_w: second-operand ca_iter_state, positioned on the
|
|
1975
|
+
currently-active weights slab (caller iterates st_w
|
|
1976
|
+
alongside st_src — see mkkernel array_arg emit).
|
|
1977
|
+
- p_w: second-operand slab data pointer (char *) yielded by
|
|
1978
|
+
ca_iter_state_next_slab_axes(&st_w, &p_w, NULL).
|
|
1979
|
+
|
|
1980
|
+
REDUCE expression binds BOTH `v` (source cell, type T) AND `w` (weights
|
|
1981
|
+
cell, type T_W), in addition to `acc` and `idx` from the base macro.
|
|
1982
|
+
The mask `m` applies to the SOURCE only (weights mask is overlaid onto
|
|
1983
|
+
source mask at dispatcher time, per Q3 (A) legacy parity). */
|
|
1984
|
+
#define CA_SLAB_REDUCE_ARRAY_T_EX(T, T_W, st, p, m, st_w, p_w, acc, INIT, REDUCE, masked_cnt) do { \
|
|
1985
|
+
(acc) = (INIT); \
|
|
1986
|
+
int8_t __sraK = (st).slab_ndim; \
|
|
1987
|
+
int8_t __sraOuterK = __sraK - 1; \
|
|
1988
|
+
ca_size_t __sraInnerN = (st).slab_dims[__sraK - 1]; \
|
|
1989
|
+
ca_size_t __sraInnerS = (st).slab_strides[__sraK - 1]; \
|
|
1990
|
+
ca_size_t __sraInnerWS = (st_w).slab_strides[__sraK - 1]; \
|
|
1991
|
+
ca_size_t __sraInnerMS = (st).slab_mask_strides[__sraK - 1]; \
|
|
1992
|
+
int __sraContig = (__sraInnerS == (ca_size_t) sizeof(T)); \
|
|
1993
|
+
int __sraContigW = (__sraInnerWS == (ca_size_t) sizeof(T_W)); \
|
|
1994
|
+
int __sraMaskU = (__sraInnerMS == 1); \
|
|
1995
|
+
ca_size_t __sraOC = 1; \
|
|
1996
|
+
for ( int8_t __sak = 0; __sak < __sraOuterK; __sak++ ) \
|
|
1997
|
+
__sraOC *= (st).slab_dims[__sak]; \
|
|
1998
|
+
/* Slab-collapse: fold all slab axes into one flat inner loop when the \
|
|
1999
|
+
whole K-D slab is row-major contiguous for BOTH source and weights \
|
|
2000
|
+
(and mask if present). Same structural win as the unweighted \
|
|
2001
|
+
CA_SLAB_REDUCE_T_*_EX macros: kills the per-outer multi-index offset \
|
|
2002
|
+
recompute + carry that dominates when the innermost slab axis is \
|
|
2003
|
+
small. No-op for 1-D slabs (__sraOuterK == 0 leaves __sraOC == 1). \
|
|
2004
|
+
Weights share slab_dims with source (shape invariant), so the dim \
|
|
2005
|
+
cascade uses (st).slab_dims for both stride tables. */ \
|
|
2006
|
+
{ \
|
|
2007
|
+
int __sraFlat = __sraContig && __sraContigW; \
|
|
2008
|
+
for ( int8_t __sak = (int8_t)(__sraK - 2); __sak >= 0 && __sraFlat; __sak-- ) { \
|
|
2009
|
+
if ( (st).slab_strides[__sak] != \
|
|
2010
|
+
(st).slab_dims[__sak + 1] * (st).slab_strides[__sak + 1] ) \
|
|
2011
|
+
__sraFlat = 0; \
|
|
2012
|
+
if ( (st_w).slab_strides[__sak] != \
|
|
2013
|
+
(st).slab_dims[__sak + 1] * (st_w).slab_strides[__sak + 1] ) \
|
|
2014
|
+
__sraFlat = 0; \
|
|
2015
|
+
} \
|
|
2016
|
+
if ( __sraFlat && (m) != NULL ) { \
|
|
2017
|
+
if ( ! __sraMaskU ) __sraFlat = 0; \
|
|
2018
|
+
for ( int8_t __sak = (int8_t)(__sraK - 2); __sak >= 0 && __sraFlat; __sak-- ) \
|
|
2019
|
+
if ( (st).slab_mask_strides[__sak] != \
|
|
2020
|
+
(st).slab_dims[__sak + 1] * (st).slab_mask_strides[__sak + 1] ) \
|
|
2021
|
+
__sraFlat = 0; \
|
|
2022
|
+
} \
|
|
2023
|
+
if ( __sraFlat ) { __sraInnerN = (st).slab_elements; __sraOC = 1; } \
|
|
2024
|
+
} \
|
|
2025
|
+
ca_size_t __sraIdx[CA_RANK_MAX] = { 0 }; \
|
|
2026
|
+
ca_size_t idx = 0; \
|
|
2027
|
+
for ( ca_size_t __sao = 0; __sao < __sraOC; __sao++ ) { \
|
|
2028
|
+
ca_size_t __sraDoff = 0, __sraWoff = 0, __sraMoff = 0; \
|
|
2029
|
+
for ( int8_t __sak = 0; __sak < __sraOuterK; __sak++ ) { \
|
|
2030
|
+
__sraDoff += __sraIdx[__sak] * (st).slab_strides[__sak]; \
|
|
2031
|
+
__sraWoff += __sraIdx[__sak] * (st_w).slab_strides[__sak]; \
|
|
2032
|
+
__sraMoff += __sraIdx[__sak] * (st).slab_mask_strides[__sak]; \
|
|
2033
|
+
} \
|
|
2034
|
+
const char *__sraQ = (const char *)(p) + __sraDoff; \
|
|
2035
|
+
const char *__sraWQ = (const char *)(p_w) + __sraWoff; \
|
|
2036
|
+
if ( (m) == NULL ) { \
|
|
2037
|
+
if ( __sraContig && __sraContigW ) { \
|
|
2038
|
+
const T *__sraSrc = (const T *) __sraQ; \
|
|
2039
|
+
const T_W *__sraWgt = (const T_W *) __sraWQ; \
|
|
2040
|
+
for ( ca_size_t __saj = 0; __saj < __sraInnerN; __saj++ ) { \
|
|
2041
|
+
T v = __sraSrc[__saj]; \
|
|
2042
|
+
T_W w = __sraWgt[__saj]; \
|
|
2043
|
+
REDUCE; \
|
|
2044
|
+
idx++; \
|
|
2045
|
+
} \
|
|
2046
|
+
} else { \
|
|
2047
|
+
for ( ca_size_t __saj = 0; __saj < __sraInnerN; __saj++ ) { \
|
|
2048
|
+
T v = *(const T *)(__sraQ + __saj * __sraInnerS); \
|
|
2049
|
+
T_W w = *(const T_W *)(__sraWQ + __saj * __sraInnerWS); \
|
|
2050
|
+
REDUCE; \
|
|
2051
|
+
idx++; \
|
|
2052
|
+
} \
|
|
2053
|
+
} \
|
|
2054
|
+
} else { \
|
|
2055
|
+
const boolean8_t *__sraMM = (const boolean8_t *)(m) + __sraMoff; \
|
|
2056
|
+
if ( __sraContig && __sraContigW && __sraMaskU ) { \
|
|
2057
|
+
const T *__sraSrc = (const T *) __sraQ; \
|
|
2058
|
+
const T_W *__sraWgt = (const T_W *) __sraWQ; \
|
|
2059
|
+
for ( ca_size_t __saj = 0; __saj < __sraInnerN; __saj++ ) { \
|
|
2060
|
+
if ( ! __sraMM[__saj] ) { \
|
|
2061
|
+
T v = __sraSrc[__saj]; \
|
|
2062
|
+
T_W w = __sraWgt[__saj]; \
|
|
2063
|
+
REDUCE; \
|
|
2064
|
+
} else { \
|
|
2065
|
+
(masked_cnt)++; \
|
|
2066
|
+
} \
|
|
2067
|
+
idx++; \
|
|
2068
|
+
} \
|
|
2069
|
+
} else { \
|
|
2070
|
+
for ( ca_size_t __saj = 0; __saj < __sraInnerN; __saj++ ) { \
|
|
2071
|
+
if ( ! __sraMM[__saj * __sraInnerMS] ) { \
|
|
2072
|
+
T v = *(const T *)(__sraQ + __saj * __sraInnerS); \
|
|
2073
|
+
T_W w = *(const T_W *)(__sraWQ + __saj * __sraInnerWS); \
|
|
2074
|
+
REDUCE; \
|
|
2075
|
+
} else { \
|
|
2076
|
+
(masked_cnt)++; \
|
|
2077
|
+
} \
|
|
2078
|
+
idx++; \
|
|
2079
|
+
} \
|
|
2080
|
+
} \
|
|
2081
|
+
} \
|
|
2082
|
+
for ( int8_t __sak = __sraOuterK - 1; __sak >= 0; __sak-- ) { \
|
|
2083
|
+
if ( ++__sraIdx[__sak] < (st).slab_dims[__sak] ) break; \
|
|
2084
|
+
__sraIdx[__sak] = 0; \
|
|
2085
|
+
} \
|
|
2086
|
+
} \
|
|
2087
|
+
(void) idx; \
|
|
2088
|
+
} while (0)
|
|
2089
|
+
|
|
2090
|
+
#define CA_SLAB_REDUCE_ARRAY_T(T, T_W, st, p, m, st_w, p_w, acc, INIT, REDUCE) do { \
|
|
2091
|
+
ca_size_t __sra_throwaway_mc = 0; \
|
|
2092
|
+
CA_SLAB_REDUCE_ARRAY_T_EX(T, T_W, st, p, m, st_w, p_w, acc, INIT, REDUCE, \
|
|
2093
|
+
__sra_throwaway_mc); \
|
|
2094
|
+
(void) __sra_throwaway_mc; \
|
|
2095
|
+
} while (0)
|
|
2096
|
+
|
|
2097
|
+
/* CA_SLAB_REDUCE_ARRAY_T_PLUS_EX (SL.1.4b):
|
|
2098
|
+
* Parallel-array PLUS variant of CA_SLAB_REDUCE_ARRAY_T_EX.
|
|
2099
|
+
* no-mask + both-contig inner loop carries `#pragma omp simd
|
|
2100
|
+
* reduction(+:acc)`. Used by :wsum / :wmean — author REDUCE
|
|
2101
|
+
* expression is `acc += (double) v * (double) w`, which clang +
|
|
2102
|
+
* `-fopenmp-simd` lowers to NEON `fmla.2d` (interleave-4) with
|
|
2103
|
+
* reduction-tree merge. Other branches (masked, non-contig)
|
|
2104
|
+
* stay identical to _EX.
|
|
2105
|
+
*
|
|
2106
|
+
* PoC bench (SL.1.4b, 2026-06-12):
|
|
2107
|
+
* wsum f64 N=1M: 908 us -> ~150 us (= the expected 50-60 GB/s)
|
|
2108
|
+
* wmean f64 N=1M: 1212 us -> ~150 us (same path + 1 extra acc)
|
|
2109
|
+
*/
|
|
2110
|
+
#define CA_SLAB_REDUCE_ARRAY_T_PLUS_EX(T, T_W, st, p, m, st_w, p_w, acc, \
|
|
2111
|
+
INIT, REDUCE, masked_cnt) do { \
|
|
2112
|
+
(acc) = (INIT); \
|
|
2113
|
+
int8_t __sraK = (st).slab_ndim; \
|
|
2114
|
+
int8_t __sraOuterK = __sraK - 1; \
|
|
2115
|
+
ca_size_t __sraInnerN = (st).slab_dims[__sraK - 1]; \
|
|
2116
|
+
ca_size_t __sraInnerS = (st).slab_strides[__sraK - 1]; \
|
|
2117
|
+
ca_size_t __sraInnerWS = (st_w).slab_strides[__sraK - 1]; \
|
|
2118
|
+
ca_size_t __sraInnerMS = (st).slab_mask_strides[__sraK - 1]; \
|
|
2119
|
+
int __sraContig = (__sraInnerS == (ca_size_t) sizeof(T)); \
|
|
2120
|
+
int __sraContigW = (__sraInnerWS == (ca_size_t) sizeof(T_W)); \
|
|
2121
|
+
int __sraMaskU = (__sraInnerMS == 1); \
|
|
2122
|
+
ca_size_t __sraOC = 1; \
|
|
2123
|
+
for ( int8_t __sak = 0; __sak < __sraOuterK; __sak++ ) \
|
|
2124
|
+
__sraOC *= (st).slab_dims[__sak]; \
|
|
2125
|
+
/* Slab-collapse: fold all slab axes into one flat inner loop when the \
|
|
2126
|
+
whole K-D slab is row-major contiguous for BOTH source and weights \
|
|
2127
|
+
(and mask if present). Same structural win as the unweighted \
|
|
2128
|
+
CA_SLAB_REDUCE_T_*_EX macros: kills the per-outer multi-index offset \
|
|
2129
|
+
recompute + carry that dominates when the innermost slab axis is \
|
|
2130
|
+
small. No-op for 1-D slabs (__sraOuterK == 0 leaves __sraOC == 1). \
|
|
2131
|
+
Weights share slab_dims with source (shape invariant), so the dim \
|
|
2132
|
+
cascade uses (st).slab_dims for both stride tables. */ \
|
|
2133
|
+
{ \
|
|
2134
|
+
int __sraFlat = __sraContig && __sraContigW; \
|
|
2135
|
+
for ( int8_t __sak = (int8_t)(__sraK - 2); __sak >= 0 && __sraFlat; __sak-- ) { \
|
|
2136
|
+
if ( (st).slab_strides[__sak] != \
|
|
2137
|
+
(st).slab_dims[__sak + 1] * (st).slab_strides[__sak + 1] ) \
|
|
2138
|
+
__sraFlat = 0; \
|
|
2139
|
+
if ( (st_w).slab_strides[__sak] != \
|
|
2140
|
+
(st).slab_dims[__sak + 1] * (st_w).slab_strides[__sak + 1] ) \
|
|
2141
|
+
__sraFlat = 0; \
|
|
2142
|
+
} \
|
|
2143
|
+
if ( __sraFlat && (m) != NULL ) { \
|
|
2144
|
+
if ( ! __sraMaskU ) __sraFlat = 0; \
|
|
2145
|
+
for ( int8_t __sak = (int8_t)(__sraK - 2); __sak >= 0 && __sraFlat; __sak-- ) \
|
|
2146
|
+
if ( (st).slab_mask_strides[__sak] != \
|
|
2147
|
+
(st).slab_dims[__sak + 1] * (st).slab_mask_strides[__sak + 1] ) \
|
|
2148
|
+
__sraFlat = 0; \
|
|
2149
|
+
} \
|
|
2150
|
+
if ( __sraFlat ) { __sraInnerN = (st).slab_elements; __sraOC = 1; } \
|
|
2151
|
+
} \
|
|
2152
|
+
ca_size_t __sraIdx[CA_RANK_MAX] = { 0 }; \
|
|
2153
|
+
ca_size_t idx = 0; \
|
|
2154
|
+
for ( ca_size_t __sao = 0; __sao < __sraOC; __sao++ ) { \
|
|
2155
|
+
ca_size_t __sraDoff = 0, __sraWoff = 0, __sraMoff = 0; \
|
|
2156
|
+
for ( int8_t __sak = 0; __sak < __sraOuterK; __sak++ ) { \
|
|
2157
|
+
__sraDoff += __sraIdx[__sak] * (st).slab_strides[__sak]; \
|
|
2158
|
+
__sraWoff += __sraIdx[__sak] * (st_w).slab_strides[__sak]; \
|
|
2159
|
+
__sraMoff += __sraIdx[__sak] * (st).slab_mask_strides[__sak]; \
|
|
2160
|
+
} \
|
|
2161
|
+
const char *__sraQ = (const char *)(p) + __sraDoff; \
|
|
2162
|
+
const char *__sraWQ = (const char *)(p_w) + __sraWoff; \
|
|
2163
|
+
if ( (m) == NULL ) { \
|
|
2164
|
+
if ( __sraContig && __sraContigW ) { \
|
|
2165
|
+
const T *__sraSrc = (const T *) __sraQ; \
|
|
2166
|
+
const T_W *__sraWgt = (const T_W *) __sraWQ; \
|
|
2167
|
+
_CA_SIMD_PLUS(acc) \
|
|
2168
|
+
for ( ca_size_t __saj = 0; __saj < __sraInnerN; __saj++ ) { \
|
|
2169
|
+
T v = __sraSrc[__saj]; \
|
|
2170
|
+
T_W w = __sraWgt[__saj]; \
|
|
2171
|
+
REDUCE; \
|
|
2172
|
+
idx++; \
|
|
2173
|
+
} \
|
|
2174
|
+
} else { \
|
|
2175
|
+
for ( ca_size_t __saj = 0; __saj < __sraInnerN; __saj++ ) { \
|
|
2176
|
+
T v = *(const T *)(__sraQ + __saj * __sraInnerS); \
|
|
2177
|
+
T_W w = *(const T_W *)(__sraWQ + __saj * __sraInnerWS); \
|
|
2178
|
+
REDUCE; \
|
|
2179
|
+
idx++; \
|
|
2180
|
+
} \
|
|
2181
|
+
} \
|
|
2182
|
+
} else { \
|
|
2183
|
+
const boolean8_t *__sraMM = (const boolean8_t *)(m) + __sraMoff; \
|
|
2184
|
+
if ( __sraContig && __sraContigW && __sraMaskU ) { \
|
|
2185
|
+
const T *__sraSrc = (const T *) __sraQ; \
|
|
2186
|
+
const T_W *__sraWgt = (const T_W *) __sraWQ; \
|
|
2187
|
+
for ( ca_size_t __saj = 0; __saj < __sraInnerN; __saj++ ) { \
|
|
2188
|
+
if ( ! __sraMM[__saj] ) { \
|
|
2189
|
+
T v = __sraSrc[__saj]; \
|
|
2190
|
+
T_W w = __sraWgt[__saj]; \
|
|
2191
|
+
REDUCE; \
|
|
2192
|
+
} else { \
|
|
2193
|
+
(masked_cnt)++; \
|
|
2194
|
+
} \
|
|
2195
|
+
idx++; \
|
|
2196
|
+
} \
|
|
2197
|
+
} else { \
|
|
2198
|
+
for ( ca_size_t __saj = 0; __saj < __sraInnerN; __saj++ ) { \
|
|
2199
|
+
if ( ! __sraMM[__saj * __sraInnerMS] ) { \
|
|
2200
|
+
T v = *(const T *)(__sraQ + __saj * __sraInnerS); \
|
|
2201
|
+
T_W w = *(const T_W *)(__sraWQ + __saj * __sraInnerWS); \
|
|
2202
|
+
REDUCE; \
|
|
2203
|
+
} else { \
|
|
2204
|
+
(masked_cnt)++; \
|
|
2205
|
+
} \
|
|
2206
|
+
idx++; \
|
|
2207
|
+
} \
|
|
2208
|
+
} \
|
|
2209
|
+
} \
|
|
2210
|
+
for ( int8_t __sak = __sraOuterK - 1; __sak >= 0; __sak-- ) { \
|
|
2211
|
+
if ( ++__sraIdx[__sak] < (st).slab_dims[__sak] ) break; \
|
|
2212
|
+
__sraIdx[__sak] = 0; \
|
|
2213
|
+
} \
|
|
2214
|
+
} \
|
|
2215
|
+
(void) idx; \
|
|
2216
|
+
} while (0)
|
|
2217
|
+
|
|
2218
|
+
#define CA_SLAB_REDUCE_ARRAY_T_PLUS(T, T_W, st, p, m, st_w, p_w, acc, \
|
|
2219
|
+
INIT, REDUCE) do { \
|
|
2220
|
+
ca_size_t __sra_throwaway_mc = 0; \
|
|
2221
|
+
CA_SLAB_REDUCE_ARRAY_T_PLUS_EX(T, T_W, st, p, m, st_w, p_w, acc, INIT, \
|
|
2222
|
+
REDUCE, __sra_throwaway_mc); \
|
|
2223
|
+
(void) __sra_throwaway_mc; \
|
|
2224
|
+
} while (0)
|
|
2225
|
+
|
|
2226
|
+
#define CA_SLAB_REDUCE_F64(st, p, m, acc, INIT, REDUCE) \
|
|
2227
|
+
CA_SLAB_REDUCE_T(double, st, p, m, acc, INIT, REDUCE)
|
|
2228
|
+
#define CA_SLAB_REDUCE_F32(st, p, m, acc, INIT, REDUCE) \
|
|
2229
|
+
CA_SLAB_REDUCE_T(float, st, p, m, acc, INIT, REDUCE)
|
|
2230
|
+
#define CA_SLAB_REDUCE_I32(st, p, m, acc, INIT, REDUCE) \
|
|
2231
|
+
CA_SLAB_REDUCE_T(int32_t, st, p, m, acc, INIT, REDUCE)
|
|
2232
|
+
#define CA_SLAB_REDUCE_I64(st, p, m, acc, INIT, REDUCE) \
|
|
2233
|
+
CA_SLAB_REDUCE_T(int64_t, st, p, m, acc, INIT, REDUCE)
|
|
2234
|
+
|
|
2235
|
+
/* CA_SLAB_MAP_T(T_IN, T_OUT, ...): generic per-cell transform from
|
|
2236
|
+
input slab to output slab, walking both in lockstep. T_IN is the
|
|
2237
|
+
input element load type, T_OUT is the output element store type.
|
|
2238
|
+
Same outer K-1 carry + innermost SIMD inner shape as
|
|
2239
|
+
CA_SLAB_REDUCE_T, with contig hoist on both sides.
|
|
2240
|
+
|
|
2241
|
+
Author-supplied:
|
|
2242
|
+
- T_IN / T_OUT: element C types (load / store).
|
|
2243
|
+
- MAP_EXPR: statement binding `r` (T_OUT, output lvalue)
|
|
2244
|
+
given `v` (T_IN, current input element).
|
|
2245
|
+
Example: `r = sqrt(v)`, `r = (T_OUT)(v * v + 1)`.
|
|
2246
|
+
|
|
2247
|
+
Engine-supplied:
|
|
2248
|
+
- st_in / p_in: input ca_iter_state + slab data ptr.
|
|
2249
|
+
- st_out / p_out: output ca_iter_state + slab data ptr.
|
|
2250
|
+
|
|
2251
|
+
Both states must share slab geometry (= same slab_ndim and slab_dims),
|
|
2252
|
+
typically by initialising both with the same policy + axes on
|
|
2253
|
+
shape-equal CArrays. Mask handling is **not** done by this macro —
|
|
2254
|
+
if your input is masked, see §6.2 mask propagation discussion in
|
|
2255
|
+
docs/authoring/HOW_TO_WRITE_KERNEL.md. Caller is responsible for invoking
|
|
2256
|
+
ca_iter_state_sync_slab on the output state after each slab.
|
|
2257
|
+
|
|
2258
|
+
Convenience aliases below: CA_SLAB_MAP_F64 (T_IN = T_OUT = double). */
|
|
2259
|
+
#define CA_SLAB_MAP_T(T_IN, T_OUT, st_in, p_in, st_out, p_out, MAP_EXPR) do { \
|
|
2260
|
+
int8_t __mK = (st_in).slab_ndim; \
|
|
2261
|
+
int8_t __mOuterK = __mK - 1; \
|
|
2262
|
+
ca_size_t __mInN = (st_in).slab_dims[__mK - 1]; \
|
|
2263
|
+
ca_size_t __mInS = (st_in).slab_strides[__mK - 1]; \
|
|
2264
|
+
ca_size_t __mOutS = (st_out).slab_strides[__mK - 1]; \
|
|
2265
|
+
int __mIContig = (__mInS == (ca_size_t) sizeof(T_IN)); \
|
|
2266
|
+
int __mOContig = (__mOutS == (ca_size_t) sizeof(T_OUT)); \
|
|
2267
|
+
ca_size_t __mOC = 1; \
|
|
2268
|
+
for ( int8_t __mk = 0; __mk < __mOuterK; __mk++ ) \
|
|
2269
|
+
__mOC *= (st_in).slab_dims[__mk]; \
|
|
2270
|
+
ca_size_t __mIdx[CA_RANK_MAX] = { 0 }; \
|
|
2271
|
+
for ( ca_size_t __mo = 0; __mo < __mOC; __mo++ ) { \
|
|
2272
|
+
ca_size_t __mIOff = 0, __mOOff = 0; \
|
|
2273
|
+
for ( int8_t __mk = 0; __mk < __mOuterK; __mk++ ) { \
|
|
2274
|
+
__mIOff += __mIdx[__mk] * (st_in).slab_strides[__mk]; \
|
|
2275
|
+
__mOOff += __mIdx[__mk] * (st_out).slab_strides[__mk]; \
|
|
2276
|
+
} \
|
|
2277
|
+
const char *__mQi = (const char *)(p_in) + __mIOff; \
|
|
2278
|
+
char *__mQo = (char *) (p_out) + __mOOff; \
|
|
2279
|
+
if ( __mIContig && __mOContig ) { \
|
|
2280
|
+
const T_IN *__mSi = (const T_IN *) __mQi; \
|
|
2281
|
+
T_OUT *__mSo = (T_OUT *) __mQo; \
|
|
2282
|
+
for ( ca_size_t __mj = 0; __mj < __mInN; __mj++ ) { \
|
|
2283
|
+
T_IN v = __mSi[__mj]; \
|
|
2284
|
+
T_OUT r; \
|
|
2285
|
+
MAP_EXPR; \
|
|
2286
|
+
__mSo[__mj] = r; \
|
|
2287
|
+
} \
|
|
2288
|
+
} else { \
|
|
2289
|
+
for ( ca_size_t __mj = 0; __mj < __mInN; __mj++ ) { \
|
|
2290
|
+
T_IN v = *(const T_IN *)(__mQi + __mj * __mInS); \
|
|
2291
|
+
T_OUT r; \
|
|
2292
|
+
MAP_EXPR; \
|
|
2293
|
+
*(T_OUT *)(__mQo + __mj * __mOutS) = r; \
|
|
2294
|
+
} \
|
|
2295
|
+
} \
|
|
2296
|
+
for ( int8_t __mk = __mOuterK - 1; __mk >= 0; __mk-- ) { \
|
|
2297
|
+
if ( ++__mIdx[__mk] < (st_in).slab_dims[__mk] ) break; \
|
|
2298
|
+
__mIdx[__mk] = 0; \
|
|
2299
|
+
} \
|
|
2300
|
+
} \
|
|
2301
|
+
} while (0)
|
|
2302
|
+
|
|
2303
|
+
#define CA_SLAB_MAP_F64(st_in, p_in, st_out, p_out, MAP_EXPR) \
|
|
2304
|
+
CA_SLAB_MAP_T(double, double, st_in, p_in, st_out, p_out, MAP_EXPR)
|
|
2305
|
+
|
|
2306
|
+
/* CA_SLAB_SCAN_T(T_LOAD, T_OUT, ...): cumulative / prefix-scan walk
|
|
2307
|
+
that combines a reduction (running accumulator) with a map (per-cell
|
|
2308
|
+
output write). Walks input and output slabs in lockstep; the
|
|
2309
|
+
accumulator `acc` is reset to INIT at the start of each macro call
|
|
2310
|
+
(= once per outer next_slab_axes iteration, so per "fiber" along the
|
|
2311
|
+
scan axis).
|
|
2312
|
+
|
|
2313
|
+
Author-supplied:
|
|
2314
|
+
- T_LOAD / T_OUT: input load type / output store type.
|
|
2315
|
+
- INIT: initial value for `acc` (e.g., "0", "1",
|
|
2316
|
+
"T_LIMIT_HI" -- but the latter is resolved by
|
|
2317
|
+
the generator, not the macro itself).
|
|
2318
|
+
- STEP: statement binding `v` (input element, T_LOAD),
|
|
2319
|
+
`r` (output lvalue, T_OUT), and `acc` (running
|
|
2320
|
+
accumulator, T_OUT). Example:
|
|
2321
|
+
cumsum: `acc += v; r = acc`
|
|
2322
|
+
cummax: `if (v > acc) acc = v; r = acc`
|
|
2323
|
+
cumcount: `(void) v; r = ++acc`
|
|
2324
|
+
|
|
2325
|
+
Engine-supplied:
|
|
2326
|
+
- st_in / p_in / m_in: input ca_iter_state + slab + mask.
|
|
2327
|
+
- st_out / p_out: output ca_iter_state + slab pointer.
|
|
2328
|
+
|
|
2329
|
+
Mask semantics: when m_in != NULL, masked input cells skip the STEP
|
|
2330
|
+
(acc preserved) and write the *current* `acc` to the output. This
|
|
2331
|
+
matches legacy cumsum / cumcount: the output at a masked position
|
|
2332
|
+
reflects the running aggregate up to (excluding) this cell. The
|
|
2333
|
+
"write acc, not 0" choice keeps the output value continuous for the
|
|
2334
|
+
common scan semantics (sum / count / product / max / min) and avoids
|
|
2335
|
+
silent breaks of "running aggregate" downstream. Output mask
|
|
2336
|
+
propagation is NOT done here -- if needed, the caller writes to
|
|
2337
|
+
op_mask separately (future mask_policy for scan).
|
|
2338
|
+
|
|
2339
|
+
For the common 1-axis scan case (the only one the generator emits),
|
|
2340
|
+
slab_ndim is 1 so the macro's outer K-1 carry collapses to a single
|
|
2341
|
+
inner walk. K-D slab support follows the same row-major shape as
|
|
2342
|
+
CA_SLAB_REDUCE_T / CA_SLAB_MAP_T. */
|
|
2343
|
+
#define CA_SLAB_SCAN_T(T_LOAD, T_OUT, st_in, p_in, m_in, \
|
|
2344
|
+
st_out, p_out, INIT, STEP) do { \
|
|
2345
|
+
T_OUT acc = (INIT); \
|
|
2346
|
+
int8_t __ssK = (st_in).slab_ndim; \
|
|
2347
|
+
int8_t __ssOuterK = __ssK - 1; \
|
|
2348
|
+
ca_size_t __ssInnerN = (st_in).slab_dims[__ssK - 1]; \
|
|
2349
|
+
ca_size_t __ssInS = (st_in).slab_strides[__ssK - 1]; \
|
|
2350
|
+
ca_size_t __ssOutS = (st_out).slab_strides[__ssK - 1]; \
|
|
2351
|
+
ca_size_t __ssInMS = (st_in).slab_mask_strides[__ssK - 1]; \
|
|
2352
|
+
int __ssIContig = (__ssInS == (ca_size_t) sizeof(T_LOAD)); \
|
|
2353
|
+
int __ssOContig = (__ssOutS == (ca_size_t) sizeof(T_OUT)); \
|
|
2354
|
+
int __ssMaskU = (__ssInMS == 1); \
|
|
2355
|
+
ca_size_t __ssOC = 1; \
|
|
2356
|
+
for ( int8_t __sk = 0; __sk < __ssOuterK; __sk++ ) \
|
|
2357
|
+
__ssOC *= (st_in).slab_dims[__sk]; \
|
|
2358
|
+
ca_size_t __ssIdx[CA_RANK_MAX] = { 0 }; \
|
|
2359
|
+
for ( ca_size_t __so = 0; __so < __ssOC; __so++ ) { \
|
|
2360
|
+
ca_size_t __ssDoff = 0, __ssOOff = 0, __ssMoff = 0; \
|
|
2361
|
+
for ( int8_t __sk = 0; __sk < __ssOuterK; __sk++ ) { \
|
|
2362
|
+
__ssDoff += __ssIdx[__sk] * (st_in).slab_strides[__sk]; \
|
|
2363
|
+
__ssOOff += __ssIdx[__sk] * (st_out).slab_strides[__sk]; \
|
|
2364
|
+
__ssMoff += __ssIdx[__sk] * (st_in).slab_mask_strides[__sk]; \
|
|
2365
|
+
} \
|
|
2366
|
+
const char *__ssQi = (const char *)(p_in) + __ssDoff; \
|
|
2367
|
+
char *__ssQo = (char *) (p_out) + __ssOOff; \
|
|
2368
|
+
if ( (m_in) == NULL ) { \
|
|
2369
|
+
if ( __ssIContig && __ssOContig ) { \
|
|
2370
|
+
const T_LOAD *__ssSi = (const T_LOAD *) __ssQi; \
|
|
2371
|
+
T_OUT *__ssSo = (T_OUT *) __ssQo; \
|
|
2372
|
+
for ( ca_size_t __sj = 0; __sj < __ssInnerN; __sj++ ) { \
|
|
2373
|
+
T_LOAD v = __ssSi[__sj]; \
|
|
2374
|
+
T_OUT r; \
|
|
2375
|
+
STEP; \
|
|
2376
|
+
__ssSo[__sj] = r; \
|
|
2377
|
+
} \
|
|
2378
|
+
} else { \
|
|
2379
|
+
for ( ca_size_t __sj = 0; __sj < __ssInnerN; __sj++ ) { \
|
|
2380
|
+
T_LOAD v = *(const T_LOAD *)(__ssQi + __sj * __ssInS); \
|
|
2381
|
+
T_OUT r; \
|
|
2382
|
+
STEP; \
|
|
2383
|
+
*(T_OUT *)(__ssQo + __sj * __ssOutS) = r; \
|
|
2384
|
+
} \
|
|
2385
|
+
} \
|
|
2386
|
+
} else { \
|
|
2387
|
+
const boolean8_t *__ssMM = (const boolean8_t *)(m_in) + __ssMoff; \
|
|
2388
|
+
for ( ca_size_t __sj = 0; __sj < __ssInnerN; __sj++ ) { \
|
|
2389
|
+
T_OUT r; \
|
|
2390
|
+
if ( ! __ssMM[__ssMaskU ? __sj : __sj * __ssInMS] ) { \
|
|
2391
|
+
T_LOAD v = __ssIContig \
|
|
2392
|
+
? ((const T_LOAD *) __ssQi)[__sj] \
|
|
2393
|
+
: *(const T_LOAD *)(__ssQi + __sj * __ssInS); \
|
|
2394
|
+
STEP; \
|
|
2395
|
+
} else { \
|
|
2396
|
+
r = acc; /* masked: write current running aggregate (legacy parity) */ \
|
|
2397
|
+
} \
|
|
2398
|
+
if ( __ssOContig ) \
|
|
2399
|
+
((T_OUT *) __ssQo)[__sj] = r; \
|
|
2400
|
+
else \
|
|
2401
|
+
*(T_OUT *)(__ssQo + __sj * __ssOutS) = r; \
|
|
2402
|
+
} \
|
|
2403
|
+
} \
|
|
2404
|
+
for ( int8_t __sk = __ssOuterK - 1; __sk >= 0; __sk-- ) { \
|
|
2405
|
+
if ( ++__ssIdx[__sk] < (st_in).slab_dims[__sk] ) break; \
|
|
2406
|
+
__ssIdx[__sk] = 0; \
|
|
2407
|
+
} \
|
|
2408
|
+
} \
|
|
2409
|
+
} while (0)
|
|
2410
|
+
|
|
2411
|
+
/* CA_SLAB_SCAN_T_GATED(T_LOAD, T_OUT, ...): variant of CA_SLAB_SCAN_T for
|
|
2412
|
+
extremum scans (cummax / cummin) whose accumulator has no identity, so
|
|
2413
|
+
the running value is undefined until the first present cell of a fiber.
|
|
2414
|
+
Adds a per-fiber `int seen` flag and an output-mask base `m_out`.
|
|
2415
|
+
|
|
2416
|
+
Behavior differs from CA_SLAB_SCAN_T only on masked input cells:
|
|
2417
|
+
- while !seen (leading masked cells, before any present value): the
|
|
2418
|
+
output cell is UNDEF -- the mask bit at m_out is set instead of
|
|
2419
|
+
leaking the init sentinel (T_LIMIT_LO / T_LIMIT_HI / Qnil).
|
|
2420
|
+
- once a present cell has been processed (seen = 1): a masked cell
|
|
2421
|
+
holds the running extremum, unmasked (= identical to the non-gated
|
|
2422
|
+
"write acc" legacy parity).
|
|
2423
|
+
With no input mask (m_in == NULL) there is no unseen region, so the walk
|
|
2424
|
+
is byte-identical to CA_SLAB_SCAN_T and m_out is never touched.
|
|
2425
|
+
|
|
2426
|
+
`seen` tracks the boundary explicitly rather than testing acc against the
|
|
2427
|
+
sentinel: a real datum may equal the sentinel, so a value-compare would
|
|
2428
|
+
spuriously re-mask a genuine T_LIMIT_LO / T_LIMIT_HI extremum.
|
|
2429
|
+
|
|
2430
|
+
m_out is the output mask base for this fiber, parallel to p_out (= the
|
|
2431
|
+
caller passes co->mask->ptr + (p_out - co->ptr) / sizeof(T_OUT)). It is
|
|
2432
|
+
valid only because the scan output is always a fresh contiguous entity
|
|
2433
|
+
whose value slab aliases co->ptr (ALIAS_CONTIG); the mask element offset
|
|
2434
|
+
parallel to a value byte offset X is X / sizeof(T_OUT). m_out may be NULL
|
|
2435
|
+
(defensive: falls back to holding the sentinel unmasked). */
|
|
2436
|
+
#define CA_SLAB_SCAN_T_GATED(T_LOAD, T_OUT, st_in, p_in, m_in, \
|
|
2437
|
+
st_out, p_out, m_out, INIT, STEP) do { \
|
|
2438
|
+
T_OUT acc = (INIT); \
|
|
2439
|
+
int8_t __ssK = (st_in).slab_ndim; \
|
|
2440
|
+
int8_t __ssOuterK = __ssK - 1; \
|
|
2441
|
+
ca_size_t __ssInnerN = (st_in).slab_dims[__ssK - 1]; \
|
|
2442
|
+
ca_size_t __ssInS = (st_in).slab_strides[__ssK - 1]; \
|
|
2443
|
+
ca_size_t __ssOutS = (st_out).slab_strides[__ssK - 1]; \
|
|
2444
|
+
ca_size_t __ssInMS = (st_in).slab_mask_strides[__ssK - 1]; \
|
|
2445
|
+
int __ssIContig = (__ssInS == (ca_size_t) sizeof(T_LOAD)); \
|
|
2446
|
+
int __ssOContig = (__ssOutS == (ca_size_t) sizeof(T_OUT)); \
|
|
2447
|
+
int __ssMaskU = (__ssInMS == 1); \
|
|
2448
|
+
ca_size_t __ssMoStep = __ssOutS / (ca_size_t) sizeof(T_OUT); \
|
|
2449
|
+
ca_size_t __ssOC = 1; \
|
|
2450
|
+
for ( int8_t __sk = 0; __sk < __ssOuterK; __sk++ ) \
|
|
2451
|
+
__ssOC *= (st_in).slab_dims[__sk]; \
|
|
2452
|
+
ca_size_t __ssIdx[CA_RANK_MAX] = { 0 }; \
|
|
2453
|
+
for ( ca_size_t __so = 0; __so < __ssOC; __so++ ) { \
|
|
2454
|
+
ca_size_t __ssDoff = 0, __ssOOff = 0, __ssMoff = 0; \
|
|
2455
|
+
for ( int8_t __sk = 0; __sk < __ssOuterK; __sk++ ) { \
|
|
2456
|
+
__ssDoff += __ssIdx[__sk] * (st_in).slab_strides[__sk]; \
|
|
2457
|
+
__ssOOff += __ssIdx[__sk] * (st_out).slab_strides[__sk]; \
|
|
2458
|
+
__ssMoff += __ssIdx[__sk] * (st_in).slab_mask_strides[__sk]; \
|
|
2459
|
+
} \
|
|
2460
|
+
const char *__ssQi = (const char *)(p_in) + __ssDoff; \
|
|
2461
|
+
char *__ssQo = (char *) (p_out) + __ssOOff; \
|
|
2462
|
+
ca_size_t __ssMoBase = __ssOOff / (ca_size_t) sizeof(T_OUT); \
|
|
2463
|
+
int __ssSeen = 0; \
|
|
2464
|
+
if ( (m_in) == NULL ) { \
|
|
2465
|
+
if ( __ssIContig && __ssOContig ) { \
|
|
2466
|
+
const T_LOAD *__ssSi = (const T_LOAD *) __ssQi; \
|
|
2467
|
+
T_OUT *__ssSo = (T_OUT *) __ssQo; \
|
|
2468
|
+
for ( ca_size_t __sj = 0; __sj < __ssInnerN; __sj++ ) { \
|
|
2469
|
+
T_LOAD v = __ssSi[__sj]; \
|
|
2470
|
+
T_OUT r; \
|
|
2471
|
+
STEP; \
|
|
2472
|
+
__ssSo[__sj] = r; \
|
|
2473
|
+
} \
|
|
2474
|
+
} else { \
|
|
2475
|
+
for ( ca_size_t __sj = 0; __sj < __ssInnerN; __sj++ ) { \
|
|
2476
|
+
T_LOAD v = *(const T_LOAD *)(__ssQi + __sj * __ssInS); \
|
|
2477
|
+
T_OUT r; \
|
|
2478
|
+
STEP; \
|
|
2479
|
+
*(T_OUT *)(__ssQo + __sj * __ssOutS) = r; \
|
|
2480
|
+
} \
|
|
2481
|
+
} \
|
|
2482
|
+
} else { \
|
|
2483
|
+
const boolean8_t *__ssMM = (const boolean8_t *)(m_in) + __ssMoff; \
|
|
2484
|
+
for ( ca_size_t __sj = 0; __sj < __ssInnerN; __sj++ ) { \
|
|
2485
|
+
T_OUT r; \
|
|
2486
|
+
if ( ! __ssMM[__ssMaskU ? __sj : __sj * __ssInMS] ) { \
|
|
2487
|
+
T_LOAD v = __ssIContig \
|
|
2488
|
+
? ((const T_LOAD *) __ssQi)[__sj] \
|
|
2489
|
+
: *(const T_LOAD *)(__ssQi + __sj * __ssInS); \
|
|
2490
|
+
STEP; \
|
|
2491
|
+
__ssSeen = 1; \
|
|
2492
|
+
} else if ( ! __ssSeen && (m_out) != NULL ) { \
|
|
2493
|
+
((boolean8_t *)(m_out))[__ssMoBase + __sj * __ssMoStep] = 1; \
|
|
2494
|
+
r = acc; /* value slot masked; sentinel held, never read */ \
|
|
2495
|
+
} else { \
|
|
2496
|
+
r = acc; /* masked after first present: hold running extremum */\
|
|
2497
|
+
} \
|
|
2498
|
+
if ( __ssOContig ) \
|
|
2499
|
+
((T_OUT *) __ssQo)[__sj] = r; \
|
|
2500
|
+
else \
|
|
2501
|
+
*(T_OUT *)(__ssQo + __sj * __ssOutS) = r; \
|
|
2502
|
+
} \
|
|
2503
|
+
} \
|
|
2504
|
+
for ( int8_t __sk = __ssOuterK - 1; __sk >= 0; __sk-- ) { \
|
|
2505
|
+
if ( ++__ssIdx[__sk] < (st_in).slab_dims[__sk] ) break; \
|
|
2506
|
+
__ssIdx[__sk] = 0; \
|
|
2507
|
+
} \
|
|
2508
|
+
} \
|
|
2509
|
+
} while (0)
|
|
2510
|
+
|
|
2511
|
+
/* CA_SLAB_SCAN_TA(T_LOAD, T_OUT, T_ACC, ...): variant of CA_SLAB_SCAN_T
|
|
2512
|
+
that decouples the accumulator type T_ACC from the output type T_OUT
|
|
2513
|
+
and additionally exposes a `first` flag to STEP marking the first
|
|
2514
|
+
live (unmasked) cell of each fiber.
|
|
2515
|
+
|
|
2516
|
+
Use case: "adjacent-compare" scans like uniq_scan, where the
|
|
2517
|
+
accumulator holds the last seen INPUT value (T_LOAD) while the output
|
|
2518
|
+
is a per-cell boolean flag (T_OUT = boolean8_t). STEP can branch on
|
|
2519
|
+
`first` to special-case the first unmasked cell of each fiber.
|
|
2520
|
+
|
|
2521
|
+
STEP sees: v (T_LOAD, current input), r (T_OUT lvalue, output), acc
|
|
2522
|
+
(T_ACC, running accumulator), first (int, 1 if this is the first
|
|
2523
|
+
unmasked cell of this fiber else 0).
|
|
2524
|
+
|
|
2525
|
+
Masked input cells skip STEP entirely and write r = 0 to the output
|
|
2526
|
+
(a neutral value safe for downstream `mask |= r` scatter). The
|
|
2527
|
+
accumulator is preserved across masked cells so STEP sees a coherent
|
|
2528
|
+
"last live value" trail. */
|
|
2529
|
+
#define CA_SLAB_SCAN_TA(T_LOAD, T_OUT, T_ACC, st_in, p_in, m_in, \
|
|
2530
|
+
st_out, p_out, INIT, STEP) do { \
|
|
2531
|
+
T_ACC acc = (INIT); \
|
|
2532
|
+
int8_t __ssK = (st_in).slab_ndim; \
|
|
2533
|
+
int8_t __ssOuterK = __ssK - 1; \
|
|
2534
|
+
ca_size_t __ssInnerN = (st_in).slab_dims[__ssK - 1]; \
|
|
2535
|
+
ca_size_t __ssInS = (st_in).slab_strides[__ssK - 1]; \
|
|
2536
|
+
ca_size_t __ssOutS = (st_out).slab_strides[__ssK - 1]; \
|
|
2537
|
+
ca_size_t __ssInMS = (st_in).slab_mask_strides[__ssK - 1]; \
|
|
2538
|
+
int __ssIContig = (__ssInS == (ca_size_t) sizeof(T_LOAD)); \
|
|
2539
|
+
int __ssOContig = (__ssOutS == (ca_size_t) sizeof(T_OUT)); \
|
|
2540
|
+
int __ssMaskU = (__ssInMS == 1); \
|
|
2541
|
+
ca_size_t __ssOC = 1; \
|
|
2542
|
+
for ( int8_t __sk = 0; __sk < __ssOuterK; __sk++ ) \
|
|
2543
|
+
__ssOC *= (st_in).slab_dims[__sk]; \
|
|
2544
|
+
ca_size_t __ssIdx[CA_RANK_MAX] = { 0 }; \
|
|
2545
|
+
for ( ca_size_t __so = 0; __so < __ssOC; __so++ ) { \
|
|
2546
|
+
ca_size_t __ssDoff = 0, __ssOOff = 0, __ssMoff = 0; \
|
|
2547
|
+
for ( int8_t __sk = 0; __sk < __ssOuterK; __sk++ ) { \
|
|
2548
|
+
__ssDoff += __ssIdx[__sk] * (st_in).slab_strides[__sk]; \
|
|
2549
|
+
__ssOOff += __ssIdx[__sk] * (st_out).slab_strides[__sk]; \
|
|
2550
|
+
__ssMoff += __ssIdx[__sk] * (st_in).slab_mask_strides[__sk]; \
|
|
2551
|
+
} \
|
|
2552
|
+
const char *__ssQi = (const char *)(p_in) + __ssDoff; \
|
|
2553
|
+
char *__ssQo = (char *) (p_out) + __ssOOff; \
|
|
2554
|
+
int __ssFirst = 1; \
|
|
2555
|
+
if ( (m_in) == NULL ) { \
|
|
2556
|
+
for ( ca_size_t __sj = 0; __sj < __ssInnerN; __sj++ ) { \
|
|
2557
|
+
T_LOAD v = __ssIContig \
|
|
2558
|
+
? ((const T_LOAD *) __ssQi)[__sj] \
|
|
2559
|
+
: *(const T_LOAD *)(__ssQi + __sj * __ssInS); \
|
|
2560
|
+
T_OUT r; \
|
|
2561
|
+
int first = __ssFirst; \
|
|
2562
|
+
STEP; \
|
|
2563
|
+
__ssFirst = 0; \
|
|
2564
|
+
if ( __ssOContig ) \
|
|
2565
|
+
((T_OUT *) __ssQo)[__sj] = r; \
|
|
2566
|
+
else \
|
|
2567
|
+
*(T_OUT *)(__ssQo + __sj * __ssOutS) = r; \
|
|
2568
|
+
} \
|
|
2569
|
+
} else { \
|
|
2570
|
+
const boolean8_t *__ssMM = (const boolean8_t *)(m_in) + __ssMoff; \
|
|
2571
|
+
for ( ca_size_t __sj = 0; __sj < __ssInnerN; __sj++ ) { \
|
|
2572
|
+
T_OUT r; \
|
|
2573
|
+
if ( ! __ssMM[__ssMaskU ? __sj : __sj * __ssInMS] ) { \
|
|
2574
|
+
T_LOAD v = __ssIContig \
|
|
2575
|
+
? ((const T_LOAD *) __ssQi)[__sj] \
|
|
2576
|
+
: *(const T_LOAD *)(__ssQi + __sj * __ssInS); \
|
|
2577
|
+
int first = __ssFirst; \
|
|
2578
|
+
STEP; \
|
|
2579
|
+
__ssFirst = 0; \
|
|
2580
|
+
} else { \
|
|
2581
|
+
r = (T_OUT) 0; /* masked: neutral output for downstream OR */ \
|
|
2582
|
+
} \
|
|
2583
|
+
if ( __ssOContig ) \
|
|
2584
|
+
((T_OUT *) __ssQo)[__sj] = r; \
|
|
2585
|
+
else \
|
|
2586
|
+
*(T_OUT *)(__ssQo + __sj * __ssOutS) = r; \
|
|
2587
|
+
} \
|
|
2588
|
+
} \
|
|
2589
|
+
for ( int8_t __sk = __ssOuterK - 1; __sk >= 0; __sk-- ) { \
|
|
2590
|
+
if ( ++__ssIdx[__sk] < (st_in).slab_dims[__sk] ) break; \
|
|
2591
|
+
__ssIdx[__sk] = 0; \
|
|
2592
|
+
} \
|
|
2593
|
+
} \
|
|
2594
|
+
} while (0)
|
|
2595
|
+
|
|
2596
|
+
/* ---- Ruby surface ---------------------------------------------------- */
|
|
2597
|
+
/* Called from Init_carray_ext (ruby_carray.c). Registers the smoke
|
|
2598
|
+
test stub (CArray.t1_step1_smoke) used by
|
|
2599
|
+
spec_ai/test_t1_kernel_iterator_step1.rb. This is step-1 scaffolding
|
|
2600
|
+
only; later steps may replace or remove it. */
|
|
2601
|
+
void Init_ca_kernel_iterator (void);
|
|
2602
|
+
|
|
2603
|
+
#endif /* CA_KERNEL_ITERATOR_H */
|