carray 2.0.1 → 3.0.1

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