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,1296 @@
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
+ for (k = 0; k < ndim; k++) {
575
+ if (strides[k] % cnative[k] != 0) { aligned = 0; break; }
576
+ src_step[k] = strides[k] / cnative[k];
577
+ }
578
+
579
+ if (!aligned) {
580
+ ca_size_t idx[CA_RANK_MAX], doff = 0, base = 0;
581
+ for (k = 0; k < ndim; k++) base += starts[k] * cnative[k];
582
+ for (k = 0; k < ndim; k++) idx[k] = 0;
583
+ while (1) {
584
+ ca_size_t coff = base, cidx[CA_RANK_MAX];
585
+ for (k = 0; k < ndim; k++) coff += idx[k] * strides[k];
586
+ ca_addr2index((CArray *) ca, coff / ca->bytes, cidx);
587
+ ca_select_axis_func_xfer_index(ca, cidx, d + doff, dir);
588
+ doff += ca->bytes;
589
+ k = ndim - 1;
590
+ while (k >= 0) { if (++idx[k] < counts[k]) break; idx[k] = 0; k--; }
591
+ if (k < 0) break;
592
+ }
593
+ return;
594
+ }
595
+
596
+ /* TRUE positions of the indirect axis are pre-computed in ca->indices[]
597
+ at setup (length = ca->dim[iax] = count_true). */
598
+ tpos = ca->indices;
599
+
600
+ /* Fast path: when parent is attached (ptr != NULL), translate the
601
+ sub-region request (starts/counts/src_step) into a transient
602
+ ca_axis_desc_t[] sub-descriptor and call
603
+ ca_axis_dispatch_gather/_scatter directly. Same engine entry
604
+ ca_select_axis_func_xfer_all uses (minus the ca_attach(parent) call,
605
+ which per-region functions must not do). Collapses count_true
606
+ outer-dispatch calls into 1 batched engine call. Mirrors the CAGrid
607
+ xfer_stride fast path, with indirect_axis playing the role of an INDEX
608
+ axis.
609
+
610
+ Parent unattached (= view parent) falls through to the per-row loop
611
+ below. */
612
+ if ( parent->ptr ) {
613
+ ca_axis_desc_t sub_axes[CA_RANK_MAX];
614
+ ca_size_t parent_axis_dims[CA_RANK_MAX];
615
+ ca_size_t *index_buf = NULL;
616
+ volatile VALUE index_holder = Qnil;
617
+ ca_size_t total_elements = 1;
618
+ ca_size_t j;
619
+
620
+ for ( k = 0; k < ndim; k++ ) {
621
+ parent_axis_dims[k] = parent->dim[k];
622
+ total_elements *= counts[k];
623
+ if ( k == iax ) {
624
+ /* INDIRECT axis */
625
+ sub_axes[k].kind = CA_AXIS_KIND_INDEX;
626
+ sub_axes[k].count = counts[k];
627
+ sub_axes[k].start = 0;
628
+ sub_axes[k].step = 0;
629
+ if ( src_step[k] == 1 ) {
630
+ /* zero-copy pointer offset */
631
+ sub_axes[k].indices = (ca_size_t *)(tpos + starts[k]);
632
+ } else {
633
+ /* sub-sampled or reversed: materialise sub-indices */
634
+ index_buf = ALLOCV_N(ca_size_t, index_holder, counts[k]);
635
+ for ( j = 0; j < counts[k]; j++ ) {
636
+ index_buf[j] = tpos[starts[k] + j * src_step[k]];
637
+ }
638
+ sub_axes[k].indices = index_buf;
639
+ }
640
+ } else {
641
+ /* AP / STRIDE axis */
642
+ sub_axes[k].kind = CA_AXIS_KIND_STRIDE;
643
+ sub_axes[k].count = counts[k];
644
+ sub_axes[k].start = ca->ap_start[k] + starts[k] * ca->ap_step[k];
645
+ sub_axes[k].step = ca->ap_step[k] * src_step[k];
646
+ sub_axes[k].indices = NULL;
647
+ }
648
+ }
649
+
650
+ if ( dir == CA_XFER_GET ) {
651
+ ca_axis_dispatch_gather(parent, parent_axis_dims, sub_axes, ndim,
652
+ ca->bytes, total_elements, NULL, d);
653
+ } else {
654
+ ca_axis_dispatch_scatter(parent, parent_axis_dims, sub_axes, ndim,
655
+ ca->bytes, total_elements, d);
656
+ }
657
+
658
+ if ( index_buf != NULL ) ALLOCV_END(index_holder);
659
+ return;
660
+ }
661
+
662
+ /* Fallback (parent unattached): per-row outer loop + per-row
663
+ ca_xfer_stride/ca_xfer_addrs. */
664
+ for (k = 0; k < ndim; k++) o[k] = 0;
665
+
666
+ while (1) {
667
+ ca_size_t pbase = 0, doff = 0;
668
+ for (k = 0; k < inner; k++) {
669
+ ca_size_t gpos = starts[k] + o[k] * src_step[k];
670
+ ca_size_t ppos = (k == iax) ? tpos[gpos]
671
+ : (ca->ap_start[k] + gpos * ca->ap_step[k]);
672
+ pbase += ppos * pnative[k];
673
+ doff += o[k] * dstride[k];
674
+ }
675
+
676
+ if (inner != iax) { /* AP inner axis -> STRIDE run */
677
+ ca_size_t pstarts[CA_RANK_MAX], pcounts[CA_RANK_MAX], pstrides[CA_RANK_MAX];
678
+ ca_size_t inner_pbase =
679
+ (ca->ap_start[inner] + starts[inner] * ca->ap_step[inner]) * pnative[inner];
680
+ ca_addr2index((CArray *) parent, (pbase + inner_pbase) / parent->bytes, pstarts);
681
+ for (k = 0; k < ndim; k++) { pcounts[k] = 1; pstrides[k] = 0; }
682
+ pcounts[inner] = counts[inner];
683
+ pstrides[inner] = src_step[inner] * ca->ap_step[inner] * pnative[inner];
684
+ ca_xfer_stride(parent, pstarts, pcounts, pstrides, d + doff, dir);
685
+ }
686
+ else { /* indirect inner axis -> gather */
687
+ ca_size_t *addrs;
688
+ ca_size_t j, n = counts[inner];
689
+ volatile VALUE holder;
690
+ addrs = ALLOCV_N(ca_size_t, holder, n);
691
+ for (j = 0; j < n; j++) {
692
+ ca_size_t gpos = starts[inner] + j * src_step[inner];
693
+ addrs[j] = (pbase + tpos[gpos] * pnative[inner]) / parent->bytes;
694
+ }
695
+ ca_xfer_addrs(parent, n, addrs, d + doff, dir);
696
+ ALLOCV_END(holder);
697
+ }
698
+
699
+ k = inner - 1;
700
+ while (k >= 0) { if (++o[k] < counts[k]) break; o[k] = 0; k--; }
701
+ if (k < 0) break;
702
+ }
703
+ }
704
+
705
+ static void
706
+ ca_select_axis_func_allocate (void *ap)
707
+ {
708
+ CASelectAxis *ca = (CASelectAxis *) ap;
709
+ ca_attach(ca->parent);
710
+ ca->ptr = xmalloc(ca_length(ca));
711
+ }
712
+
713
+ /* attach / sync / copy_data / sync_data / fill_data all go through the
714
+ common descriptor engine. This file holds only describe_axes for
715
+ translating CSA state into the shared descriptor; the engine in
716
+ ext/ca_axis_dispatch.c does the rest. */
717
+
718
+ static void
719
+ ca_select_axis_func_attach (void *ap)
720
+ {
721
+ CASelectAxis *ca = (CASelectAxis *) ap;
722
+ ca_axis_desc_t desc[CA_RANK_MAX];
723
+ ca_size_t pdims[CA_RANK_MAX];
724
+ ca_attach(ca->parent);
725
+ ca_select_axis_describe_axes(ca, desc, pdims);
726
+ ca->ptr = ca_axis_dispatch_attach(ca->parent, pdims, desc, ca->ndim,
727
+ ca->bytes, ca->elements, NULL);
728
+ }
729
+
730
+ static void
731
+ ca_select_axis_func_sync (void *ap)
732
+ {
733
+ CASelectAxis *ca = (CASelectAxis *) ap;
734
+ ca_axis_desc_t desc[CA_RANK_MAX];
735
+ ca_size_t pdims[CA_RANK_MAX];
736
+ ca_select_axis_describe_axes(ca, desc, pdims);
737
+ ca_axis_dispatch_scatter(ca->parent, pdims, desc, ca->ndim, ca->bytes,
738
+ ca->elements, ca->ptr);
739
+ ca_sync(ca->parent);
740
+ }
741
+
742
+ static void
743
+ ca_select_axis_func_detach (void *ap)
744
+ {
745
+ CASelectAxis *ca = (CASelectAxis *) ap;
746
+ if ( ca->ptr ) {
747
+ xfree(ca->ptr);
748
+ ca->ptr = NULL;
749
+ }
750
+ /* indices[] lives for the view's lifetime; freed in free_ca_select_axis. */
751
+ ca_detach(ca->parent);
752
+ }
753
+
754
+ /* xfer_all: fast path on parent->ptr, with no silent transitive attach
755
+ of the parent. A cold parent uses a proper 2-pass (materialise the
756
+ parent into scratch, expose it as parent->ptr, run the fast path, then
757
+ write scratch back for PUT). */
758
+ static void
759
+ ca_select_axis_func_run_fast_path (CASelectAxis *ca, char *data, int dir)
760
+ {
761
+ ca_axis_desc_t desc[CA_RANK_MAX];
762
+ ca_size_t pdims[CA_RANK_MAX];
763
+ ca_select_axis_describe_axes(ca, desc, pdims);
764
+ if ( dir == CA_XFER_GET ) {
765
+ ca_axis_dispatch_gather(ca->parent, pdims, desc, ca->ndim, ca->bytes,
766
+ ca->elements, NULL, data);
767
+ } else {
768
+ ca_axis_dispatch_scatter(ca->parent, pdims, desc, ca->ndim, ca->bytes,
769
+ ca->elements, data);
770
+ }
771
+ }
772
+
773
+ static void
774
+ ca_select_axis_func_xfer_all (void *ap, void *data, int dir)
775
+ {
776
+ CASelectAxis *ca = (CASelectAxis *) ap;
777
+ if ( ca->parent->ptr ) {
778
+ ca_select_axis_func_run_fast_path(ca, (char *) data, dir);
779
+ return;
780
+ }
781
+ /* Proper 2-pass cold fallback. */
782
+ {
783
+ volatile VALUE holder;
784
+ CArray *parent = ca->parent;
785
+ ca_size_t plen = parent->elements * parent->bytes;
786
+ char *parent_scratch = ALLOCV_N(char, holder, plen);
787
+ char *parent_ptr_saved = parent->ptr;
788
+ ca_xfer_all(parent, parent_scratch, CA_XFER_GET);
789
+ parent->ptr = parent_scratch;
790
+ ca_select_axis_func_run_fast_path(ca, (char *) data, dir);
791
+ if ( dir == CA_XFER_PUT ) {
792
+ ca_xfer_all(parent, parent_scratch, CA_XFER_PUT);
793
+ }
794
+ parent->ptr = parent_ptr_saved;
795
+ ALLOCV_END(holder);
796
+ }
797
+ }
798
+
799
+ static void
800
+ ca_select_axis_func_fill_data (void *ap, void *val)
801
+ {
802
+ CASelectAxis *ca = (CASelectAxis *) ap;
803
+ ca_axis_desc_t desc[CA_RANK_MAX];
804
+ ca_size_t pdims[CA_RANK_MAX];
805
+
806
+ ca_select_axis_describe_axes(ca, desc, pdims);
807
+
808
+ /* Writing the selected cells used to go through a whole-parent attach and
809
+ sync. Where that attach is a gather rather than an alias, the cells this
810
+ view did not select make the round trip for nothing -- and through a
811
+ lossy layer they do not come back the same. Hand each slab to the parent
812
+ as a region instead. (PROPOSAL_PARTIAL_FILL_WHOLE_ROOT_WRITEBACK.md) */
813
+ if ( !ca_is_attached(ca->parent) && !ca_attach_is_alias(ca->parent) ) {
814
+ ca_axis_dispatch_fill_value_via_parent(ca->parent, pdims, desc, ca->ndim,
815
+ ca->bytes, ca->elements, val);
816
+ return;
817
+ }
818
+
819
+ ca_attach(ca->parent);
820
+ ca_axis_dispatch_fill_value(ca->parent, pdims, desc, ca->ndim, ca->bytes,
821
+ ca->elements, val);
822
+ ca_sync(ca->parent);
823
+ ca_detach(ca->parent);
824
+ }
825
+
826
+ static void
827
+ ca_select_axis_func_create_mask (void *ap)
828
+ {
829
+ CASelectAxis *ca = (CASelectAxis *) ap;
830
+ ca_update_mask(ca->parent);
831
+ if ( ! ca->parent->mask ) {
832
+ ca_create_mask(ca->parent);
833
+ }
834
+ ca->mask = ca_select_axis_new(ca->parent->mask, ca->indirect_axis,
835
+ ca->selector,
836
+ ca->ap_start, ca->ap_count, ca->ap_step);
837
+ }
838
+
839
+ ca_operation_function_t ca_select_axis_func = {
840
+ -1, /* CA_OBJ_SELECT_AXIS, populated at install time */
841
+ CA_VIEW_ARRAY,
842
+ free_ca_select_axis,
843
+ ca_select_axis_func_clone,
844
+ ca_select_axis_func_allocate,
845
+ ca_select_axis_func_attach,
846
+ ca_select_axis_func_sync,
847
+ ca_select_axis_func_detach,
848
+ ca_select_axis_func_fill_data,
849
+ ca_select_axis_func_create_mask,
850
+ ca_select_axis_func_xfer_index,
851
+ ca_select_axis_func_xfer_addrs,
852
+ ca_select_axis_func_fold_stride,
853
+ ca_select_axis_func_xfer_stride,
854
+ ca_select_axis_func_xfer_all,
855
+ };
856
+
857
+ /* ------------------------------------------------------------------- */
858
+ /* Ruby surface (debug constructor + indexer dispatch) */
859
+ /* ------------------------------------------------------------------- */
860
+
861
+ static VALUE
862
+ rb_ca_select_axis_s_allocate (VALUE klass)
863
+ {
864
+ CASelectAxis *ca;
865
+ return TypedData_Make_Struct(klass, CASelectAxis, &caselectaxis_data_type, ca);
866
+ }
867
+
868
+ static VALUE
869
+ rb_ca_select_axis_initialize_copy (VALUE self, VALUE other)
870
+ {
871
+ CASelectAxis *ca, *cs;
872
+ TypedData_Get_Struct(self, CASelectAxis, &caselectaxis_data_type, ca);
873
+ TypedData_Get_Struct(other, CASelectAxis, &caselectaxis_data_type, cs);
874
+ if ( ca_func[CA_OBJ_SELECT_AXIS].pool_init ) {
875
+ ca_array_pool_alloc(ca, CA_OBJ_SELECT_AXIS, cs->parent->ndim);
876
+ }
877
+ ca_select_axis_setup(ca, cs->parent, cs->indirect_axis, cs->selector,
878
+ cs->ap_start, cs->ap_count, cs->ap_step);
879
+ return self;
880
+ }
881
+
882
+ /* Debug-only Ruby ctor:
883
+ CASelectAxis._new_debug(parent, indirect_axis, selector,
884
+ ap_start_arr, ap_count_arr, ap_step_arr)
885
+
886
+ ap_*_arr are Arrays of size parent.ndim. Entries for the indirect axis
887
+ are ignored. Intended for testing only; production dispatch runs
888
+ through rb_ca_select_axis (from the `[]` indexer in carray_access.c). */
889
+ static VALUE
890
+ rb_ca_select_axis_s_new_debug (VALUE klass, VALUE rparent, VALUE rindirect,
891
+ VALUE rselector, VALUE rap_start,
892
+ VALUE rap_count, VALUE rap_step)
893
+ {
894
+ CArray *parent, *selector;
895
+ int8_t indirect_axis = (int8_t) NUM2INT(rindirect);
896
+ int8_t ndim;
897
+ int8_t k;
898
+ VALUE obj;
899
+ CASelectAxis *ca;
900
+
901
+ Check_Type(rap_start, T_ARRAY);
902
+ Check_Type(rap_count, T_ARRAY);
903
+ Check_Type(rap_step, T_ARRAY);
904
+
905
+ TypedData_Get_Struct(rparent, CArray, &carray_data_type, parent);
906
+ TypedData_Get_Struct(rselector, CArray, &carray_data_type, selector);
907
+
908
+ ndim = parent->ndim;
909
+ if ( RARRAY_LEN(rap_start) != ndim ||
910
+ RARRAY_LEN(rap_count) != ndim ||
911
+ RARRAY_LEN(rap_step) != ndim ) {
912
+ rb_raise(rb_eArgError,
913
+ "CASelectAxis._new_debug: ap_* arrays must have length %d", ndim);
914
+ }
915
+
916
+ ca_size_t ap_start_buf[CA_RANK_MAX];
917
+ ca_size_t ap_count_buf[CA_RANK_MAX];
918
+ ca_size_t ap_step_buf[CA_RANK_MAX];
919
+ for ( k = 0; k < ndim; k++ ) {
920
+ ap_start_buf[k] = (ca_size_t) NUM2LL(rb_ary_entry(rap_start, k));
921
+ ap_count_buf[k] = (ca_size_t) NUM2LL(rb_ary_entry(rap_count, k));
922
+ ap_step_buf[k] = (ca_size_t) NUM2LL(rb_ary_entry(rap_step, k));
923
+ }
924
+
925
+ obj = TypedData_Make_Struct(klass, CASelectAxis, &caselectaxis_data_type, ca);
926
+ ca_select_axis_setup(ca, parent, indirect_axis, selector,
927
+ ap_start_buf, ap_count_buf, ap_step_buf);
928
+ rb_ivar_set(obj, rb_intern("_parent"), rparent);
929
+ rb_ivar_set(obj, rb_intern("_selector"), rselector);
930
+ return obj;
931
+ }
932
+
933
+ /* ------------------------------------------------------------------- */
934
+ /* Indexer dispatch hook: called from rb_ca_grid */
935
+ /* ------------------------------------------------------------------- */
936
+
937
+ /* Eligibility check: route ca[*spec] to CASelectAxis only when CSA's
938
+ slab fast path applies AND CSA is expected to outperform CAGrid:
939
+ - argc == ca->ndim
940
+ - exactly one boolean CArray argument; rest are nil / Integer / Range
941
+ - the boolean is on axis 0 (outermost) — slab fast path criterion
942
+
943
+ Pattern A (mask outer + AP inner) is the only configuration where CSA's
944
+ single-walk slab xfer beats CAGrid's recursive innermost-contig attach.
945
+ Other configurations (interleaved AP+INDIRECT, indirect inner, AP with
946
+ step != 1) are routed to CAGrid to avoid regressions. */
947
+ int
948
+ rb_ca_select_axis_eligible_p (int argc, VALUE *argv, VALUE self)
949
+ {
950
+ CArray *ca;
951
+ int i;
952
+
953
+ /* Cheapest rejection first: argv[0] must be T_DATA (CArray-shaped).
954
+ Everything else (nil, Fixnum, Range, ...) fails TYPE check in O(1). */
955
+ if ( argc < 1 ) return 0;
956
+ if ( TYPE(argv[0]) != T_DATA ) return 0;
957
+
958
+ /* Now the slower checks: is it a CArray, is it boolean. */
959
+ if ( ! rb_obj_is_carray(argv[0]) ) return 0;
960
+ if ( ! rb_ca_is_boolean_type(argv[0]) ) return 0;
961
+
962
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
963
+ if ( argc != ca->ndim ) return 0;
964
+
965
+ /* axis 0 is the boolean (indirect). Walk rest; require nil/Integer/Range. */
966
+ for ( i = 1; i < argc; i++ ) {
967
+ VALUE arg = argv[i];
968
+ if ( NIL_P(arg) ) continue;
969
+ if ( FIXNUM_P(arg) ) continue;
970
+ if ( TYPE(arg) == T_DATA ) return 0; /* CArray at axis > 0 → bail */
971
+ if ( rb_obj_is_kind_of(arg, rb_cRange) ) continue;
972
+ if ( rb_obj_is_kind_of(arg, rb_cInteger) ) continue;
973
+ return 0;
974
+ }
975
+
976
+ return 1;
977
+ }
978
+
979
+ /* Construct a CASelectAxis from the indexer's argc/argv. Assumes
980
+ rb_ca_select_axis_eligible_p has already returned 1. */
981
+ VALUE
982
+ rb_ca_select_axis (int argc, VALUE *argv, VALUE self)
983
+ {
984
+ CArray *parent;
985
+ CArray *selector = NULL;
986
+ ca_size_t ap_start[CA_RANK_MAX];
987
+ ca_size_t ap_count[CA_RANK_MAX];
988
+ ca_size_t ap_step[CA_RANK_MAX];
989
+ int8_t indirect_axis = -1;
990
+ VALUE rselector_keep = Qnil;
991
+ VALUE obj;
992
+ CASelectAxis *ca;
993
+ int i;
994
+
995
+ TypedData_Get_Struct(self, CArray, &carray_data_type, parent);
996
+
997
+ if ( argc != parent->ndim ) {
998
+ rb_raise(rb_eArgError,
999
+ "CASelectAxis dispatch: argc (%d) != ndim (%d)", argc, parent->ndim);
1000
+ }
1001
+
1002
+ for ( i = 0; i < argc; i++ ) {
1003
+ VALUE arg = argv[i];
1004
+
1005
+ if ( NIL_P(arg) ) {
1006
+ ap_start[i] = 0;
1007
+ ap_count[i] = parent->dim[i];
1008
+ ap_step[i] = 1;
1009
+ }
1010
+ else if ( FIXNUM_P(arg) || rb_obj_is_kind_of(arg, rb_cInteger) ) {
1011
+ ca_size_t n = NUM2LL(arg);
1012
+ if ( n < 0 ) n += parent->dim[i];
1013
+ if ( n < 0 || n >= parent->dim[i] ) {
1014
+ rb_raise(rb_eIndexError,
1015
+ "CASelectAxis: index %lld out of range at axis %d",
1016
+ (long long) n, i);
1017
+ }
1018
+ ap_start[i] = n;
1019
+ ap_count[i] = 1;
1020
+ ap_step[i] = 1;
1021
+ }
1022
+ else if ( rb_obj_is_kind_of(arg, rb_cRange) ) {
1023
+ VALUE rfirst = rb_funcall(arg, rb_intern("first"), 0);
1024
+ VALUE rlast = rb_funcall(arg, rb_intern("last"), 0);
1025
+ int exclusive = RTEST(rb_funcall(arg, rb_intern("exclude_end?"), 0));
1026
+ ca_size_t a = NUM2LL(rfirst);
1027
+ ca_size_t b = NUM2LL(rlast);
1028
+ if ( a < 0 ) a += parent->dim[i];
1029
+ if ( b < 0 ) b += parent->dim[i];
1030
+ ca_size_t cnt = exclusive ? (b - a) : (b - a + 1);
1031
+ if ( cnt <= 0 ) {
1032
+ rb_raise(rb_eIndexError,
1033
+ "CASelectAxis: empty range at axis %d", i);
1034
+ }
1035
+ if ( a < 0 || a >= parent->dim[i] ||
1036
+ (a + cnt - 1) < 0 || (a + cnt - 1) >= parent->dim[i] ) {
1037
+ rb_raise(rb_eIndexError,
1038
+ "CASelectAxis: range [%lld..%lld] out of bounds at axis %d",
1039
+ (long long) a, (long long) (a + cnt - 1), i);
1040
+ }
1041
+ ap_start[i] = a;
1042
+ ap_count[i] = cnt;
1043
+ ap_step[i] = 1;
1044
+ }
1045
+ else if ( rb_obj_is_carray(arg) && rb_ca_is_boolean_type(arg) ) {
1046
+ TypedData_Get_Struct(arg, CArray, &carray_data_type, selector);
1047
+ indirect_axis = (int8_t) i;
1048
+ rselector_keep = arg;
1049
+ ap_start[i] = 0;
1050
+ ap_count[i] = 0; /* unused for indirect axis */
1051
+ ap_step[i] = 1;
1052
+ }
1053
+ else {
1054
+ rb_raise(rb_eArgError,
1055
+ "CASelectAxis: unsupported index type at axis %d", i);
1056
+ }
1057
+ }
1058
+
1059
+ if ( indirect_axis < 0 ) {
1060
+ rb_raise(rb_eRuntimeError,
1061
+ "CASelectAxis dispatch: no boolean axis found (eligibility bug)");
1062
+ }
1063
+
1064
+ obj = TypedData_Make_Struct(rb_cCASelectAxis, CASelectAxis,
1065
+ &caselectaxis_data_type, ca);
1066
+ ca_select_axis_setup(ca, parent, indirect_axis, selector,
1067
+ ap_start, ap_count, ap_step);
1068
+
1069
+ /* Keep Ruby objects alive (parent and selector references). */
1070
+ rb_ivar_set(obj, rb_intern("_parent"), self);
1071
+ rb_ivar_set(obj, rb_intern("_selector"), rselector_keep);
1072
+ return obj;
1073
+ }
1074
+
1075
+ /* ------------------------------------------------------------------- */
1076
+ /* Producer interface: emit the per-axis descriptor array.
1077
+ Writes ca->ndim entries into `out` (caller-allocated) and the
1078
+ matching effective parent dim into `out_parent_dims` (caller-
1079
+ allocated, sized CA_RANK_MAX). CSA is a per-axis view (descriptor.
1080
+ ndim == parent.ndim), so out_parent_dims is just parent->dim
1081
+ copied through. */
1082
+
1083
+ void
1084
+ ca_select_axis_describe_axes (void *ap, ca_axis_desc_t *out,
1085
+ ca_size_t *out_parent_dims)
1086
+ {
1087
+ CASelectAxis *ca = (CASelectAxis *) ap;
1088
+ int8_t k;
1089
+ for ( k = 0; k < ca->ndim; k++ ) {
1090
+ out_parent_dims[k] = ca->parent->dim[k];
1091
+ if ( k == ca->indirect_axis ) {
1092
+ if ( ca->stride_kind ) {
1093
+ /* constant-step run -> STRIDE kind. */
1094
+ out[k].kind = CA_AXIS_KIND_STRIDE;
1095
+ out[k].count = ca->dim[k];
1096
+ out[k].start = ca->stride_start;
1097
+ out[k].step = ca->stride_step;
1098
+ out[k].indices = NULL;
1099
+ } else {
1100
+ out[k].kind = CA_AXIS_KIND_INDEX;
1101
+ out[k].count = ca->dim[k];
1102
+ out[k].start = 0;
1103
+ out[k].step = 0;
1104
+ out[k].indices = ca->indices;
1105
+ }
1106
+ } else {
1107
+ out[k].kind = CA_AXIS_KIND_STRIDE;
1108
+ out[k].count = ca->ap_count[k];
1109
+ out[k].start = ca->ap_start[k];
1110
+ out[k].step = ca->ap_step[k];
1111
+ out[k].indices = NULL;
1112
+ }
1113
+ }
1114
+ }
1115
+
1116
+ #ifdef CARRAY_DEV_BUILD
1117
+ /* ============================================================
1118
+ * Debug accessors (dev-only, stripped in release)
1119
+ *
1120
+ * Gated by CARRAY_DEV_BUILD (enabled via `extconf.rb --enable-dev-build`
1121
+ * or `CARRAY_DEV=1 rake build_ext`). They expose the per-axis descriptor
1122
+ * and the engine's raw output to Ruby so spec_ai can pin them; nothing in
1123
+ * lib/ or the rest of ext/ consumes them. CAGrid carries the same four
1124
+ * under the same fence.
1125
+ * ============================================================ */
1126
+
1127
+ /* Returns an Array of per-axis Arrays:
1128
+ STRIDE axis -> [:stride, count, start, step]
1129
+ INDEX axis -> [:index, count, [indices...]] */
1130
+ static VALUE
1131
+ rb_ca_select_axis_describe_axes (VALUE self)
1132
+ {
1133
+ CASelectAxis *ca;
1134
+ ca_axis_desc_t desc[CA_RANK_MAX];
1135
+ VALUE arr, entry, idx_ary;
1136
+ int8_t k;
1137
+ ca_size_t i;
1138
+
1139
+ ca_size_t pdims[CA_RANK_MAX];
1140
+ TypedData_Get_Struct(self, CASelectAxis, &caselectaxis_data_type, ca);
1141
+ ca_select_axis_describe_axes(ca, desc, pdims);
1142
+
1143
+ arr = rb_ary_new_capa(ca->ndim);
1144
+ for ( k = 0; k < ca->ndim; k++ ) {
1145
+ if ( desc[k].kind == CA_AXIS_KIND_STRIDE ) {
1146
+ entry = rb_ary_new_capa(4);
1147
+ rb_ary_push(entry, ID2SYM(rb_intern("stride")));
1148
+ rb_ary_push(entry, SIZE2NUM(desc[k].count));
1149
+ rb_ary_push(entry, SIZE2NUM(desc[k].start));
1150
+ rb_ary_push(entry, SIZE2NUM(desc[k].step));
1151
+ } else {
1152
+ entry = rb_ary_new_capa(3);
1153
+ rb_ary_push(entry, ID2SYM(rb_intern("index")));
1154
+ rb_ary_push(entry, SIZE2NUM(desc[k].count));
1155
+ idx_ary = rb_ary_new_capa(desc[k].count);
1156
+ for ( i = 0; i < desc[k].count; i++ ) {
1157
+ rb_ary_push(idx_ary, SIZE2NUM(desc[k].indices[i]));
1158
+ }
1159
+ rb_ary_push(entry, idx_ary);
1160
+ }
1161
+ rb_ary_push(arr, entry);
1162
+ }
1163
+ return arr;
1164
+ }
1165
+
1166
+ /* Returns the raw bytes produced by ca_axis_dispatch_attach as a String.
1167
+ Compared against `view.to_ca.dump_binary` in tests to verify binary
1168
+ equality with the existing attach path. */
1169
+ static VALUE
1170
+ rb_ca_select_axis_dispatch_attach_debug (VALUE self)
1171
+ {
1172
+ CASelectAxis *ca;
1173
+ ca_axis_desc_t desc[CA_RANK_MAX];
1174
+ char *buf;
1175
+ VALUE str;
1176
+
1177
+ ca_size_t pdims[CA_RANK_MAX];
1178
+ TypedData_Get_Struct(self, CASelectAxis, &caselectaxis_data_type, ca);
1179
+ ca_select_axis_describe_axes(ca, desc, pdims);
1180
+
1181
+ ca_attach(ca->parent);
1182
+ buf = ca_axis_dispatch_attach(ca->parent, pdims, desc, ca->ndim, ca->bytes,
1183
+ ca->elements, NULL);
1184
+ ca_detach(ca->parent);
1185
+
1186
+ str = rb_str_new(buf, ca->elements * ca->bytes);
1187
+ xfree(buf);
1188
+ return str;
1189
+ }
1190
+
1191
+ /* Feeds a String of bytes (length == elements * bytes) through
1192
+ ca_axis_dispatch_scatter and returns nil. Mutates the parent; tests
1193
+ compare the resulting parent state against the same scatter performed
1194
+ through the view's sync path. */
1195
+ static VALUE
1196
+ rb_ca_select_axis_dispatch_scatter_debug (VALUE self, VALUE in_str)
1197
+ {
1198
+ CASelectAxis *ca;
1199
+ ca_axis_desc_t desc[CA_RANK_MAX];
1200
+ ca_size_t expected;
1201
+
1202
+ Check_Type(in_str, T_STRING);
1203
+ TypedData_Get_Struct(self, CASelectAxis, &caselectaxis_data_type, ca);
1204
+
1205
+ expected = ca->elements * ca->bytes;
1206
+ if ( (ca_size_t) RSTRING_LEN(in_str) != expected ) {
1207
+ rb_raise(rb_eArgError,
1208
+ "CASelectAxis#_dispatch_scatter_debug: input string length %lld "
1209
+ "!= expected %lld",
1210
+ (long long) RSTRING_LEN(in_str), (long long) expected);
1211
+ }
1212
+ ca_size_t pdims[CA_RANK_MAX];
1213
+ ca_select_axis_describe_axes(ca, desc, pdims);
1214
+
1215
+ ca_attach(ca->parent);
1216
+ ca_axis_dispatch_scatter(ca->parent, pdims, desc, ca->ndim, ca->bytes,
1217
+ ca->elements, RSTRING_PTR(in_str));
1218
+ ca_sync(ca->parent);
1219
+ ca_detach(ca->parent);
1220
+ return Qnil;
1221
+ }
1222
+
1223
+ /* Broadcasts a String of length == bytes (one value) into every selected
1224
+ parent cell through ca_axis_dispatch_fill_value. */
1225
+ static VALUE
1226
+ rb_ca_select_axis_dispatch_fill_value_debug (VALUE self, VALUE val_str)
1227
+ {
1228
+ CASelectAxis *ca;
1229
+ ca_axis_desc_t desc[CA_RANK_MAX];
1230
+
1231
+ Check_Type(val_str, T_STRING);
1232
+ TypedData_Get_Struct(self, CASelectAxis, &caselectaxis_data_type, ca);
1233
+
1234
+ if ( (ca_size_t) RSTRING_LEN(val_str) != ca->bytes ) {
1235
+ rb_raise(rb_eArgError,
1236
+ "CASelectAxis#_dispatch_fill_value_debug: value string length %lld "
1237
+ "!= bytes %lld",
1238
+ (long long) RSTRING_LEN(val_str), (long long) ca->bytes);
1239
+ }
1240
+ ca_size_t pdims[CA_RANK_MAX];
1241
+ ca_select_axis_describe_axes(ca, desc, pdims);
1242
+
1243
+ ca_attach(ca->parent);
1244
+ ca_axis_dispatch_fill_value(ca->parent, pdims, desc, ca->ndim, ca->bytes,
1245
+ ca->elements, RSTRING_PTR(val_str));
1246
+ ca_sync(ca->parent);
1247
+ ca_detach(ca->parent);
1248
+ return Qnil;
1249
+ }
1250
+ #endif /* CARRAY_DEV_BUILD */
1251
+
1252
+ /* ------------------------------------------------------------------- */
1253
+
1254
+ void
1255
+ Init_ca_obj_select_axis (void)
1256
+ {
1257
+ rb_cCASelectAxis = rb_define_class("CASelectAxis", rb_cCAView);
1258
+ rb_cCASelectAxisMask = rb_define_class("CASelectAxisMask", rb_cCASelectAxis);
1259
+
1260
+ ca_select_axis_func.struct_size = sizeof(CASelectAxis);
1261
+ ca_select_axis_func.pool_bytes = ca_select_axis_pool_bytes;
1262
+ ca_select_axis_func.pool_init = ca_select_axis_pool_init;
1263
+
1264
+ CA_OBJ_SELECT_AXIS = ca_install_obj_type(rb_cCASelectAxis,
1265
+ &caselectaxis_data_type,
1266
+ rb_cCASelectAxisMask,
1267
+ &caselectaxis_mask_data_type,
1268
+ &ca_select_axis_func, sizeof(ca_select_axis_func));
1269
+ rb_define_const(rb_cObject, "CA_OBJ_SELECT_AXIS", INT2NUM(CA_OBJ_SELECT_AXIS));
1270
+
1271
+ rb_define_alloc_func(rb_cCASelectAxis, rb_ca_select_axis_s_allocate);
1272
+ rb_define_method(rb_cCASelectAxis, "initialize_copy",
1273
+ rb_ca_select_axis_initialize_copy, 1);
1274
+ /* Debug-only ctor for testing; not part of the public API. */
1275
+ rb_define_singleton_method(rb_cCASelectAxis, "_new_debug",
1276
+ rb_ca_select_axis_s_new_debug, 6);
1277
+
1278
+ #ifdef CARRAY_DEV_BUILD
1279
+ /* debug accessors (dev-only, stripped in release).
1280
+ _dispatch_attach_debug returns the raw ca_axis_dispatch_attach bytes
1281
+ as a Ruby String so tests can assert binary equality with the
1282
+ existing attach output. */
1283
+ rb_define_method(rb_cCASelectAxis, "_describe_axes",
1284
+ rb_ca_select_axis_describe_axes, 0);
1285
+ rb_define_method(rb_cCASelectAxis, "_dispatch_attach_debug",
1286
+ rb_ca_select_axis_dispatch_attach_debug, 0);
1287
+ rb_define_method(rb_cCASelectAxis, "_dispatch_scatter_debug",
1288
+ rb_ca_select_axis_dispatch_scatter_debug, 1);
1289
+ rb_define_method(rb_cCASelectAxis, "_dispatch_fill_value_debug",
1290
+ rb_ca_select_axis_dispatch_fill_value_debug, 1);
1291
+ #endif
1292
+
1293
+ /* Benchmark-only bypass setter on CArray class. */
1294
+ rb_define_singleton_method(rb_cCArray, "_csa_bypass=", rb_ca_s_csa_bypass_eq, 1);
1295
+ rb_define_singleton_method(rb_cCArray, "_csa_bypass?", rb_ca_s_csa_bypass_p, 0);
1296
+ }