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.
Files changed (339) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +5 -25
  3. data/CHANGELOG.md +16 -0
  4. data/LICENSE +1 -1
  5. data/NEWS.md +3 -0
  6. data/README.md +128 -44
  7. data/carray.gemspec +22 -24
  8. data/ext/ca_array_pool.c +91 -0
  9. data/ext/ca_axis_descriptor.h +186 -0
  10. data/ext/ca_axis_dispatch.c +924 -0
  11. data/ext/ca_axis_group.c +1208 -0
  12. data/ext/ca_bincmp_dispatch.c +76 -0
  13. data/ext/ca_bincmp_dispatch.h +85 -0
  14. data/ext/ca_binop_dispatch.c +125 -0
  15. data/ext/ca_binop_dispatch.h +159 -0
  16. data/ext/ca_categorical_iterator.c +1375 -0
  17. data/ext/ca_compare.c +94 -0
  18. data/ext/ca_compare.h +26 -0
  19. data/ext/ca_composite_dispatch.c +414 -0
  20. data/ext/ca_composite_dispatch.h +116 -0
  21. data/ext/ca_for_buffer.h +96 -0
  22. data/ext/ca_for_each_element.h +241 -0
  23. data/ext/ca_group_iter.c +304 -0
  24. data/ext/ca_iter_substrate.h +325 -0
  25. data/ext/ca_kernel_iterator.c +4321 -0
  26. data/ext/ca_kernel_iterator.h +2603 -0
  27. data/ext/ca_moncmp_dispatch.c +37 -0
  28. data/ext/ca_moncmp_dispatch.h +62 -0
  29. data/ext/ca_monop_dispatch.c +200 -0
  30. data/ext/ca_monop_dispatch.h +235 -0
  31. data/ext/ca_obj_array.c +355 -359
  32. data/ext/ca_obj_bincmp.c +809 -0
  33. data/ext/ca_obj_binop.c +892 -0
  34. data/ext/ca_obj_bitarray.c +369 -164
  35. data/ext/ca_obj_bitfield.c +294 -234
  36. data/ext/ca_obj_block.c +189 -711
  37. data/ext/ca_obj_byte_swap.c +766 -0
  38. data/ext/ca_obj_const_string.c +965 -0
  39. data/ext/ca_obj_face.c +670 -0
  40. data/ext/ca_obj_face.h +247 -0
  41. data/ext/ca_obj_fake.c +228 -100
  42. data/ext/ca_obj_farray.c +54 -441
  43. data/ext/ca_obj_field.c +82 -529
  44. data/ext/ca_obj_fixlen_string.c +306 -0
  45. data/ext/ca_obj_grid.c +858 -440
  46. data/ext/ca_obj_meld.c +1034 -0
  47. data/ext/ca_obj_moncmp.c +569 -0
  48. data/ext/ca_obj_monop.c +1111 -0
  49. data/ext/ca_obj_object.c +774 -298
  50. data/ext/ca_obj_record.c +468 -0
  51. data/ext/ca_obj_reduce.c +97 -82
  52. data/ext/ca_obj_refer.c +569 -459
  53. data/ext/ca_obj_remap.c +475 -0
  54. data/ext/ca_obj_repeat.c +92 -477
  55. data/ext/ca_obj_roll.c +616 -0
  56. data/ext/ca_obj_select.c +344 -296
  57. data/ext/ca_obj_select_axis.c +1296 -0
  58. data/ext/ca_obj_shift.c +230 -792
  59. data/ext/ca_obj_source.c +78 -0
  60. data/ext/ca_obj_stack.c +1173 -0
  61. data/ext/ca_obj_stride.c +2501 -0
  62. data/ext/ca_obj_string.c +268 -0
  63. data/ext/ca_obj_tile.c +614 -0
  64. data/ext/ca_obj_time.c +546 -0
  65. data/ext/ca_obj_timedelta.c +435 -0
  66. data/ext/ca_obj_transpose.c +62 -516
  67. data/ext/ca_obj_triop.c +746 -0
  68. data/ext/ca_obj_unbound_repeat.c +208 -241
  69. data/ext/ca_obj_window.c +1131 -563
  70. data/ext/ca_op_byte_swap.c +175 -0
  71. data/ext/ca_op_ipower.c +319 -0
  72. data/ext/ca_op_powi.h +88 -0
  73. data/ext/ca_sort_kernels.h +132 -0
  74. data/ext/ca_sweep_engine.c +430 -0
  75. data/ext/ca_sweep_engine.h +157 -0
  76. data/ext/ca_transform_common.c +228 -0
  77. data/ext/ca_triop_dispatch.c +55 -0
  78. data/ext/ca_triop_dispatch.h +62 -0
  79. data/ext/carray.h +795 -402
  80. data/ext/carray_access.c +831 -711
  81. data/ext/carray_attribute.c +98 -330
  82. data/ext/carray_bincount.c +255 -0
  83. data/ext/carray_broadcast.c +283 -0
  84. data/ext/carray_call_cfunc.c +1360 -828
  85. data/ext/carray_call_cfunc.h +160 -0
  86. data/ext/carray_cast.c +1212 -301
  87. data/ext/carray_cast_func.rb +81 -40
  88. data/ext/carray_class.c +53 -63
  89. data/ext/carray_config.h +28 -0
  90. data/ext/carray_conversion.c +350 -346
  91. data/ext/carray_copy.c +156 -268
  92. data/ext/carray_core.c +1342 -199
  93. data/ext/carray_count.c +312 -0
  94. data/ext/carray_data_type.c +43 -19
  95. data/ext/carray_element.c +585 -213
  96. data/ext/carray_factorize.c +2542 -0
  97. data/ext/carray_generate.c +230 -559
  98. data/ext/carray_histogram.c +490 -0
  99. data/ext/carray_hold.c +228 -0
  100. data/ext/carray_index_classifier.c +1035 -0
  101. data/ext/carray_index_classifier.h +27 -0
  102. data/ext/carray_internal.h +120 -0
  103. data/ext/carray_kernels_bincmp.c +4445 -0
  104. data/ext/carray_kernels_binop.c +10979 -0
  105. data/ext/carray_kernels_init.c +36 -0
  106. data/ext/carray_kernels_map.c +3466 -0
  107. data/ext/carray_kernels_moncmp.c +2096 -0
  108. data/ext/carray_kernels_monop.c +18312 -0
  109. data/ext/carray_kernels_reduce_aggregate.c +25836 -0
  110. data/ext/carray_kernels_reduce_boolean.c +329 -0
  111. data/ext/carray_kernels_reduce_cumulative.c +14592 -0
  112. data/ext/carray_kernels_reduce_extreme.c +16947 -0
  113. data/ext/carray_kernels_reduce_variance.c +3909 -0
  114. data/ext/carray_kernels_scan.c +3692 -0
  115. data/ext/carray_kernels_search.c +32137 -0
  116. data/ext/carray_kernels_sort.c +10625 -0
  117. data/ext/carray_kernels_triop.c +1391 -0
  118. data/ext/carray_lazy.c +567 -0
  119. data/ext/carray_loop.c +88 -200
  120. data/ext/carray_mask.c +848 -154
  121. data/ext/carray_math_kernel.h +120 -0
  122. data/ext/carray_mathfunc.c +10 -241
  123. data/ext/carray_median_percentile.c +1257 -0
  124. data/ext/carray_memory_view.c +1625 -0
  125. data/ext/carray_operator.c +1526 -318
  126. data/ext/carray_order.c +664 -1394
  127. data/ext/carray_partition.c +416 -0
  128. data/ext/carray_random.c +518 -0
  129. data/ext/carray_scatter.c +357 -0
  130. data/ext/carray_slab.c +1219 -0
  131. data/ext/carray_slab.h +84 -0
  132. data/ext/carray_sort.c +829 -0
  133. data/ext/carray_sort_kernel.c +620 -0
  134. data/ext/carray_struct.c +695 -0
  135. data/ext/carray_test.c +343 -229
  136. data/ext/carray_undef.c +34 -17
  137. data/ext/carray_utils.c +175 -74
  138. data/ext/extconf.rb +216 -55
  139. data/ext/mk_call_cfunc.rb +480 -0
  140. data/ext/mkkernel.rb +8842 -0
  141. data/ext/ruby_carray.c +202 -101
  142. data/ext/version.h +4 -14
  143. data/ext/version.rb +5 -13
  144. data/lib/carray/arrow_tensor.rb +401 -0
  145. data/lib/carray/attribute.rb +166 -0
  146. data/lib/carray/autoload_carray.rb +220 -0
  147. data/lib/carray/autoload_method_extension.rb +44 -0
  148. data/lib/carray/axis_group.rb +711 -0
  149. data/lib/carray/basics.rb +481 -0
  150. data/lib/carray/bincount_nd.rb +358 -0
  151. data/lib/carray/block_iterator.rb +604 -0
  152. data/lib/carray/boolean_reduce.rb +109 -0
  153. data/lib/carray/categorical.rb +561 -0
  154. data/lib/carray/categorical_iterator.rb +1062 -0
  155. data/lib/carray/complex.rb +150 -0
  156. data/lib/carray/conditional.rb +216 -0
  157. data/lib/carray/const_string.rb +228 -0
  158. data/lib/carray/construct.rb +139 -328
  159. data/lib/carray/core_extensions.rb +240 -0
  160. data/lib/carray/data_type_extension.rb +233 -0
  161. data/lib/carray/fixlen_string.rb +95 -0
  162. data/lib/carray/frame/concat.rb +132 -0
  163. data/lib/carray/frame/convert.rb +95 -0
  164. data/lib/carray/frame/csv_parser.rb +211 -0
  165. data/lib/carray/frame/frame.rb +649 -0
  166. data/lib/carray/frame/group.rb +186 -0
  167. data/lib/carray/frame/io.rb +164 -0
  168. data/lib/carray/frame/join.rb +248 -0
  169. data/lib/carray/frame/records.rb +99 -0
  170. data/lib/carray/frame/sort.rb +113 -0
  171. data/lib/carray/frame/verbs.rb +299 -0
  172. data/lib/carray/frame.rb +16 -0
  173. data/lib/carray/histogram.rb +512 -0
  174. data/lib/carray/inspect.rb +37 -20
  175. data/lib/carray/iterator.rb +57 -349
  176. data/lib/carray/lazy.rb +889 -0
  177. data/lib/carray/mask_gap_fill.rb +200 -0
  178. data/lib/carray/math.rb +78 -342
  179. data/lib/carray/meld_reduce.rb +289 -0
  180. data/lib/carray/methods/align_addr.rb +116 -0
  181. data/lib/carray/methods/bin.rb +128 -0
  182. data/lib/carray/methods/bincount.rb +87 -0
  183. data/lib/carray/methods/bit_string.rb +92 -0
  184. data/lib/carray/methods/broadcast.rb +63 -0
  185. data/lib/carray/methods/choose.rb +39 -0
  186. data/lib/carray/methods/composition.rb +280 -0
  187. data/lib/carray/methods/gather_nd.rb +206 -0
  188. data/lib/carray/methods/index.rb +39 -0
  189. data/lib/carray/methods/insert_block.rb +99 -0
  190. data/lib/carray/methods/is_in.rb +141 -0
  191. data/lib/carray/methods/join.rb +90 -0
  192. data/lib/carray/methods/locate_addr.rb +47 -0
  193. data/lib/carray/methods/mask_duplicates.rb +41 -0
  194. data/lib/carray/methods/meshgrid.rb +91 -0
  195. data/lib/carray/methods/mode.rb +126 -0
  196. data/lib/carray/methods/nunique.rb +46 -0
  197. data/lib/carray/methods/resize.rb +56 -0
  198. data/lib/carray/methods/snap.rb +156 -0
  199. data/lib/carray/methods/string_format.rb +57 -0
  200. data/lib/carray/methods/unique.rb +47 -0
  201. data/lib/carray/methods/value_counts.rb +71 -0
  202. data/lib/carray/mkmf.rb +124 -101
  203. data/lib/carray/runtime.rb +108 -0
  204. data/lib/carray/serialize.rb +478 -167
  205. data/lib/carray/slab_iterator.rb +292 -0
  206. data/lib/carray/stack.rb +291 -0
  207. data/lib/carray/string.rb +56 -180
  208. data/lib/carray/string_operation_extension.rb +289 -0
  209. data/lib/carray/struct.rb +335 -323
  210. data/lib/carray/struct_builder.rb +697 -0
  211. data/lib/carray/table.rb +41 -2
  212. data/lib/carray/time.rb +2255 -38
  213. data/lib/carray/window_iterator.rb +655 -0
  214. data/lib/carray.rb +55 -57
  215. metadata +163 -130
  216. data/Rakefile +0 -51
  217. data/TODO.md +0 -18
  218. data/ext/ca_iter_block.c +0 -257
  219. data/ext/ca_iter_dimension.c +0 -299
  220. data/ext/ca_iter_window.c +0 -214
  221. data/ext/ca_obj_mapping.c +0 -644
  222. data/ext/carray_iterator.c +0 -641
  223. data/ext/carray_math.rb +0 -850
  224. data/ext/carray_numeric.c +0 -259
  225. data/ext/carray_sort_addr.c +0 -254
  226. data/ext/carray_stat.c +0 -2100
  227. data/ext/carray_stat_proc.rb +0 -1999
  228. data/ext/mkmath.rb +0 -741
  229. data/ext/ruby_ccomplex.c +0 -509
  230. data/ext/ruby_float_func.c +0 -86
  231. data/lib/carray/array.rb +0 -8
  232. data/lib/carray/autoload/autoload_base.rb +0 -19
  233. data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
  234. data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
  235. data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
  236. data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
  237. data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
  238. data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
  239. data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
  240. data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
  241. data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
  242. data/lib/carray/autoload/autoload_gem_random.rb +0 -8
  243. data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
  244. data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
  245. data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
  246. data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
  247. data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
  248. data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
  249. data/lib/carray/autoload/autoload_object_link.rb +0 -1
  250. data/lib/carray/autoload/autoload_object_pack.rb +0 -2
  251. data/lib/carray/autoload.rb +0 -141
  252. data/lib/carray/basic.rb +0 -191
  253. data/lib/carray/broadcast.rb +0 -101
  254. data/lib/carray/compose.rb +0 -315
  255. data/lib/carray/convert.rb +0 -115
  256. data/lib/carray/info.rb +0 -110
  257. data/lib/carray/io/imagemagick.rb +0 -235
  258. data/lib/carray/mask.rb +0 -102
  259. data/lib/carray/math/histogram.rb +0 -177
  260. data/lib/carray/math/recurrence.rb +0 -93
  261. data/lib/carray/object/ca_obj_iterator.rb +0 -50
  262. data/lib/carray/object/ca_obj_link.rb +0 -50
  263. data/lib/carray/object/ca_obj_pack.rb +0 -99
  264. data/lib/carray/obsolete.rb +0 -256
  265. data/lib/carray/ordering.rb +0 -181
  266. data/lib/carray/testing.rb +0 -51
  267. data/lib/carray/transform.rb +0 -109
  268. data/misc/Methods.ja.md +0 -182
  269. data/misc/NOTE +0 -51
  270. data/spec/Classes/CABitfield_spec.rb +0 -58
  271. data/spec/Classes/CABlockIterator_spec.rb +0 -114
  272. data/spec/Classes/CABlock_spec.rb +0 -205
  273. data/spec/Classes/CAField_spec.rb +0 -39
  274. data/spec/Classes/CAGrid_spec.rb +0 -75
  275. data/spec/Classes/CAMap_spec.rb +0 -0
  276. data/spec/Classes/CAMapping_spec.rb +0 -105
  277. data/spec/Classes/CAObject_attribute_spec.rb +0 -33
  278. data/spec/Classes/CAObject_spec.rb +0 -33
  279. data/spec/Classes/CARefer_spec.rb +0 -93
  280. data/spec/Classes/CARepeat_spec.rb +0 -65
  281. data/spec/Classes/CASelect_spec.rb +0 -22
  282. data/spec/Classes/CAShift_spec.rb +0 -16
  283. data/spec/Classes/CAStruct_spec.rb +0 -71
  284. data/spec/Classes/CATranspose_spec.rb +0 -60
  285. data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
  286. data/spec/Classes/CAWindow_spec.rb +0 -54
  287. data/spec/Classes/CAWrap_spec.rb +0 -8
  288. data/spec/Classes/CArray_spec.rb +0 -184
  289. data/spec/Classes/CScalar_spec.rb +0 -55
  290. data/spec/Classes/ex1.rb +0 -46
  291. data/spec/Features/feature_130_spec.rb +0 -19
  292. data/spec/Features/feature_attributes_spec.rb +0 -280
  293. data/spec/Features/feature_boolean_spec.rb +0 -98
  294. data/spec/Features/feature_broadcast.rb +0 -116
  295. data/spec/Features/feature_cast_function.rb +0 -19
  296. data/spec/Features/feature_cast_spec.rb +0 -33
  297. data/spec/Features/feature_class_spec.rb +0 -84
  298. data/spec/Features/feature_complex_spec.rb +0 -42
  299. data/spec/Features/feature_composite_spec.rb +0 -124
  300. data/spec/Features/feature_convert_spec.rb +0 -46
  301. data/spec/Features/feature_copy_spec.rb +0 -123
  302. data/spec/Features/feature_creation_spec.rb +0 -84
  303. data/spec/Features/feature_element_spec.rb +0 -144
  304. data/spec/Features/feature_extream_spec.rb +0 -54
  305. data/spec/Features/feature_generate_spec.rb +0 -74
  306. data/spec/Features/feature_index_spec.rb +0 -69
  307. data/spec/Features/feature_mask_spec.rb +0 -580
  308. data/spec/Features/feature_math_spec.rb +0 -97
  309. data/spec/Features/feature_order_spec.rb +0 -146
  310. data/spec/Features/feature_ref_store_spec.rb +0 -209
  311. data/spec/Features/feature_serialization_spec.rb +0 -125
  312. data/spec/Features/feature_stat_spec.rb +0 -397
  313. data/spec/Features/feature_virtual_spec.rb +0 -48
  314. data/spec/Features/method_eq_spec.rb +0 -81
  315. data/spec/Features/method_is_nan_spec.rb +0 -12
  316. data/spec/Features/method_map_spec.rb +0 -54
  317. data/spec/Features/method_max_with.rb +0 -20
  318. data/spec/Features/method_min_with.rb +0 -19
  319. data/spec/Features/method_ne_spec.rb +0 -18
  320. data/spec/Features/method_project_spec.rb +0 -188
  321. data/spec/Features/method_ref_spec.rb +0 -27
  322. data/spec/Features/method_round_spec.rb +0 -11
  323. data/spec/Features/method_s_linspace_spec.rb +0 -48
  324. data/spec/Features/method_s_span_spec.rb +0 -14
  325. data/spec/Features/method_seq_spec.rb +0 -47
  326. data/spec/Features/method_sort_with.rb +0 -43
  327. data/spec/Features/method_sorted_with.rb +0 -29
  328. data/spec/Features/method_span_spec.rb +0 -42
  329. data/spec/Features/method_wrap_readonly_spec.rb +0 -43
  330. data/spec/UnitTest/test_CAVirtual.rb +0 -214
  331. data/spec/spec_all.rb +0 -10
  332. data/utils/ca_ase.rb +0 -21
  333. data/utils/ca_methods.rb +0 -15
  334. data/utils/cast_checker.rb +0 -30
  335. data/utils/convert_test.rb +0 -73
  336. data/utils/extract_yard.rb +0 -22
  337. data/utils/guess_shape.rb +0 -76
  338. data/utils/monkey_patch_methods.rb +0 -62
  339. 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 */