carray 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +5 -25
  3. data/CHANGELOG.md +16 -0
  4. data/LICENSE +1 -1
  5. data/NEWS.md +3 -0
  6. data/README.md +128 -44
  7. data/carray.gemspec +22 -24
  8. data/ext/ca_array_pool.c +91 -0
  9. data/ext/ca_axis_descriptor.h +186 -0
  10. data/ext/ca_axis_dispatch.c +924 -0
  11. data/ext/ca_axis_group.c +1208 -0
  12. data/ext/ca_bincmp_dispatch.c +76 -0
  13. data/ext/ca_bincmp_dispatch.h +85 -0
  14. data/ext/ca_binop_dispatch.c +125 -0
  15. data/ext/ca_binop_dispatch.h +159 -0
  16. data/ext/ca_categorical_iterator.c +1375 -0
  17. data/ext/ca_compare.c +94 -0
  18. data/ext/ca_compare.h +26 -0
  19. data/ext/ca_composite_dispatch.c +414 -0
  20. data/ext/ca_composite_dispatch.h +116 -0
  21. data/ext/ca_for_buffer.h +96 -0
  22. data/ext/ca_for_each_element.h +241 -0
  23. data/ext/ca_group_iter.c +304 -0
  24. data/ext/ca_iter_substrate.h +325 -0
  25. data/ext/ca_kernel_iterator.c +4321 -0
  26. data/ext/ca_kernel_iterator.h +2603 -0
  27. data/ext/ca_moncmp_dispatch.c +37 -0
  28. data/ext/ca_moncmp_dispatch.h +62 -0
  29. data/ext/ca_monop_dispatch.c +200 -0
  30. data/ext/ca_monop_dispatch.h +235 -0
  31. data/ext/ca_obj_array.c +355 -359
  32. data/ext/ca_obj_bincmp.c +809 -0
  33. data/ext/ca_obj_binop.c +892 -0
  34. data/ext/ca_obj_bitarray.c +369 -164
  35. data/ext/ca_obj_bitfield.c +294 -234
  36. data/ext/ca_obj_block.c +189 -711
  37. data/ext/ca_obj_byte_swap.c +766 -0
  38. data/ext/ca_obj_const_string.c +965 -0
  39. data/ext/ca_obj_face.c +670 -0
  40. data/ext/ca_obj_face.h +247 -0
  41. data/ext/ca_obj_fake.c +228 -100
  42. data/ext/ca_obj_farray.c +54 -441
  43. data/ext/ca_obj_field.c +82 -529
  44. data/ext/ca_obj_fixlen_string.c +306 -0
  45. data/ext/ca_obj_grid.c +858 -440
  46. data/ext/ca_obj_meld.c +1034 -0
  47. data/ext/ca_obj_moncmp.c +569 -0
  48. data/ext/ca_obj_monop.c +1111 -0
  49. data/ext/ca_obj_object.c +774 -298
  50. data/ext/ca_obj_record.c +468 -0
  51. data/ext/ca_obj_reduce.c +97 -82
  52. data/ext/ca_obj_refer.c +569 -459
  53. data/ext/ca_obj_remap.c +475 -0
  54. data/ext/ca_obj_repeat.c +92 -477
  55. data/ext/ca_obj_roll.c +616 -0
  56. data/ext/ca_obj_select.c +344 -296
  57. data/ext/ca_obj_select_axis.c +1296 -0
  58. data/ext/ca_obj_shift.c +230 -792
  59. data/ext/ca_obj_source.c +78 -0
  60. data/ext/ca_obj_stack.c +1173 -0
  61. data/ext/ca_obj_stride.c +2501 -0
  62. data/ext/ca_obj_string.c +268 -0
  63. data/ext/ca_obj_tile.c +614 -0
  64. data/ext/ca_obj_time.c +546 -0
  65. data/ext/ca_obj_timedelta.c +435 -0
  66. data/ext/ca_obj_transpose.c +62 -516
  67. data/ext/ca_obj_triop.c +746 -0
  68. data/ext/ca_obj_unbound_repeat.c +208 -241
  69. data/ext/ca_obj_window.c +1131 -563
  70. data/ext/ca_op_byte_swap.c +175 -0
  71. data/ext/ca_op_ipower.c +319 -0
  72. data/ext/ca_op_powi.h +88 -0
  73. data/ext/ca_sort_kernels.h +132 -0
  74. data/ext/ca_sweep_engine.c +430 -0
  75. data/ext/ca_sweep_engine.h +157 -0
  76. data/ext/ca_transform_common.c +228 -0
  77. data/ext/ca_triop_dispatch.c +55 -0
  78. data/ext/ca_triop_dispatch.h +62 -0
  79. data/ext/carray.h +795 -402
  80. data/ext/carray_access.c +831 -711
  81. data/ext/carray_attribute.c +98 -330
  82. data/ext/carray_bincount.c +255 -0
  83. data/ext/carray_broadcast.c +283 -0
  84. data/ext/carray_call_cfunc.c +1360 -828
  85. data/ext/carray_call_cfunc.h +160 -0
  86. data/ext/carray_cast.c +1212 -301
  87. data/ext/carray_cast_func.rb +81 -40
  88. data/ext/carray_class.c +53 -63
  89. data/ext/carray_config.h +28 -0
  90. data/ext/carray_conversion.c +350 -346
  91. data/ext/carray_copy.c +156 -268
  92. data/ext/carray_core.c +1342 -199
  93. data/ext/carray_count.c +312 -0
  94. data/ext/carray_data_type.c +43 -19
  95. data/ext/carray_element.c +585 -213
  96. data/ext/carray_factorize.c +2542 -0
  97. data/ext/carray_generate.c +230 -559
  98. data/ext/carray_histogram.c +490 -0
  99. data/ext/carray_hold.c +228 -0
  100. data/ext/carray_index_classifier.c +1035 -0
  101. data/ext/carray_index_classifier.h +27 -0
  102. data/ext/carray_internal.h +120 -0
  103. data/ext/carray_kernels_bincmp.c +4445 -0
  104. data/ext/carray_kernels_binop.c +10979 -0
  105. data/ext/carray_kernels_init.c +36 -0
  106. data/ext/carray_kernels_map.c +3466 -0
  107. data/ext/carray_kernels_moncmp.c +2096 -0
  108. data/ext/carray_kernels_monop.c +18312 -0
  109. data/ext/carray_kernels_reduce_aggregate.c +25836 -0
  110. data/ext/carray_kernels_reduce_boolean.c +329 -0
  111. data/ext/carray_kernels_reduce_cumulative.c +14592 -0
  112. data/ext/carray_kernels_reduce_extreme.c +16947 -0
  113. data/ext/carray_kernels_reduce_variance.c +3909 -0
  114. data/ext/carray_kernels_scan.c +3692 -0
  115. data/ext/carray_kernels_search.c +32137 -0
  116. data/ext/carray_kernels_sort.c +10625 -0
  117. data/ext/carray_kernels_triop.c +1391 -0
  118. data/ext/carray_lazy.c +567 -0
  119. data/ext/carray_loop.c +88 -200
  120. data/ext/carray_mask.c +848 -154
  121. data/ext/carray_math_kernel.h +120 -0
  122. data/ext/carray_mathfunc.c +10 -241
  123. data/ext/carray_median_percentile.c +1257 -0
  124. data/ext/carray_memory_view.c +1625 -0
  125. data/ext/carray_operator.c +1526 -318
  126. data/ext/carray_order.c +664 -1394
  127. data/ext/carray_partition.c +416 -0
  128. data/ext/carray_random.c +518 -0
  129. data/ext/carray_scatter.c +357 -0
  130. data/ext/carray_slab.c +1219 -0
  131. data/ext/carray_slab.h +84 -0
  132. data/ext/carray_sort.c +829 -0
  133. data/ext/carray_sort_kernel.c +620 -0
  134. data/ext/carray_struct.c +695 -0
  135. data/ext/carray_test.c +343 -229
  136. data/ext/carray_undef.c +34 -17
  137. data/ext/carray_utils.c +175 -74
  138. data/ext/extconf.rb +216 -55
  139. data/ext/mk_call_cfunc.rb +480 -0
  140. data/ext/mkkernel.rb +8842 -0
  141. data/ext/ruby_carray.c +202 -101
  142. data/ext/version.h +4 -14
  143. data/ext/version.rb +5 -13
  144. data/lib/carray/arrow_tensor.rb +401 -0
  145. data/lib/carray/attribute.rb +166 -0
  146. data/lib/carray/autoload_carray.rb +220 -0
  147. data/lib/carray/autoload_method_extension.rb +44 -0
  148. data/lib/carray/axis_group.rb +711 -0
  149. data/lib/carray/basics.rb +481 -0
  150. data/lib/carray/bincount_nd.rb +358 -0
  151. data/lib/carray/block_iterator.rb +604 -0
  152. data/lib/carray/boolean_reduce.rb +109 -0
  153. data/lib/carray/categorical.rb +561 -0
  154. data/lib/carray/categorical_iterator.rb +1062 -0
  155. data/lib/carray/complex.rb +150 -0
  156. data/lib/carray/conditional.rb +216 -0
  157. data/lib/carray/const_string.rb +228 -0
  158. data/lib/carray/construct.rb +139 -328
  159. data/lib/carray/core_extensions.rb +240 -0
  160. data/lib/carray/data_type_extension.rb +233 -0
  161. data/lib/carray/fixlen_string.rb +95 -0
  162. data/lib/carray/frame/concat.rb +132 -0
  163. data/lib/carray/frame/convert.rb +95 -0
  164. data/lib/carray/frame/csv_parser.rb +211 -0
  165. data/lib/carray/frame/frame.rb +649 -0
  166. data/lib/carray/frame/group.rb +186 -0
  167. data/lib/carray/frame/io.rb +164 -0
  168. data/lib/carray/frame/join.rb +248 -0
  169. data/lib/carray/frame/records.rb +99 -0
  170. data/lib/carray/frame/sort.rb +113 -0
  171. data/lib/carray/frame/verbs.rb +299 -0
  172. data/lib/carray/frame.rb +16 -0
  173. data/lib/carray/histogram.rb +512 -0
  174. data/lib/carray/inspect.rb +37 -20
  175. data/lib/carray/iterator.rb +57 -349
  176. data/lib/carray/lazy.rb +889 -0
  177. data/lib/carray/mask_gap_fill.rb +200 -0
  178. data/lib/carray/math.rb +78 -342
  179. data/lib/carray/meld_reduce.rb +289 -0
  180. data/lib/carray/methods/align_addr.rb +116 -0
  181. data/lib/carray/methods/bin.rb +128 -0
  182. data/lib/carray/methods/bincount.rb +87 -0
  183. data/lib/carray/methods/bit_string.rb +92 -0
  184. data/lib/carray/methods/broadcast.rb +63 -0
  185. data/lib/carray/methods/choose.rb +39 -0
  186. data/lib/carray/methods/composition.rb +280 -0
  187. data/lib/carray/methods/gather_nd.rb +206 -0
  188. data/lib/carray/methods/index.rb +39 -0
  189. data/lib/carray/methods/insert_block.rb +99 -0
  190. data/lib/carray/methods/is_in.rb +141 -0
  191. data/lib/carray/methods/join.rb +90 -0
  192. data/lib/carray/methods/locate_addr.rb +47 -0
  193. data/lib/carray/methods/mask_duplicates.rb +41 -0
  194. data/lib/carray/methods/meshgrid.rb +91 -0
  195. data/lib/carray/methods/mode.rb +126 -0
  196. data/lib/carray/methods/nunique.rb +46 -0
  197. data/lib/carray/methods/resize.rb +56 -0
  198. data/lib/carray/methods/snap.rb +156 -0
  199. data/lib/carray/methods/string_format.rb +57 -0
  200. data/lib/carray/methods/unique.rb +47 -0
  201. data/lib/carray/methods/value_counts.rb +71 -0
  202. data/lib/carray/mkmf.rb +124 -101
  203. data/lib/carray/runtime.rb +108 -0
  204. data/lib/carray/serialize.rb +478 -167
  205. data/lib/carray/slab_iterator.rb +292 -0
  206. data/lib/carray/stack.rb +291 -0
  207. data/lib/carray/string.rb +56 -180
  208. data/lib/carray/string_operation_extension.rb +289 -0
  209. data/lib/carray/struct.rb +335 -323
  210. data/lib/carray/struct_builder.rb +697 -0
  211. data/lib/carray/table.rb +41 -2
  212. data/lib/carray/time.rb +2255 -38
  213. data/lib/carray/window_iterator.rb +655 -0
  214. data/lib/carray.rb +55 -57
  215. metadata +163 -130
  216. data/Rakefile +0 -51
  217. data/TODO.md +0 -18
  218. data/ext/ca_iter_block.c +0 -257
  219. data/ext/ca_iter_dimension.c +0 -299
  220. data/ext/ca_iter_window.c +0 -214
  221. data/ext/ca_obj_mapping.c +0 -644
  222. data/ext/carray_iterator.c +0 -641
  223. data/ext/carray_math.rb +0 -850
  224. data/ext/carray_numeric.c +0 -259
  225. data/ext/carray_sort_addr.c +0 -254
  226. data/ext/carray_stat.c +0 -2100
  227. data/ext/carray_stat_proc.rb +0 -1999
  228. data/ext/mkmath.rb +0 -741
  229. data/ext/ruby_ccomplex.c +0 -509
  230. data/ext/ruby_float_func.c +0 -86
  231. data/lib/carray/array.rb +0 -8
  232. data/lib/carray/autoload/autoload_base.rb +0 -19
  233. data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
  234. data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
  235. data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
  236. data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
  237. data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
  238. data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
  239. data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
  240. data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
  241. data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
  242. data/lib/carray/autoload/autoload_gem_random.rb +0 -8
  243. data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
  244. data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
  245. data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
  246. data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
  247. data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
  248. data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
  249. data/lib/carray/autoload/autoload_object_link.rb +0 -1
  250. data/lib/carray/autoload/autoload_object_pack.rb +0 -2
  251. data/lib/carray/autoload.rb +0 -141
  252. data/lib/carray/basic.rb +0 -191
  253. data/lib/carray/broadcast.rb +0 -101
  254. data/lib/carray/compose.rb +0 -315
  255. data/lib/carray/convert.rb +0 -115
  256. data/lib/carray/info.rb +0 -110
  257. data/lib/carray/io/imagemagick.rb +0 -235
  258. data/lib/carray/mask.rb +0 -102
  259. data/lib/carray/math/histogram.rb +0 -177
  260. data/lib/carray/math/recurrence.rb +0 -93
  261. data/lib/carray/object/ca_obj_iterator.rb +0 -50
  262. data/lib/carray/object/ca_obj_link.rb +0 -50
  263. data/lib/carray/object/ca_obj_pack.rb +0 -99
  264. data/lib/carray/obsolete.rb +0 -256
  265. data/lib/carray/ordering.rb +0 -181
  266. data/lib/carray/testing.rb +0 -51
  267. data/lib/carray/transform.rb +0 -109
  268. data/misc/Methods.ja.md +0 -182
  269. data/misc/NOTE +0 -51
  270. data/spec/Classes/CABitfield_spec.rb +0 -58
  271. data/spec/Classes/CABlockIterator_spec.rb +0 -114
  272. data/spec/Classes/CABlock_spec.rb +0 -205
  273. data/spec/Classes/CAField_spec.rb +0 -39
  274. data/spec/Classes/CAGrid_spec.rb +0 -75
  275. data/spec/Classes/CAMap_spec.rb +0 -0
  276. data/spec/Classes/CAMapping_spec.rb +0 -105
  277. data/spec/Classes/CAObject_attribute_spec.rb +0 -33
  278. data/spec/Classes/CAObject_spec.rb +0 -33
  279. data/spec/Classes/CARefer_spec.rb +0 -93
  280. data/spec/Classes/CARepeat_spec.rb +0 -65
  281. data/spec/Classes/CASelect_spec.rb +0 -22
  282. data/spec/Classes/CAShift_spec.rb +0 -16
  283. data/spec/Classes/CAStruct_spec.rb +0 -71
  284. data/spec/Classes/CATranspose_spec.rb +0 -60
  285. data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
  286. data/spec/Classes/CAWindow_spec.rb +0 -54
  287. data/spec/Classes/CAWrap_spec.rb +0 -8
  288. data/spec/Classes/CArray_spec.rb +0 -184
  289. data/spec/Classes/CScalar_spec.rb +0 -55
  290. data/spec/Classes/ex1.rb +0 -46
  291. data/spec/Features/feature_130_spec.rb +0 -19
  292. data/spec/Features/feature_attributes_spec.rb +0 -280
  293. data/spec/Features/feature_boolean_spec.rb +0 -98
  294. data/spec/Features/feature_broadcast.rb +0 -116
  295. data/spec/Features/feature_cast_function.rb +0 -19
  296. data/spec/Features/feature_cast_spec.rb +0 -33
  297. data/spec/Features/feature_class_spec.rb +0 -84
  298. data/spec/Features/feature_complex_spec.rb +0 -42
  299. data/spec/Features/feature_composite_spec.rb +0 -124
  300. data/spec/Features/feature_convert_spec.rb +0 -46
  301. data/spec/Features/feature_copy_spec.rb +0 -123
  302. data/spec/Features/feature_creation_spec.rb +0 -84
  303. data/spec/Features/feature_element_spec.rb +0 -144
  304. data/spec/Features/feature_extream_spec.rb +0 -54
  305. data/spec/Features/feature_generate_spec.rb +0 -74
  306. data/spec/Features/feature_index_spec.rb +0 -69
  307. data/spec/Features/feature_mask_spec.rb +0 -580
  308. data/spec/Features/feature_math_spec.rb +0 -97
  309. data/spec/Features/feature_order_spec.rb +0 -146
  310. data/spec/Features/feature_ref_store_spec.rb +0 -209
  311. data/spec/Features/feature_serialization_spec.rb +0 -125
  312. data/spec/Features/feature_stat_spec.rb +0 -397
  313. data/spec/Features/feature_virtual_spec.rb +0 -48
  314. data/spec/Features/method_eq_spec.rb +0 -81
  315. data/spec/Features/method_is_nan_spec.rb +0 -12
  316. data/spec/Features/method_map_spec.rb +0 -54
  317. data/spec/Features/method_max_with.rb +0 -20
  318. data/spec/Features/method_min_with.rb +0 -19
  319. data/spec/Features/method_ne_spec.rb +0 -18
  320. data/spec/Features/method_project_spec.rb +0 -188
  321. data/spec/Features/method_ref_spec.rb +0 -27
  322. data/spec/Features/method_round_spec.rb +0 -11
  323. data/spec/Features/method_s_linspace_spec.rb +0 -48
  324. data/spec/Features/method_s_span_spec.rb +0 -14
  325. data/spec/Features/method_seq_spec.rb +0 -47
  326. data/spec/Features/method_sort_with.rb +0 -43
  327. data/spec/Features/method_sorted_with.rb +0 -29
  328. data/spec/Features/method_span_spec.rb +0 -42
  329. data/spec/Features/method_wrap_readonly_spec.rb +0 -43
  330. data/spec/UnitTest/test_CAVirtual.rb +0 -214
  331. data/spec/spec_all.rb +0 -10
  332. data/utils/ca_ase.rb +0 -21
  333. data/utils/ca_methods.rb +0 -15
  334. data/utils/cast_checker.rb +0 -30
  335. data/utils/convert_test.rb +0 -73
  336. data/utils/extract_yard.rb +0 -22
  337. data/utils/guess_shape.rb +0 -76
  338. data/utils/monkey_patch_methods.rb +0 -62
  339. data/utils/remove_resource_fork.sh +0 -5
