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,1306 @@
1
+ /* ---------------------------------------------------------------------------
2
+
3
+ CASelectAxis: per-axis boolean selection view (sister of CAGrid for integer
4
+ index). Mixes AP-axes (nil/Integer/Range) with one boolean-INDIRECT axis,
5
+ and gathers each per-INDIRECT slab using row-major byte strides.
6
+
7
+ Sibling of ca_obj_grid.c (CAGrid, the integer-index counterpart); both
8
+ emit a per-axis descriptor consumed by the shared engine in
9
+ ca_axis_dispatch.c.
10
+
11
+ Scope:
12
+ - single INDIRECT axis (boolean), other axes AP (nil/Integer/Range)
13
+ - parent must be an entity CArray (no CAStride parent)
14
+ - selector copied at construction (fully static, fragility-free)
15
+ - selector must not have a mask (hard reject)
16
+ - slab copy uses straightforward nested-loop memcpy (no SIMD fast paths)
17
+
18
+ ---------------------------------------------------------------------------- */
19
+
20
+ #include "carray.h"
21
+
22
+ typedef struct {
23
+ /* CAView prefix (mirrors carray.h ca_virtual layout) */
24
+ int16_t obj_type;
25
+ int8_t data_type;
26
+ int8_t ndim;
27
+ int32_t flags;
28
+ ca_size_t bytes;
29
+ ca_size_t elements;
30
+ ca_size_t *dim;
31
+ char *ptr;
32
+ CArray *mask;
33
+ char *_pool; /* framework-managed pool buffer (NULL = legacy ALLOC_N path). */
34
+ CArray *parent;
35
+ uint32_t attach;
36
+ uint8_t nosync;
37
+ /* --- CASelectAxis tail ---
38
+ Single INDIRECT axis (boolean); the other axes are AP. */
39
+ int8_t indirect_axis; /* axis index of the boolean-selected axis */
40
+ CArray *selector; /* owned copy of boolean CArray (no mask) */
41
+ ca_size_t *ap_start; /* per axis (size ndim); unused for indirect axis */
42
+ ca_size_t *ap_count; /* per axis (size ndim); = dim[k] for AP axes */
43
+ ca_size_t *ap_step; /* per axis (size ndim); unused for indirect axis */
44
+ /* Derived at attach time; freed at detach. */
45
+ ca_size_t *indices; /* resolved indices for indirect axis (length = dim[indirect_axis]) */
46
+ /* Constant-step detection on indices. When indices form a constant-step
47
+ sequence (= consecutive TRUE block, full-TRUE mask, equally-spaced
48
+ mask), describe_axes emits STRIDE kind instead of INDEX, letting the
49
+ engine's STRIDE fast path (contiguous memcpy when step==1) replace
50
+ per-cell INDEX lookups. */
51
+ uint8_t stride_kind; /* 1 if indices form constant-step run */
52
+ ca_size_t stride_start;
53
+ ca_size_t stride_step;
54
+ } CASelectAxis;
55
+
56
+ static size_t
57
+ ca_select_axis_dsize (const void *ap)
58
+ {
59
+ const CASelectAxis *ca = (const CASelectAxis *) ap;
60
+ return sizeof(CASelectAxis)
61
+ + ca->ndim * sizeof(ca_size_t) /* dim */
62
+ + 3 * ca->ndim * sizeof(ca_size_t); /* ap_start, ap_count, ap_step */
63
+ }
64
+
65
+ /* Pool framework hooks: the four ndim-sized arrays (dim, ap_start,
66
+ ap_count, ap_step) move into one _pool buffer. The variable-size
67
+ `indices` (count_true) and the `selector` CArray copy stay separate. */
68
+ static size_t
69
+ ca_select_axis_pool_bytes (int8_t ndim)
70
+ {
71
+ ca_size_t n = (ndim > 0) ? ndim : 1;
72
+ return 4 * (size_t) n * sizeof(ca_size_t);
73
+ }
74
+
75
+ static void
76
+ ca_select_axis_pool_init (void *ap, int8_t ndim)
77
+ {
78
+ CASelectAxis *ca = (CASelectAxis *) ap;
79
+ ca_size_t n = (ndim > 0) ? ndim : 1;
80
+ ca_size_t *base = (ca_size_t *) ca->_pool;
81
+ ca->dim = base + 0 * n;
82
+ ca->ap_start = base + 1 * n;
83
+ ca->ap_count = base + 2 * n;
84
+ ca->ap_step = base + 3 * n;
85
+ }
86
+
87
+ const rb_data_type_t caselectaxis_data_type = {
88
+ .parent = &caview_data_type,
89
+ .wrap_struct_name = "CASelectAxis",
90
+ .function = {
91
+ .dmark = ca_mark,
92
+ .dfree = ca_free,
93
+ .dsize = ca_select_axis_dsize,
94
+ .dcompact = NULL
95
+ },
96
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
97
+ };
98
+
99
+ const rb_data_type_t caselectaxis_mask_data_type = {
100
+ .parent = &caselectaxis_data_type,
101
+ .wrap_struct_name = "CASelectAxisMask",
102
+ .function = {
103
+ .dmark = NULL,
104
+ .dfree = ca_free_nop,
105
+ .dsize = ca_select_axis_dsize,
106
+ .dcompact = NULL
107
+ },
108
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
109
+ };
110
+
111
+ static int8_t CA_OBJ_SELECT_AXIS;
112
+
113
+ /* Benchmark-only bypass flag: when non-zero, rb_ca_grid skips the
114
+ CASelectAxis dispatch hook and falls through to the pre-CSA CAGrid path.
115
+ Exposed to Ruby as CArray._csa_bypass=. Production code never touches it. */
116
+ int ca_csa_dispatch_bypass = 0;
117
+
118
+ static VALUE
119
+ rb_ca_s_csa_bypass_eq (VALUE klass, VALUE val)
120
+ {
121
+ (void) klass;
122
+ ca_csa_dispatch_bypass = RTEST(val) ? 1 : 0;
123
+ return val;
124
+ }
125
+
126
+ static VALUE
127
+ rb_ca_s_csa_bypass_p (VALUE klass)
128
+ {
129
+ (void) klass;
130
+ return ca_csa_dispatch_bypass ? Qtrue : Qfalse;
131
+ }
132
+
133
+ static VALUE rb_cCASelectAxis;
134
+ static VALUE rb_cCASelectAxisMask;
135
+
136
+ /* Forward declarations of internal helpers */
137
+ static CArray *ca_select_axis_new (CArray *parent, int8_t indirect_axis,
138
+ CArray *selector,
139
+ ca_size_t *ap_start,
140
+ ca_size_t *ap_count,
141
+ ca_size_t *ap_step);
142
+
143
+ /* ------------------------------------------------------------------- */
144
+ /* Setup / constructor / destructor */
145
+ /* ------------------------------------------------------------------- */
146
+
147
+ static int
148
+ ca_select_axis_setup (CASelectAxis *ca, CArray *parent, int8_t indirect_axis,
149
+ CArray *selector,
150
+ ca_size_t *ap_start, ca_size_t *ap_count, ca_size_t *ap_step)
151
+ {
152
+ int8_t k;
153
+ int8_t ndim = parent->ndim;
154
+
155
+ /* Validate indirect axis range */
156
+ if ( indirect_axis < 0 || indirect_axis >= ndim ) {
157
+ rb_raise(rb_eArgError,
158
+ "CASelectAxis: indirect_axis %d out of range [0, %d)",
159
+ indirect_axis, ndim);
160
+ }
161
+
162
+ /* Validate selector */
163
+ if ( ! ca_is_boolean_type(selector) ) {
164
+ rb_raise(rb_eArgError,
165
+ "CASelectAxis: selector must be a boolean CArray");
166
+ }
167
+ if ( selector->ndim != 1 ) {
168
+ rb_raise(rb_eArgError,
169
+ "CASelectAxis: selector must be 1-D");
170
+ }
171
+ if ( selector->dim[0] != parent->dim[indirect_axis] ) {
172
+ rb_raise(rb_eArgError,
173
+ "CASelectAxis: selector length (%lld) != parent dim[%d] (%lld)",
174
+ (long long) selector->dim[0],
175
+ (int) indirect_axis,
176
+ (long long) parent->dim[indirect_axis]);
177
+ }
178
+ if ( ca_has_mask(selector) ) {
179
+ rb_raise(rb_eArgError,
180
+ "CASelectAxis: selector boolean array must not have a mask. "
181
+ "Use `selector.strip_mask(false)` etc. to flatten first.");
182
+ }
183
+
184
+ /* Validate AP axes */
185
+ for ( k = 0; k < ndim; k++ ) {
186
+ if ( k == indirect_axis ) continue;
187
+ if ( ap_step[k] == 0 ) {
188
+ rb_raise(rb_eArgError, "CASelectAxis: ap_step[%d] is 0", (int) k);
189
+ }
190
+ if ( ap_count[k] <= 0 ) {
191
+ rb_raise(rb_eArgError, "CASelectAxis: ap_count[%d] must be positive", (int) k);
192
+ }
193
+ {
194
+ ca_size_t start_v = ap_start[k];
195
+ ca_size_t last_v = ap_start[k] + (ap_count[k] - 1) * ap_step[k];
196
+ if ( start_v < 0 || start_v >= parent->dim[k] ) {
197
+ rb_raise(rb_eIndexError,
198
+ "CASelectAxis: ap_start[%d]=%lld out of range [0, %lld)",
199
+ (int) k, (long long) start_v, (long long) parent->dim[k]);
200
+ }
201
+ if ( last_v < 0 || last_v >= parent->dim[k] ) {
202
+ rb_raise(rb_eIndexError,
203
+ "CASelectAxis: ap last index at axis %d (%lld) out of range [0, %lld)",
204
+ (int) k, (long long) last_v, (long long) parent->dim[k]);
205
+ }
206
+ }
207
+ }
208
+
209
+ /* Initialise struct */
210
+ ca->obj_type = CA_OBJ_SELECT_AXIS;
211
+ ca->data_type = parent->data_type;
212
+ ca->flags = 0;
213
+ ca->ndim = ndim;
214
+ ca->bytes = parent->bytes;
215
+ ca->ptr = NULL;
216
+ ca->mask = NULL;
217
+ ca->parent = parent;
218
+ ca->attach = 0;
219
+ ca->nosync = 0;
220
+ ca->indirect_axis = indirect_axis;
221
+ ca->indices = NULL;
222
+
223
+ /* Copy selector (view owns its own copy; immune to external mutation).
224
+ The selector is small (1 axis length), so the copy cost is acceptable,
225
+ and it avoids the fragility CASelect has from live references. */
226
+ ca->selector = ca_copy(selector);
227
+
228
+ /* Allocate dim and AP arrays (pool path: already wired by pool_init). */
229
+ if ( ! ca->_pool ) {
230
+ ca->dim = ALLOC_N(ca_size_t, ndim);
231
+ ca->ap_start = ALLOC_N(ca_size_t, ndim);
232
+ ca->ap_count = ALLOC_N(ca_size_t, ndim);
233
+ ca->ap_step = ALLOC_N(ca_size_t, ndim);
234
+ }
235
+
236
+ /* Walk selector twice: first to count (auto-vectorisable), then to
237
+ snapshot TRUE positions into exactly-sized indices[]. Allocating to
238
+ the exact count avoids large over-allocations for sparse selectors.
239
+ This is the SOLE selector-walk phase in the view's lifecycle. */
240
+ ca_size_t count_true = 0;
241
+ ca_size_t n_input = ca->selector->elements;
242
+ {
243
+ boolean8_t *s = (boolean8_t *) ca->selector->ptr;
244
+ ca_size_t i;
245
+ for ( i = 0; i < n_input; i++ ) {
246
+ count_true += s[i]; /* boolean stored as 0/1, no branch */
247
+ }
248
+ }
249
+ ca->indices = ALLOC_N(ca_size_t, count_true > 0 ? count_true : 1);
250
+ {
251
+ boolean8_t *s = (boolean8_t *) ca->selector->ptr;
252
+ ca_size_t i, j = 0;
253
+ for ( i = 0; i < n_input; i++ ) {
254
+ if ( s[i] ) ca->indices[j++] = i;
255
+ }
256
+ }
257
+
258
+ /* Constant-step detection: do indices[] form a constant-step sequence?
259
+ When YES, describe_axes emits STRIDE kind (start/step/count) instead of INDEX,
260
+ unlocking the engine's contig-memcpy fast path for common patterns
261
+ (all-TRUE mask, contiguous TRUE block, equally-spaced mask). */
262
+ ca->stride_kind = 0;
263
+ ca->stride_start = 0;
264
+ ca->stride_step = 1;
265
+ if ( count_true >= 2 ) {
266
+ ca_size_t step = ca->indices[1] - ca->indices[0];
267
+ ca_size_t i;
268
+ int is_const = 1;
269
+ for ( i = 2; i < count_true; i++ ) {
270
+ if ( ca->indices[i] - ca->indices[i-1] != step ) { is_const = 0; break; }
271
+ }
272
+ if ( is_const ) {
273
+ ca->stride_kind = 1;
274
+ ca->stride_start = ca->indices[0];
275
+ ca->stride_step = step;
276
+ }
277
+ } else if ( count_true == 1 ) {
278
+ /* single TRUE: degenerate STRIDE (count=1, step irrelevant) */
279
+ ca->stride_kind = 1;
280
+ ca->stride_start = ca->indices[0];
281
+ ca->stride_step = 1;
282
+ }
283
+
284
+ /* Populate dim and AP arrays */
285
+ for ( k = 0; k < ndim; k++ ) {
286
+ if ( k == indirect_axis ) {
287
+ ca->dim[k] = count_true;
288
+ ca->ap_start[k] = 0;
289
+ ca->ap_count[k] = count_true;
290
+ ca->ap_step[k] = 1;
291
+ } else {
292
+ ca->dim[k] = ap_count[k];
293
+ ca->ap_start[k] = ap_start[k];
294
+ ca->ap_count[k] = ap_count[k];
295
+ ca->ap_step[k] = ap_step[k];
296
+ }
297
+ }
298
+
299
+ /* Compute total elements */
300
+ ca->elements = 1;
301
+ for ( k = 0; k < ndim; k++ ) {
302
+ ca->elements *= ca->dim[k];
303
+ }
304
+
305
+ return 0;
306
+ }
307
+
308
+ static CArray *
309
+ ca_select_axis_new (CArray *parent, int8_t indirect_axis,
310
+ CArray *selector,
311
+ ca_size_t *ap_start, ca_size_t *ap_count, ca_size_t *ap_step)
312
+ {
313
+ CASelectAxis *ca = (CASelectAxis *) ca_array_alloc(CA_OBJ_SELECT_AXIS, parent->ndim);
314
+ ca_select_axis_setup(ca, parent, indirect_axis, selector,
315
+ ap_start, ap_count, ap_step);
316
+ return (CArray *) ca;
317
+ }
318
+
319
+ static void
320
+ free_ca_select_axis (void *ap)
321
+ {
322
+ CASelectAxis *ca = (CASelectAxis *) ap;
323
+ if ( ca != NULL ) {
324
+ ca_free(ca->mask);
325
+ if ( ca->selector ) ca_free(ca->selector);
326
+ if ( ca->indices ) xfree(ca->indices); /* variable-size, separate */
327
+ if ( ca->_pool ) {
328
+ /* dim/ap_start/ap_count/ap_step all live in _pool. */
329
+ ca_array_free(ca);
330
+ }
331
+ else {
332
+ if ( ca->ap_start ) xfree(ca->ap_start);
333
+ if ( ca->ap_count ) xfree(ca->ap_count);
334
+ if ( ca->ap_step ) xfree(ca->ap_step);
335
+ if ( ca->dim ) xfree(ca->dim);
336
+ xfree(ca);
337
+ }
338
+ }
339
+ }
340
+
341
+ /* ------------------------------------------------------------------- */
342
+ /* Operation functions */
343
+ /* */
344
+ /* All gather / scatter / fill paths delegate to the common descriptor */
345
+ /* engine in ext/ca_axis_dispatch.c. This file holds only view-specific */
346
+ /* concerns: setup, describe_axes, mask, lifecycle. */
347
+ /* ------------------------------------------------------------------- */
348
+
349
+ static void *
350
+ ca_select_axis_func_clone (void *ap)
351
+ {
352
+ CASelectAxis *ca = (CASelectAxis *) ap;
353
+ return ca_select_axis_new(ca->parent, ca->indirect_axis, ca->selector,
354
+ ca->ap_start, ca->ap_count, ca->ap_step);
355
+ }
356
+
357
+ /* Compute parent index for a single output index, without requiring attach.
358
+ Uses the pre-computed ca->indices[] snapshot (built once at setup) for the
359
+ indirect axis -- O(1) indirect load. */
360
+ static void
361
+ ca_select_axis_output_to_parent_idx (CASelectAxis *ca, ca_size_t *out_idx,
362
+ ca_size_t *parent_idx)
363
+ {
364
+ int8_t k;
365
+ int8_t iax = ca->indirect_axis;
366
+ for ( k = 0; k < ca->ndim; k++ ) {
367
+ if ( k == iax ) {
368
+ parent_idx[k] = ca->indices[out_idx[k]];
369
+ } else {
370
+ parent_idx[k] = ca->ap_start[k] + out_idx[k] * ca->ap_step[k];
371
+ }
372
+ }
373
+ }
374
+
375
+ /* per-cell logic lives in ca_select_axis_func_xfer_index; forward-declared
376
+ for the region-delivery paths below. */
377
+ static void ca_select_axis_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir);
378
+
379
+ /* dir-unified per-cell. Direct addressing when attached; otherwise
380
+ translate the output index to
381
+ parent's index and delegate to ca_xfer_index (which handles boundary-view
382
+ parents safely -- replaces the old ptr_at_index-based path). */
383
+ static void
384
+ ca_select_axis_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
385
+ {
386
+ CASelectAxis *ca = (CASelectAxis *) ap;
387
+ if ( ca->ptr ) {
388
+ ca_size_t addr = ca_index2addr((CArray *) ca, idx);
389
+ char *p = ca->ptr + ca->bytes * addr;
390
+ if (dir == CA_XFER_GET) memcpy(data, p, ca->bytes);
391
+ else memcpy(p, data, ca->bytes);
392
+ return;
393
+ }
394
+ /* Not attached: compute parent's index, delegate. */
395
+ ca_size_t pidx[CA_RANK_MAX];
396
+ ca_select_axis_output_to_parent_idx(ca, idx, pidx);
397
+ ca_xfer_index(ca->parent, pidx, data, dir);
398
+ }
399
+
400
+ /* Batched address gather/scatter. Per-axis output->parent index remap,
401
+ always in-bounds. ONE parent ca_xfer_addrs call -- no whole-view attach.
402
+
403
+ Fast path: when the addrs are a whole-view sequential run
404
+ [0..elements-1] and parent.ptr is present, bypass the ALLOCV per-cell
405
+ remap loop and dispatch directly to ca_axis_dispatch_gather/_scatter
406
+ (same engine xfer_all uses). This fires for the hot pattern
407
+ ca[:is_not_masked] / ca[mostly_true_mask], which degenerates to
408
+ whole-view sequential addrs through this slot. Sub-region sequential
409
+ addrs are not lifted (would require row-major rectangular
410
+ reconstruction); the per-cell path handles them. No ca_attach added;
411
+ parent.ptr is opportunistic. */
412
+ static void
413
+ ca_select_axis_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
414
+ void *data, int dir)
415
+ {
416
+ CASelectAxis *ca = (CASelectAxis *) ap;
417
+ CArray *parent = ca->parent;
418
+ ca_size_t *paddrs;
419
+ ca_size_t i, base;
420
+ volatile VALUE holder;
421
+
422
+ /* Opportunistic fast path. ca_resolve_attached_root_via_identity lifts
423
+ parent->ptr through an identity CAStride compose-fold (= a CARefer
424
+ reshape view over an entity, etc.). */
425
+ if ( n == ca->elements
426
+ && ca_xfer_addrs_is_sequential_run(n, addrs, &base) && base == 0 ) {
427
+ CArray *eff_parent = ca_resolve_attached_root_via_identity(parent);
428
+ if ( eff_parent->ptr ) {
429
+ ca_axis_desc_t sub_axes[CA_RANK_MAX];
430
+ ca_size_t parent_axis_dims[CA_RANK_MAX];
431
+ int8_t k, iax = ca->indirect_axis;
432
+ for ( k = 0; k < ca->ndim; k++ ) {
433
+ parent_axis_dims[k] = parent->dim[k];
434
+ if ( k == iax ) {
435
+ sub_axes[k].kind = CA_AXIS_KIND_INDEX;
436
+ sub_axes[k].count = ca->dim[k];
437
+ sub_axes[k].start = 0;
438
+ sub_axes[k].step = 0;
439
+ sub_axes[k].indices = ca->indices; /* pre-computed TRUE positions */
440
+ } else {
441
+ sub_axes[k].kind = CA_AXIS_KIND_STRIDE;
442
+ sub_axes[k].count = ca->dim[k];
443
+ sub_axes[k].start = ca->ap_start[k];
444
+ sub_axes[k].step = ca->ap_step[k];
445
+ sub_axes[k].indices = NULL;
446
+ }
447
+ }
448
+ if ( dir == CA_XFER_GET ) {
449
+ ca_axis_dispatch_gather(eff_parent, parent_axis_dims, sub_axes, ca->ndim,
450
+ ca->bytes, ca->elements, NULL, (char *) data);
451
+ } else {
452
+ ca_axis_dispatch_scatter(eff_parent, parent_axis_dims, sub_axes, ca->ndim,
453
+ ca->bytes, ca->elements, (char *) data);
454
+ }
455
+ return;
456
+ }
457
+ }
458
+
459
+ /* Per-cell remap fallback (arbitrary addrs, view parent, partial). */
460
+ paddrs = ALLOCV_N(ca_size_t, holder, n);
461
+ for (i = 0; i < n; i++) {
462
+ ca_size_t vidx[CA_RANK_MAX], pidx[CA_RANK_MAX];
463
+ ca_addr2index((CArray *) ca, addrs[i], vidx);
464
+ ca_select_axis_output_to_parent_idx(ca, vidx, pidx);
465
+ paddrs[i] = ca_index2addr(ca->parent, pidx);
466
+ }
467
+ ca_xfer_addrs(ca->parent, n, paddrs, data, dir);
468
+ ALLOCV_END(holder);
469
+ }
470
+
471
+ /* fold_stride: a CSA folds when every axis is stride-expressible. AP
472
+ (non-indirect) axes always are
473
+ (start_k + i*step_k). The boolean-selected (indirect) axis folds only when
474
+ it selects exactly one element (dim == 1): the single TRUE position bakes
475
+ into the base. A multi-element selection is a true gather -> decline.
476
+ Synthesises a CAStride over parent and composes f through it. */
477
+ static int
478
+ ca_select_axis_func_fold_stride (void *ap, ca_fold_t *f, void **next_parent)
479
+ {
480
+ CASelectAxis *ca = (CASelectAxis *) ap;
481
+ CArray *parent = ca->parent;
482
+ int8_t iax = ca->indirect_axis;
483
+ ca_size_t pstride[CA_RANK_MAX];
484
+ ca_size_t synth_strides[CA_RANK_MAX];
485
+ ca_size_t synth_base = 0;
486
+ ca_size_t next_strides[CA_RANK_MAX];
487
+ ca_size_t next_base;
488
+ ca_size_t s;
489
+ CAStride tmp, synth;
490
+ int8_t k;
491
+
492
+ if ( ca->dim[iax] != 1 ) {
493
+ return 0; /* multi-element boolean selection -> true gather, decline */
494
+ }
495
+
496
+ /* parent row-major byte strides (ca->bytes == parent->bytes) */
497
+ s = ca->bytes;
498
+ for (k = ca->ndim - 1; k >= 0; k--) {
499
+ pstride[k] = s;
500
+ s *= parent->dim[k];
501
+ }
502
+
503
+ for (k = 0; k < ca->ndim; k++) {
504
+ if ( k == iax ) {
505
+ /* single TRUE position (dim==1 case) -> baked into base (count 1).
506
+ Pre-computed in ca->indices[0] at setup; no re-scan of selector. */
507
+ synth_strides[k] = 0;
508
+ synth_base += ca->indices[0] * pstride[k];
509
+ }
510
+ else {
511
+ synth_strides[k] = ca->ap_step[k] * pstride[k];
512
+ synth_base += ca->ap_start[k] * pstride[k];
513
+ }
514
+ }
515
+
516
+ tmp.ndim = f->ndim;
517
+ tmp.bytes = ca->bytes;
518
+ tmp.dim = f->counts;
519
+ tmp.strides = f->strides;
520
+ tmp.base_offset = f->base;
521
+
522
+ synth.ndim = ca->ndim;
523
+ synth.bytes = ca->bytes;
524
+ synth.dim = ca->dim;
525
+ synth.strides = synth_strides;
526
+ synth.base_offset = synth_base;
527
+
528
+ if (!ca_stride_compose_through(&tmp, &synth, next_strides, &next_base)) {
529
+ return 0;
530
+ }
531
+
532
+ for (k = 0; k < f->ndim; k++) f->strides[k] = next_strides[k];
533
+ f->base = next_base;
534
+ *next_parent = parent;
535
+ return 1;
536
+ }
537
+
538
+ /* xfer_stride: structural region delivery when a multi-select CSA is the
539
+ (declining) fold boundary. Mirrors CAGrid:
540
+ the indirect (boolean-selected) axis behaves like INDEX, the AP axes like
541
+ STRIDE. Iterate the outer axes; deliver the innermost axis as one run --
542
+ parent.xfer_stride for an AP inner axis, parent.xfer_addrs for the indirect
543
+ inner axis. data is contiguous (semantics b, no whole-view attach).
544
+ The indirect axis's TRUE positions are resolved once from the selector
545
+ (snapshot, no attach). Non-axis-aligned / unexpected requests fall back to
546
+ per-cell delivery (the wiring guards ndim == parent ndim). */
547
+ static void
548
+ ca_select_axis_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
549
+ ca_size_t *strides, void *data, int dir)
550
+ {
551
+ CASelectAxis *ca = (CASelectAxis *) ap;
552
+ CArray *parent = ca->parent;
553
+ int8_t ndim = ca->ndim;
554
+ int8_t inner = ndim - 1;
555
+ int8_t iax = ca->indirect_axis;
556
+ ca_size_t pnative[CA_RANK_MAX]; /* parent row-major byte strides */
557
+ ca_size_t cnative[CA_RANK_MAX]; /* CSA row-major byte strides */
558
+ ca_size_t dstride[CA_RANK_MAX]; /* data row-major byte strides over counts */
559
+ ca_size_t src_step[CA_RANK_MAX];
560
+ ca_size_t o[CA_RANK_MAX];
561
+ const ca_size_t *tpos; /* TRUE positions of indirect axis (= ca->indices) */
562
+ ca_size_t s;
563
+ int8_t k;
564
+ int aligned = 1;
565
+ char *d = (char *) data;
566
+
567
+ s = parent->bytes;
568
+ for (k = ndim - 1; k >= 0; k--) { pnative[k] = s; s *= parent->dim[k]; }
569
+ s = ca->bytes;
570
+ for (k = ndim - 1; k >= 0; k--) { cnative[k] = s; s *= ca->dim[k]; }
571
+ s = ca->bytes;
572
+ for (k = ndim - 1; k >= 0; k--) { dstride[k] = s; s *= counts[k]; }
573
+
574
+ /* The request is over the view's addresses, so a transposed / flat request
575
+ is legal and must not be composed axis-by-axis; see
576
+ ca_xfer_stride_request_is_axis_box (carray.h). */
577
+ if ( ! ca_xfer_stride_request_is_axis_box(ca, starts, counts, strides) ) {
578
+ aligned = 0;
579
+ }
580
+ else {
581
+ for (k = 0; k < ndim; k++) {
582
+ if (strides[k] % cnative[k] != 0) { aligned = 0; break; }
583
+ src_step[k] = strides[k] / cnative[k];
584
+ }
585
+ }
586
+
587
+ if (!aligned) {
588
+ ca_size_t idx[CA_RANK_MAX], doff = 0, base = 0;
589
+ for (k = 0; k < ndim; k++) base += starts[k] * cnative[k];
590
+ for (k = 0; k < ndim; k++) idx[k] = 0;
591
+ while (1) {
592
+ ca_size_t coff = base, cidx[CA_RANK_MAX];
593
+ for (k = 0; k < ndim; k++) coff += idx[k] * strides[k];
594
+ ca_addr2index((CArray *) ca, coff / ca->bytes, cidx);
595
+ ca_select_axis_func_xfer_index(ca, cidx, d + doff, dir);
596
+ doff += ca->bytes;
597
+ k = ndim - 1;
598
+ while (k >= 0) { if (++idx[k] < counts[k]) break; idx[k] = 0; k--; }
599
+ if (k < 0) break;
600
+ }
601
+ return;
602
+ }
603
+
604
+ /* TRUE positions of the indirect axis are pre-computed in ca->indices[]
605
+ at setup (length = ca->dim[iax] = count_true). */
606
+ tpos = ca->indices;
607
+
608
+ /* Fast path: when parent is attached (ptr != NULL), translate the
609
+ sub-region request (starts/counts/src_step) into a transient
610
+ ca_axis_desc_t[] sub-descriptor and call
611
+ ca_axis_dispatch_gather/_scatter directly. Same engine entry
612
+ ca_select_axis_func_xfer_all uses (minus the ca_attach(parent) call,
613
+ which per-region functions must not do). Collapses count_true
614
+ outer-dispatch calls into 1 batched engine call. Mirrors the CAGrid
615
+ xfer_stride fast path, with indirect_axis playing the role of an INDEX
616
+ axis.
617
+
618
+ Parent unattached (= view parent) falls through to the per-row loop
619
+ below. */
620
+ if ( parent->ptr ) {
621
+ ca_axis_desc_t sub_axes[CA_RANK_MAX];
622
+ ca_size_t parent_axis_dims[CA_RANK_MAX];
623
+ ca_size_t *index_buf = NULL;
624
+ volatile VALUE index_holder = Qnil;
625
+ ca_size_t total_elements = 1;
626
+ ca_size_t j;
627
+
628
+ for ( k = 0; k < ndim; k++ ) {
629
+ parent_axis_dims[k] = parent->dim[k];
630
+ total_elements *= counts[k];
631
+ if ( k == iax ) {
632
+ /* INDIRECT axis */
633
+ sub_axes[k].kind = CA_AXIS_KIND_INDEX;
634
+ sub_axes[k].count = counts[k];
635
+ sub_axes[k].start = 0;
636
+ sub_axes[k].step = 0;
637
+ if ( src_step[k] == 1 ) {
638
+ /* zero-copy pointer offset */
639
+ sub_axes[k].indices = (ca_size_t *)(tpos + starts[k]);
640
+ } else {
641
+ /* sub-sampled or reversed: materialise sub-indices */
642
+ index_buf = ALLOCV_N(ca_size_t, index_holder, counts[k]);
643
+ for ( j = 0; j < counts[k]; j++ ) {
644
+ index_buf[j] = tpos[starts[k] + j * src_step[k]];
645
+ }
646
+ sub_axes[k].indices = index_buf;
647
+ }
648
+ } else {
649
+ /* AP / STRIDE axis */
650
+ sub_axes[k].kind = CA_AXIS_KIND_STRIDE;
651
+ sub_axes[k].count = counts[k];
652
+ sub_axes[k].start = ca->ap_start[k] + starts[k] * ca->ap_step[k];
653
+ sub_axes[k].step = ca->ap_step[k] * src_step[k];
654
+ sub_axes[k].indices = NULL;
655
+ }
656
+ }
657
+
658
+ if ( dir == CA_XFER_GET ) {
659
+ ca_axis_dispatch_gather(parent, parent_axis_dims, sub_axes, ndim,
660
+ ca->bytes, total_elements, NULL, d);
661
+ } else {
662
+ ca_axis_dispatch_scatter(parent, parent_axis_dims, sub_axes, ndim,
663
+ ca->bytes, total_elements, d);
664
+ }
665
+
666
+ if ( index_buf != NULL ) ALLOCV_END(index_holder);
667
+ return;
668
+ }
669
+
670
+ /* Fallback (parent unattached): per-row outer loop + per-row
671
+ ca_xfer_stride/ca_xfer_addrs. */
672
+ for (k = 0; k < ndim; k++) o[k] = 0;
673
+
674
+ while (1) {
675
+ ca_size_t pbase = 0, doff = 0;
676
+ for (k = 0; k < inner; k++) {
677
+ ca_size_t gpos = starts[k] + o[k] * src_step[k];
678
+ ca_size_t ppos = (k == iax) ? tpos[gpos]
679
+ : (ca->ap_start[k] + gpos * ca->ap_step[k]);
680
+ pbase += ppos * pnative[k];
681
+ doff += o[k] * dstride[k];
682
+ }
683
+
684
+ if (inner != iax) { /* AP inner axis -> STRIDE run */
685
+ ca_size_t pstarts[CA_RANK_MAX], pcounts[CA_RANK_MAX], pstrides[CA_RANK_MAX];
686
+ ca_size_t inner_pbase =
687
+ (ca->ap_start[inner] + starts[inner] * ca->ap_step[inner]) * pnative[inner];
688
+ ca_addr2index((CArray *) parent, (pbase + inner_pbase) / parent->bytes, pstarts);
689
+ for (k = 0; k < ndim; k++) { pcounts[k] = 1; pstrides[k] = 0; }
690
+ pcounts[inner] = counts[inner];
691
+ pstrides[inner] = src_step[inner] * ca->ap_step[inner] * pnative[inner];
692
+ ca_xfer_stride(parent, pstarts, pcounts, pstrides, d + doff, dir);
693
+ }
694
+ else { /* indirect inner axis -> gather */
695
+ ca_size_t *addrs;
696
+ ca_size_t j, n = counts[inner];
697
+ volatile VALUE holder;
698
+ addrs = ALLOCV_N(ca_size_t, holder, n);
699
+ for (j = 0; j < n; j++) {
700
+ ca_size_t gpos = starts[inner] + j * src_step[inner];
701
+ addrs[j] = (pbase + tpos[gpos] * pnative[inner]) / parent->bytes;
702
+ }
703
+ ca_xfer_addrs(parent, n, addrs, d + doff, dir);
704
+ ALLOCV_END(holder);
705
+ }
706
+
707
+ k = inner - 1;
708
+ while (k >= 0) { if (++o[k] < counts[k]) break; o[k] = 0; k--; }
709
+ if (k < 0) break;
710
+ }
711
+ }
712
+
713
+ static void
714
+ ca_select_axis_func_allocate (void *ap)
715
+ {
716
+ CASelectAxis *ca = (CASelectAxis *) ap;
717
+ ca_attach(ca->parent);
718
+ ca->ptr = xmalloc(ca_length(ca));
719
+ }
720
+
721
+ /* attach / sync / copy_data / sync_data / fill_data all go through the
722
+ common descriptor engine. This file holds only describe_axes for
723
+ translating CSA state into the shared descriptor; the engine in
724
+ ext/ca_axis_dispatch.c does the rest. */
725
+
726
+ static void
727
+ ca_select_axis_func_attach (void *ap)
728
+ {
729
+ CASelectAxis *ca = (CASelectAxis *) ap;
730
+ ca_axis_desc_t desc[CA_RANK_MAX];
731
+ ca_size_t pdims[CA_RANK_MAX];
732
+ ca_attach(ca->parent);
733
+ ca_select_axis_describe_axes(ca, desc, pdims);
734
+ ca->ptr = ca_axis_dispatch_attach(ca->parent, pdims, desc, ca->ndim,
735
+ ca->bytes, ca->elements, NULL);
736
+ }
737
+
738
+ static void
739
+ ca_select_axis_func_sync (void *ap)
740
+ {
741
+ CASelectAxis *ca = (CASelectAxis *) ap;
742
+ ca_axis_desc_t desc[CA_RANK_MAX];
743
+ ca_size_t pdims[CA_RANK_MAX];
744
+ ca_select_axis_describe_axes(ca, desc, pdims);
745
+ ca_axis_dispatch_scatter(ca->parent, pdims, desc, ca->ndim, ca->bytes,
746
+ ca->elements, ca->ptr);
747
+ ca_sync(ca->parent);
748
+ }
749
+
750
+ static void
751
+ ca_select_axis_func_detach (void *ap)
752
+ {
753
+ CASelectAxis *ca = (CASelectAxis *) ap;
754
+ if ( ca->ptr ) {
755
+ xfree(ca->ptr);
756
+ ca->ptr = NULL;
757
+ }
758
+ /* indices[] lives for the view's lifetime; freed in free_ca_select_axis. */
759
+ ca_detach(ca->parent);
760
+ }
761
+
762
+ /* xfer_all: fast path on parent->ptr, with no silent transitive attach
763
+ of the parent. A cold parent uses a proper 2-pass (materialise the
764
+ parent into scratch, expose it as parent->ptr, run the fast path, then
765
+ write scratch back for PUT). */
766
+ static void
767
+ ca_select_axis_func_run_fast_path (CASelectAxis *ca, char *data, int dir)
768
+ {
769
+ ca_axis_desc_t desc[CA_RANK_MAX];
770
+ ca_size_t pdims[CA_RANK_MAX];
771
+ ca_select_axis_describe_axes(ca, desc, pdims);
772
+ if ( dir == CA_XFER_GET ) {
773
+ ca_axis_dispatch_gather(ca->parent, pdims, desc, ca->ndim, ca->bytes,
774
+ ca->elements, NULL, data);
775
+ } else {
776
+ ca_axis_dispatch_scatter(ca->parent, pdims, desc, ca->ndim, ca->bytes,
777
+ ca->elements, data);
778
+ }
779
+ }
780
+
781
+ static void
782
+ ca_select_axis_func_xfer_all (void *ap, void *data, int dir)
783
+ {
784
+ CASelectAxis *ca = (CASelectAxis *) ap;
785
+ if ( ca->parent->ptr ) {
786
+ ca_select_axis_func_run_fast_path(ca, (char *) data, dir);
787
+ return;
788
+ }
789
+ /* Proper 2-pass cold fallback. */
790
+ {
791
+ volatile VALUE holder;
792
+ CArray *parent = ca->parent;
793
+ ca_size_t plen = parent->elements * parent->bytes;
794
+ char *parent_scratch = ALLOCV_N(char, holder, plen);
795
+ char *parent_ptr_saved = parent->ptr;
796
+ ca_xfer_all(parent, parent_scratch, CA_XFER_GET);
797
+ parent->ptr = parent_scratch;
798
+ ca_select_axis_func_run_fast_path(ca, (char *) data, dir);
799
+ if ( dir == CA_XFER_PUT ) {
800
+ ca_xfer_all(parent, parent_scratch, CA_XFER_PUT);
801
+ }
802
+ parent->ptr = parent_ptr_saved;
803
+ ALLOCV_END(holder);
804
+ }
805
+ }
806
+
807
+ static void
808
+ ca_select_axis_func_fill_data (void *ap, void *val)
809
+ {
810
+ CASelectAxis *ca = (CASelectAxis *) ap;
811
+ ca_axis_desc_t desc[CA_RANK_MAX];
812
+ ca_size_t pdims[CA_RANK_MAX];
813
+
814
+ ca_select_axis_describe_axes(ca, desc, pdims);
815
+
816
+ /* Writing the selected cells used to go through a whole-parent attach and
817
+ sync. Where that attach is a gather rather than an alias, the cells this
818
+ view did not select make the round trip for nothing -- and through a
819
+ lossy layer they do not come back the same. Hand each slab to the parent
820
+ as a region instead. (PROPOSAL_PARTIAL_FILL_WHOLE_ROOT_WRITEBACK.md) */
821
+ if ( !ca_is_attached(ca->parent) && !ca_attach_is_alias(ca->parent) ) {
822
+ ca_axis_dispatch_fill_value_via_parent(ca->parent, pdims, desc, ca->ndim,
823
+ ca->bytes, ca->elements, val);
824
+ return;
825
+ }
826
+
827
+ ca_attach(ca->parent);
828
+ ca_axis_dispatch_fill_value(ca->parent, pdims, desc, ca->ndim, ca->bytes,
829
+ ca->elements, val);
830
+ ca_sync(ca->parent);
831
+ ca_detach(ca->parent);
832
+ }
833
+
834
+ static void
835
+ ca_select_axis_func_create_mask (void *ap)
836
+ {
837
+ CASelectAxis *ca = (CASelectAxis *) ap;
838
+ ca_update_mask(ca->parent);
839
+ if ( ! ca->parent->mask ) {
840
+ ca_create_mask(ca->parent);
841
+ }
842
+ ca->mask = ca_select_axis_new(ca->parent->mask, ca->indirect_axis,
843
+ ca->selector,
844
+ ca->ap_start, ca->ap_count, ca->ap_step);
845
+ }
846
+
847
+ ca_operation_function_t ca_select_axis_func = {
848
+ -1, /* CA_OBJ_SELECT_AXIS, populated at install time */
849
+ CA_VIEW_ARRAY,
850
+ free_ca_select_axis,
851
+ ca_select_axis_func_clone,
852
+ ca_select_axis_func_allocate,
853
+ ca_select_axis_func_attach,
854
+ ca_select_axis_func_sync,
855
+ ca_select_axis_func_detach,
856
+ ca_select_axis_func_fill_data,
857
+ ca_select_axis_func_create_mask,
858
+ ca_select_axis_func_xfer_index,
859
+ ca_select_axis_func_xfer_addrs,
860
+ ca_select_axis_func_fold_stride,
861
+ ca_select_axis_func_xfer_stride,
862
+ ca_select_axis_func_xfer_all,
863
+ };
864
+
865
+ /* ------------------------------------------------------------------- */
866
+ /* Ruby surface (debug constructor + indexer dispatch) */
867
+ /* ------------------------------------------------------------------- */
868
+
869
+ static VALUE
870
+ rb_ca_select_axis_s_allocate (VALUE klass)
871
+ {
872
+ CASelectAxis *ca;
873
+ return TypedData_Make_Struct(klass, CASelectAxis, &caselectaxis_data_type, ca);
874
+ }
875
+
876
+ static VALUE
877
+ rb_ca_select_axis_initialize_copy (VALUE self, VALUE other)
878
+ {
879
+ CASelectAxis *ca, *cs;
880
+ TypedData_Get_Struct(self, CASelectAxis, &caselectaxis_data_type, ca);
881
+ TypedData_Get_Struct(other, CASelectAxis, &caselectaxis_data_type, cs);
882
+ if ( ca_func[CA_OBJ_SELECT_AXIS].pool_init ) {
883
+ ca_array_pool_alloc(ca, CA_OBJ_SELECT_AXIS, cs->parent->ndim);
884
+ }
885
+ ca_select_axis_setup(ca, cs->parent, cs->indirect_axis, cs->selector,
886
+ cs->ap_start, cs->ap_count, cs->ap_step);
887
+ return self;
888
+ }
889
+
890
+ /* Debug-only Ruby ctor:
891
+ CASelectAxis._new_debug(parent, indirect_axis, selector,
892
+ ap_start_arr, ap_count_arr, ap_step_arr)
893
+
894
+ ap_*_arr are Arrays of size parent.ndim. Entries for the indirect axis
895
+ are ignored. Intended for testing only; production dispatch runs
896
+ through rb_ca_select_axis (from the `[]` indexer in carray_access.c). */
897
+ static VALUE
898
+ rb_ca_select_axis_s_new_debug (VALUE klass, VALUE rparent, VALUE rindirect,
899
+ VALUE rselector, VALUE rap_start,
900
+ VALUE rap_count, VALUE rap_step)
901
+ {
902
+ CArray *parent, *selector;
903
+ int8_t indirect_axis = (int8_t) NUM2INT(rindirect);
904
+ int8_t ndim;
905
+ int8_t k;
906
+ VALUE obj;
907
+ CASelectAxis *ca;
908
+
909
+ Check_Type(rap_start, T_ARRAY);
910
+ Check_Type(rap_count, T_ARRAY);
911
+ Check_Type(rap_step, T_ARRAY);
912
+
913
+ TypedData_Get_Struct(rparent, CArray, &carray_data_type, parent);
914
+ TypedData_Get_Struct(rselector, CArray, &carray_data_type, selector);
915
+
916
+ ndim = parent->ndim;
917
+ if ( RARRAY_LEN(rap_start) != ndim ||
918
+ RARRAY_LEN(rap_count) != ndim ||
919
+ RARRAY_LEN(rap_step) != ndim ) {
920
+ rb_raise(rb_eArgError,
921
+ "CASelectAxis._new_debug: ap_* arrays must have length %d", ndim);
922
+ }
923
+
924
+ ca_size_t ap_start_buf[CA_RANK_MAX];
925
+ ca_size_t ap_count_buf[CA_RANK_MAX];
926
+ ca_size_t ap_step_buf[CA_RANK_MAX];
927
+ for ( k = 0; k < ndim; k++ ) {
928
+ ap_start_buf[k] = (ca_size_t) NUM2LL(rb_ary_entry(rap_start, k));
929
+ ap_count_buf[k] = (ca_size_t) NUM2LL(rb_ary_entry(rap_count, k));
930
+ ap_step_buf[k] = (ca_size_t) NUM2LL(rb_ary_entry(rap_step, k));
931
+ }
932
+
933
+ obj = TypedData_Make_Struct(klass, CASelectAxis, &caselectaxis_data_type, ca);
934
+ ca_select_axis_setup(ca, parent, indirect_axis, selector,
935
+ ap_start_buf, ap_count_buf, ap_step_buf);
936
+ rb_ca_set_parent(obj, rparent);
937
+ rb_ivar_set(obj, rb_intern("_selector"), rselector);
938
+ return obj;
939
+ }
940
+
941
+ /* ------------------------------------------------------------------- */
942
+ /* Indexer dispatch hook: called from rb_ca_grid */
943
+ /* ------------------------------------------------------------------- */
944
+
945
+ /* Eligibility check: route ca[*spec] to CASelectAxis only when CSA's
946
+ slab fast path applies AND CSA is expected to outperform CAGrid:
947
+ - argc == ca->ndim
948
+ - exactly one boolean CArray argument; rest are nil / Integer / Range
949
+ - the boolean is on axis 0 (outermost) — slab fast path criterion
950
+
951
+ Pattern A (mask outer + AP inner) is the only configuration where CSA's
952
+ single-walk slab xfer beats CAGrid's recursive innermost-contig attach.
953
+ Other configurations (interleaved AP+INDIRECT, indirect inner, AP with
954
+ step != 1) are routed to CAGrid to avoid regressions. */
955
+ int
956
+ rb_ca_select_axis_eligible_p (int argc, VALUE *argv, VALUE self)
957
+ {
958
+ CArray *ca;
959
+ int i;
960
+
961
+ /* Cheapest rejection first: argv[0] must be T_DATA (CArray-shaped).
962
+ Everything else (nil, Fixnum, Range, ...) fails TYPE check in O(1). */
963
+ if ( argc < 1 ) return 0;
964
+ if ( TYPE(argv[0]) != T_DATA ) return 0;
965
+
966
+ /* Now the slower checks: is it a CArray, is it boolean. */
967
+ if ( ! rb_obj_is_carray(argv[0]) ) return 0;
968
+ if ( ! rb_ca_is_boolean_type(argv[0]) ) return 0;
969
+
970
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
971
+ if ( argc != ca->ndim ) return 0;
972
+
973
+ /* axis 0 is the boolean (indirect). Walk rest; require nil/Integer/Range. */
974
+ for ( i = 1; i < argc; i++ ) {
975
+ VALUE arg = argv[i];
976
+ if ( NIL_P(arg) ) continue;
977
+ if ( FIXNUM_P(arg) ) continue;
978
+ if ( TYPE(arg) == T_DATA ) return 0; /* CArray at axis > 0 → bail */
979
+ if ( rb_obj_is_kind_of(arg, rb_cRange) ) continue;
980
+ if ( rb_obj_is_kind_of(arg, rb_cInteger) ) continue;
981
+ return 0;
982
+ }
983
+
984
+ return 1;
985
+ }
986
+
987
+ /* Construct a CASelectAxis from the indexer's argc/argv. Assumes
988
+ rb_ca_select_axis_eligible_p has already returned 1. */
989
+ VALUE
990
+ rb_ca_select_axis (int argc, VALUE *argv, VALUE self)
991
+ {
992
+ CArray *parent;
993
+ CArray *selector = NULL;
994
+ ca_size_t ap_start[CA_RANK_MAX];
995
+ ca_size_t ap_count[CA_RANK_MAX];
996
+ ca_size_t ap_step[CA_RANK_MAX];
997
+ int8_t indirect_axis = -1;
998
+ VALUE rselector_keep = Qnil;
999
+ VALUE obj;
1000
+ CASelectAxis *ca;
1001
+ int i;
1002
+
1003
+ TypedData_Get_Struct(self, CArray, &carray_data_type, parent);
1004
+
1005
+ if ( argc != parent->ndim ) {
1006
+ rb_raise(rb_eArgError,
1007
+ "CASelectAxis dispatch: argc (%d) != ndim (%d)", argc, parent->ndim);
1008
+ }
1009
+
1010
+ for ( i = 0; i < argc; i++ ) {
1011
+ VALUE arg = argv[i];
1012
+
1013
+ if ( NIL_P(arg) ) {
1014
+ ap_start[i] = 0;
1015
+ ap_count[i] = parent->dim[i];
1016
+ ap_step[i] = 1;
1017
+ }
1018
+ else if ( FIXNUM_P(arg) || rb_obj_is_kind_of(arg, rb_cInteger) ) {
1019
+ ca_size_t n = NUM2LL(arg);
1020
+ if ( n < 0 ) n += parent->dim[i];
1021
+ if ( n < 0 || n >= parent->dim[i] ) {
1022
+ rb_raise(rb_eIndexError,
1023
+ "CASelectAxis: index %lld out of range at axis %d",
1024
+ (long long) n, i);
1025
+ }
1026
+ ap_start[i] = n;
1027
+ ap_count[i] = 1;
1028
+ ap_step[i] = 1;
1029
+ }
1030
+ else if ( rb_obj_is_kind_of(arg, rb_cRange) ) {
1031
+ VALUE rfirst = rb_funcall(arg, rb_intern("first"), 0);
1032
+ VALUE rlast = rb_funcall(arg, rb_intern("last"), 0);
1033
+ int exclusive = RTEST(rb_funcall(arg, rb_intern("exclude_end?"), 0));
1034
+ ca_size_t a = NUM2LL(rfirst);
1035
+ ca_size_t b = NUM2LL(rlast);
1036
+ if ( a < 0 ) a += parent->dim[i];
1037
+ if ( b < 0 ) b += parent->dim[i];
1038
+ ca_size_t cnt = exclusive ? (b - a) : (b - a + 1);
1039
+ if ( cnt <= 0 ) {
1040
+ rb_raise(rb_eIndexError,
1041
+ "CASelectAxis: empty range at axis %d", i);
1042
+ }
1043
+ if ( a < 0 || a >= parent->dim[i] ||
1044
+ (a + cnt - 1) < 0 || (a + cnt - 1) >= parent->dim[i] ) {
1045
+ rb_raise(rb_eIndexError,
1046
+ "CASelectAxis: range [%lld..%lld] out of bounds at axis %d",
1047
+ (long long) a, (long long) (a + cnt - 1), i);
1048
+ }
1049
+ ap_start[i] = a;
1050
+ ap_count[i] = cnt;
1051
+ ap_step[i] = 1;
1052
+ }
1053
+ else if ( rb_obj_is_carray(arg) && rb_ca_is_boolean_type(arg) ) {
1054
+ TypedData_Get_Struct(arg, CArray, &carray_data_type, selector);
1055
+ indirect_axis = (int8_t) i;
1056
+ rselector_keep = arg;
1057
+ ap_start[i] = 0;
1058
+ ap_count[i] = 0; /* unused for indirect axis */
1059
+ ap_step[i] = 1;
1060
+ }
1061
+ else {
1062
+ rb_raise(rb_eArgError,
1063
+ "CASelectAxis: unsupported index type at axis %d", i);
1064
+ }
1065
+ }
1066
+
1067
+ if ( indirect_axis < 0 ) {
1068
+ rb_raise(rb_eRuntimeError,
1069
+ "CASelectAxis dispatch: no boolean axis found (eligibility bug)");
1070
+ }
1071
+
1072
+ obj = TypedData_Make_Struct(rb_cCASelectAxis, CASelectAxis,
1073
+ &caselectaxis_data_type, ca);
1074
+ ca_select_axis_setup(ca, parent, indirect_axis, selector,
1075
+ ap_start, ap_count, ap_step);
1076
+
1077
+ /* Keep Ruby objects alive (parent and selector references). The parent
1078
+ goes through rb_ca_set_parent so #parent, #root_array and #ancestors
1079
+ read the same link here as they do through every other view. */
1080
+ rb_ca_set_parent(obj, self);
1081
+ rb_ivar_set(obj, rb_intern("_selector"), rselector_keep);
1082
+ return obj;
1083
+ }
1084
+
1085
+ /* ------------------------------------------------------------------- */
1086
+ /* Producer interface: emit the per-axis descriptor array.
1087
+ Writes ca->ndim entries into `out` (caller-allocated) and the
1088
+ matching effective parent dim into `out_parent_dims` (caller-
1089
+ allocated, sized CA_RANK_MAX). CSA is a per-axis view (descriptor.
1090
+ ndim == parent.ndim), so out_parent_dims is just parent->dim
1091
+ copied through. */
1092
+
1093
+ void
1094
+ ca_select_axis_describe_axes (void *ap, ca_axis_desc_t *out,
1095
+ ca_size_t *out_parent_dims)
1096
+ {
1097
+ CASelectAxis *ca = (CASelectAxis *) ap;
1098
+ int8_t k;
1099
+ for ( k = 0; k < ca->ndim; k++ ) {
1100
+ out_parent_dims[k] = ca->parent->dim[k];
1101
+ if ( k == ca->indirect_axis ) {
1102
+ if ( ca->stride_kind ) {
1103
+ /* constant-step run -> STRIDE kind. */
1104
+ out[k].kind = CA_AXIS_KIND_STRIDE;
1105
+ out[k].count = ca->dim[k];
1106
+ out[k].start = ca->stride_start;
1107
+ out[k].step = ca->stride_step;
1108
+ out[k].indices = NULL;
1109
+ } else {
1110
+ out[k].kind = CA_AXIS_KIND_INDEX;
1111
+ out[k].count = ca->dim[k];
1112
+ out[k].start = 0;
1113
+ out[k].step = 0;
1114
+ out[k].indices = ca->indices;
1115
+ }
1116
+ } else {
1117
+ out[k].kind = CA_AXIS_KIND_STRIDE;
1118
+ out[k].count = ca->ap_count[k];
1119
+ out[k].start = ca->ap_start[k];
1120
+ out[k].step = ca->ap_step[k];
1121
+ out[k].indices = NULL;
1122
+ }
1123
+ }
1124
+ }
1125
+
1126
+ #ifdef CARRAY_DEV_BUILD
1127
+ /* ============================================================
1128
+ * Debug accessors (dev-only, stripped in release)
1129
+ *
1130
+ * Gated by CARRAY_DEV_BUILD (enabled via `extconf.rb --enable-dev-build`
1131
+ * or `CARRAY_DEV=1 rake build_ext`). They expose the per-axis descriptor
1132
+ * and the engine's raw output to Ruby so spec_ai can pin them; nothing in
1133
+ * lib/ or the rest of ext/ consumes them. CAGrid carries the same four
1134
+ * under the same fence.
1135
+ * ============================================================ */
1136
+
1137
+ /* Returns an Array of per-axis Arrays:
1138
+ STRIDE axis -> [:stride, count, start, step]
1139
+ INDEX axis -> [:index, count, [indices...]] */
1140
+ static VALUE
1141
+ rb_ca_select_axis_describe_axes (VALUE self)
1142
+ {
1143
+ CASelectAxis *ca;
1144
+ ca_axis_desc_t desc[CA_RANK_MAX];
1145
+ VALUE arr, entry, idx_ary;
1146
+ int8_t k;
1147
+ ca_size_t i;
1148
+
1149
+ ca_size_t pdims[CA_RANK_MAX];
1150
+ TypedData_Get_Struct(self, CASelectAxis, &caselectaxis_data_type, ca);
1151
+ ca_select_axis_describe_axes(ca, desc, pdims);
1152
+
1153
+ arr = rb_ary_new_capa(ca->ndim);
1154
+ for ( k = 0; k < ca->ndim; k++ ) {
1155
+ if ( desc[k].kind == CA_AXIS_KIND_STRIDE ) {
1156
+ entry = rb_ary_new_capa(4);
1157
+ rb_ary_push(entry, ID2SYM(rb_intern("stride")));
1158
+ rb_ary_push(entry, SIZE2NUM(desc[k].count));
1159
+ rb_ary_push(entry, SIZE2NUM(desc[k].start));
1160
+ rb_ary_push(entry, SIZE2NUM(desc[k].step));
1161
+ } else {
1162
+ entry = rb_ary_new_capa(3);
1163
+ rb_ary_push(entry, ID2SYM(rb_intern("index")));
1164
+ rb_ary_push(entry, SIZE2NUM(desc[k].count));
1165
+ idx_ary = rb_ary_new_capa(desc[k].count);
1166
+ for ( i = 0; i < desc[k].count; i++ ) {
1167
+ rb_ary_push(idx_ary, SIZE2NUM(desc[k].indices[i]));
1168
+ }
1169
+ rb_ary_push(entry, idx_ary);
1170
+ }
1171
+ rb_ary_push(arr, entry);
1172
+ }
1173
+ return arr;
1174
+ }
1175
+
1176
+ /* Returns the raw bytes produced by ca_axis_dispatch_attach as a String.
1177
+ Compared against `view.to_ca.dump_binary` in tests to verify binary
1178
+ equality with the existing attach path. */
1179
+ static VALUE
1180
+ rb_ca_select_axis_dispatch_attach_debug (VALUE self)
1181
+ {
1182
+ CASelectAxis *ca;
1183
+ ca_axis_desc_t desc[CA_RANK_MAX];
1184
+ char *buf;
1185
+ VALUE str;
1186
+
1187
+ ca_size_t pdims[CA_RANK_MAX];
1188
+ TypedData_Get_Struct(self, CASelectAxis, &caselectaxis_data_type, ca);
1189
+ ca_select_axis_describe_axes(ca, desc, pdims);
1190
+
1191
+ ca_attach(ca->parent);
1192
+ buf = ca_axis_dispatch_attach(ca->parent, pdims, desc, ca->ndim, ca->bytes,
1193
+ ca->elements, NULL);
1194
+ ca_detach(ca->parent);
1195
+
1196
+ str = rb_str_new(buf, ca->elements * ca->bytes);
1197
+ xfree(buf);
1198
+ return str;
1199
+ }
1200
+
1201
+ /* Feeds a String of bytes (length == elements * bytes) through
1202
+ ca_axis_dispatch_scatter and returns nil. Mutates the parent; tests
1203
+ compare the resulting parent state against the same scatter performed
1204
+ through the view's sync path. */
1205
+ static VALUE
1206
+ rb_ca_select_axis_dispatch_scatter_debug (VALUE self, VALUE in_str)
1207
+ {
1208
+ CASelectAxis *ca;
1209
+ ca_axis_desc_t desc[CA_RANK_MAX];
1210
+ ca_size_t expected;
1211
+
1212
+ Check_Type(in_str, T_STRING);
1213
+ TypedData_Get_Struct(self, CASelectAxis, &caselectaxis_data_type, ca);
1214
+
1215
+ expected = ca->elements * ca->bytes;
1216
+ if ( (ca_size_t) RSTRING_LEN(in_str) != expected ) {
1217
+ rb_raise(rb_eArgError,
1218
+ "CASelectAxis#_dispatch_scatter_debug: input string length %lld "
1219
+ "!= expected %lld",
1220
+ (long long) RSTRING_LEN(in_str), (long long) expected);
1221
+ }
1222
+ ca_size_t pdims[CA_RANK_MAX];
1223
+ ca_select_axis_describe_axes(ca, desc, pdims);
1224
+
1225
+ ca_attach(ca->parent);
1226
+ ca_axis_dispatch_scatter(ca->parent, pdims, desc, ca->ndim, ca->bytes,
1227
+ ca->elements, RSTRING_PTR(in_str));
1228
+ ca_sync(ca->parent);
1229
+ ca_detach(ca->parent);
1230
+ return Qnil;
1231
+ }
1232
+
1233
+ /* Broadcasts a String of length == bytes (one value) into every selected
1234
+ parent cell through ca_axis_dispatch_fill_value. */
1235
+ static VALUE
1236
+ rb_ca_select_axis_dispatch_fill_value_debug (VALUE self, VALUE val_str)
1237
+ {
1238
+ CASelectAxis *ca;
1239
+ ca_axis_desc_t desc[CA_RANK_MAX];
1240
+
1241
+ Check_Type(val_str, T_STRING);
1242
+ TypedData_Get_Struct(self, CASelectAxis, &caselectaxis_data_type, ca);
1243
+
1244
+ if ( (ca_size_t) RSTRING_LEN(val_str) != ca->bytes ) {
1245
+ rb_raise(rb_eArgError,
1246
+ "CASelectAxis#_dispatch_fill_value_debug: value string length %lld "
1247
+ "!= bytes %lld",
1248
+ (long long) RSTRING_LEN(val_str), (long long) ca->bytes);
1249
+ }
1250
+ ca_size_t pdims[CA_RANK_MAX];
1251
+ ca_select_axis_describe_axes(ca, desc, pdims);
1252
+
1253
+ ca_attach(ca->parent);
1254
+ ca_axis_dispatch_fill_value(ca->parent, pdims, desc, ca->ndim, ca->bytes,
1255
+ ca->elements, RSTRING_PTR(val_str));
1256
+ ca_sync(ca->parent);
1257
+ ca_detach(ca->parent);
1258
+ return Qnil;
1259
+ }
1260
+ #endif /* CARRAY_DEV_BUILD */
1261
+
1262
+ /* ------------------------------------------------------------------- */
1263
+
1264
+ void
1265
+ Init_ca_obj_select_axis (void)
1266
+ {
1267
+ rb_cCASelectAxis = rb_define_class("CASelectAxis", rb_cCAView);
1268
+ rb_cCASelectAxisMask = rb_define_class("CASelectAxisMask", rb_cCASelectAxis);
1269
+
1270
+ ca_select_axis_func.struct_size = sizeof(CASelectAxis);
1271
+ ca_select_axis_func.pool_bytes = ca_select_axis_pool_bytes;
1272
+ ca_select_axis_func.pool_init = ca_select_axis_pool_init;
1273
+
1274
+ CA_OBJ_SELECT_AXIS = ca_install_obj_type(rb_cCASelectAxis,
1275
+ &caselectaxis_data_type,
1276
+ rb_cCASelectAxisMask,
1277
+ &caselectaxis_mask_data_type,
1278
+ &ca_select_axis_func, sizeof(ca_select_axis_func));
1279
+ rb_define_const(rb_cObject, "CA_OBJ_SELECT_AXIS", INT2NUM(CA_OBJ_SELECT_AXIS));
1280
+
1281
+ rb_define_alloc_func(rb_cCASelectAxis, rb_ca_select_axis_s_allocate);
1282
+ rb_define_method(rb_cCASelectAxis, "initialize_copy",
1283
+ rb_ca_select_axis_initialize_copy, 1);
1284
+ /* Debug-only ctor for testing; not part of the public API. */
1285
+ rb_define_singleton_method(rb_cCASelectAxis, "_new_debug",
1286
+ rb_ca_select_axis_s_new_debug, 6);
1287
+
1288
+ #ifdef CARRAY_DEV_BUILD
1289
+ /* debug accessors (dev-only, stripped in release).
1290
+ _dispatch_attach_debug returns the raw ca_axis_dispatch_attach bytes
1291
+ as a Ruby String so tests can assert binary equality with the
1292
+ existing attach output. */
1293
+ rb_define_method(rb_cCASelectAxis, "_describe_axes",
1294
+ rb_ca_select_axis_describe_axes, 0);
1295
+ rb_define_method(rb_cCASelectAxis, "_dispatch_attach_debug",
1296
+ rb_ca_select_axis_dispatch_attach_debug, 0);
1297
+ rb_define_method(rb_cCASelectAxis, "_dispatch_scatter_debug",
1298
+ rb_ca_select_axis_dispatch_scatter_debug, 1);
1299
+ rb_define_method(rb_cCASelectAxis, "_dispatch_fill_value_debug",
1300
+ rb_ca_select_axis_dispatch_fill_value_debug, 1);
1301
+ #endif
1302
+
1303
+ /* Benchmark-only bypass setter on CArray class. */
1304
+ rb_define_singleton_method(rb_cCArray, "_csa_bypass=", rb_ca_s_csa_bypass_eq, 1);
1305
+ rb_define_singleton_method(rb_cCArray, "_csa_bypass?", rb_ca_s_csa_bypass_p, 0);
1306
+ }