@@ -0,0 +1,924 @@
1
+ /* ---------------------------------------------------------------------------
2
+
3
+ Per-axis descriptor common engine. Provides gather / scatter /
4
+ fill_value / for_each_slab primitives shared by CSA, CAGrid, and
5
+ other axis-descriptor-emitting views.
6
+
7
+ Single-loop algorithm (shared by gather and scatter):
8
+ 0. Merge adjacent contig-mergeable STRIDE descriptor axes in
9
+ place. This reduces N-d strided iteration patterns that are
10
+ internally contiguous to a single STRIDE axis spanning the
11
+ merged span, before the slab detector or prefix iterator run.
12
+ 1. Identify the innermost run of consecutive STRIDE-step1 axes
13
+ that form a contiguous sub-rectangle in parent row-major.
14
+ That run is the "slab" — one memcpy per outer iteration.
15
+ 2. Iterate the remaining (prefix) axes row-major. Each axis may
16
+ be STRIDE or INDEX; both contribute to the parent offset
17
+ uniformly.
18
+ 3. Output / input buffer is contiguous row-major over the view
19
+ shape; the slab span is transferred per iteration.
20
+
21
+ Slab degenerates naturally:
22
+ - slab covers all axes -> single memcpy from a single offset
23
+ - slab covers no axes -> per-cell memcpy (slab_bytes = bytes)
24
+ - mixed -> per-iteration slab memcpy
25
+
26
+ Contig criterion for the slab run: walking innermost -> outward
27
+ through the run, once we see an axis with count < mdim[k], every
28
+ outer axis in the run must have count == 1. This guarantees the
29
+ slab span is a tight prefix of a parent row. (`mdim[k]` is the
30
+ *effective* parent dim per axis after axis-merge — equal to
31
+ parent->dim[k] for unmerged axes.)
32
+
33
+ Scatter semantics:
34
+ - Iteration order = output row-major
35
+ (= axes[k].count outer-to-inner).
36
+ - Duplicate INDEX values write the same parent cell multiple
37
+ times; last-write-wins is the natural memcpy-based result.
38
+ The engine does not check or assume uniqueness.
39
+
40
+ ---------------------------------------------------------------------------- */
41
+
42
+ #include "carray.h"
43
+ #include "ca_iter_substrate.h"
44
+ #include "ca_composite_dispatch.h"
45
+
46
+ #include <string.h>
47
+
48
+ /* Compute parent row-major byte strides from a dimension array. */
49
+ static void
50
+ ca_axis_dispatch_build_pstrides (ca_size_t *pstrides,
51
+ const ca_size_t *dim,
52
+ int8_t ndim,
53
+ ca_size_t bytes)
54
+ {
55
+ ca_size_t s = bytes;
56
+ int8_t k;
57
+ for ( k = ndim - 1; k >= 0; k-- ) {
58
+ pstrides[k] = s;
59
+ s *= dim[k];
60
+ }
61
+ }
62
+
63
+ /* In-place merge of adjacent contig-mergeable STRIDE descriptor
64
+ axes.
65
+
66
+ Inputs (all arrays sized CA_RANK_MAX):
67
+ axes - descriptor array
68
+ pstrides - parent byte stride per axis (derived from mdim/bytes)
69
+ mdim - effective parent dim per axis (parent->dim initially)
70
+ ndim_inout - read and written
71
+
72
+ Merge condition for adjacent k, k+1 (both must be STRIDE):
73
+ step[k] * pstrides[k] == count[k+1] * step[k+1] * pstrides[k+1]
74
+ i.e. one full traversal of axis k+1's `count` cells at its effective
75
+ byte stride exactly equals one step of axis k. Under this condition
76
+ the two axes describe a single uniform-stride traversal of
77
+ count[k]*count[k+1] cells representable as one STRIDE axis.
78
+
79
+ Post-merge values for the surviving axis (placed at position k):
80
+ start = start[k] * mdim[k+1] + start[k+1]
81
+ count = count[k] * count[k+1]
82
+ step = step[k+1]
83
+ pstride = pstrides[k+1]
84
+ mdim = mdim[k] * mdim[k+1]
85
+
86
+ INDEX axes act as fences (merge is not attempted across them).
87
+ Sign-agnostic: works for negative steps as long as the equation
88
+ holds. Iterates to fixpoint. */
89
+ void
90
+ ca_axis_dispatch_merge (ca_axis_desc_t *axes,
91
+ ca_size_t *pstrides,
92
+ ca_size_t *mdim,
93
+ int8_t *ndim_inout)
94
+ {
95
+ int8_t ndim = *ndim_inout;
96
+ int8_t k, j;
97
+ int changed;
98
+
99
+ do {
100
+ changed = 0;
101
+ for ( k = 0; k + 1 < ndim; k++ ) {
102
+ if ( axes[k].kind != CA_AXIS_KIND_STRIDE ) continue;
103
+ if ( axes[k+1].kind != CA_AXIS_KIND_STRIDE ) continue;
104
+
105
+ ca_size_t ebs_k = axes[k].step * pstrides[k];
106
+ ca_size_t ebs_kp1 = axes[k+1].step * pstrides[k+1];
107
+
108
+ /* Defensive: a zero effective inner stride would make the
109
+ equation degenerate; skip. (Genuine stride-0 / CARepeat
110
+ axes don't reach this engine - they go through CAStride.) */
111
+ if ( ebs_kp1 == 0 ) continue;
112
+
113
+ if ( ebs_k != axes[k+1].count * ebs_kp1 ) continue;
114
+
115
+ /* Merge k and k+1 into a single STRIDE axis at position k. */
116
+ axes[k].start = axes[k].start * mdim[k+1] + axes[k+1].start;
117
+ axes[k].count = axes[k].count * axes[k+1].count;
118
+ axes[k].step = axes[k+1].step;
119
+ axes[k].indices = NULL;
120
+ pstrides[k] = pstrides[k+1];
121
+ mdim[k] = mdim[k] * mdim[k+1];
122
+
123
+ /* Shift remaining left. */
124
+ for ( j = k + 1; j + 1 < ndim; j++ ) {
125
+ axes[j] = axes[j+1];
126
+ pstrides[j] = pstrides[j+1];
127
+ mdim[j] = mdim[j+1];
128
+ }
129
+ ndim--;
130
+ changed = 1;
131
+ break; /* restart scan from index 0 */
132
+ }
133
+ } while ( changed );
134
+
135
+ *ndim_inout = ndim;
136
+ }
137
+
138
+ /* Slab detection: identify the innermost STRIDE-step1 contig run.
139
+ Operates on the (possibly merged) axes and the matching `mdim`
140
+ (effective parent dim per axis).
141
+
142
+ On return:
143
+ *slab_start = innermost prefix axis index (exclusive); ndim if
144
+ no slab axis exists
145
+ *slab_bytes = product of slab axes' counts * bytes
146
+ *slab_base = sum of slab axes' (start * pstrides) - INDEX axes
147
+ never enter the slab, so this is well-defined */
148
+ void
149
+ ca_axis_dispatch_layout (ca_axis_desc_t *axes,
150
+ const ca_size_t *pstrides,
151
+ const ca_size_t *mdim,
152
+ int8_t ndim,
153
+ ca_size_t bytes,
154
+ int8_t *slab_start,
155
+ ca_size_t *slab_bytes,
156
+ ca_size_t *slab_base)
157
+ {
158
+ int8_t k;
159
+
160
+ /* Identify innermost slab run of STRIDE-step1 axes that's contig in
161
+ parent. saw_partial: once a partial axis (count < mdim[k]) is
162
+ seen, every outer axis in the run must have count == 1. */
163
+ int8_t sstart = ndim;
164
+ ca_size_t sbytes = bytes;
165
+ {
166
+ int saw_partial = 0;
167
+ for ( k = ndim - 1; k >= 0; k-- ) {
168
+ if ( axes[k].kind != CA_AXIS_KIND_STRIDE ) break;
169
+ if ( axes[k].step != 1 ) break;
170
+ if ( saw_partial ) {
171
+ if ( axes[k].count != 1 ) break;
172
+ } else if ( axes[k].count != mdim[k] ) {
173
+ saw_partial = 1;
174
+ }
175
+ sstart = k;
176
+ sbytes *= axes[k].count;
177
+ }
178
+ }
179
+ *slab_start = sstart;
180
+ *slab_bytes = sbytes;
181
+
182
+ /* Slab base offset in parent (sum of axes[k].start * pstrides[k]
183
+ for axes in the slab run; INDEX axes never enter the slab). */
184
+ ca_size_t base = 0;
185
+ for ( k = sstart; k < ndim; k++ ) {
186
+ base += axes[k].start * pstrides[k];
187
+ }
188
+ *slab_base = base;
189
+ }
190
+
191
+ /* Build merged layout from caller-provided axes/ndim/parent_axis_dims.
192
+ Copies into the caller's out_* arrays (each must be sized
193
+ CA_RANK_MAX), builds pstrides, runs the axis-merge pass to fixpoint,
194
+ and writes the merged ndim into *out_ndim. Subsequent code uses
195
+ only the out_* arrays - parent->dim is intentionally NOT referenced;
196
+ the producer's `parent_axis_dims[]` carries the effective per-axis
197
+ dim. This is what allows flat-index views to claim parent.ndim = 1
198
+ even though parent->ndim > 1 physically. */
199
+ void
200
+ ca_axis_dispatch_prepare (const ca_size_t *parent_axis_dims,
201
+ const ca_axis_desc_t *axes,
202
+ int8_t ndim,
203
+ ca_size_t bytes,
204
+ ca_axis_desc_t *out_axes,
205
+ ca_size_t *out_pstrides,
206
+ ca_size_t *out_mdim,
207
+ int8_t *out_ndim)
208
+ {
209
+ int8_t k;
210
+ for ( k = 0; k < ndim; k++ ) {
211
+ out_axes[k] = axes[k];
212
+ out_mdim[k] = parent_axis_dims[k];
213
+ }
214
+ ca_axis_dispatch_build_pstrides(out_pstrides, out_mdim, ndim, bytes);
215
+ *out_ndim = ndim;
216
+ ca_axis_dispatch_merge(out_axes, out_pstrides, out_mdim, out_ndim);
217
+ }
218
+
219
+ /* The prefix-axis typedefs (ca_op_axis_kind_t, ca_op_prefix_axis_t)
220
+ and the inline offset helper (ca_axis_dispatch_prefix_offset) live
221
+ in ca_iter_substrate.h. See that header for declarations. */
222
+
223
+ /* Build pre-classified prefix axis array from the (post-merge, post-
224
+ layout) axes/pstrides for indices [0..slab_start-1]. Algebraically
225
+ equivalent to the raw inline computation; precomputes byte-unit
226
+ start/step so the inner loop does one multiply per STRIDE axis and
227
+ one indirect load + one multiply per INDEX axis (vs. two multiplies
228
+ per STRIDE axis in the raw form). SHIFT axes keep start/step in
229
+ element units (for ca_bounds_normalize_index) and carry size0 /
230
+ policy from the descriptor. */
231
+ void
232
+ ca_axis_dispatch_classify_prefix (const ca_axis_desc_t *axes,
233
+ const ca_size_t *pstrides,
234
+ int8_t slab_start,
235
+ ca_op_prefix_axis_t *prefix)
236
+ {
237
+ int8_t k;
238
+ for ( k = 0; k < slab_start; k++ ) {
239
+ prefix[k].count = axes[k].count;
240
+ if ( axes[k].kind == CA_AXIS_KIND_STRIDE ) {
241
+ prefix[k].kind = CA_OP_AXIS_STRIDE;
242
+ prefix[k].byte_start = axes[k].start * pstrides[k];
243
+ prefix[k].byte_step = axes[k].step * pstrides[k];
244
+ prefix[k].indices = NULL;
245
+ prefix[k].byte_pstride = 0;
246
+ } else if ( axes[k].kind == CA_AXIS_KIND_INDEX ) {
247
+ prefix[k].kind = CA_OP_AXIS_INDEX;
248
+ prefix[k].byte_start = 0;
249
+ prefix[k].byte_step = 0;
250
+ prefix[k].indices = axes[k].indices;
251
+ prefix[k].byte_pstride = pstrides[k];
252
+ } else { /* CA_AXIS_KIND_SHIFT */
253
+ prefix[k].kind = CA_OP_AXIS_SHIFT;
254
+ prefix[k].byte_start = 0;
255
+ prefix[k].byte_step = 0;
256
+ prefix[k].indices = NULL;
257
+ prefix[k].byte_pstride = pstrides[k];
258
+ prefix[k].shift_start = axes[k].start;
259
+ prefix[k].shift_step = axes[k].step;
260
+ prefix[k].size0 = axes[k].size0;
261
+ prefix[k].policy = axes[k].policy;
262
+ }
263
+ }
264
+ }
265
+
266
+ /* ca_axis_dispatch_prefix_offset moved to ca_iter_substrate.h
267
+ (static inline so it inlines into _xfer / _fill_value below and into
268
+ other consumer TUs such as ca_kernel_iterator.c). */
269
+
270
+ /* Fill every selected parent cell with `val` (bytes-wide). Shares
271
+ the slab layout helper with gather/scatter; the inner write is a
272
+ per-element memcpy from `val` (the slab might be wider than one
273
+ element, e.g. when consecutive STRIDE axes form a contig run, so we
274
+ iterate within the slab).
275
+
276
+ Used by func_fill_data (e.g. `view[nil] = scalar` / `view.fill(x)`)
277
+ and by the per-slab callbacks below.
278
+
279
+ Delegates to `ca_fill_typed` for SIMD-friendly typed-store loops
280
+ (4-byte / 8-byte typed stores that the compiler autovectorises on
281
+ NEON / SSE2), avoiding a per-element memcpy loop. */
282
+ static void
283
+ ca_axis_dispatch_fill_slab (char *dst, const void *val,
284
+ ca_size_t bytes, ca_size_t slab_bytes)
285
+ {
286
+ ca_size_t n = slab_bytes / bytes;
287
+ ca_fill_typed(dst, (const char *) val, bytes, n);
288
+ }
289
+
290
+ /* Generic per-slab driver (`ca_axis_dispatch_for_each_slab`) plus
291
+ thin gather / scatter / fill_value callbacks around it. The
292
+ kernel_iterator descriptor path hooks into the driver for
293
+ descriptor-routed slab walks.
294
+
295
+ The all-slab fast path (slab_start == 0) is preserved as a single
296
+ cb invocation — no special-casing; the same cb decides what to do
297
+ with the slab. */
298
+
299
+ /* Callback context for gather / scatter (they share the layout - both
300
+ carry a buf+off cursor and gather additionally carries bound_fill). */
301
+ typedef struct {
302
+ char *buf;
303
+ ca_size_t off;
304
+ const void *bound_fill;
305
+ ca_size_t bytes;
306
+ char *parent_ptr;
307
+ } ca_axis_xfer_ctx_t;
308
+
309
+ static void
310
+ ca_axis_dispatch_gather_cb (ca_size_t off, int oob, ca_size_t n, void *vctx)
311
+ {
312
+ ca_axis_xfer_ctx_t *c = (ca_axis_xfer_ctx_t *) vctx;
313
+ char *p = c->parent_ptr + off;
314
+ if ( oob ) {
315
+ /* gather + OOB: write bound_fill across the slab (if provided);
316
+ otherwise leave the output cells untouched. */
317
+ if ( c->bound_fill ) {
318
+ ca_axis_dispatch_fill_slab(c->buf + c->off, c->bound_fill,
319
+ c->bytes, n);
320
+ }
321
+ } else {
322
+ memcpy(c->buf + c->off, p, n);
323
+ }
324
+ c->off += n;
325
+ }
326
+
327
+ static void
328
+ ca_axis_dispatch_scatter_cb (ca_size_t off, int oob, ca_size_t n, void *vctx)
329
+ {
330
+ ca_axis_xfer_ctx_t *c = (ca_axis_xfer_ctx_t *) vctx;
331
+ char *p = c->parent_ptr + off;
332
+ /* scatter + OOB: skip parent write entirely. */
333
+ if ( ! oob ) {
334
+ memcpy(p, c->buf + c->off, n);
335
+ }
336
+ c->off += n;
337
+ }
338
+
339
+ /* Callback context for fill_value (broadcast a single value). parent is set
340
+ when the value goes through the parent's own fill_stride instead of a
341
+ buffer this side has attached. */
342
+ typedef struct {
343
+ const void *val;
344
+ ca_size_t bytes;
345
+ char *parent_ptr;
346
+ CArray *parent;
347
+ } ca_axis_fill_ctx_t;
348
+
349
+ static void
350
+ ca_axis_dispatch_fill_value_cb (ca_size_t off, int oob, ca_size_t n, void *vctx)
351
+ {
352
+ ca_axis_fill_ctx_t *c = (ca_axis_fill_ctx_t *) vctx;
353
+ if ( oob ) return;
354
+ if ( c->parent ) {
355
+ /* One slab is one contiguous run of parent cells, which is a region the
356
+ parent can fill for itself -- no borrowed buffer, so nothing outside
357
+ the run is read or written. */
358
+ ca_size_t count = n / c->bytes;
359
+ ca_size_t step = 1;
360
+ ca_fill_stride(c->parent, off / c->bytes, 1, &count, &step,
361
+ (void *) c->val);
362
+ }
363
+ else {
364
+ ca_axis_dispatch_fill_slab(c->parent_ptr + off, c->val, c->bytes, n);
365
+ }
366
+ }
367
+
368
+ /* Generic per-slab driver. Walks the (post-merge / post-layout)
369
+ prefix axes row-major and invokes `cb` once per slab iteration.
370
+ The all-slab case degenerates to a single `cb` invocation with
371
+ oob = 0. Empty views (total_elements == 0) yield no callbacks.
372
+
373
+ The callback advances its own buf/value cursor via `ctx` -- the
374
+ driver carries no per-slab cursor state. */
375
+ void
376
+ ca_axis_dispatch_for_each_slab (CArray *parent,
377
+ const ca_size_t *parent_axis_dims,
378
+ ca_axis_desc_t *axes,
379
+ int8_t ndim,
380
+ ca_size_t bytes,
381
+ ca_size_t total_elements,
382
+ ca_slab_cb_t cb,
383
+ void *ctx)
384
+ {
385
+ ca_axis_desc_t laxes[CA_RANK_MAX];
386
+ ca_size_t pstrides[CA_RANK_MAX];
387
+ ca_size_t mdim[CA_RANK_MAX];
388
+ ca_size_t idx[CA_RANK_MAX];
389
+ ca_op_prefix_axis_t prefix[CA_RANK_MAX];
390
+ int8_t lndim;
391
+ int8_t slab_start;
392
+ ca_size_t slab_bytes, slab_base;
393
+ int8_t k;
394
+
395
+ if ( total_elements == 0 ) {
396
+ return;
397
+ }
398
+
399
+ ca_axis_dispatch_prepare(parent_axis_dims, axes, ndim, bytes,
400
+ laxes, pstrides, mdim, &lndim);
401
+ ca_axis_dispatch_layout(laxes, pstrides, mdim, lndim, bytes,
402
+ &slab_start, &slab_bytes, &slab_base);
403
+
404
+ /* All-slab fast path: single cb invocation. Slab detection breaks
405
+ on kind != STRIDE, so SHIFT axes never enter this branch and
406
+ oob = 0 is guaranteed. */
407
+ if ( slab_start == 0 ) {
408
+ cb(slab_base, 0, slab_bytes, ctx);
409
+ return;
410
+ }
411
+
412
+ /* Pre-classify prefix axes once. Inner loop becomes branchless
413
+ modulo a per-axis kind tag whose value is constant across
414
+ iters. */
415
+ ca_axis_dispatch_classify_prefix(laxes, pstrides, slab_start, prefix);
416
+
417
+ /* General path: iterate prefix axes [0..slab_start-1] row-major,
418
+ invoke cb per iteration. */
419
+ for ( k = 0; k < slab_start; k++ ) idx[k] = 0;
420
+
421
+ ca_size_t n_iters = total_elements / (slab_bytes / bytes);
422
+ ca_size_t n;
423
+ for ( n = 0; n < n_iters; n++ ) {
424
+ int oob;
425
+ ca_size_t poff = slab_base
426
+ + ca_axis_dispatch_prefix_offset(prefix, idx,
427
+ slab_start, &oob);
428
+ cb(poff, oob, slab_bytes, ctx);
429
+
430
+ /* Advance idx row-major over prefix axes. */
431
+ for ( k = slab_start - 1; k >= 0; k-- ) {
432
+ if ( ++idx[k] < prefix[k].count ) break;
433
+ idx[k] = 0;
434
+ }
435
+ }
436
+ }
437
+
438
+ /* Per-cell gather/scatter fast path.
439
+
440
+ When `ca_axis_dispatch_layout` cannot extend the innermost slab
441
+ run (innermost axis is INDEX, or STRIDE with step != 1)
442
+ `slab_bytes` degenerates to `bytes`. The generic for_each_slab
443
+ loop would then run `total_elements` callback invocations, each
444
+ doing a runtime-size memcpy of one cell — the function-pointer cb
445
+ blocks inlining and the runtime size blocks typed-store
446
+ specialisation.
447
+
448
+ When in per-cell mode AND the inner axis is STRIDE/INDEX, bypass
449
+ the cb and hoist the innermost axis as a kind-specialised,
450
+ bytes-typed inner loop. Outer axes still ride the prefix
451
+ odometer. SHIFT inner falls back to for_each_slab (its OOB
452
+ semantics live in the cb). `bytes` not in {1,2,4,8} falls back to
453
+ memcpy in the inner loop, still winning from removing cb dispatch
454
+ and the outer-only odometer. */
455
+
456
+ static void
457
+ ca_axis_dispatch_percell_gather (CArray *parent,
458
+ ca_axis_desc_t *laxes,
459
+ const ca_size_t *pstrides,
460
+ int8_t lndim,
461
+ ca_size_t bytes,
462
+ ca_size_t slab_base,
463
+ const void *bound_fill,
464
+ char *out_buf)
465
+ {
466
+ int8_t inner = lndim - 1;
467
+ int8_t outers_n = inner;
468
+ ca_op_prefix_axis_t outer_prefix[CA_RANK_MAX];
469
+ ca_size_t outer_idx[CA_RANK_MAX];
470
+ ca_size_t n_outer = 1;
471
+ int8_t k;
472
+
473
+ if ( outers_n > 0 ) {
474
+ ca_axis_dispatch_classify_prefix(laxes, pstrides, outers_n, outer_prefix);
475
+ for ( k = 0; k < outers_n; k++ ) {
476
+ outer_idx[k] = 0;
477
+ n_outer *= outer_prefix[k].count;
478
+ }
479
+ }
480
+
481
+ ca_axis_kind_t inner_kind = laxes[inner].kind;
482
+ ca_size_t inner_count = laxes[inner].count;
483
+ ca_size_t inner_pstride = pstrides[inner];
484
+ ca_size_t inner_base = laxes[inner].start * inner_pstride;
485
+ ca_size_t inner_step = laxes[inner].step * inner_pstride;
486
+ const ca_size_t *inner_indices = laxes[inner].indices;
487
+ ca_size_t row_dst_bytes = inner_count * bytes;
488
+
489
+ ca_size_t no;
490
+ for ( no = 0; no < n_outer; no++ ) {
491
+ int oob = 0;
492
+ ca_size_t outer_off = 0;
493
+ if ( outers_n > 0 ) {
494
+ outer_off = ca_axis_dispatch_prefix_offset(outer_prefix, outer_idx,
495
+ outers_n, &oob);
496
+ }
497
+ char *row_dst = out_buf + no * row_dst_bytes;
498
+
499
+ if ( oob ) {
500
+ if ( bound_fill ) {
501
+ ca_axis_dispatch_fill_slab(row_dst, bound_fill, bytes, row_dst_bytes);
502
+ }
503
+ }
504
+ else {
505
+ char *row_src_base = parent->ptr + slab_base + outer_off;
506
+ ca_size_t j;
507
+ if ( inner_kind == CA_AXIS_KIND_STRIDE ) {
508
+ char *src = row_src_base + inner_base;
509
+ switch ( bytes ) {
510
+ case 1:
511
+ for (j = 0; j < inner_count; j++)
512
+ *(uint8_t *)(row_dst + j) = *(uint8_t *)(src + j*inner_step);
513
+ break;
514
+ case 2:
515
+ for (j = 0; j < inner_count; j++)
516
+ *(uint16_t *)(row_dst + j*2) = *(uint16_t *)(src + j*inner_step);
517
+ break;
518
+ case 4:
519
+ for (j = 0; j < inner_count; j++)
520
+ *(uint32_t *)(row_dst + j*4) = *(uint32_t *)(src + j*inner_step);
521
+ break;
522
+ case 8:
523
+ for (j = 0; j < inner_count; j++)
524
+ *(uint64_t *)(row_dst + j*8) = *(uint64_t *)(src + j*inner_step);
525
+ break;
526
+ default:
527
+ for (j = 0; j < inner_count; j++)
528
+ memcpy(row_dst + j*bytes, src + j*inner_step, bytes);
529
+ break;
530
+ }
531
+ }
532
+ else { /* CA_AXIS_KIND_INDEX */
533
+ switch ( bytes ) {
534
+ case 1:
535
+ for (j = 0; j < inner_count; j++)
536
+ *(uint8_t *)(row_dst + j) =
537
+ *(uint8_t *)(row_src_base + inner_indices[j]*inner_pstride);
538
+ break;
539
+ case 2:
540
+ for (j = 0; j < inner_count; j++)
541
+ *(uint16_t *)(row_dst + j*2) =
542
+ *(uint16_t *)(row_src_base + inner_indices[j]*inner_pstride);
543
+ break;
544
+ case 4:
545
+ for (j = 0; j < inner_count; j++)
546
+ *(uint32_t *)(row_dst + j*4) =
547
+ *(uint32_t *)(row_src_base + inner_indices[j]*inner_pstride);
548
+ break;
549
+ case 8:
550
+ for (j = 0; j < inner_count; j++)
551
+ *(uint64_t *)(row_dst + j*8) =
552
+ *(uint64_t *)(row_src_base + inner_indices[j]*inner_pstride);
553
+ break;
554
+ default:
555
+ for (j = 0; j < inner_count; j++)
556
+ memcpy(row_dst + j*bytes,
557
+ row_src_base + inner_indices[j]*inner_pstride, bytes);
558
+ break;
559
+ }
560
+ }
561
+ }
562
+
563
+ if ( outers_n > 0 ) {
564
+ for ( k = outers_n - 1; k >= 0; k-- ) {
565
+ if ( ++outer_idx[k] < outer_prefix[k].count ) break;
566
+ outer_idx[k] = 0;
567
+ }
568
+ }
569
+ }
570
+ }
571
+
572
+ /* Gather engine: fill a caller-provided buffer with the view's
573
+ data. Caller must have parent->ptr valid (parent attached) and
574
+ `out_buf` large enough to hold total_elements * bytes.
575
+
576
+ Generic path is a thin wrapper around `for_each_slab`; per-cell
577
+ mode (slab_bytes == bytes, inner ∈ {STRIDE, INDEX}) dispatches to
578
+ the specialised inner-axis-hoisted loop above. */
579
+ void
580
+ ca_axis_dispatch_gather (CArray *parent,
581
+ const ca_size_t *parent_axis_dims,
582
+ ca_axis_desc_t *axes,
583
+ int8_t ndim,
584
+ ca_size_t bytes,
585
+ ca_size_t total_elements,
586
+ const void *bound_fill,
587
+ char *out_buf)
588
+ {
589
+ ca_axis_desc_t laxes[CA_RANK_MAX];
590
+ ca_size_t pstrides[CA_RANK_MAX];
591
+ ca_size_t mdim[CA_RANK_MAX];
592
+ int8_t lndim, sstart;
593
+ ca_size_t sbytes, sbase;
594
+
595
+ if ( total_elements == 0 ) return;
596
+
597
+ ca_axis_dispatch_prepare(parent_axis_dims, axes, ndim, bytes,
598
+ laxes, pstrides, mdim, &lndim);
599
+ ca_axis_dispatch_layout(laxes, pstrides, mdim, lndim, bytes,
600
+ &sstart, &sbytes, &sbase);
601
+
602
+ if ( sstart == lndim && lndim >= 1
603
+ && (laxes[lndim-1].kind == CA_AXIS_KIND_STRIDE
604
+ || laxes[lndim-1].kind == CA_AXIS_KIND_INDEX) ) {
605
+ ca_axis_dispatch_percell_gather(parent, laxes, pstrides, lndim, bytes,
606
+ sbase, bound_fill, out_buf);
607
+ return;
608
+ }
609
+
610
+ /* Row-select fast path: outer = single INDEX axis, slab inner
611
+ promoted (row-major boolean/fancy select + materialise). An
612
+ inlined tight loop replaces `for_each_slab`'s function-pointer
613
+ cb dispatch on the dataframe-like row-select idiom.
614
+ Eligibility:
615
+ - sstart == 1 (exactly one prefix axis outside the slab)
616
+ - the prefix axis is INDEX (= no OOB, no SHIFT)
617
+ - parent->ptr attached (cold path stays on for_each_slab). */
618
+ if ( sstart == 1
619
+ && laxes[0].kind == CA_AXIS_KIND_INDEX
620
+ && parent->ptr != NULL ) {
621
+ const ca_size_t *indices = laxes[0].indices;
622
+ ca_size_t n = laxes[0].count;
623
+ ca_size_t pstride = pstrides[0];
624
+ char *base = parent->ptr + sbase;
625
+ ca_size_t i;
626
+ /* Hot bytes specializations using __builtin_memcpy with literal size
627
+ so clang lowers to NEON ldp/stp directly (no libc memcpy call). */
628
+ if ( sbytes == 8 ) {
629
+ for ( i = 0; i < n; i++ ) {
630
+ *(uint64_t *)(out_buf + i*8) =
631
+ *(uint64_t *)(base + indices[i] * pstride);
632
+ }
633
+ } else if ( sbytes == 16 ) {
634
+ for ( i = 0; i < n; i++ ) {
635
+ __builtin_memcpy(out_buf + i*16, base + indices[i] * pstride, 16);
636
+ }
637
+ } else if ( sbytes == 32 ) {
638
+ for ( i = 0; i < n; i++ ) {
639
+ __builtin_memcpy(out_buf + i*32, base + indices[i] * pstride, 32);
640
+ }
641
+ } else if ( sbytes == 64 ) {
642
+ for ( i = 0; i < n; i++ ) {
643
+ __builtin_memcpy(out_buf + i*64, base + indices[i] * pstride, 64);
644
+ }
645
+ } else if ( sbytes == 128 ) {
646
+ for ( i = 0; i < n; i++ ) {
647
+ __builtin_memcpy(out_buf + i*128, base + indices[i] * pstride, 128);
648
+ }
649
+ } else if ( sbytes == 256 ) {
650
+ for ( i = 0; i < n; i++ ) {
651
+ __builtin_memcpy(out_buf + i*256, base + indices[i] * pstride, 256);
652
+ }
653
+ } else {
654
+ for ( i = 0; i < n; i++ ) {
655
+ memcpy(out_buf + i * sbytes, base + indices[i] * pstride, sbytes);
656
+ }
657
+ }
658
+ return;
659
+ }
660
+
661
+ {
662
+ ca_axis_xfer_ctx_t ctx = {
663
+ .buf = out_buf, .off = 0, .bound_fill = bound_fill, .bytes = bytes,
664
+ .parent_ptr = parent->ptr
665
+ };
666
+ ca_axis_dispatch_for_each_slab(parent, parent_axis_dims, axes, ndim,
667
+ bytes, total_elements,
668
+ ca_axis_dispatch_gather_cb, &ctx);
669
+ }
670
+ }
671
+
672
+ /* Allocate-and-gather wrapper. Returns a malloced buffer of size
673
+ total_elements * bytes (or a 1-byte placeholder when total_elements
674
+ == 0; avoids malloc(0) implementation variance). Caller must xfree. */
675
+ char *
676
+ ca_axis_dispatch_attach (CArray *parent,
677
+ const ca_size_t *parent_axis_dims,
678
+ ca_axis_desc_t *axes,
679
+ int8_t ndim,
680
+ ca_size_t bytes,
681
+ ca_size_t total_elements,
682
+ const void *bound_fill)
683
+ {
684
+ ca_size_t out_len = total_elements * bytes;
685
+ char *out = xmalloc(out_len > 0 ? out_len : 1);
686
+ ca_axis_dispatch_gather(parent, parent_axis_dims, axes, ndim, bytes,
687
+ total_elements, bound_fill, out);
688
+ return out;
689
+ }
690
+
691
+ /* Scatter engine: write the caller-provided buffer back into
692
+ parent. Caller must have parent->ptr valid (parent attached) and
693
+ `in_buf` sized total_elements * bytes.
694
+
695
+ Semantics: output row-major iteration order; duplicate INDEX
696
+ values produce last-write-wins. Thin wrapper around for_each_slab
697
+ + scatter_cb; per-cell mode dispatches to
698
+ `ca_axis_dispatch_percell_scatter` below. */
699
+ /* Mirror of percell_gather: per-cell PUT path used when slab_bytes ==
700
+ bytes and inner ∈ {STRIDE, INDEX}. OOB outer rows are skipped (scatter
701
+ semantics). */
702
+ static void
703
+ ca_axis_dispatch_percell_scatter (CArray *parent,
704
+ ca_axis_desc_t *laxes,
705
+ const ca_size_t *pstrides,
706
+ int8_t lndim,
707
+ ca_size_t bytes,
708
+ ca_size_t slab_base,
709
+ const char *in_buf)
710
+ {
711
+ int8_t inner = lndim - 1;
712
+ int8_t outers_n = inner;
713
+ ca_op_prefix_axis_t outer_prefix[CA_RANK_MAX];
714
+ ca_size_t outer_idx[CA_RANK_MAX];
715
+ ca_size_t n_outer = 1;
716
+ int8_t k;
717
+
718
+ if ( outers_n > 0 ) {
719
+ ca_axis_dispatch_classify_prefix(laxes, pstrides, outers_n, outer_prefix);
720
+ for ( k = 0; k < outers_n; k++ ) {
721
+ outer_idx[k] = 0;
722
+ n_outer *= outer_prefix[k].count;
723
+ }
724
+ }
725
+
726
+ ca_axis_kind_t inner_kind = laxes[inner].kind;
727
+ ca_size_t inner_count = laxes[inner].count;
728
+ ca_size_t inner_pstride = pstrides[inner];
729
+ ca_size_t inner_base = laxes[inner].start * inner_pstride;
730
+ ca_size_t inner_step = laxes[inner].step * inner_pstride;
731
+ const ca_size_t *inner_indices = laxes[inner].indices;
732
+ ca_size_t row_src_bytes = inner_count * bytes;
733
+
734
+ ca_size_t no;
735
+ for ( no = 0; no < n_outer; no++ ) {
736
+ int oob = 0;
737
+ ca_size_t outer_off = 0;
738
+ if ( outers_n > 0 ) {
739
+ outer_off = ca_axis_dispatch_prefix_offset(outer_prefix, outer_idx,
740
+ outers_n, &oob);
741
+ }
742
+
743
+ if ( !oob ) {
744
+ const char *row_src = in_buf + no * row_src_bytes;
745
+ char *row_dst_base = parent->ptr + slab_base + outer_off;
746
+ ca_size_t j;
747
+ if ( inner_kind == CA_AXIS_KIND_STRIDE ) {
748
+ char *dst = row_dst_base + inner_base;
749
+ switch ( bytes ) {
750
+ case 1:
751
+ for (j = 0; j < inner_count; j++)
752
+ *(uint8_t *)(dst + j*inner_step) = *(uint8_t *)(row_src + j);
753
+ break;
754
+ case 2:
755
+ for (j = 0; j < inner_count; j++)
756
+ *(uint16_t *)(dst + j*inner_step) = *(uint16_t *)(row_src + j*2);
757
+ break;
758
+ case 4:
759
+ for (j = 0; j < inner_count; j++)
760
+ *(uint32_t *)(dst + j*inner_step) = *(uint32_t *)(row_src + j*4);
761
+ break;
762
+ case 8:
763
+ for (j = 0; j < inner_count; j++)
764
+ *(uint64_t *)(dst + j*inner_step) = *(uint64_t *)(row_src + j*8);
765
+ break;
766
+ default:
767
+ for (j = 0; j < inner_count; j++)
768
+ memcpy(dst + j*inner_step, row_src + j*bytes, bytes);
769
+ break;
770
+ }
771
+ }
772
+ else { /* CA_AXIS_KIND_INDEX */
773
+ switch ( bytes ) {
774
+ case 1:
775
+ for (j = 0; j < inner_count; j++)
776
+ *(uint8_t *)(row_dst_base + inner_indices[j]*inner_pstride) =
777
+ *(uint8_t *)(row_src + j);
778
+ break;
779
+ case 2:
780
+ for (j = 0; j < inner_count; j++)
781
+ *(uint16_t *)(row_dst_base + inner_indices[j]*inner_pstride) =
782
+ *(uint16_t *)(row_src + j*2);
783
+ break;
784
+ case 4:
785
+ for (j = 0; j < inner_count; j++)
786
+ *(uint32_t *)(row_dst_base + inner_indices[j]*inner_pstride) =
787
+ *(uint32_t *)(row_src + j*4);
788
+ break;
789
+ case 8:
790
+ for (j = 0; j < inner_count; j++)
791
+ *(uint64_t *)(row_dst_base + inner_indices[j]*inner_pstride) =
792
+ *(uint64_t *)(row_src + j*8);
793
+ break;
794
+ default:
795
+ for (j = 0; j < inner_count; j++)
796
+ memcpy(row_dst_base + inner_indices[j]*inner_pstride,
797
+ row_src + j*bytes, bytes);
798
+ break;
799
+ }
800
+ }
801
+ }
802
+
803
+ if ( outers_n > 0 ) {
804
+ for ( k = outers_n - 1; k >= 0; k-- ) {
805
+ if ( ++outer_idx[k] < outer_prefix[k].count ) break;
806
+ outer_idx[k] = 0;
807
+ }
808
+ }
809
+ }
810
+ }
811
+
812
+ void
813
+ ca_axis_dispatch_scatter (CArray *parent,
814
+ const ca_size_t *parent_axis_dims,
815
+ ca_axis_desc_t *axes,
816
+ int8_t ndim,
817
+ ca_size_t bytes,
818
+ ca_size_t total_elements,
819
+ const char *in_buf)
820
+ {
821
+ ca_axis_desc_t laxes[CA_RANK_MAX];
822
+ ca_size_t pstrides[CA_RANK_MAX];
823
+ ca_size_t mdim[CA_RANK_MAX];
824
+ int8_t lndim, sstart;
825
+ ca_size_t sbytes, sbase;
826
+
827
+ if ( total_elements == 0 ) return;
828
+
829
+ ca_axis_dispatch_prepare(parent_axis_dims, axes, ndim, bytes,
830
+ laxes, pstrides, mdim, &lndim);
831
+ ca_axis_dispatch_layout(laxes, pstrides, mdim, lndim, bytes,
832
+ &sstart, &sbytes, &sbase);
833
+
834
+ if ( sstart == lndim && lndim >= 1
835
+ && (laxes[lndim-1].kind == CA_AXIS_KIND_STRIDE
836
+ || laxes[lndim-1].kind == CA_AXIS_KIND_INDEX) ) {
837
+ ca_axis_dispatch_percell_scatter(parent, laxes, pstrides, lndim, bytes,
838
+ sbase, in_buf);
839
+ return;
840
+ }
841
+
842
+ {
843
+ /* scatter_cb only reads from buf, so the const_cast is safe.
844
+ bound_fill is irrelevant on scatter (OOB cells are skipped). */
845
+ ca_axis_xfer_ctx_t ctx = {
846
+ .buf = (char *) in_buf, .off = 0, .bound_fill = NULL, .bytes = bytes,
847
+ .parent_ptr = parent->ptr
848
+ };
849
+ ca_axis_dispatch_for_each_slab(parent, parent_axis_dims, axes, ndim,
850
+ bytes, total_elements,
851
+ ca_axis_dispatch_scatter_cb, &ctx);
852
+ }
853
+ }
854
+
855
+ /* Broadcast-fill engine: write the single value `val` (bytes wide)
856
+ into every selected parent cell. Caller must have parent->ptr
857
+ valid.
858
+
859
+ Semantics: same iteration as scatter (output row-major). For
860
+ duplicate INDEX values the same value is written N times — the
861
+ end-state is identical to a single write, so last-write-wins is
862
+ trivially satisfied. Thin wrapper around for_each_slab +
863
+ fill_value_cb. */
864
+ void
865
+ ca_axis_dispatch_fill_value (CArray *parent,
866
+ const ca_size_t *parent_axis_dims,
867
+ ca_axis_desc_t *axes,
868
+ int8_t ndim,
869
+ ca_size_t bytes,
870
+ ca_size_t total_elements,
871
+ const void *val)
872
+ {
873
+ ca_axis_fill_ctx_t ctx = { .val = val, .bytes = bytes,
874
+ .parent_ptr = parent->ptr, .parent = NULL };
875
+ ca_axis_dispatch_for_each_slab(parent, parent_axis_dims, axes, ndim,
876
+ bytes, total_elements,
877
+ ca_axis_dispatch_fill_value_cb, &ctx);
878
+ }
879
+
880
+ /* Same walk, but each slab is handed to the parent as a region of its own
881
+ rather than written through a pointer into it. A gather view whose parent
882
+ has to be materialised to be addressed can then write the cells it selected
883
+ without the parent being pulled in whole and pushed back. */
884
+ void
885
+ ca_axis_dispatch_fill_value_via_parent (CArray *parent,
886
+ const ca_size_t *parent_axis_dims,
887
+ ca_axis_desc_t *axes,
888
+ int8_t ndim,
889
+ ca_size_t bytes,
890
+ ca_size_t total_elements,
891
+ const void *val)
892
+ {
893
+ ca_axis_fill_ctx_t ctx = { .val = val, .bytes = bytes,
894
+ .parent_ptr = NULL, .parent = parent };
895
+ ca_axis_dispatch_for_each_slab(parent, parent_axis_dims, axes, ndim,
896
+ bytes, total_elements,
897
+ ca_axis_dispatch_fill_value_cb, &ctx);
898
+ }
899
+
900
+ /* ==========================================================================
901
+ Innermost-STRIDE L2 alias helpers for kernel_iterator descriptor
902
+ source routing. Called from ca_kernel_iterator.c to decide
903
+ whether the descriptor source can alias into an L2-strided walk.
904
+ ========================================================================== */
905
+
906
+ int
907
+ ca_axis_dispatch_is_innermost_stride (const ca_axis_desc_t *descs,
908
+ int8_t ndim)
909
+ {
910
+ if ( ndim <= 0 ) return 0;
911
+ return descs[ndim - 1].kind == CA_AXIS_KIND_STRIDE;
912
+ }
913
+
914
+ int
915
+ ca_axis_dispatch_outer_has_shift (const ca_axis_desc_t *descs,
916
+ int8_t ndim)
917
+ {
918
+ int8_t k;
919
+ if ( ndim <= 1 ) return 0;
920
+ for ( k = 0; k < ndim - 1; k++ ) {
921
+ if ( descs[k].kind == CA_AXIS_KIND_SHIFT ) return 1;
922
+ }
923
+ return 0;
924
+ }