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,2584 @@
1
+ /* ---------------------------------------------------------------------------
2
+
3
+ CAStride: generic strided view array. Holds byte-unit strides
4
+ (negative allowed) and a byte-unit base_offset relative to parent->ptr.
5
+ Two-mode operation:
6
+
7
+ detached (ptr == NULL): each element access computes
8
+ parent->ptr + base_offset + sum_k(idx[k] * strides[k])
9
+
10
+ attached (ptr != NULL): own contiguous row-major buffer, populated
11
+ by gather copy on attach, scattered back on sync, freed on
12
+ final detach.
13
+
14
+ This is the whole strided-view family: CARefer and CABlock carry a tail
15
+ of their own on top of this prefix, CATranspose / CAFarray / CARepeat /
16
+ CAField are plain typedefs of it. All of them inherit the operation
17
+ table below unchanged, so a fast path added here reaches every one.
18
+ devel/CAStride.md is the reference for writing a subclass.
19
+
20
+ ---------------------------------------------------------------------------- */
21
+
22
+ #include "carray.h"
23
+ #include "ca_iter_substrate.h"
24
+ #include "ca_obj_face.h" /* ca_is_face, used by the compose-fold walk */
25
+
26
+ static size_t
27
+ ca_stride_dsize (const void *ap)
28
+ {
29
+ const CAStride *ca = (const CAStride *) ap;
30
+ /* dim and strides are each ALLOC_N(ndim) (legacy) or wired into the
31
+ framework-managed _pool buffer (pool path). Either way the live byte
32
+ accounting is the struct plus 2*ndim ca_size_t cells. */
33
+ return sizeof(CAStride) + 2 * ca->ndim * sizeof(ca_size_t);
34
+ }
35
+
36
+ /* Pool framework hooks.
37
+ When ca_func[obj_type].pool_init runs against the buffer allocated by
38
+ ca_array_pool_alloc, dim and strides are wired into a single contiguous
39
+ region instead of taking two separate ALLOC_N calls. The legacy
40
+ ALLOC_N path stays available for any obj_type that has not registered
41
+ these hooks yet (= ca->_pool stays NULL through setup). */
42
+ static size_t
43
+ ca_stride_pool_bytes (int8_t ndim)
44
+ {
45
+ ca_size_t n = (ndim > 0) ? ndim : 1;
46
+ return 2 * (size_t) n * sizeof(ca_size_t);
47
+ }
48
+
49
+ static void
50
+ ca_stride_pool_init (void *ap, int8_t ndim)
51
+ {
52
+ CAStride *ca = (CAStride *) ap;
53
+ ca_size_t n = (ndim > 0) ? ndim : 1;
54
+ ca_size_t *base = (ca_size_t *) ca->_pool;
55
+ ca->dim = base + 0 * n;
56
+ ca->strides = base + 1 * n;
57
+ }
58
+
59
+ const rb_data_type_t castride_data_type = {
60
+ .parent = &caview_data_type,
61
+ .wrap_struct_name = "CAStride",
62
+ .function = {
63
+ .dmark = ca_mark,
64
+ .dfree = ca_free,
65
+ .dsize = ca_stride_dsize,
66
+ .dcompact = NULL
67
+ },
68
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
69
+ };
70
+
71
+ const rb_data_type_t castride_mask_data_type = {
72
+ .parent = &castride_data_type,
73
+ .wrap_struct_name = "CAStrideMask",
74
+ .function = {
75
+ .dmark = NULL,
76
+ .dfree = ca_free_nop,
77
+ .dsize = ca_stride_dsize,
78
+ .dcompact = NULL
79
+ },
80
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
81
+ };
82
+
83
+ VALUE rb_cCAStride;
84
+ VALUE rb_cCAStrideMask;
85
+ int8_t CA_OBJ_STRIDE; /* assigned at Init time via ca_install_obj_type */
86
+
87
+ /* ------------------------------------------------------------------- */
88
+
89
+ /* `obj_type` is the dispatch tag stored in ca->obj_type. Pass
90
+ CA_OBJ_STRIDE when constructing a plain CAStride; pass the
91
+ subclass's own obj_type (e.g. CA_OBJ_TRANSPOSE) when used from a
92
+ subclass setup -- this avoids the "stamp it again after setup"
93
+ override pattern and makes the C-level dispatch wire up correctly
94
+ on the first try. */
95
+ int
96
+ ca_stride_setup (CAStride *ca, int8_t obj_type, CArray *parent,
97
+ int8_t data_type, ca_size_t bytes,
98
+ int8_t ndim, ca_size_t *dim,
99
+ ca_size_t *strides, ca_size_t base_offset)
100
+ {
101
+ ca_size_t elements;
102
+ int i;
103
+
104
+ if (ndim < 0 || ndim > CA_RANK_MAX) {
105
+ rb_raise(rb_eArgError, "invalid ndim %d", (int) ndim);
106
+ }
107
+ elements = 1;
108
+ for (i = 0; i < ndim; i++) {
109
+ if (dim[i] < 0) {
110
+ rb_raise(rb_eIndexError,
111
+ "invalid size for %i-th dimension (negative)", i);
112
+ }
113
+ elements *= dim[i];
114
+ }
115
+
116
+ ca->obj_type = obj_type;
117
+ ca->data_type = data_type;
118
+ ca->flags = 0;
119
+ ca->ndim = ndim;
120
+ ca->bytes = bytes;
121
+ ca->elements = elements;
122
+ ca->ptr = NULL;
123
+ ca->mask = NULL;
124
+ ca->parent = parent;
125
+ ca->attach = 0;
126
+ ca->nosync = 0;
127
+ if ( ! ca->_pool ) {
128
+ /* Legacy path: caller used ALLOC(CAStride) without ca_array_alloc,
129
+ so dim/strides need their own backing. Pool path callers have
130
+ these already wired by ca_stride_pool_init. */
131
+ ca->dim = ALLOC_N(ca_size_t, ndim > 0 ? ndim : 1);
132
+ ca->strides = ALLOC_N(ca_size_t, ndim > 0 ? ndim : 1);
133
+ }
134
+ ca->base_offset = base_offset;
135
+
136
+ for (i = 0; i < ndim; i++) {
137
+ ca->dim[i] = dim[i];
138
+ ca->strides[i] = strides[i];
139
+ }
140
+
141
+ if (parent && ca_has_mask(parent)) {
142
+ ca_create_mask(ca);
143
+ }
144
+
145
+ return 0;
146
+ }
147
+
148
+ CAStride *
149
+ ca_stride_new (int8_t obj_type, CArray *parent,
150
+ int8_t data_type, ca_size_t bytes,
151
+ int8_t ndim, ca_size_t *dim,
152
+ ca_size_t *strides, ca_size_t base_offset)
153
+ {
154
+ CAStride *ca = (CAStride *) ca_array_alloc(obj_type, ndim);
155
+ ca_stride_setup(ca, obj_type, parent,
156
+ data_type, bytes, ndim, dim, strides, base_offset);
157
+ return ca;
158
+ }
159
+
160
+ static void
161
+ free_ca_stride (void *ap)
162
+ {
163
+ CAStride *ca = (CAStride *) ap;
164
+ if (ca == NULL) return;
165
+ ca_free(ca->mask);
166
+ if (ca->_pool) {
167
+ /* Pool path: one xfree covers dim/strides via the pool buffer,
168
+ another covers the struct. */
169
+ ca_array_free(ca);
170
+ } else {
171
+ /* Legacy path: free dim/strides individually. */
172
+ xfree(ca->dim);
173
+ xfree(ca->strides);
174
+ xfree(ca);
175
+ }
176
+ }
177
+
178
+ static int ca_layout_is_contiguous (int8_t ndim, const ca_size_t *dim,
179
+ const ca_size_t *strides, ca_size_t bytes);
180
+ /* ca_stride_xfer_with_layout / ca_stride_merge_axes are declared in
181
+ ca_iter_substrate.h — the kernel iterator drives them too. */
182
+
183
+ /* Forward decl: ca_stride_func defined later in the file. */
184
+ extern ca_operation_function_t ca_stride_func;
185
+
186
+ /* Shared cache-tiled transpose helper, defined in carray_core.c. Called by
187
+ three paths: the central dispatcher's ptr path, our root-direct memcpy
188
+ path, and ca_stride_xfer_with_layout's tile branch. Takes a raw src base
189
+ ptr and bytes so all three callers can pass whatever ptr + bytes pair they
190
+ already hold. */
191
+ void ca_xfer_stride_tiled_transpose_2d (char *src_base, ca_size_t bytes,
192
+ ca_size_t *counts, ca_size_t *strides,
193
+ char *data, int dir);
194
+
195
+ /* Public form of the family test the fold walk below uses inline. The
196
+ family is defined by the operation table, not by a class or an
197
+ obj_type list: every member was installed with a copy of
198
+ ca_stride_func, so an externally installed view that shares the
199
+ table answers true as well. See carray.h for what membership does
200
+ and does not promise. */
201
+ int
202
+ ca_is_stride_family (const void *ap)
203
+ {
204
+ const CArray *ca = (const CArray *) ap;
205
+ return ( ca_func[ca->obj_type].attach == ca_stride_func.attach );
206
+ }
207
+
208
+ /* Compose `leaf->strides` and `leaf->base_offset` (which live in `parent`'s
209
+ own logical row-major contig byte space) into `out_strides` and
210
+ `out_base` expressed in `parent->parent`'s byte space.
211
+ Both `leaf` and `parent` are CAStride views; `leaf->parent == parent`.
212
+ Returns 1 on clean decomposition, 0 on failure (a stride that does not
213
+ align with parent's logical layout -- e.g. a synthetic stride that
214
+ crosses parent dim boundaries non-aligned). On 0, caller falls back to
215
+ materialise-parent path. */
216
+ int
217
+ ca_stride_compose_through (CAStride *leaf, CAStride *parent,
218
+ ca_size_t *out_strides, ca_size_t *out_base)
219
+ {
220
+ ca_size_t prod[CA_RANK_MAX + 1];
221
+ ca_size_t base_idx[CA_RANK_MAX]; /* base position in each parent dim */
222
+ int8_t k, m;
223
+
224
+ /* prod[k] = product of parent->dim[k..ndim-1] (in elements) */
225
+ prod[parent->ndim] = 1;
226
+ for (k = parent->ndim - 1; k >= 0; k--)
227
+ prod[k] = prod[k + 1] * parent->dim[k];
228
+
229
+ /* Compose base offset first. We need the per-dim base position
230
+ to validate stride composition against parent dim bounds (a small
231
+ forward stride starting near the end of a parent dim wraps into
232
+ the next dim with the wrong stride; the bounds check below needs
233
+ to know where in the dim we start).
234
+
235
+ Sub-element offset (= leaf->base_offset % parent->bytes != 0) is
236
+ captured into `sub_byte` and folded into out_base at the end,
237
+ rather than rejected. This handles CAField over CAStride family
238
+ (= second-or-later field of a multi-field record) and CARefer
239
+ byte-reinterpret + offset patterns. Per-cell memcpy in the hot
240
+ path uses LEAF's bytes (= field width), so a non-parent-aligned
241
+ base is correct. */
242
+ ca_size_t flat = leaf->base_offset / parent->bytes;
243
+ ca_size_t sub_byte = leaf->base_offset % parent->bytes;
244
+ ca_size_t base = parent->base_offset;
245
+ for (m = 0; m < parent->ndim; m++) {
246
+ base_idx[m] = flat / prod[m + 1];
247
+ flat -= base_idx[m] * prod[m + 1];
248
+ if (base_idx[m] >= parent->dim[m]) return 0;
249
+ base += base_idx[m] * parent->strides[m];
250
+ }
251
+ if (flat != 0) return 0;
252
+ *out_base = base + sub_byte;
253
+
254
+ /* Compose each leaf dim's stride.
255
+ Validity rule: leaf dim k must advance exactly one parent dim
256
+ (not cross multiple parent dims) AND stay within that parent
257
+ dim's bounds across leaf's full extent *given the base position*.
258
+ Otherwise the leaf's traversal would wrap across parent dim
259
+ boundaries, which is a non-strided access pattern that cannot
260
+ be folded. */
261
+ for (k = 0; k < leaf->ndim; k++) {
262
+ if (leaf->strides[k] % parent->bytes != 0) return 0;
263
+ ca_size_t advance = leaf->strides[k] / parent->bytes;
264
+ ca_size_t composed = 0;
265
+ int nonzero_count = 0;
266
+ int nonzero_dim = -1;
267
+ ca_size_t nonzero_step = 0;
268
+ for (m = 0; m < parent->ndim; m++) {
269
+ ca_size_t step = advance / prod[m + 1];
270
+ advance -= step * prod[m + 1];
271
+ composed += step * parent->strides[m];
272
+ if (step != 0) {
273
+ nonzero_count++;
274
+ nonzero_dim = m;
275
+ nonzero_step = step;
276
+ }
277
+ }
278
+ if (advance != 0) return 0;
279
+ if (nonzero_count > 1) return 0; /* crosses parent dims */
280
+ if (nonzero_count == 1) {
281
+ /* Final position in parent dim after the full leaf extent.
282
+ For a forward step it must stay strictly below dim; for a
283
+ backward step it must stay at or above 0. */
284
+ ca_size_t final_pos =
285
+ base_idx[nonzero_dim] + (leaf->dim[k] - 1) * nonzero_step;
286
+ if (nonzero_step > 0) {
287
+ if (final_pos >= parent->dim[nonzero_dim]) return 0;
288
+ } else {
289
+ if (final_pos < 0) return 0;
290
+ }
291
+ }
292
+ out_strides[k] = composed;
293
+ }
294
+
295
+ return 1;
296
+ }
297
+
298
+ /* Walk up the CAStride chain composing strides and base_offset until we
299
+ reach a non-CAStride parent (entity or non-stride view like CAReduce).
300
+ `out_strides` and `out_base` describe the leaf's element layout in
301
+ *out_root's ptr-byte space.
302
+ On any composition failure (non-aligned stride), returns the deepest
303
+ successfully-composed root (which may be the immediate parent or an
304
+ intermediate). The chain is always foldable for at least one step in
305
+ theory; the conservative return is the parent itself.
306
+ Caller must ca_attach(*out_root) if !ca_is_attached(*out_root) before
307
+ reading from (*out_root)->ptr + *out_base. Composition writes into
308
+ the provided ndim-sized out_strides buffer. */
309
+ /* CAWindow operation table, read by the fill_data wasted-gather gate below
310
+ to recognise a CAWindow root. Compose-fold itself does not special-case
311
+ CAWindow: that lives in CAWindow's own fold_stride slot. */
312
+ extern ca_operation_function_t ca_window_func;
313
+
314
+ /* Hybrid compose-fold walk. Two kinds of participant fold a leaf's stride coordinates one hop closer to the root:
315
+
316
+ - CAStride family: recognised open-inline by func-pointer comparison
317
+ (ca_func[obj_type].attach == ca_stride_func.attach), composed via
318
+ ca_stride_compose_through (stride machinery's own self-knowledge).
319
+ - sometimes-fold participants (CAWindow now; CAGrid/CSA/CATile later):
320
+ dispatched through the fold_stride operation slot, which composes the
321
+ fold state into the next parent's space or declines (-> boundary).
322
+
323
+ No view names beyond the CAStride family appear here; new participants
324
+ join by implementing fold_stride (open/closed principle on the foreign
325
+ axis). No new flags: the CAStride family is detected by attach-pointer
326
+ identity, fold participation by fold_stride != NULL. */
327
+ void
328
+ ca_stride_compose_to_root (CAStride *leaf,
329
+ CArray **out_root,
330
+ ca_size_t *out_strides,
331
+ ca_size_t *out_base)
332
+ {
333
+ ca_fold_t f;
334
+ CArray *cur = leaf->parent;
335
+ int8_t k;
336
+
337
+ f.ndim = leaf->ndim;
338
+ f.base = leaf->base_offset;
339
+ for (k = 0; k < leaf->ndim; k++) {
340
+ f.strides[k] = leaf->strides[k];
341
+ f.counts[k] = leaf->dim[k]; /* extent, used by compose-through bounds */
342
+ }
343
+
344
+ while (1) {
345
+ if (ca_func[cur->obj_type].attach == ca_stride_func.attach) {
346
+ /* OPEN: CAStride family. Compose f (leaf-in-cur-space) through the
347
+ CAStride parent into cur->parent's space. */
348
+ CAStride *p = (CAStride *) cur;
349
+ CAStride tmp;
350
+ ca_size_t next_strides[CA_RANK_MAX];
351
+ ca_size_t next_base;
352
+ tmp.ndim = f.ndim;
353
+ tmp.bytes = leaf->bytes;
354
+ tmp.dim = f.counts; /* extent in cur space */
355
+ tmp.strides = f.strides;
356
+ tmp.base_offset = f.base;
357
+ if (!ca_stride_compose_through(&tmp, p, next_strides, &next_base)) {
358
+ break;
359
+ }
360
+ for (k = 0; k < f.ndim; k++) f.strides[k] = next_strides[k];
361
+ f.base = next_base;
362
+ cur = p->parent;
363
+ }
364
+ else if (ca_func[cur->obj_type].fold_stride) {
365
+ /* DUCK: sometimes-fold participant. It composes f and advances, or
366
+ declines (-> cur is the fold boundary). */
367
+ void *next;
368
+ if (!ca_func[cur->obj_type].fold_stride(cur, &f, &next)) {
369
+ break;
370
+ }
371
+ cur = (CArray *) next;
372
+ }
373
+ else if (ca_is_face(cur)) {
374
+ /* Face is layout-identity over its parent (= byte-for-byte alias via
375
+ ca_face_attach, same data_type / bytes / strides). Walk through
376
+ as an identity step so the composed (strides, base) carry into
377
+ parent's space unchanged. Without this, compose stops at Face
378
+ and partial materialise / Face xfer_stride delegate paths re-
379
+ enter the dispatcher with the ROOT's bytes interpretation
380
+ (= entity FIXLEN bytes) instead of the LEAF's bytes (= e.g. f64
381
+ field width), causing a heap buffer overflow + wrong-value bulk
382
+ gather (= reporter's bug: CARecord chain + CAField bulk path). */
383
+ cur = ((CAView *) cur)->parent;
384
+ }
385
+ else {
386
+ break; /* boundary: cur is the root we expose */
387
+ }
388
+ }
389
+
390
+ *out_root = cur;
391
+ *out_base = f.base;
392
+ for (k = 0; k < leaf->ndim; k++) out_strides[k] = f.strides[k];
393
+ }
394
+
395
+ /* Resolves a candidate parent through identity CAStride compose-fold to find
396
+ an attached root.
397
+ Returns the resolved CArray (or the original `cand` if cand already has
398
+ ptr, isn't CAStride family, or doesn't identity-compose to a ptr-bearing
399
+ root). Used by view xfer_addrs slots (CSA / CAGrid / CASelect) to lift
400
+ the parent->ptr gate through view CAStride layers when the compose
401
+ is element-mapping identity (= simple reshape / alias).
402
+
403
+ "Identity compose-fold" semantics:
404
+ - cand->bytes == root->bytes (no byte reinterpret)
405
+ - composed_base == 0
406
+ - composed_strides[k] match row-major over cand->dim with cand->bytes
407
+
408
+ When true, the cand's flat byte addressing equals root->ptr's flat byte
409
+ addressing for the first `cand->elements * cand->bytes` bytes. The
410
+ axis_dispatch engine can then use root->ptr as parent->ptr directly
411
+ (with cand's logical shape passed via parent_axis_dims unchanged).
412
+
413
+ This unblocks the chain pattern a.flatten[idx].reshape(*idx.shape) where
414
+ intermediate CARefer layers are view (no explicit attach) but
415
+ element-identity-aliased to the leaf entity. */
416
+ CArray *
417
+ ca_resolve_attached_root_via_identity (CArray *cand)
418
+ {
419
+ CAStride *cs;
420
+ CArray *root;
421
+ ca_size_t composed_strides[CA_RANK_MAX];
422
+ ca_size_t composed_base;
423
+ ca_size_t s;
424
+ int8_t k;
425
+
426
+ if ( cand->ptr ) return cand;
427
+ if ( ca_func[cand->obj_type].attach != ca_stride_func.attach ) return cand;
428
+
429
+ cs = (CAStride *) cand;
430
+ ca_stride_compose_to_root(cs, &root, composed_strides, &composed_base);
431
+ if ( !root->ptr ) return cand;
432
+ if ( cs->bytes != root->bytes ) return cand;
433
+ if ( composed_base != 0 ) return cand;
434
+
435
+ s = root->bytes;
436
+ for ( k = cs->ndim - 1; k >= 0; k-- ) {
437
+ if ( composed_strides[k] != s ) return cand;
438
+ s *= cs->dim[k];
439
+ }
440
+ return root;
441
+ }
442
+
443
+ /* ------------------------------------------------------------------- */
444
+
445
+ static void *
446
+ ca_stride_func_clone (void *ap)
447
+ {
448
+ CAStride *ca = (CAStride *) ap;
449
+ /* Preserve the subclass identity (CATranspose, etc.) by reusing
450
+ the source's obj_type rather than hardcoding CA_OBJ_STRIDE. */
451
+ return ca_stride_new(ca->obj_type, ca->parent, ca->data_type, ca->bytes,
452
+ ca->ndim, ca->dim, ca->strides, ca->base_offset);
453
+ }
454
+
455
+ /* CAREFUL: per-cell access paths must not call ca_attach on any ancestor.
456
+ When ca->ptr is NULL the cell is delegated one hop to the parent instead;
457
+ attaching would materialise the whole parent to read one cell (ruinous for
458
+ a non-trivial chain) and would hand back a dangling pointer after the
459
+ matching ca_detach. The delegation recurses and bottoms out at an entity,
460
+ whose ptr is always live.
461
+
462
+ Byte-offset arithmetic: leaf's strides are in bytes relative to
463
+ parent's ptr space. off = base_offset + Σ idx[k]*strides[k] is the
464
+ byte offset into parent. Split into (addr, sub) = (off / parent.bytes,
465
+ off % parent.bytes); parent.ptr_at_addr(addr) returns the pointer to
466
+ parent's cell, and sub handles the byte-mismatched reinterpret case
467
+ (= CAField .real / .imag over complex). */
468
+
469
+ /* Direction-unified per-cell transfer. Shares the offset computation; the alias / attached-parent cases differ only
470
+ by memcpy direction, the non-attached delegate path branches GET/PUT. */
471
+ static void
472
+ ca_stride_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
473
+ {
474
+ CAStride *ca = (CAStride *) ap;
475
+ ca_size_t off;
476
+ int8_t k;
477
+
478
+ if (ca->ptr) {
479
+ /* Attached: row-major direct address (ca_index2addr inlined into the
480
+ loop below for symmetry with the un-attached branches). */
481
+ ca_size_t addr = ca_index2addr((CArray *) ca, idx);
482
+ char *p = ca->ptr + ca->bytes * addr;
483
+ if (dir == CA_XFER_GET) memcpy(data, p, ca->bytes);
484
+ else memcpy(p, data, ca->bytes);
485
+ return;
486
+ }
487
+
488
+ off = ca->base_offset;
489
+ for (k = 0; k < ca->ndim; k++) {
490
+ off += idx[k] * ca->strides[k];
491
+ }
492
+
493
+ if (ca_is_attached(ca->parent)) {
494
+ char *p = ca->parent->ptr + off;
495
+ if (dir == CA_XFER_GET) memcpy(data, p, ca->bytes);
496
+ else memcpy(p, data, ca->bytes);
497
+ return;
498
+ }
499
+
500
+ /* Parent not attached: delegate via the public addr dispatchers (no attach).
501
+ Handles per-view transforms and byte-mismatched reinterpret (CAField). */
502
+ {
503
+ ca_size_t pbytes = ca->parent->bytes;
504
+ if (ca->bytes == pbytes && off % pbytes == 0) {
505
+ /* Aligned single-cell delegate: use the parent's INDEX path, not the
506
+ addr path. ca_fetch_addr/ca_store_addr route through ca_xfer_addrs,
507
+ which for a multi-region parent (CAStack) does an O(K) bucket scan +
508
+ per-call ALLOCV -- an O(K)-per-cell catastrophe for per-cell access
509
+ over CAStride-over-CAStack. addr2index + xfer_index is O(ndim). */
510
+ ca_size_t pidx[CA_RANK_MAX];
511
+ ca_addr2index(ca->parent, off / pbytes, pidx);
512
+ ca_xfer_index(ca->parent, pidx, data, dir);
513
+ }
514
+ else {
515
+ char buf[64]; /* parent cell width <= 16 in practice */
516
+ char *scratch = (pbytes <= (ca_size_t) sizeof(buf)) ? buf : xmalloc(pbytes);
517
+ if (dir == CA_XFER_GET) {
518
+ ca_fetch_addr(ca->parent, off / pbytes, scratch);
519
+ memcpy(data, scratch + (off % pbytes), ca->bytes);
520
+ }
521
+ else {
522
+ /* read-modify-write the parent cell for sub-byte reinterpret */
523
+ ca_fetch_addr(ca->parent, off / pbytes, scratch);
524
+ memcpy(scratch + (off % pbytes), data, ca->bytes);
525
+ ca_store_addr(ca->parent, off / pbytes, scratch);
526
+ }
527
+ if (scratch != buf) xfree(scratch);
528
+ }
529
+ }
530
+ }
531
+
532
+ /* Batched address gather/scatter.
533
+
534
+ Reached only when ca->ptr == NULL (the central dispatcher handles the
535
+ alias / attached / entity case with a direct memcpy fast path). Compose
536
+ the whole CAStride chain to its root ONCE, translate every addr to the
537
+ root's flat element address with affine arithmetic, then hand the whole
538
+ list to the root in a SINGLE ca_xfer_addrs call -- no whole-view attach,
539
+ no per-cell view dispatch through the intermediate views.
540
+
541
+ The root may itself be a non-foldable view (e.g. CASelect, CAFake); the
542
+ recursive ca_xfer_addrs call lets that view translate one more hop. The
543
+ recursion bottoms at an entity whose ptr is live.
544
+
545
+ Byte-mismatched reinterpret (CAField .real/.imag over complex, where
546
+ ca->bytes != root->bytes or the byte offset is not a multiple of the
547
+ root cell) cannot be expressed as a flat root address, so those cells
548
+ fall back to the per-cell xfer_index delegate (which also avoids attach). */
549
+ static void
550
+ ca_stride_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
551
+ void *data, int dir)
552
+ {
553
+ CAStride *ca = (CAStride *) ap;
554
+ CArray *root;
555
+ ca_size_t composed_strides[CA_RANK_MAX];
556
+ ca_size_t composed_base;
557
+ ca_size_t rbytes;
558
+ ca_size_t *paddrs;
559
+ ca_size_t i, base;
560
+ int8_t k;
561
+ int all_aligned = 1;
562
+ volatile VALUE holder;
563
+ char *d = (char *) data;
564
+
565
+ ca_stride_compose_to_root(ca, &root, composed_strides, &composed_base);
566
+ rbytes = root->bytes;
567
+
568
+ /* Fast path: identity transform detection -- when the addr remap composed_base +
569
+ Σ idx[k]*composed_strides[k] reduces to an identity mapping over the
570
+ root's flat byte space (= simple reshape, same bytes, row-major
571
+ composed_strides), forward addrs as-is to root without the ALLOCV +
572
+ per-cell remap loop. Cascades through chain a[idx_2d] (= outer
573
+ reshape -> CAGrid -> inner reshape -> entity): outer CARefer is a
574
+ simple reshape so addrs pass through, then CAGrid Y.1.b fast path
575
+ triggers on the recursive call, then inner CARefer simple reshape
576
+ pass-through to entity. Detection is O(ndim) + O(n). */
577
+ if ( n == ca->elements
578
+ && ca->bytes == rbytes && composed_base == 0
579
+ && ca_xfer_addrs_is_sequential_run(n, addrs, &base) && base == 0 ) {
580
+ ca_size_t s = rbytes;
581
+ int is_identity = 1;
582
+ for ( k = ca->ndim - 1; k >= 0; k-- ) {
583
+ if ( composed_strides[k] != s ) { is_identity = 0; break; }
584
+ s *= ca->dim[k];
585
+ }
586
+ if ( is_identity ) {
587
+ ca_xfer_addrs(root, n, addrs, data, dir);
588
+ return;
589
+ }
590
+ }
591
+
592
+ paddrs = ALLOCV_N(ca_size_t, holder, n);
593
+ for ( i = 0; i < n; i++ ) {
594
+ ca_size_t idx[CA_RANK_MAX];
595
+ ca_size_t off = composed_base;
596
+ ca_addr2index((CArray *) ca, addrs[i], idx);
597
+ for ( k = 0; k < ca->ndim; k++ ) {
598
+ off += idx[k] * composed_strides[k];
599
+ }
600
+ if ( ca->bytes == rbytes && off % rbytes == 0 ) {
601
+ paddrs[i] = off / rbytes;
602
+ }
603
+ else {
604
+ all_aligned = 0;
605
+ break;
606
+ }
607
+ }
608
+
609
+ if ( all_aligned ) {
610
+ ca_xfer_addrs(root, n, paddrs, data, dir);
611
+ }
612
+ else {
613
+ /* byte-mismatched reinterpret: deliver cell by cell via xfer_index
614
+ (still no whole-view attach -- delegates one cell at a time). */
615
+ for ( i = 0; i < n; i++ ) {
616
+ ca_size_t idx[CA_RANK_MAX];
617
+ ca_addr2index((CArray *) ca, addrs[i], idx);
618
+ ca_stride_func_xfer_index(ca, idx, d + i * ca->bytes, dir);
619
+ }
620
+ }
621
+ ALLOCV_END(holder);
622
+ }
623
+
624
+ /* Optimised region delivery. Reached when ca->ptr == NULL (the central dispatcher handles the alias / attached case).
625
+ A transform parent (CAFake/CAByteSwap) recursing parent.xfer_stride lands
626
+ here; without this the request would fall to the dispatcher's per-cell path
627
+ (which re-composes each cell). Instead compose the chain to its root ONCE
628
+ and translate the request's strided access into the root's byte space, then
629
+ hand the whole region to the root in a SINGLE ca_xfer_stride (entity -> ptr
630
+ memcpy; boundary view -> one recursion).
631
+
632
+ CAREFUL: the request is given over this view's linear ADDRESSES (carray.h
633
+ xfer_stride contract), so request axis k does NOT have to be view axis k.
634
+ A caller is free to hand over a transposed region -- counts/strides in one
635
+ order, the packed destination in another -- which is exactly what a
636
+ column-major backend (carray-linalg's Fortran-LAPACK gather) does. Matching
637
+ request axis k to view axis k by dividing strides[k] by the axis-k native
638
+ step looks right and is wrong: an (n, 1) view has the same native step on
639
+ both axes, so a transposed request divides cleanly and then composes the
640
+ n-cell walk onto the length-1 axis, whose parent stride is 0 -- delivering
641
+ the first cell n times, with no error anywhere. Ask ca_stride_region_axes
642
+ which view axis each request axis really moves (the same question
643
+ fill_stride asks), and fall back to the per-cell walk when the region is
644
+ not a box over our axes.
645
+
646
+ Byte-matching requests only; the byte-mismatch reinterpret (CAField
647
+ .real/.imag) and non-box access fall back to per-cell xfer_index (which
648
+ handles the sub-byte case). */
649
+
650
+ static int ca_stride_region_axes (CAStride *ca, ca_size_t base, int8_t ndim,
651
+ ca_size_t *counts, ca_size_t *steps,
652
+ ca_size_t *base_idx, int8_t *axis_of,
653
+ ca_size_t *mult);
654
+
655
+ static void
656
+ ca_stride_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
657
+ ca_size_t *strides, void *data, int dir)
658
+ {
659
+ CAStride *ca = (CAStride *) ap;
660
+ CArray *root;
661
+ ca_size_t composed_strides[CA_RANK_MAX];
662
+ ca_size_t composed_base;
663
+ ca_size_t view_native[CA_RANK_MAX];
664
+ ca_size_t root_stride[CA_RANK_MAX];
665
+ ca_size_t steps[CA_RANK_MAX];
666
+ ca_size_t base_idx[CA_RANK_MAX];
667
+ ca_size_t mult[CA_RANK_MAX];
668
+ int8_t axis_of[CA_RANK_MAX];
669
+ ca_size_t root_base;
670
+ ca_size_t base_addr = 0;
671
+ ca_size_t s;
672
+ int8_t ndim = ca->ndim, k;
673
+ int aligned = 1;
674
+ char *d = (char *) data;
675
+
676
+ ca_stride_compose_to_root(ca, &root, composed_strides, &composed_base);
677
+
678
+ s = ca->bytes;
679
+ for (k = ndim - 1; k >= 0; k--) { view_native[k] = s; s *= ca->dim[k]; }
680
+ for (k = 0; k < ndim; k++) base_addr += starts[k] * view_native[k];
681
+
682
+ if (ca->bytes != root->bytes) {
683
+ aligned = 0;
684
+ }
685
+ else {
686
+ for (k = 0; k < ndim; k++) {
687
+ if ( strides[k] % ca->bytes != 0 ) { aligned = 0; break; }
688
+ steps[k] = strides[k] / ca->bytes;
689
+ }
690
+ if ( aligned ) {
691
+ aligned = ca_stride_region_axes(ca, base_addr / ca->bytes, ndim,
692
+ counts, steps, base_idx, axis_of, mult);
693
+ }
694
+ }
695
+
696
+ if ( aligned ) {
697
+ /* Each request axis now names the view axis it moves (axis_of) and by how
698
+ many of that axis' cells (mult); a count-1 axis moves nothing and gets
699
+ stride 0, which the walk never follows. */
700
+ root_base = composed_base;
701
+ for (k = 0; k < ca->ndim; k++) {
702
+ root_base += base_idx[k] * composed_strides[k];
703
+ }
704
+ for (k = 0; k < ndim; k++) {
705
+ root_stride[k] = ( axis_of[k] >= 0 )
706
+ ? mult[k] * composed_strides[axis_of[k]]
707
+ : 0;
708
+ }
709
+ }
710
+
711
+ /* Cold root that answers regions: compose the request into its addresses
712
+ and hand it over whole, exactly as xfer_all does for the whole view. A
713
+ root with no memory to lend (a lazy transform, a CAObject over a file)
714
+ has no ptr to walk, but it can still produce a region on request -- and
715
+ asking it once beats asking it once per cell, which is what the per-cell
716
+ descent below would do. Chunked consumers (the binop / sweep drivers'
717
+ per-chunk gather) arrive here, so the difference is the whole cost of
718
+ the transfer, not a constant factor.
719
+
720
+ The gate is xfer_all's: the root must have the slot, share this view's
721
+ cell width (else the composed offsets are not whole root elements), and
722
+ carry the same ndim (else its index space cannot hold this request's
723
+ axes). Anything narrower keeps the per-cell descent, which is correct
724
+ for all of them. Direction is not part of the gate: a root that refuses
725
+ writes refuses them per cell as well. */
726
+ if (aligned && !root->ptr && ca_func[root->obj_type].xfer_stride
727
+ && ca->bytes == root->bytes && ndim == root->ndim) {
728
+ ca_size_t rstarts[CA_RANK_MAX];
729
+ if ( root_base % root->bytes == 0 ) {
730
+ ca_size_t raddr = root_base / root->bytes;
731
+ if ( raddr >= 0 && raddr < root->elements ) {
732
+ ca_addr2index(root, raddr, rstarts);
733
+ ca_xfer_stride(root, rstarts, counts, root_stride, d, dir);
734
+ return;
735
+ }
736
+ }
737
+ }
738
+
739
+ /* Per-cell fallback (correct, no whole-view attach): byte-mismatch
740
+ reinterpret (CAField), a region that is not a box over our axes (a
741
+ transposed request onto a degenerate axis, a flat index over several
742
+ axes), or a cold non-entity root the branch above could not hand a
743
+ region to (its ndim differs from the view's -- e.g. a reshape over a
744
+ boundary -- or it has no region slot). ca_stride_func_xfer_index
745
+ composes one hop and delegates to the parent. */
746
+ if (!aligned || !root->ptr) {
747
+ ca_size_t idx[CA_RANK_MAX], doff = 0;
748
+ for (k = 0; k < ndim; k++) idx[k] = 0;
749
+ while (1) {
750
+ ca_size_t off = base_addr, vmidx[CA_RANK_MAX];
751
+ for (k = 0; k < ndim; k++) off += idx[k] * strides[k];
752
+ ca_addr2index((CArray *) ca, off / ca->bytes, vmidx);
753
+ ca_stride_func_xfer_index(ca, vmidx, d + doff, dir);
754
+ doff += ca->bytes;
755
+ k = ndim - 1;
756
+ while (k >= 0) { if (++idx[k] < counts[k]) break; idx[k] = 0; k--; }
757
+ if (k < 0) break;
758
+ }
759
+ return;
760
+ }
761
+
762
+ /* Structural: root has a live ptr (entity / attached). The request is
763
+ already in root's BYTE space (root_base / root_stride above), so the walk
764
+ runs in the VIEW's ndim -- independent of root's own ndim, which is what
765
+ lets a reshape view over a 1-D entity through. compose happened once.
766
+ Slab-merge, tile-block and the general driver all live in the shared
767
+ walker, which the central dispatcher's structural path also uses. */
768
+ ca_xfer_strided_walk(root->ptr + root_base, ca->bytes, ndim,
769
+ counts, root_stride, d, dir);
770
+ }
771
+
772
+ /* Match a region given over this view's addresses to this view's own axes.
773
+
774
+ The region can only be handed on if it is a box here: each of its axes has
775
+ to advance exactly one of ours and stay inside it for the whole traversal.
776
+ That is the same rule ca_stride_compose_through applies to a leaf against
777
+ its parent, asked here about a request instead -- and for the same reason,
778
+ since a request that carries from the end of one axis into the start of the
779
+ next has no per-axis step to carry down. A flat index over a multi-axis
780
+ view is exactly that shape and belongs on the per-cell walk.
781
+
782
+ Fills axis_of[k] with the view axis request axis k moves, and mult[k] with
783
+ how far. Returns 0 if the region is not a box. An axis of count 1 never
784
+ moves and is left unassigned (axis_of[k] = -1). */
785
+
786
+ static int
787
+ ca_stride_region_axes (CAStride *ca, ca_size_t base, int8_t ndim,
788
+ ca_size_t *counts, ca_size_t *steps,
789
+ ca_size_t *base_idx, int8_t *axis_of, ca_size_t *mult)
790
+ {
791
+ ca_size_t native[CA_RANK_MAX];
792
+ int used[CA_RANK_MAX];
793
+ ca_size_t s = 1;
794
+ int8_t j, k;
795
+
796
+ for (j = ca->ndim - 1; j >= 0; j--) { native[j] = s; s *= ca->dim[j]; }
797
+ for (j = 0; j < ca->ndim; j++) used[j] = 0;
798
+
799
+ if ( base < 0 || base >= ca->elements ) return 0;
800
+ ca_addr2index((CArray *) ca, base, base_idx);
801
+
802
+ for (k = 0; k < ndim; k++) {
803
+ int8_t found = -1;
804
+ ca_size_t q = 0;
805
+
806
+ if ( counts[k] <= 1 ) { axis_of[k] = -1; mult[k] = 0; continue; }
807
+ if ( steps[k] <= 0 ) return 0;
808
+
809
+ for (j = 0; j < ca->ndim; j++) {
810
+ ca_size_t qq;
811
+ if ( used[j] || ca->dim[j] <= 1 ) continue;
812
+ if ( steps[k] % native[j] != 0 ) continue;
813
+ qq = steps[k] / native[j];
814
+ if ( qq < 1 || qq >= ca->dim[j] ) continue;
815
+ if ( base_idx[j] + (counts[k] - 1) * qq >= ca->dim[j] ) continue;
816
+ if ( found >= 0 ) return 0; /* ambiguous: refuse rather than guess */
817
+ found = j;
818
+ q = qq;
819
+ }
820
+ if ( found < 0 ) return 0;
821
+ used[found] = 1;
822
+ axis_of[k] = found;
823
+ mult[k] = q;
824
+ }
825
+ return 1;
826
+ }
827
+
828
+ /* Compose the region into root's addresses and hand it on -- one value, one
829
+ hop, no attach. compose carries this view's axes into root's byte space,
830
+ so once each request axis is matched to one of ours the rest is a multiply.
831
+ root's ndim never enters into it, which is what lets a view that drops or
832
+ reorders axes hand its region down.
833
+
834
+ The byte-per-cell check is the same one xfer_stride makes: a view that
835
+ reinterprets width (CAField over a complex entity for .real) addresses
836
+ root in units root does not share, so there is no address to hand over
837
+ and the per-cell descent stands in. */
838
+
839
+ static void
840
+ ca_stride_func_fill_stride (void *ap, ca_size_t base, int8_t ndim,
841
+ ca_size_t *counts, ca_size_t *steps, void *ptr)
842
+ {
843
+ CAStride *ca = (CAStride *) ap;
844
+ CArray *root;
845
+ ca_size_t composed_strides[CA_RANK_MAX];
846
+ ca_size_t composed_base;
847
+ ca_size_t root_steps[CA_RANK_MAX];
848
+ ca_size_t base_idx[CA_RANK_MAX];
849
+ ca_size_t mult[CA_RANK_MAX];
850
+ int8_t axis_of[CA_RANK_MAX];
851
+ ca_size_t root_base;
852
+ int8_t k;
853
+
854
+ if ( ! ca_stride_region_axes(ca, base, ndim, counts, steps,
855
+ base_idx, axis_of, mult) ) {
856
+ ca_fill_stride_default(ca, base, ndim, counts, steps, ptr);
857
+ return;
858
+ }
859
+
860
+ ca_stride_compose_to_root(ca, &root, composed_strides, &composed_base);
861
+
862
+ if ( ca->bytes != root->bytes ) {
863
+ ca_fill_stride_default(ca, base, ndim, counts, steps, ptr);
864
+ return;
865
+ }
866
+
867
+ root_base = composed_base;
868
+ for (k = 0; k < ca->ndim; k++) {
869
+ root_base += base_idx[k] * composed_strides[k];
870
+ }
871
+ if ( root_base % root->bytes != 0 ) {
872
+ ca_fill_stride_default(ca, base, ndim, counts, steps, ptr);
873
+ return;
874
+ }
875
+
876
+ for (k = 0; k < ndim; k++) {
877
+ ca_size_t st = ( axis_of[k] >= 0 )
878
+ ? mult[k] * composed_strides[axis_of[k]]
879
+ : 0;
880
+ if ( st % root->bytes != 0 ) {
881
+ ca_fill_stride_default(ca, base, ndim, counts, steps, ptr);
882
+ return;
883
+ }
884
+ root_steps[k] = st / root->bytes;
885
+ }
886
+
887
+ ca_fill_stride(root, root_base / root->bytes, ndim, counts,
888
+ root_steps, ptr);
889
+ }
890
+
891
+ extern int ca_stride_is_contiguous (CAStride *ca); /* defined below; non-static for Tier A */
892
+
893
+ /* Alias fast path:
894
+ When the view's strides describe a contiguous row-major run, the
895
+ view's logical memory is identical to a slice of the parent's
896
+ memory. We can skip allocating an own buffer and just point
897
+ ca->ptr into the parent. This makes attach O(1) for the common
898
+ "reshape / row-block / fully-covered slice" cases.
899
+
900
+ - attach / allocate: if contig, alias; otherwise allocate + (for
901
+ attach) gather.
902
+ - sync: if contig, the writes already landed in parent's memory,
903
+ so just propagate sync upward. Otherwise scatter.
904
+ - detach: if contig, ca->ptr was a borrow into parent and must
905
+ not be freed. Otherwise xfree.
906
+
907
+ ca_stride_is_contiguous is stable across the attach/detach
908
+ lifecycle of a view (its inputs -- strides[], dim[], bytes -- are
909
+ immutable), so checking it again at detach/sync time is safe.
910
+
911
+ Note: this preserves the byte semantics for byte-reinterpret views
912
+ (different bytes/data_type from parent) too -- aliasing the parent
913
+ pointer is exactly what byte-reinterpret needs. */
914
+
915
+ /* Fold-in-attach: instead of attaching the immediate parent and using
916
+ ca->parent->ptr + ca->base_offset, we walk up the CAStride chain
917
+ composing strides into the root entity's byte space, and attach
918
+ only the root.
919
+
920
+ Always pair ca_attach(root) with ca_detach(root) (regardless of
921
+ root's prior attach state); this preserves attach-count symmetry
922
+ even when root is the entity (which is "always attached" but the
923
+ counter still tracks borrowers). Compose is deterministic
924
+ (strides/base_offset are immutable), so sync/detach re-run the
925
+ walk and reach the same root and composed layout.
926
+
927
+ ... except when the root is not an entity. Then it has no memory of
928
+ its own to borrow, and ca_attach(root) means "produce all of yourself
929
+ into a buffer" -- O(root) however few cells this view covers, which for
930
+ the lazy backings CAObject exists to serve (a file, a DB, a paged fetch)
931
+ is not slow but fatal. Such a root is asked for regions instead: this
932
+ view owns a buffer, ca_copy_data / ca_sync_data fill and drain it
933
+ through xfer_all, and the root is never attached.
934
+
935
+ The branch must be re-derivable at sync and detach time from the same
936
+ inputs, or detach frees a pointer it does not own. So it asks what the
937
+ root IS (entity? region-capable?), both immutable, and never whether the
938
+ root happens to be attached right now, which is not. */
939
+
940
+ /* Does this composed root have no memory to lend -- so that borrowing a
941
+ pointer from it means producing all of it first? See the note above.
942
+ Also consulted by the kernel iterator, which faces the same choice when it
943
+ composes a source down to its root.
944
+
945
+ Entities hold their own buffer. A CAStride-family root is one the fold
946
+ declined to walk through (a byte reinterpret, say); its own attach folds
947
+ onward as it always has, so leave that chain alone. What is left is the
948
+ boundary views that compute or fetch their contents -- CAObject, the lazy
949
+ per-element transforms, whatever a companion gem installed -- and of those,
950
+ only the ones with an xfer_stride slot can answer a region request at all.
951
+ The rest have nothing better than materialising, so they keep doing it once
952
+ under attach rather than once per transfer. */
953
+ int
954
+ ca_root_lends_no_memory (void *ap)
955
+ {
956
+ CArray *root = (CArray *) ap;
957
+
958
+ if ( ca_is_entity(root) ) return 0;
959
+ if ( ca_func[root->obj_type].attach == ca_stride_func.attach ) return 0;
960
+ return ca_func[root->obj_type].xfer_stride != NULL;
961
+ }
962
+
963
+ /* ca_attach_is_alias asks the same question from carray_core.c: a view whose
964
+ attach owns its buffer does not alias its parent, so writes through
965
+ ca->ptr need a ca_sync and callers must not assume otherwise. */
966
+ int
967
+ ca_stride_attach_aliases_root (CAStride *ca)
968
+ {
969
+ CArray *root;
970
+ ca_size_t composed_strides[CA_RANK_MAX];
971
+ ca_size_t composed_base;
972
+
973
+ /* Already holding a ptr: ca_attach only bumps the counter and hands that
974
+ ptr back, so what the root would have done does not arise. The slab
975
+ iterator relies on this -- it lends its view a buffer per iteration and
976
+ leaves base_offset meaningless, so re-deriving the data from the root
977
+ would read the wrong cells. */
978
+ if ( ca->ptr != NULL ) {
979
+ return 1;
980
+ }
981
+ ca_stride_compose_to_root(ca, &root, composed_strides, &composed_base);
982
+ return !ca_root_lends_no_memory(root);
983
+ }
984
+
985
+ static void
986
+ ca_stride_func_allocate (void *ap)
987
+ {
988
+ CAStride *ca = (CAStride *) ap;
989
+ CArray *root;
990
+ ca_size_t composed_strides[CA_RANK_MAX];
991
+ ca_size_t composed_base;
992
+
993
+ ca_stride_compose_to_root(ca, &root, composed_strides, &composed_base);
994
+ if (ca_root_lends_no_memory(root)) {
995
+ ca->ptr = xmalloc(ca_length(ca));
996
+ return;
997
+ }
998
+ ca_attach(root);
999
+ if (ca_layout_is_contiguous(ca->ndim, ca->dim, composed_strides, ca->bytes)) {
1000
+ ca->ptr = root->ptr + composed_base;
1001
+ } else {
1002
+ ca->ptr = xmalloc(ca_length(ca));
1003
+ }
1004
+ }
1005
+
1006
+ static void
1007
+ ca_stride_func_attach (void *ap)
1008
+ {
1009
+ CAStride *ca = (CAStride *) ap;
1010
+ CArray *root;
1011
+ ca_size_t composed_strides[CA_RANK_MAX];
1012
+ ca_size_t composed_base;
1013
+
1014
+ ca_stride_compose_to_root(ca, &root, composed_strides, &composed_base);
1015
+ if (ca_root_lends_no_memory(root)) {
1016
+ /* Gather into a local buffer and publish it only once it is filled, so
1017
+ ca->ptr stays NULL for the duration of the request -- a half-attached
1018
+ view with a live ptr is what makes the per-cell dispatchers bypass the
1019
+ transfer slots. */
1020
+ char *buf = xmalloc(ca_length(ca));
1021
+ ca_copy_data(ca, buf); /* region request, root stays cold */
1022
+ ca->ptr = buf;
1023
+ return;
1024
+ }
1025
+ ca_attach(root);
1026
+ if (ca_layout_is_contiguous(ca->ndim, ca->dim, composed_strides, ca->bytes)) {
1027
+ ca->ptr = root->ptr + composed_base;
1028
+ } else {
1029
+ ca->ptr = xmalloc(ca_length(ca));
1030
+ ca_stride_xfer_with_layout(ca, 0, root->ptr + composed_base, composed_strides);
1031
+ }
1032
+ }
1033
+
1034
+ static void
1035
+ ca_stride_func_sync (void *ap)
1036
+ {
1037
+ CAStride *ca = (CAStride *) ap;
1038
+ CArray *root;
1039
+ ca_size_t composed_strides[CA_RANK_MAX];
1040
+ ca_size_t composed_base;
1041
+
1042
+ ca_stride_compose_to_root(ca, &root, composed_strides, &composed_base);
1043
+ if (ca_root_lends_no_memory(root)) {
1044
+ /* xfer_all PUT writes through to the entity, recursing a hop per view,
1045
+ so there is no ca_sync(root) to follow it with. */
1046
+ ca_sync_data(ca, ca->ptr);
1047
+ return;
1048
+ }
1049
+ if (!ca_layout_is_contiguous(ca->ndim, ca->dim, composed_strides, ca->bytes)) {
1050
+ ca_stride_xfer_with_layout(ca, 1, root->ptr + composed_base, composed_strides);
1051
+ }
1052
+ ca_sync(root);
1053
+ }
1054
+
1055
+ static void
1056
+ ca_stride_func_detach (void *ap)
1057
+ {
1058
+ CAStride *ca = (CAStride *) ap;
1059
+ CArray *root;
1060
+ ca_size_t composed_strides[CA_RANK_MAX];
1061
+ ca_size_t composed_base;
1062
+
1063
+ ca_stride_compose_to_root(ca, &root, composed_strides, &composed_base);
1064
+ if (ca_root_lends_no_memory(root)) {
1065
+ xfree(ca->ptr); /* always our own; root was never attached */
1066
+ ca->ptr = NULL;
1067
+ return;
1068
+ }
1069
+ if (!ca_layout_is_contiguous(ca->ndim, ca->dim, composed_strides, ca->bytes)) {
1070
+ xfree(ca->ptr);
1071
+ }
1072
+ ca->ptr = NULL;
1073
+ ca_detach(root);
1074
+ }
1075
+
1076
+ /* Bridges a leaf whose ndim is smaller than the resolved root's -- an axis was dropped by integer
1077
+ indexing (e.g. s[100,nil,nil] over a (K,180,360) CAStack) -- so the natural
1078
+ partial-materialise xfer_stride path can still run. Reinsert each dropped
1079
+ root axis as a degenerate count=1 axis, producing a full root-ndim region
1080
+ request; the root's xfer_stride then delivers only the requested region
1081
+ (CAStack slices just the touched parents, etc.) instead of the consumer
1082
+ materialising the whole root.
1083
+
1084
+ Succeeds only for a pure axis drop with no transpose / reshape / strided
1085
+ sub-block / byte reinterpret on the surviving axes: ca->bytes == root->bytes,
1086
+ and the leaf's composed strides form a strictly forward subsequence of the
1087
+ root's row-major native strides. When it returns 0 (reshape / axis-merge /
1088
+ permute / step>1 slice) the caller keeps the 2-pass fallback, so correctness
1089
+ is never at risk -- only the partial-cost win is forgone.
1090
+
1091
+ Note: the count=1 axes are never iterated by the root's xfer_stride, so the
1092
+ destination buffer (row-major over the leaf's surviving dims) is laid out
1093
+ identically whether the degenerate axes are present or not. */
1094
+ static int
1095
+ ca_stride_bridge_dropped_axes (CAStride *ca, CArray *root,
1096
+ ca_size_t *composed_strides,
1097
+ ca_size_t composed_base,
1098
+ ca_size_t *r_starts, ca_size_t *r_counts,
1099
+ ca_size_t *r_strides)
1100
+ {
1101
+ ca_size_t native[CA_RANK_MAX];
1102
+ ca_size_t s;
1103
+ int8_t i, j;
1104
+
1105
+ if ( ca->bytes != root->bytes ) return 0; /* byte reinterpret */
1106
+ if ( ca->ndim >= root->ndim ) return 0; /* not an axis drop */
1107
+
1108
+ s = root->bytes;
1109
+ for ( i = root->ndim - 1; i >= 0; i-- ) { native[i] = s; s *= root->dim[i]; }
1110
+
1111
+ /* per-axis root indices recovered from the composed byte base (this also
1112
+ carries the dropped axes' selected positions, e.g. lat=5). */
1113
+ ca_addr2index(root, composed_base / root->bytes, r_starts);
1114
+
1115
+ j = 0;
1116
+ for ( i = 0; i < ca->ndim; i++ ) {
1117
+ while ( j < root->ndim && native[j] != composed_strides[i] ) {
1118
+ r_counts[j] = 1; /* dropped axis -> degenerate */
1119
+ r_strides[j] = native[j];
1120
+ j++;
1121
+ }
1122
+ if ( j >= root->ndim ) return 0; /* stride not a forward native match */
1123
+ /* The surviving region must fit within the matched root axis. A stride
1124
+ match alone is not enough: a flatten/axis-merge reshape (e.g. a 2x2
1125
+ CAGrid viewed as 1-D length 4) matches the innermost native stride but
1126
+ its extent overflows the axis (4 > 2), which would scatter out of
1127
+ bounds. Reject -> 2-pass fallback keeps such reshapes correct. */
1128
+ if ( r_starts[j] + ca->dim[i] > root->dim[j] ) return 0;
1129
+ r_counts[j] = ca->dim[i]; /* surviving axis -> leaf extent */
1130
+ r_strides[j] = composed_strides[i];
1131
+ j++;
1132
+ }
1133
+ while ( j < root->ndim ) { /* trailing dropped axes */
1134
+ r_counts[j] = 1;
1135
+ r_strides[j] = native[j];
1136
+ j++;
1137
+ }
1138
+ return 1;
1139
+ }
1140
+
1141
+ static void
1142
+ ca_stride_func_xfer_all (void *ap, void *data, int dir)
1143
+ {
1144
+ /* Whole-view transfer (step 4): direction-unified merge of copy_data /
1145
+ sync_data. Compose leaf strides up through the CAStride parent chain and
1146
+ gather/scatter directly from/to the resolved root (entity or first
1147
+ non-CAStride ancestor), skipping materialisation of intermediate CAStride
1148
+ views. */
1149
+ CAStride *ca = (CAStride *) ap;
1150
+ CArray *root;
1151
+ ca_size_t composed_strides[CA_RANK_MAX];
1152
+ ca_size_t composed_base;
1153
+ char *ptr0 = ca->ptr;
1154
+ char *ptr = (char *) data;
1155
+
1156
+ ca_stride_compose_to_root(ca, &root, composed_strides, &composed_base);
1157
+
1158
+ /* Partial materialise: when the fold stops at a cold boundary view that knows how to deliver a region (has an
1159
+ xfer_stride slot), request only this view's composed region instead of
1160
+ attaching (materialising) the whole boundary. ca_is_attached(root) is the
1161
+ direct gate: entity / already-attached roots have a live ptr and take the
1162
+ bulk path below; only a cold boundary view reaches here. xfer_stride PUT
1163
+ writes through to the entity (recursing each hop), so no separate
1164
+ ca_sync(root) is needed. Un-slotted boundaries stay on the bulk path
1165
+ (no per-cell regression).
1166
+
1167
+ The region is stated in the root's own address space, so it can only be
1168
+ handed over when this view addresses the root in units the root shares:
1169
+ the same cell width, and a base that lands on a root element. A byte
1170
+ reinterpret (CARefer to a narrower data_type over a cold view) has
1171
+ neither -- the counts passed below are this view's cells, which the root
1172
+ reads as its own and answers with root->bytes apiece, overrunning the
1173
+ caller's buffer. Those requests fall through to the whole-root
1174
+ materialise, which addresses the root in bytes and needs no such
1175
+ agreement. Same rule xfer_stride and fill_stride already apply. */
1176
+ if ( !ca_is_attached(root) && ca_func[root->obj_type].xfer_stride
1177
+ && ca->bytes == root->bytes && composed_base % root->bytes == 0 ) {
1178
+ ca_size_t starts[CA_RANK_MAX];
1179
+ if ( ca->ndim == root->ndim ) {
1180
+ ca_addr2index(root, composed_base / root->bytes, starts);
1181
+ ca_xfer_stride(root, starts, ca->dim, composed_strides, ptr, dir);
1182
+ return;
1183
+ }
1184
+ else {
1185
+ /* Leaf dropped an axis (ndim < root->ndim). Reinsert the dropped
1186
+ axes as degenerate count=1 axes so the region request matches the
1187
+ root's ndim, then run the same partial-materialise path. Falls
1188
+ through to the 2-pass fallback when the chain isn't a pure drop. */
1189
+ ca_size_t r_counts[CA_RANK_MAX], r_strides[CA_RANK_MAX];
1190
+ if ( ca_stride_bridge_dropped_axes(ca, root, composed_strides,
1191
+ composed_base, starts,
1192
+ r_counts, r_strides) ) {
1193
+ ca_xfer_stride(root, starts, r_counts, r_strides, ptr, dir);
1194
+ return;
1195
+ }
1196
+
1197
+ /* Reshape / transpose that ADDS axes over a 1-D cold boundary root
1198
+ (ndim > root->ndim), e.g. big.swap_bytes[[pos, n*2]].reshape(n, 2)
1199
+ whose root is a CAMonOp / CAFake / CABinOp per-element transform.
1200
+ The view's shape can't be expressed in the root's single axis, but
1201
+ the root bytes it touches form a bounded flat span. Materialise
1202
+ ONLY that span (not the whole root) via one contiguous region
1203
+ request, then run the strided gather/scatter against it. Without
1204
+ this the code drops to the whole-root 2-pass fallback below, making
1205
+ a small per-record view over a large lazy root cost O(root) each --
1206
+ quadratic across a per-record loop.
1207
+
1208
+ Gated to root->ndim == 1 (a flat span is exactly one strided box, so
1209
+ the request is exact) and ca->bytes == root->bytes (no byte
1210
+ reinterpret, so the span endpoints are whole root elements). A wider
1211
+ lazy root has no single strided box to ask for, so it takes the
1212
+ whole-root fallback below -- correct, just not partial. */
1213
+ if ( root->ndim == 1 && ca->bytes == root->bytes ) {
1214
+ ca_size_t span_lo = composed_base;
1215
+ ca_size_t span_hi = composed_base + ca->bytes;
1216
+ int8_t k;
1217
+ for ( k = 0; k < ca->ndim; k++ ) {
1218
+ ca_size_t ext = (ca->dim[k] - 1) * composed_strides[k];
1219
+ if ( composed_strides[k] >= 0 ) span_hi += ext;
1220
+ else span_lo += ext;
1221
+ }
1222
+ {
1223
+ volatile VALUE holder;
1224
+ ca_size_t rlo = span_lo / root->bytes;
1225
+ ca_size_t rcount = (span_hi - span_lo) / root->bytes;
1226
+ ca_size_t rstep = root->bytes;
1227
+ char *scratch = ALLOCV_N(char, holder, rcount * root->bytes);
1228
+ ca_xfer_stride(root, &rlo, &rcount, &rstep, scratch, CA_XFER_GET);
1229
+ ca->ptr = ptr;
1230
+ ca_stride_xfer_with_layout(ca, (dir == CA_XFER_PUT) ? 1 : 0,
1231
+ scratch + (composed_base - span_lo),
1232
+ composed_strides);
1233
+ ca->ptr = ptr0;
1234
+ if ( dir == CA_XFER_PUT ) {
1235
+ ca_xfer_stride(root, &rlo, &rcount, &rstep, scratch, CA_XFER_PUT);
1236
+ }
1237
+ ALLOCV_END(holder);
1238
+ }
1239
+ return;
1240
+ }
1241
+ }
1242
+ }
1243
+
1244
+ if ( ca_is_attached(root) ) {
1245
+ /* Hot path: root has live ptr (entity / pre-attached). Direct
1246
+ strided gather/scatter through composed strides, no attach. */
1247
+ ca->ptr = ptr;
1248
+ ca_stride_xfer_with_layout(ca, (dir == CA_XFER_PUT) ? 1 : 0,
1249
+ root->ptr + composed_base, composed_strides);
1250
+ ca->ptr = ptr0;
1251
+ if ( dir == CA_XFER_PUT ) {
1252
+ ca_sync(root); /* propagate scatter up to root's storage */
1253
+ }
1254
+ return;
1255
+ }
1256
+
1257
+ /* Cold root without an xfer_stride slot, or an ndim mismatch the bridges
1258
+ above could not express: materialise the root into scratch via
1259
+ ca_xfer_all and run the direct strided gather/scatter against that.
1260
+ CAREFUL: do not "simplify" this to ca_attach(root) -- a transfer slot
1261
+ that attaches its parent materialises it behind the caller's back, and
1262
+ the per-cell xfer_addrs alternative explodes in cost once the parent is
1263
+ itself a view chain. */
1264
+ {
1265
+ volatile VALUE holder;
1266
+ ca_size_t rlen = root->elements * root->bytes;
1267
+ char *root_scratch = ALLOCV_N(char, holder, rlen);
1268
+ char *root_ptr_saved = root->ptr;
1269
+ ca_xfer_all(root, root_scratch, CA_XFER_GET);
1270
+ root->ptr = root_scratch;
1271
+ ca->ptr = ptr;
1272
+ ca_stride_xfer_with_layout(ca, (dir == CA_XFER_PUT) ? 1 : 0,
1273
+ root->ptr + composed_base, composed_strides);
1274
+ ca->ptr = ptr0;
1275
+ if ( dir == CA_XFER_PUT ) {
1276
+ /* Push back scratch (modified by scatter) to root. */
1277
+ ca_xfer_all(root, root_scratch, CA_XFER_PUT);
1278
+ }
1279
+ root->ptr = root_ptr_saved;
1280
+ ALLOCV_END(holder);
1281
+ }
1282
+ }
1283
+
1284
+ static void
1285
+ ca_stride_func_fill_data (void *ap, void *ptr)
1286
+ {
1287
+ /* Write `*ptr` to every element at the strided positions covered
1288
+ by this view, composing through the CAStride chain to write
1289
+ directly into the resolved root. Skips materialising any
1290
+ intermediate CAStride view.
1291
+
1292
+ Axis-merge is applied to the composed strides before the inner write
1293
+ loop. When merge collapses to a contig
1294
+ run on the innermost axis (mstrides[mndim-1] == bytes), fill that
1295
+ run with a tight memcpy-pattern loop instead of per-element memcpy.
1296
+ This converts e.g. mid_axis_3d's per-element 8-byte writes into
1297
+ 200 iterations of "fill 80KB" each. */
1298
+ CAStride *ca = (CAStride *) ap;
1299
+ CArray *root;
1300
+ ca_size_t composed_strides[CA_RANK_MAX];
1301
+ ca_size_t composed_base;
1302
+ ca_size_t mdim[CA_RANK_MAX];
1303
+ ca_size_t mstrides[CA_RANK_MAX];
1304
+ int8_t mndim;
1305
+ ca_size_t idx[CA_RANK_MAX];
1306
+ int8_t k;
1307
+ ca_size_t n;
1308
+ ca_size_t bytes = ca->bytes;
1309
+
1310
+ if (ca->elements == 0) return;
1311
+
1312
+ ca_stride_compose_to_root(ca, &root, composed_strides, &composed_base);
1313
+
1314
+ /* If compose stopped at a non-foldable parent (= neither CAStride family nor interior-only
1315
+ CAWindow), the upcoming ca_attach(root) would gather root's data
1316
+ into scratch only to overwrite every byte with *ptr -- wasted
1317
+ work. Delegate to root.fill_data instead.
1318
+ Safety gates:
1319
+ (1) elements match -- our view covers every root cell;
1320
+ (2) bytes match -- our cell width equals root's cell width.
1321
+ Without (2), a type-reinterpreting CAStride (CAField over a
1322
+ complex entity for `.real`/`.imag`) would corrupt root: root
1323
+ would read more bytes than ptr points to, overwriting cells the
1324
+ view did not intend to touch.
1325
+ Mask is independent of broadcast scalar fill (CArray semantics:
1326
+ fill writes data only, mask state preserved), so delegate path
1327
+ and existing path are mask-equivalent. */
1328
+ if ( ca_func[root->obj_type].attach != ca_stride_func.attach &&
1329
+ ca_func[root->obj_type].attach != ca_window_func.attach &&
1330
+ ca->elements == root->elements &&
1331
+ ca->bytes == root->bytes ) {
1332
+ ca_func[root->obj_type].fill_data(root, ptr);
1333
+ return;
1334
+ }
1335
+
1336
+ /* The gate above only covers the half where the view spans all of root;
1337
+ "fill everything I cover" is a correct request to pass on only then.
1338
+ For anything short of that the old path attached root, and if that
1339
+ attach is a gather rather than an alias it pulls in the whole root and
1340
+ syncs it all back -- cells the caller never addressed make the round
1341
+ trip, and through a lossy transform layer they come back changed. Hand
1342
+ root the region instead.
1343
+
1344
+ What falls through is root already holding its data: an entity, whose
1345
+ ptr is live at rest, or a view someone outside is holding attached.
1346
+ Either way the strided write below lands in memory that is already
1347
+ there, so composing into it directly is both cheaper than a region
1348
+ hand-off and the reason the loop is written this way. */
1349
+ if ( !ca_is_attached(root) ) {
1350
+ ca_fill_stride_whole(ca, ptr);
1351
+ return;
1352
+ }
1353
+
1354
+ /* Local copies + merge */
1355
+ mndim = ca->ndim;
1356
+ for (k = 0; k < mndim; k++) {
1357
+ mdim[k] = ca->dim[k];
1358
+ mstrides[k] = composed_strides[k];
1359
+ }
1360
+ ca_stride_merge_axes(mstrides, mdim, &mndim);
1361
+
1362
+ /* Iterate prefix axes; fill inner run per iteration. */
1363
+ ca_size_t inner_count = mdim[mndim - 1];
1364
+ ca_size_t inner_stride = mstrides[mndim - 1];
1365
+ ca_size_t outer_total = ca->elements / inner_count;
1366
+
1367
+ for (k = 0; k < mndim; k++) idx[k] = 0;
1368
+
1369
+ for (n = 0; n < outer_total; n++) {
1370
+ ca_size_t off = composed_base;
1371
+ for (k = 0; k < mndim - 1; k++) off += idx[k] * mstrides[k];
1372
+
1373
+ /* Inner fill: if inner_stride == bytes, the inner run is contig
1374
+ and can be filled in a tight typed loop / memset-style. Otherwise
1375
+ per-element memcpy at stride. */
1376
+ char *dst = root->ptr + off;
1377
+ if (inner_stride == bytes) {
1378
+ /* Tight contig fill: repeat the bytes-wide value inner_count times. */
1379
+ ca_size_t i;
1380
+ switch (bytes) {
1381
+ case 1: {
1382
+ int8_t v;
1383
+ memcpy(&v, ptr, 1);
1384
+ memset(dst, v, inner_count);
1385
+ break;
1386
+ }
1387
+ case 2: {
1388
+ int16_t v; memcpy(&v, ptr, 2);
1389
+ int16_t *d = (int16_t *) dst;
1390
+ for (i = 0; i < inner_count; i++) d[i] = v;
1391
+ break;
1392
+ }
1393
+ case 4: {
1394
+ int32_t v; memcpy(&v, ptr, 4);
1395
+ int32_t *d = (int32_t *) dst;
1396
+ for (i = 0; i < inner_count; i++) d[i] = v;
1397
+ break;
1398
+ }
1399
+ case 8: {
1400
+ int64_t v; memcpy(&v, ptr, 8);
1401
+ int64_t *d = (int64_t *) dst;
1402
+ for (i = 0; i < inner_count; i++) d[i] = v;
1403
+ break;
1404
+ }
1405
+ default:
1406
+ for (i = 0; i < inner_count; i++) memcpy(dst + i * bytes, ptr, bytes);
1407
+ break;
1408
+ }
1409
+ } else {
1410
+ /* Strided inner: per-element memcpy at byte stride. */
1411
+ ca_size_t i;
1412
+ for (i = 0; i < inner_count; i++) {
1413
+ memcpy(dst + i * inner_stride, ptr, bytes);
1414
+ }
1415
+ }
1416
+
1417
+ for (k = mndim - 2; k >= 0; k--) {
1418
+ if (++idx[k] < mdim[k]) break;
1419
+ idx[k] = 0;
1420
+ }
1421
+ }
1422
+
1423
+ /* The write went into root's own buffer; if that buffer is a view's
1424
+ scratch, only a sync puts it back. Nothing to detach: this path is
1425
+ reached only when root was already attached, so the attach is not
1426
+ ours to close. */
1427
+ ca_sync(root);
1428
+ }
1429
+
1430
+ static void
1431
+ ca_stride_func_create_mask (void *ap)
1432
+ {
1433
+ CAStride *ca = (CAStride *) ap;
1434
+ ca_create_mask(ca->parent);
1435
+ /* The mask for a CAStride is itself a CAStride over the parent's
1436
+ mask, with the same shape, strides, and base_offset. Mask bytes
1437
+ are 1 byte each (boolean8_t), so we need to scale strides by
1438
+ parent->mask->bytes / parent->bytes -- but for the typical case
1439
+ mask->bytes == 1 and parent->bytes is the parent's item width.
1440
+ Strides in CAStride are byte-units already, so we need to convert
1441
+ to mask-byte-units.
1442
+
1443
+ Element layout: walking idx by [1,0,...] in parent advances
1444
+ parent->ptr by strides[k] bytes (= strides[k]/parent->bytes
1445
+ elements). The mask's same advance is in mask->bytes per element,
1446
+ so mask_strides[k] = (strides[k]/parent->bytes) * mask->bytes. */
1447
+ ca_size_t mask_strides[CA_RANK_MAX];
1448
+ ca_size_t mask_offset;
1449
+ int8_t k;
1450
+ ca_size_t parent_bytes = ca->parent->bytes;
1451
+ ca_size_t mask_bytes = ca->parent->mask->bytes;
1452
+ for (k = 0; k < ca->ndim; k++) {
1453
+ mask_strides[k] = (ca->strides[k] / parent_bytes) * mask_bytes;
1454
+ }
1455
+ mask_offset = (ca->base_offset / parent_bytes) * mask_bytes;
1456
+ /* The mask of a subclassed CAStride (e.g. CATranspose) is the same
1457
+ subclass. Pass ca->obj_type through so the Ruby mask wrapper
1458
+ picks up rb_cCATransMask / rb_cCAStrideMask correctly. */
1459
+ ca->mask =
1460
+ (CArray *) ca_stride_new(ca->obj_type, ca->parent->mask,
1461
+ ca->parent->mask->data_type,
1462
+ ca->parent->mask->bytes,
1463
+ ca->ndim, ca->dim,
1464
+ mask_strides, mask_offset);
1465
+ }
1466
+
1467
+ ca_operation_function_t ca_stride_func = {
1468
+ -1, /* CA_OBJ_STRIDE: assigned at Init time */
1469
+ CA_VIEW_ARRAY,
1470
+ free_ca_stride,
1471
+ ca_stride_func_clone,
1472
+ ca_stride_func_allocate,
1473
+ ca_stride_func_attach,
1474
+ ca_stride_func_sync,
1475
+ ca_stride_func_detach,
1476
+ ca_stride_func_fill_data,
1477
+ ca_stride_func_create_mask,
1478
+ ca_stride_func_xfer_index,
1479
+ ca_stride_func_xfer_addrs,
1480
+ NULL, /* fold_stride: CAStride family is open-inline */
1481
+ ca_stride_func_xfer_stride,
1482
+ ca_stride_func_xfer_all,
1483
+ sizeof(CAStride), /* struct_size: pool framework */
1484
+ ca_stride_pool_bytes, /* pool_bytes */
1485
+ ca_stride_pool_init, /* pool_init */
1486
+ .fill_stride = ca_stride_func_fill_stride,
1487
+ };
1488
+
1489
+ /* ------------------------------------------------------------------- */
1490
+ /* gather / scatter loops */
1491
+ /* */
1492
+ /* Three fast paths followed by a correctness-first naive fallback. */
1493
+ /* Roughly in order of preference per call: */
1494
+ /* */
1495
+ /* P1 -- whole view is a single contiguous row-major run */
1496
+ /* (all strides match the natural product chain) */
1497
+ /* --> one memcpy for the entire elements * bytes. */
1498
+ /* */
1499
+ /* P2 -- innermost dim is contiguous (strides[ndim-1] == bytes) */
1500
+ /* --> outer loop with carried offset, each row copied with */
1501
+ /* memcpy. Handles the col-slice / strided-rows pattern */
1502
+ /* produced by CABlock-style views and by negative */
1503
+ /* outer-stride views (e.g. as_strided yrev). */
1504
+ /* */
1505
+ /* P3 -- innermost stride is a positive multiple of bytes */
1506
+ /* --> mcopy_step with element-stride. */
1507
+ /* */
1508
+ /* naive fallback -- per-element memcpy over a flat index walk. */
1509
+ /* Used for negative innermost stride, byte-misaligned */
1510
+ /* strides, and other shapes the fast paths can't express. */
1511
+ /* ------------------------------------------------------------------- */
1512
+
1513
+ /* True if `ca`'s strides describe a single row-major contiguous run.
1514
+ For each k: strides[k] == bytes * Product(dim[k+1:]) (innermost
1515
+ stride == bytes). dim[k] == 1 axes contribute no displacement, so
1516
+ their stride value is treated as a don't-care. */
1517
+ /* Non-static: `ca_attach_is_alias` (carray_core.c) calls it to decide whether
1518
+ a CAStride-family parent is alias-attachable, i.e. whether ca_attach is
1519
+ O(1). That predicate feeds the kernel iterator's alias decision. */
1520
+ int
1521
+ ca_stride_is_contiguous (CAStride *ca)
1522
+ {
1523
+ ca_size_t expected = ca->bytes;
1524
+ int8_t k;
1525
+ for (k = ca->ndim - 1; k >= 0; k--) {
1526
+ if (ca->dim[k] != 1 && ca->strides[k] != expected) {
1527
+ return 0;
1528
+ }
1529
+ expected *= ca->dim[k];
1530
+ }
1531
+ return 1;
1532
+ }
1533
+
1534
+ /* [MOVED] ca_stride_gather_run / ca_stride_scatter_run -> ca_iter_substrate.h
1535
+ as `static inline`. The general driver below depends on them inlining, and
1536
+ ca_transform_common.c needs the same definition; a static inline in the
1537
+ header gives both call sites the inlinable typed loops. */
1538
+
1539
+ /* Test if `strides[]` describe a row-major contiguous run over `dim[]`
1540
+ with element size `bytes`. Mirrors ca_stride_is_contiguous but
1541
+ takes an explicit strides array so the composed-fold path can re-test
1542
+ after composition. */
1543
+ static int
1544
+ ca_layout_is_contiguous (int8_t ndim, const ca_size_t *dim,
1545
+ const ca_size_t *strides, ca_size_t bytes)
1546
+ {
1547
+ ca_size_t expected = bytes;
1548
+ int8_t k;
1549
+ for (k = ndim - 1; k >= 0; k--) {
1550
+ if (dim[k] != 1 && strides[k] != expected) return 0;
1551
+ expected *= dim[k];
1552
+ }
1553
+ return 1;
1554
+ }
1555
+
1556
+ /* Merges contig-mergeable adjacent axes in-place.
1557
+ Given (strides[], dim[], ndim_inout), fold adjacent axes k, k+1 when
1558
+ they describe a single contiguous run on the strided side:
1559
+ strides[k+1] != 0 && strides[k] == strides[k+1] * dim[k+1]
1560
+ stride==0 axes (CARepeat fences) and dim==1 axes are special-cased:
1561
+ dim==1 axes are squashed first (no displacement), stride==0 axes
1562
+ are not merged with their neighbours.
1563
+
1564
+ Sign-agnostic: the condition uses signed equality so negative strides
1565
+ merge correctly as long as both adjacent strides agree in sign.
1566
+
1567
+ No mutation when the input is already "merged" (idempotent). */
1568
+ void
1569
+ ca_stride_merge_axes (ca_size_t *strides,
1570
+ ca_size_t *dim,
1571
+ int8_t *ndim_inout)
1572
+ {
1573
+ int8_t ndim = *ndim_inout;
1574
+ int8_t i, w;
1575
+
1576
+ if (ndim < 1) return;
1577
+
1578
+ /* Pass 1: squash dim==1 axes (they carry no displacement; their
1579
+ stride is a don't-care for the gather loop). */
1580
+ w = 0;
1581
+ for (i = 0; i < ndim; i++) {
1582
+ if (dim[i] == 1) continue;
1583
+ if (i != w) {
1584
+ strides[w] = strides[i];
1585
+ dim[w] = dim[i];
1586
+ }
1587
+ w++;
1588
+ }
1589
+ ndim = w;
1590
+ if (ndim < 1) {
1591
+ /* All-dim-1: keep one trivial axis for the gather loop. */
1592
+ dim[0] = 1;
1593
+ strides[0] = 0;
1594
+ ndim = 1;
1595
+ *ndim_inout = ndim;
1596
+ return;
1597
+ }
1598
+
1599
+ /* Pass 2: merge adjacent contig-mergeable pairs. Loop with explicit
1600
+ index because merges shift trailing axes inward. */
1601
+ i = 0;
1602
+ while (i + 1 < ndim) {
1603
+ if (strides[i + 1] != 0
1604
+ && strides[i] == strides[i + 1] * dim[i + 1]) {
1605
+ /* Merge axis i with axis i+1: new axis at i has the inner stride
1606
+ and the combined count. Shift the tail leftward. */
1607
+ dim[i] = dim[i] * dim[i + 1];
1608
+ strides[i] = strides[i + 1];
1609
+ for (w = i + 1; w + 1 < ndim; w++) {
1610
+ strides[w] = strides[w + 1];
1611
+ dim[w] = dim[w + 1];
1612
+ }
1613
+ ndim--;
1614
+ /* Stay at i; the new neighbour at i+1 might also merge. */
1615
+ } else {
1616
+ i++;
1617
+ }
1618
+ }
1619
+
1620
+ *ndim_inout = ndim;
1621
+ }
1622
+
1623
+ /* Generalised xfer: copy between ca->ptr (row-major contig) and the
1624
+ strided region at `base` with per-dim byte strides `strides[]`.
1625
+ `scatter == 0` gathers (strided -> contig), 1 scatters.
1626
+ Used both by the legacy ca_stride_gather/scatter wrappers (pass
1627
+ ca->strides and ca->parent->ptr + ca->base_offset) and by the
1628
+ composed-fold copy_data/sync_data (pass composed strides and a
1629
+ root-level base). */
1630
+ void
1631
+ ca_stride_xfer_with_layout (CAStride *ca, int scatter, char *base,
1632
+ const ca_size_t *strides)
1633
+ {
1634
+ ca_size_t bytes = ca->bytes;
1635
+ char *buf = ca->ptr; /* row-major side */
1636
+
1637
+ if (ca->elements == 0) return;
1638
+
1639
+ /* Copy shape + strides locally and apply axis-merge before driving the
1640
+ fast paths. Existing fast paths
1641
+ (P1 / P1.5 / general) consume the merged shape transparently:
1642
+ a fully-mergeable layout collapses to ndim=1 and lands on P1's
1643
+ whole-contig memcpy; a partially-mergeable one collapses outer
1644
+ iterations and feeds the general driver larger inner_count runs. */
1645
+ ca_size_t mdim[CA_RANK_MAX];
1646
+ ca_size_t mstrides[CA_RANK_MAX];
1647
+ int8_t mndim = ca->ndim;
1648
+ {
1649
+ int8_t i;
1650
+ for (i = 0; i < mndim; i++) {
1651
+ mdim[i] = ca->dim[i];
1652
+ mstrides[i] = strides[i];
1653
+ }
1654
+ ca_stride_merge_axes(mstrides, mdim, &mndim);
1655
+ }
1656
+
1657
+ /* P1: whole-view contiguous (in the strided side's layout) */
1658
+ if (ca_layout_is_contiguous(mndim, mdim, mstrides, bytes)) {
1659
+ if (scatter) memcpy(base, buf, ca->elements * bytes);
1660
+ else memcpy(buf, base, ca->elements * bytes);
1661
+ return;
1662
+ }
1663
+
1664
+ /* P1.5: 2D specialised fast paths for common element widths. Strides
1665
+ are byte-valued; the inner loops advance source/destination pointers
1666
+ by raw byte counts and use constant-size memcpy for the unaligned
1667
+ load/store (single-instruction on x86/arm64). */
1668
+ #define CA_STRIDE_2D_TYPED(T) \
1669
+ do { \
1670
+ ca_size_t n0 = mdim[0]; \
1671
+ ca_size_t n1 = mdim[1]; \
1672
+ ca_size_t s0 = mstrides[0]; \
1673
+ ca_size_t s1 = mstrides[1]; \
1674
+ T *bp = (T *) buf; \
1675
+ ca_size_t i, j; \
1676
+ if (scatter) { \
1677
+ for (i = 0; i < n0; i++) { \
1678
+ char *dp = base + i * s0; \
1679
+ for (j = 0; j < n1; j++) { \
1680
+ T v = *bp++; \
1681
+ memcpy(dp, &v, sizeof(T)); \
1682
+ dp += s1; \
1683
+ } \
1684
+ } \
1685
+ } else { \
1686
+ for (i = 0; i < n0; i++) { \
1687
+ const char *sp = base + i * s0; \
1688
+ for (j = 0; j < n1; j++) { \
1689
+ T v; \
1690
+ memcpy(&v, sp, sizeof(T)); \
1691
+ *bp++ = v; \
1692
+ sp += s1; \
1693
+ } \
1694
+ } \
1695
+ } \
1696
+ } while (0)
1697
+
1698
+ if (mndim == 2 && mstrides[1] != bytes) {
1699
+ switch (bytes) {
1700
+ case 1: CA_STRIDE_2D_TYPED(int8_t); return;
1701
+ case 2: CA_STRIDE_2D_TYPED(int16_t); return;
1702
+ case 4: CA_STRIDE_2D_TYPED(int32_t); return;
1703
+ case 8: CA_STRIDE_2D_TYPED(int64_t); return;
1704
+ default: break;
1705
+ }
1706
+ }
1707
+ #undef CA_STRIDE_2D_TYPED
1708
+
1709
+ /* EXPLORED AND REJECTED: a cache-tiled tile-block branch here (mirroring
1710
+ the dispatcher / root-direct ndim>=2 branch) measured as a net loss for
1711
+ the `a.transpose.to_ca` family at typical sizes -- roughly 1.6-1.9x
1712
+ slower for ndim=3 [2,500,500] and ndim=4 [4,8,100,100]. The general
1713
+ driver below already cache-streams the innermost axis through
1714
+ ca_stride_gather_run's typed memcpy, and the per-tile L1 staging cost
1715
+ dominates the small inner-pair blocks these shapes produce. Do not
1716
+ reattempt without first showing a regime where tiling wins. */
1717
+
1718
+ /* General driver. */
1719
+ ca_size_t inner_count = mdim[mndim - 1];
1720
+ ca_size_t inner_stride = mstrides[mndim - 1];
1721
+ ca_size_t outer_total = ca->elements / inner_count;
1722
+ ca_size_t idx[CA_RANK_MAX];
1723
+ int8_t k;
1724
+
1725
+ for (k = 0; k < mndim; k++) idx[k] = 0;
1726
+
1727
+ ca_size_t n;
1728
+ for (n = 0; n < outer_total; n++) {
1729
+ ca_size_t off = 0;
1730
+ for (k = 0; k < mndim - 1; k++) off += idx[k] * mstrides[k];
1731
+
1732
+ if (scatter) {
1733
+ ca_stride_scatter_run(base + off, buf, bytes, inner_count, inner_stride);
1734
+ } else {
1735
+ ca_stride_gather_run(buf, base + off, bytes, inner_count, inner_stride);
1736
+ }
1737
+ buf += inner_count * bytes;
1738
+
1739
+ for (k = mndim - 2; k >= 0; k--) {
1740
+ if (++idx[k] < mdim[k]) break;
1741
+ idx[k] = 0;
1742
+ }
1743
+ }
1744
+ }
1745
+
1746
+ /* ------------------------------------------------------------------- */
1747
+ /* Ruby-level construction and attribute readers */
1748
+ /* ------------------------------------------------------------------- */
1749
+
1750
+ VALUE
1751
+ rb_ca_stride_new (VALUE cary,
1752
+ int8_t data_type, ca_size_t bytes,
1753
+ int8_t ndim, ca_size_t *dim,
1754
+ ca_size_t *strides, ca_size_t base_offset)
1755
+ {
1756
+ volatile VALUE obj;
1757
+ CArray *parent;
1758
+ CAStride *ca;
1759
+
1760
+ rb_check_carray_object(cary);
1761
+ TypedData_Get_Struct(cary, CArray, &carray_data_type, parent);
1762
+ ca = ca_stride_new(CA_OBJ_STRIDE, parent,
1763
+ data_type, bytes, ndim, dim, strides, base_offset);
1764
+ obj = ca_wrap_struct(ca);
1765
+ rb_ca_set_parent(obj, cary);
1766
+ return obj;
1767
+ }
1768
+
1769
+ static VALUE
1770
+ rb_cs_s_allocate (VALUE klass)
1771
+ {
1772
+ CAStride *ca;
1773
+ return TypedData_Make_Struct(klass, CAStride, &castride_data_type, ca);
1774
+ }
1775
+
1776
+ static VALUE
1777
+ rb_cs_initialize_copy (VALUE self, VALUE other)
1778
+ {
1779
+ CAStride *ca, *cs;
1780
+ TypedData_Get_Struct(self, CAStride, &castride_data_type, ca);
1781
+ TypedData_Get_Struct(other, CAStride, &castride_data_type, cs);
1782
+ /* Pool framework: self was created by rb_cs_s_allocate (= TypedData_Make_Struct),
1783
+ so ca->_pool is NULL. Wire up the pool before ca_stride_setup so the
1784
+ pool branch installs dim/strides. */
1785
+ if ( ca_func[CA_OBJ_STRIDE].pool_init ) {
1786
+ ca_array_pool_alloc(ca, CA_OBJ_STRIDE, cs->ndim);
1787
+ }
1788
+ ca_stride_setup(ca, cs->obj_type, cs->parent, cs->data_type, cs->bytes,
1789
+ cs->ndim, cs->dim, cs->strides, cs->base_offset);
1790
+ rb_ca_set_parent(self, rb_ca_parent(other));
1791
+ return self;
1792
+ }
1793
+
1794
+ /* Returns the byte strides as an Array of integers. */
1795
+ static VALUE
1796
+ rb_cs_strides (VALUE self)
1797
+ {
1798
+ CAStride *cs;
1799
+ volatile VALUE ary;
1800
+ int8_t i;
1801
+ TypedData_Get_Struct(self, CAStride, &castride_data_type, cs);
1802
+ ary = rb_ary_new2(cs->ndim);
1803
+ for (i = 0; i < cs->ndim; i++) {
1804
+ rb_ary_store(ary, i, LL2NUM((long long) cs->strides[i]));
1805
+ }
1806
+ return ary;
1807
+ }
1808
+
1809
+ /* Returns the byte offset from parent->ptr to the [0,...,0] element. */
1810
+ static VALUE
1811
+ rb_cs_byte_offset (VALUE self)
1812
+ {
1813
+ CAStride *cs;
1814
+ TypedData_Get_Struct(self, CAStride, &castride_data_type, cs);
1815
+ return LL2NUM((long long) cs->base_offset);
1816
+ }
1817
+
1818
+ /* as_strided(shape:, strides:, offset: 0) -- builds a CAStride view of the
1819
+ receiver from raw byte strides. Inherits the receiver's data_type and
1820
+ bytes.
1821
+
1822
+ CAREFUL: the strides / offset are NOT bounds-checked against the
1823
+ receiver's memory. Every other view constructor derives strides that are
1824
+ known to stay inside the parent; here the caller supplies them, so an
1825
+ out-of-range combination reads or writes past the buffer. */
1826
+ static VALUE
1827
+ rb_ca_as_strided (int argc, VALUE *argv, VALUE self)
1828
+ {
1829
+ CArray *parent;
1830
+ volatile VALUE ropt = Qnil, rshape = Qnil, rstrides = Qnil, roffset = Qnil;
1831
+ ca_size_t shape[CA_RANK_MAX];
1832
+ ca_size_t strides[CA_RANK_MAX];
1833
+ ca_size_t base_offset = 0;
1834
+ int8_t ndim;
1835
+ long len, i;
1836
+
1837
+ rb_check_carray_object(self);
1838
+ TypedData_Get_Struct(self, CArray, &carray_data_type, parent);
1839
+
1840
+ rb_scan_args(argc, argv, "0:", (VALUE *) &ropt);
1841
+ if (NIL_P(ropt)) {
1842
+ rb_raise(rb_eArgError,
1843
+ "as_strided requires keyword arguments: shape:, strides:");
1844
+ }
1845
+ rb_scan_options(ropt, "shape,strides,offset",
1846
+ &rshape, &rstrides, &roffset);
1847
+ if (NIL_P(rshape) || NIL_P(rstrides)) {
1848
+ rb_raise(rb_eArgError,
1849
+ "as_strided requires both shape: and strides: keywords");
1850
+ }
1851
+ Check_Type(rshape, T_ARRAY);
1852
+ Check_Type(rstrides, T_ARRAY);
1853
+ len = RARRAY_LEN(rshape);
1854
+ if (RARRAY_LEN(rstrides) != len) {
1855
+ rb_raise(rb_eArgError,
1856
+ "shape (%ld) and strides (%ld) length mismatch",
1857
+ len, RARRAY_LEN(rstrides));
1858
+ }
1859
+ if (len <= 0 || len > CA_RANK_MAX) {
1860
+ rb_raise(rb_eArgError, "invalid ndim %ld", len);
1861
+ }
1862
+ ndim = (int8_t) len;
1863
+ for (i = 0; i < len; i++) {
1864
+ shape[i] = NUM2SIZE(RARRAY_AREF(rshape, i));
1865
+ strides[i] = NUM2SIZE(RARRAY_AREF(rstrides, i));
1866
+ }
1867
+ if (! NIL_P(roffset)) {
1868
+ base_offset = NUM2SIZE(roffset);
1869
+ }
1870
+
1871
+ return rb_ca_stride_new(self, parent->data_type, parent->bytes,
1872
+ ndim, shape, strides, base_offset);
1873
+ }
1874
+
1875
+ /* sliding_windows(*window, step: nil) -- overlapping-window view over every
1876
+ axis. Parent [d0..dN-1] becomes [(di-wi)/si+1 ..., w0..wN-1]; result rank
1877
+ is 2*ndim, so the parent's ndim must not exceed CA_RANK_MAX / 2. Truncate
1878
+ mode: no padding. Windows overlap, so the view aliases each parent cell
1879
+ from several positions. */
1880
+ static VALUE
1881
+ rb_ca_sliding_windows (int argc, VALUE *argv, VALUE self)
1882
+ {
1883
+ CArray *parent;
1884
+ volatile VALUE rposary = Qnil, ropt = Qnil, rstep = Qnil;
1885
+ ca_size_t window[CA_RANK_MAX];
1886
+ ca_size_t step[CA_RANK_MAX];
1887
+ ca_size_t outdim[CA_RANK_MAX];
1888
+ ca_size_t outstrides[CA_RANK_MAX];
1889
+ ca_size_t parent_byte_stride[CA_RANK_MAX];
1890
+ int8_t i, ndim;
1891
+ long nargs;
1892
+
1893
+ rb_check_carray_object(self);
1894
+ TypedData_Get_Struct(self, CArray, &carray_data_type, parent);
1895
+
1896
+ ndim = parent->ndim;
1897
+ if (2 * (int) ndim > CA_RANK_MAX) {
1898
+ rb_raise(rb_eArgError,
1899
+ "sliding_windows: result rank %d exceeds CA_RANK_MAX (%d)",
1900
+ 2 * (int) ndim, CA_RANK_MAX);
1901
+ }
1902
+
1903
+ rb_scan_args(argc, argv, "*:", (VALUE *) &rposary, (VALUE *) &ropt);
1904
+ if (! NIL_P(ropt)) {
1905
+ rb_scan_options(ropt, "step", &rstep);
1906
+ }
1907
+ nargs = RARRAY_LEN(rposary);
1908
+
1909
+ if (nargs == 1 && TYPE(RARRAY_AREF(rposary, 0)) == T_ARRAY) {
1910
+ volatile VALUE wary = RARRAY_AREF(rposary, 0);
1911
+ if (RARRAY_LEN(wary) != ndim) {
1912
+ rb_raise(rb_eArgError,
1913
+ "sliding_windows: window length (%ld) must equal ndim (%d)",
1914
+ RARRAY_LEN(wary), (int) ndim);
1915
+ }
1916
+ for (i = 0; i < ndim; i++) {
1917
+ window[i] = NUM2SIZE(RARRAY_AREF(wary, i));
1918
+ }
1919
+ }
1920
+ else if (nargs == ndim) {
1921
+ for (i = 0; i < ndim; i++) {
1922
+ window[i] = NUM2SIZE(RARRAY_AREF(rposary, i));
1923
+ }
1924
+ }
1925
+ else {
1926
+ rb_raise(rb_eArgError,
1927
+ "sliding_windows: expected %d window sizes (or one Array), got %ld",
1928
+ (int) ndim, nargs);
1929
+ }
1930
+
1931
+ if (NIL_P(rstep)) {
1932
+ for (i = 0; i < ndim; i++) step[i] = 1;
1933
+ }
1934
+ else if (TYPE(rstep) == T_ARRAY) {
1935
+ if (RARRAY_LEN(rstep) != ndim) {
1936
+ rb_raise(rb_eArgError,
1937
+ "sliding_windows: step length (%ld) must equal ndim (%d)",
1938
+ RARRAY_LEN(rstep), (int) ndim);
1939
+ }
1940
+ for (i = 0; i < ndim; i++) {
1941
+ step[i] = NUM2SIZE(RARRAY_AREF(rstep, i));
1942
+ }
1943
+ }
1944
+ else {
1945
+ ca_size_t s = NUM2SIZE(rstep);
1946
+ for (i = 0; i < ndim; i++) step[i] = s;
1947
+ }
1948
+
1949
+ for (i = 0; i < ndim; i++) {
1950
+ if (window[i] < 1) {
1951
+ rb_raise(rb_eArgError,
1952
+ "sliding_windows: window[%d]=%lld must be >= 1",
1953
+ (int) i, (long long) window[i]);
1954
+ }
1955
+ if (window[i] > parent->dim[i]) {
1956
+ rb_raise(rb_eArgError,
1957
+ "sliding_windows: window[%d]=%lld larger than parent dim[%d]=%lld",
1958
+ (int) i, (long long) window[i],
1959
+ (int) i, (long long) parent->dim[i]);
1960
+ }
1961
+ if (step[i] < 1) {
1962
+ rb_raise(rb_eArgError,
1963
+ "sliding_windows: step[%d]=%lld must be >= 1",
1964
+ (int) i, (long long) step[i]);
1965
+ }
1966
+ }
1967
+
1968
+ {
1969
+ ca_size_t s = parent->bytes;
1970
+ for (i = ndim - 1; i >= 0; i--) {
1971
+ parent_byte_stride[i] = s;
1972
+ s *= parent->dim[i];
1973
+ }
1974
+ }
1975
+
1976
+ for (i = 0; i < ndim; i++) {
1977
+ outdim[i] = (parent->dim[i] - window[i]) / step[i] + 1;
1978
+ outdim[ndim + i] = window[i];
1979
+ outstrides[i] = parent_byte_stride[i] * step[i];
1980
+ outstrides[ndim + i] = parent_byte_stride[i];
1981
+ }
1982
+
1983
+ return rb_ca_stride_new(self, parent->data_type, parent->bytes,
1984
+ (int8_t)(2 * ndim), outdim, outstrides, 0);
1985
+ }
1986
+
1987
+ /* unfold(*window, step: nil) -- sliding_windows over the leading `S` axes
1988
+ only, with the remaining `ndim - S` trailing axes riding along at their
1989
+ original strides. The window axes are inserted before the trailing ones,
1990
+ so the result rank is ndim + S. With S == ndim this is exactly
1991
+ sliding_windows. */
1992
+ static VALUE
1993
+ rb_ca_unfold (int argc, VALUE *argv, VALUE self)
1994
+ {
1995
+ CArray *parent;
1996
+ volatile VALUE rposary = Qnil, ropt = Qnil, rstep = Qnil;
1997
+ ca_size_t window[CA_RANK_MAX];
1998
+ ca_size_t step[CA_RANK_MAX];
1999
+ ca_size_t outdim[CA_RANK_MAX];
2000
+ ca_size_t outstrides[CA_RANK_MAX];
2001
+ ca_size_t parent_byte_stride[CA_RANK_MAX];
2002
+ int8_t i, ndim, nspatial, ntrail;
2003
+ int outrank;
2004
+ long nargs;
2005
+
2006
+ rb_check_carray_object(self);
2007
+ TypedData_Get_Struct(self, CArray, &carray_data_type, parent);
2008
+
2009
+ ndim = parent->ndim;
2010
+
2011
+ rb_scan_args(argc, argv, "*:", (VALUE *) &rposary, (VALUE *) &ropt);
2012
+ if (! NIL_P(ropt)) {
2013
+ rb_scan_options(ropt, "step", &rstep);
2014
+ }
2015
+ nargs = RARRAY_LEN(rposary);
2016
+
2017
+ if (nargs == 1 && TYPE(RARRAY_AREF(rposary, 0)) == T_ARRAY) {
2018
+ volatile VALUE wary = RARRAY_AREF(rposary, 0);
2019
+ if (RARRAY_LEN(wary) < 1 || RARRAY_LEN(wary) > ndim) {
2020
+ rb_raise(rb_eArgError,
2021
+ "unfold: window length (%ld) must be between 1 and ndim (%d)",
2022
+ RARRAY_LEN(wary), (int) ndim);
2023
+ }
2024
+ nspatial = (int8_t) RARRAY_LEN(wary);
2025
+ for (i = 0; i < nspatial; i++) {
2026
+ window[i] = NUM2SIZE(RARRAY_AREF(wary, i));
2027
+ }
2028
+ }
2029
+ else if (nargs >= 1 && nargs <= ndim) {
2030
+ nspatial = (int8_t) nargs;
2031
+ for (i = 0; i < nspatial; i++) {
2032
+ window[i] = NUM2SIZE(RARRAY_AREF(rposary, i));
2033
+ }
2034
+ }
2035
+ else {
2036
+ rb_raise(rb_eArgError,
2037
+ "unfold: expected 1..%d window sizes (or one Array), got %ld",
2038
+ (int) ndim, nargs);
2039
+ }
2040
+
2041
+ ntrail = ndim - nspatial;
2042
+ outrank = (int) ndim + (int) nspatial;
2043
+ if (outrank > CA_RANK_MAX) {
2044
+ rb_raise(rb_eArgError,
2045
+ "unfold: result rank %d exceeds CA_RANK_MAX (%d)",
2046
+ outrank, CA_RANK_MAX);
2047
+ }
2048
+
2049
+ if (NIL_P(rstep)) {
2050
+ for (i = 0; i < nspatial; i++) step[i] = 1;
2051
+ }
2052
+ else if (TYPE(rstep) == T_ARRAY) {
2053
+ if (RARRAY_LEN(rstep) != nspatial) {
2054
+ rb_raise(rb_eArgError,
2055
+ "unfold: step length (%ld) must equal window length (%d)",
2056
+ RARRAY_LEN(rstep), (int) nspatial);
2057
+ }
2058
+ for (i = 0; i < nspatial; i++) {
2059
+ step[i] = NUM2SIZE(RARRAY_AREF(rstep, i));
2060
+ }
2061
+ }
2062
+ else {
2063
+ ca_size_t s = NUM2SIZE(rstep);
2064
+ for (i = 0; i < nspatial; i++) step[i] = s;
2065
+ }
2066
+
2067
+ for (i = 0; i < nspatial; i++) {
2068
+ if (window[i] < 1) {
2069
+ rb_raise(rb_eArgError,
2070
+ "unfold: window[%d]=%lld must be >= 1",
2071
+ (int) i, (long long) window[i]);
2072
+ }
2073
+ if (window[i] > parent->dim[i]) {
2074
+ rb_raise(rb_eArgError,
2075
+ "unfold: window[%d]=%lld larger than parent dim[%d]=%lld",
2076
+ (int) i, (long long) window[i],
2077
+ (int) i, (long long) parent->dim[i]);
2078
+ }
2079
+ if (step[i] < 1) {
2080
+ rb_raise(rb_eArgError,
2081
+ "unfold: step[%d]=%lld must be >= 1",
2082
+ (int) i, (long long) step[i]);
2083
+ }
2084
+ }
2085
+
2086
+ {
2087
+ ca_size_t s = parent->bytes;
2088
+ for (i = ndim - 1; i >= 0; i--) {
2089
+ parent_byte_stride[i] = s;
2090
+ s *= parent->dim[i];
2091
+ }
2092
+ }
2093
+
2094
+ for (i = 0; i < nspatial; i++) {
2095
+ outdim[i] = (parent->dim[i] - window[i]) / step[i] + 1;
2096
+ outdim[nspatial + i] = window[i];
2097
+ outstrides[i] = parent_byte_stride[i] * step[i];
2098
+ outstrides[nspatial + i] = parent_byte_stride[i];
2099
+ }
2100
+ for (i = 0; i < ntrail; i++) {
2101
+ outdim[2 * nspatial + i] = parent->dim[nspatial + i];
2102
+ outstrides[2 * nspatial + i] = parent_byte_stride[nspatial + i];
2103
+ }
2104
+
2105
+ return rb_ca_stride_new(self, parent->data_type, parent->bytes,
2106
+ (int8_t) outrank, outdim, outstrides, 0);
2107
+ }
2108
+
2109
+ /* block_view(*block) -- non-overlapping tile view. Parent [d0..dN-1]
2110
+ becomes [d0/b0 ..., b0..bN-1]; result rank is 2*ndim. Unlike
2111
+ sliding_windows each parent dim must divide evenly, so no cell is dropped
2112
+ and none is aliased twice. */
2113
+ static VALUE
2114
+ rb_ca_block_view (int argc, VALUE *argv, VALUE self)
2115
+ {
2116
+ CArray *parent;
2117
+ volatile VALUE rposary = Qnil, ropt = Qnil;
2118
+ ca_size_t block[CA_RANK_MAX];
2119
+ ca_size_t outdim[CA_RANK_MAX];
2120
+ ca_size_t outstrides[CA_RANK_MAX];
2121
+ ca_size_t parent_byte_stride[CA_RANK_MAX];
2122
+ int8_t i, ndim;
2123
+ long nargs;
2124
+
2125
+ rb_check_carray_object(self);
2126
+ TypedData_Get_Struct(self, CArray, &carray_data_type, parent);
2127
+
2128
+ ndim = parent->ndim;
2129
+ if (2 * (int) ndim > CA_RANK_MAX) {
2130
+ rb_raise(rb_eArgError,
2131
+ "block_view: result rank %d exceeds CA_RANK_MAX (%d)",
2132
+ 2 * (int) ndim, CA_RANK_MAX);
2133
+ }
2134
+
2135
+ rb_scan_args(argc, argv, "*:", (VALUE *) &rposary, (VALUE *) &ropt);
2136
+ rb_reject_options(ropt);
2137
+ nargs = RARRAY_LEN(rposary);
2138
+
2139
+ if (nargs == 1 && TYPE(RARRAY_AREF(rposary, 0)) == T_ARRAY) {
2140
+ volatile VALUE bary = RARRAY_AREF(rposary, 0);
2141
+ if (RARRAY_LEN(bary) != ndim) {
2142
+ rb_raise(rb_eArgError,
2143
+ "block_view: block length (%ld) must equal ndim (%d)",
2144
+ RARRAY_LEN(bary), (int) ndim);
2145
+ }
2146
+ for (i = 0; i < ndim; i++) {
2147
+ block[i] = NUM2SIZE(RARRAY_AREF(bary, i));
2148
+ }
2149
+ }
2150
+ else if (nargs == ndim) {
2151
+ for (i = 0; i < ndim; i++) {
2152
+ block[i] = NUM2SIZE(RARRAY_AREF(rposary, i));
2153
+ }
2154
+ }
2155
+ else {
2156
+ rb_raise(rb_eArgError,
2157
+ "block_view: expected %d block sizes (or one Array), got %ld",
2158
+ (int) ndim, nargs);
2159
+ }
2160
+
2161
+ for (i = 0; i < ndim; i++) {
2162
+ if (block[i] < 1) {
2163
+ rb_raise(rb_eArgError,
2164
+ "block_view: block[%d]=%lld must be >= 1",
2165
+ (int) i, (long long) block[i]);
2166
+ }
2167
+ if (parent->dim[i] % block[i] != 0) {
2168
+ rb_raise(rb_eArgError,
2169
+ "block_view: parent dim[%d]=%lld is not divisible by block[%d]=%lld",
2170
+ (int) i, (long long) parent->dim[i],
2171
+ (int) i, (long long) block[i]);
2172
+ }
2173
+ }
2174
+
2175
+ {
2176
+ ca_size_t s = parent->bytes;
2177
+ for (i = ndim - 1; i >= 0; i--) {
2178
+ parent_byte_stride[i] = s;
2179
+ s *= parent->dim[i];
2180
+ }
2181
+ }
2182
+
2183
+ for (i = 0; i < ndim; i++) {
2184
+ outdim[i] = parent->dim[i] / block[i];
2185
+ outdim[ndim + i] = block[i];
2186
+ outstrides[i] = parent_byte_stride[i] * block[i];
2187
+ outstrides[ndim + i] = parent_byte_stride[i];
2188
+ }
2189
+
2190
+ return rb_ca_stride_new(self, parent->data_type, parent->bytes,
2191
+ (int8_t)(2 * ndim), outdim, outstrides, 0);
2192
+ }
2193
+
2194
+ /* defined in ca_obj_transpose.c */
2195
+ extern VALUE rb_ca_trans_new (VALUE cary, ca_size_t *imap);
2196
+
2197
+ /* dim_view(*axes) -- moves the given axes to the front, keeping the rest in
2198
+ order. A thin alias over `transposed` that names the intent, so it returns
2199
+ a CATranspose and inherits its alias path and mask propagation. */
2200
+ static VALUE
2201
+ rb_ca_dim_view (int argc, VALUE *argv, VALUE self)
2202
+ {
2203
+ CArray *parent;
2204
+ volatile VALUE rposary = Qnil, ropt = Qnil;
2205
+ ca_size_t iter_axes[CA_RANK_MAX];
2206
+ ca_size_t imap[CA_RANK_MAX];
2207
+ int8_t seen[CA_RANK_MAX];
2208
+ int8_t ndim, n_iter, i, k;
2209
+ long nargs;
2210
+
2211
+ rb_check_carray_object(self);
2212
+ TypedData_Get_Struct(self, CArray, &carray_data_type, parent);
2213
+ ndim = parent->ndim;
2214
+
2215
+ rb_scan_args(argc, argv, "*:", (VALUE *) &rposary, (VALUE *) &ropt);
2216
+ rb_reject_options(ropt);
2217
+ nargs = RARRAY_LEN(rposary);
2218
+
2219
+ /* Accept either a single Array of axes or variadic Integers. */
2220
+ if (nargs == 1 && TYPE(RARRAY_AREF(rposary, 0)) == T_ARRAY) {
2221
+ volatile VALUE aary = RARRAY_AREF(rposary, 0);
2222
+ n_iter = (int8_t) RARRAY_LEN(aary);
2223
+ if (n_iter < 1) {
2224
+ rb_raise(rb_eArgError, "dim_view: at least one iteration axis required");
2225
+ }
2226
+ if (n_iter > ndim) {
2227
+ rb_raise(rb_eArgError,
2228
+ "dim_view: too many iteration axes (%d) for ndim (%d)",
2229
+ (int) n_iter, (int) ndim);
2230
+ }
2231
+ for (i = 0; i < n_iter; i++) {
2232
+ iter_axes[i] = NUM2SIZE(RARRAY_AREF(aary, i));
2233
+ }
2234
+ }
2235
+ else if (nargs >= 1) {
2236
+ if (nargs > ndim) {
2237
+ rb_raise(rb_eArgError,
2238
+ "dim_view: too many iteration axes (%ld) for ndim (%d)",
2239
+ nargs, (int) ndim);
2240
+ }
2241
+ n_iter = (int8_t) nargs;
2242
+ for (i = 0; i < n_iter; i++) {
2243
+ iter_axes[i] = NUM2SIZE(RARRAY_AREF(rposary, i));
2244
+ }
2245
+ }
2246
+ else {
2247
+ rb_raise(rb_eArgError,
2248
+ "dim_view: at least one iteration axis required");
2249
+ }
2250
+
2251
+ /* Normalize negative indices and validate range / distinctness. */
2252
+ for (i = 0; i < ndim; i++) seen[i] = 0;
2253
+ for (i = 0; i < n_iter; i++) {
2254
+ ca_size_t a = iter_axes[i];
2255
+ if (a < 0) a += ndim;
2256
+ if (a < 0 || a >= ndim) {
2257
+ rb_raise(rb_eArgError,
2258
+ "dim_view: axis %lld out of range for ndim %d",
2259
+ (long long) iter_axes[i], (int) ndim);
2260
+ }
2261
+ if (seen[a]) {
2262
+ rb_raise(rb_eArgError,
2263
+ "dim_view: duplicate iteration axis %lld",
2264
+ (long long) a);
2265
+ }
2266
+ seen[a] = 1;
2267
+ iter_axes[i] = a;
2268
+ }
2269
+
2270
+ /* imap: iter axes first (in given order), then remaining axes
2271
+ (in original order). */
2272
+ for (i = 0; i < n_iter; i++) {
2273
+ imap[i] = iter_axes[i];
2274
+ }
2275
+ k = n_iter;
2276
+ for (i = 0; i < ndim; i++) {
2277
+ if (! seen[i]) imap[k++] = i;
2278
+ }
2279
+
2280
+ return rb_ca_trans_new(self, imap);
2281
+ }
2282
+
2283
+ /* flip(*axes) -- reverses the listed axes by negating their strides; with no
2284
+ argument every axis is flipped. The named counterpart of the indexer form
2285
+ ca[-1..0, nil, -1..0]: both give a true negative-stride view, zero copy and
2286
+ write-through. */
2287
+ /* Build the flipped CAStride view from a per-axis flip[] flag array.
2288
+ * Shared by rb_ca_flip (Ruby entry) and rb_ca_flip_axis (C-callable entry).
2289
+ * Each flip[i] == 1 reverses axis i; 0 leaves it as-is. */
2290
+ static VALUE
2291
+ rb_ca_flip_build_view (VALUE self, CArray *parent, const int8_t *flip)
2292
+ {
2293
+ int8_t ndim = parent->ndim;
2294
+ int8_t i;
2295
+ ca_size_t outdim[CA_RANK_MAX];
2296
+ ca_size_t outstrides[CA_RANK_MAX];
2297
+ ca_size_t parent_byte_stride[CA_RANK_MAX];
2298
+ ca_size_t base_offset = 0;
2299
+
2300
+ /* parent row-major byte strides */
2301
+ {
2302
+ ca_size_t s = parent->bytes;
2303
+ for (i = ndim - 1; i >= 0; i--) {
2304
+ parent_byte_stride[i] = s;
2305
+ s *= parent->dim[i];
2306
+ }
2307
+ }
2308
+
2309
+ /* Build flipped strides and the corresponding base_offset.
2310
+ A flipped axis i contributes (dim[i]-1)*parent_byte_stride[i]
2311
+ to base_offset and has its stride sign inverted. */
2312
+ for (i = 0; i < ndim; i++) {
2313
+ outdim[i] = parent->dim[i];
2314
+ if (flip[i]) {
2315
+ outstrides[i] = -parent_byte_stride[i];
2316
+ base_offset += (parent->dim[i] - 1) * parent_byte_stride[i];
2317
+ }
2318
+ else {
2319
+ outstrides[i] = parent_byte_stride[i];
2320
+ }
2321
+ }
2322
+
2323
+ VALUE obj = rb_ca_stride_new(self, parent->data_type, parent->bytes,
2324
+ ndim, outdim, outstrides, base_offset);
2325
+ CA_WRAPPER_LIFT(obj, self, parent);
2326
+ return obj;
2327
+ }
2328
+
2329
+ /* C-callable entry: flip a single axis (= the common case).
2330
+ * No rb_scan_args, safe to call directly from C. axis is normalized
2331
+ * here (Python-style negative allowed). For ext authors. */
2332
+ VALUE
2333
+ rb_ca_flip_axis (VALUE self, long axis)
2334
+ {
2335
+ CArray *parent;
2336
+ rb_check_carray_object(self);
2337
+ TypedData_Get_Struct(self, CArray, &carray_data_type, parent);
2338
+ int8_t ndim = parent->ndim;
2339
+ long a = (axis < 0) ? (axis + ndim) : axis;
2340
+ if (a < 0 || a >= ndim) {
2341
+ rb_raise(rb_eArgError,
2342
+ "flip_axis: axis %ld out of range for ndim %d",
2343
+ axis, (int) ndim);
2344
+ }
2345
+ int8_t flip[CA_RANK_MAX] = {0};
2346
+ flip[a] = 1;
2347
+ return rb_ca_flip_build_view(self, parent, flip);
2348
+ }
2349
+
2350
+ /* Ruby binding entry: parses *args (axes) -> builds flip[] -> forwards. */
2351
+ static VALUE
2352
+ rb_ca_flip (int argc, VALUE *argv, VALUE self)
2353
+ {
2354
+ CArray *parent;
2355
+ volatile VALUE rposary = Qnil, ropt = Qnil;
2356
+ ca_size_t axes[CA_RANK_MAX];
2357
+ int8_t flip[CA_RANK_MAX];
2358
+ int8_t ndim, n_axes, i;
2359
+ long nargs;
2360
+
2361
+ rb_check_carray_object(self);
2362
+ TypedData_Get_Struct(self, CArray, &carray_data_type, parent);
2363
+ ndim = parent->ndim;
2364
+
2365
+ rb_scan_args(argc, argv, "*:", (VALUE *) &rposary, (VALUE *) &ropt);
2366
+ rb_reject_options(ropt);
2367
+ nargs = RARRAY_LEN(rposary);
2368
+
2369
+ for (i = 0; i < ndim; i++) flip[i] = 0;
2370
+
2371
+ if (nargs == 0) {
2372
+ /* No args: flip every axis. */
2373
+ for (i = 0; i < ndim; i++) flip[i] = 1;
2374
+ n_axes = ndim;
2375
+ }
2376
+ else {
2377
+ if (nargs == 1 && TYPE(RARRAY_AREF(rposary, 0)) == T_ARRAY) {
2378
+ volatile VALUE aary = RARRAY_AREF(rposary, 0);
2379
+ n_axes = (int8_t) RARRAY_LEN(aary);
2380
+ if (n_axes > ndim) {
2381
+ rb_raise(rb_eArgError,
2382
+ "flip: too many axes (%d) for ndim (%d)",
2383
+ (int) n_axes, (int) ndim);
2384
+ }
2385
+ for (i = 0; i < n_axes; i++) {
2386
+ axes[i] = NUM2SIZE(RARRAY_AREF(aary, i));
2387
+ }
2388
+ }
2389
+ else {
2390
+ if (nargs > ndim) {
2391
+ rb_raise(rb_eArgError,
2392
+ "flip: too many axes (%ld) for ndim (%d)",
2393
+ nargs, (int) ndim);
2394
+ }
2395
+ n_axes = (int8_t) nargs;
2396
+ for (i = 0; i < n_axes; i++) {
2397
+ axes[i] = NUM2SIZE(RARRAY_AREF(rposary, i));
2398
+ }
2399
+ }
2400
+
2401
+ for (i = 0; i < n_axes; i++) {
2402
+ ca_size_t a = axes[i];
2403
+ if (a < 0) a += ndim;
2404
+ if (a < 0 || a >= ndim) {
2405
+ rb_raise(rb_eArgError,
2406
+ "flip: axis %lld out of range for ndim %d",
2407
+ (long long) axes[i], (int) ndim);
2408
+ }
2409
+ if (flip[a]) {
2410
+ rb_raise(rb_eArgError,
2411
+ "flip: duplicate axis %lld",
2412
+ (long long) a);
2413
+ }
2414
+ flip[a] = 1;
2415
+ }
2416
+ }
2417
+
2418
+ (void) n_axes; /* unused after validation */
2419
+ return rb_ca_flip_build_view(self, parent, flip);
2420
+ }
2421
+
2422
+ /* diagonal(offset = 0, axis: [0, 1]) -- view of one diagonal of the parent.
2423
+ The two designated axes collapse into a single diagonal axis appended at
2424
+ the END of the result; the remaining axes keep their order in front.
2425
+ `offset` shifts off the main diagonal (positive = super, negative = sub);
2426
+ an offset past the relevant axis yields an empty view. */
2427
+ static VALUE
2428
+ rb_ca_diagonal (int argc, VALUE *argv, VALUE self)
2429
+ {
2430
+ CArray *parent;
2431
+ volatile VALUE rposary = Qnil, ropt = Qnil;
2432
+ volatile VALUE raxis = Qnil, roffset = Qnil;
2433
+ ca_size_t parent_byte_stride[CA_RANK_MAX];
2434
+ ca_size_t outdim[CA_RANK_MAX];
2435
+ ca_size_t outstrides[CA_RANK_MAX];
2436
+ ca_size_t offset = 0;
2437
+ ca_size_t base_offset = 0;
2438
+ ca_size_t diag_len;
2439
+ ca_size_t ai, aj; /* normalized axis indices */
2440
+ int8_t ndim, out_k, i;
2441
+ long nargs;
2442
+
2443
+ rb_check_carray_object(self);
2444
+ TypedData_Get_Struct(self, CArray, &carray_data_type, parent);
2445
+ ndim = parent->ndim;
2446
+
2447
+ if (ndim < 2) {
2448
+ rb_raise(rb_eArgError, "diagonal: requires ndim >= 2 (got %d)", (int) ndim);
2449
+ }
2450
+
2451
+ rb_scan_args(argc, argv, "*:", (VALUE *) &rposary, (VALUE *) &ropt);
2452
+ if (! NIL_P(ropt)) {
2453
+ rb_scan_options(ropt, "axis,offset", &raxis, &roffset);
2454
+ }
2455
+ nargs = RARRAY_LEN(rposary);
2456
+
2457
+ /* offset from positional or keyword (not both) */
2458
+ if (nargs == 0) {
2459
+ if (! NIL_P(roffset)) offset = NUM2SIZE(roffset);
2460
+ }
2461
+ else if (nargs == 1) {
2462
+ if (! NIL_P(roffset)) {
2463
+ rb_raise(rb_eArgError,
2464
+ "diagonal: give offset positionally OR as keyword, not both");
2465
+ }
2466
+ offset = NUM2SIZE(RARRAY_AREF(rposary, 0));
2467
+ }
2468
+ else {
2469
+ rb_raise(rb_eArgError,
2470
+ "diagonal: too many positional args (got %ld, expected 0 or 1)",
2471
+ nargs);
2472
+ }
2473
+
2474
+ /* axes: keyword, default [0, 1] */
2475
+ ai = 0;
2476
+ aj = 1;
2477
+ if (! NIL_P(raxis)) {
2478
+ if (TYPE(raxis) != T_ARRAY || RARRAY_LEN(raxis) != 2) {
2479
+ rb_raise(rb_eArgError,
2480
+ "diagonal: axis: must be an Array of 2 integers");
2481
+ }
2482
+ ai = NUM2SIZE(RARRAY_AREF(raxis, 0));
2483
+ aj = NUM2SIZE(RARRAY_AREF(raxis, 1));
2484
+ if (ai < 0) ai += ndim;
2485
+ if (aj < 0) aj += ndim;
2486
+ if (ai < 0 || ai >= ndim) {
2487
+ rb_raise(rb_eArgError, "diagonal: axis[0] out of range for ndim %d",
2488
+ (int) ndim);
2489
+ }
2490
+ if (aj < 0 || aj >= ndim) {
2491
+ rb_raise(rb_eArgError, "diagonal: axis[1] out of range for ndim %d",
2492
+ (int) ndim);
2493
+ }
2494
+ if (ai == aj) {
2495
+ rb_raise(rb_eArgError, "diagonal: axis[0] and axis[1] must be distinct");
2496
+ }
2497
+ }
2498
+
2499
+ /* parent row-major byte strides */
2500
+ {
2501
+ ca_size_t s = parent->bytes;
2502
+ for (i = ndim - 1; i >= 0; i--) {
2503
+ parent_byte_stride[i] = s;
2504
+ s *= parent->dim[i];
2505
+ }
2506
+ }
2507
+
2508
+ /* compute diagonal length and base_offset */
2509
+ if (offset >= 0) {
2510
+ if (offset >= parent->dim[aj]) {
2511
+ diag_len = 0;
2512
+ }
2513
+ else {
2514
+ ca_size_t a = parent->dim[ai];
2515
+ ca_size_t b = parent->dim[aj] - offset;
2516
+ diag_len = (a < b) ? a : b;
2517
+ }
2518
+ base_offset = offset * parent_byte_stride[aj];
2519
+ }
2520
+ else {
2521
+ ca_size_t neg = -offset;
2522
+ if (neg >= parent->dim[ai]) {
2523
+ diag_len = 0;
2524
+ }
2525
+ else {
2526
+ ca_size_t a = parent->dim[ai] - neg;
2527
+ ca_size_t b = parent->dim[aj];
2528
+ diag_len = (a < b) ? a : b;
2529
+ }
2530
+ base_offset = neg * parent_byte_stride[ai];
2531
+ }
2532
+
2533
+ /* build output: kept axes (in original order), then diagonal axis */
2534
+ out_k = 0;
2535
+ for (i = 0; i < ndim; i++) {
2536
+ if (i == (int8_t) ai || i == (int8_t) aj) continue;
2537
+ outdim[out_k] = parent->dim[i];
2538
+ outstrides[out_k] = parent_byte_stride[i];
2539
+ out_k++;
2540
+ }
2541
+ outdim[out_k] = diag_len;
2542
+ outstrides[out_k] = parent_byte_stride[ai] + parent_byte_stride[aj];
2543
+ out_k++;
2544
+
2545
+ {
2546
+ VALUE obj = rb_ca_stride_new(self, parent->data_type, parent->bytes,
2547
+ out_k, outdim, outstrides, base_offset);
2548
+ CA_WRAPPER_LIFT(obj, self, parent);
2549
+ return obj;
2550
+ }
2551
+ }
2552
+
2553
+ void
2554
+ Init_ca_obj_stride (void)
2555
+ {
2556
+ /* rb_cCAStride and rb_cCAStrideMask are defined upfront in
2557
+ ruby_carray.c, so subclasses (CARepeat, CATranspose, CAFarray)
2558
+ can be defined before this Init runs. */
2559
+
2560
+ CA_OBJ_STRIDE = ca_install_obj_type(rb_cCAStride,
2561
+ &castride_data_type,
2562
+ rb_cCAStrideMask,
2563
+ &castride_mask_data_type,
2564
+ &ca_stride_func, sizeof(ca_stride_func));
2565
+ rb_define_const(rb_cObject, "CA_OBJ_STRIDE", INT2NUM(CA_OBJ_STRIDE));
2566
+
2567
+ rb_define_alloc_func(rb_cCAStride, rb_cs_s_allocate);
2568
+ rb_define_method(rb_cCAStride, "initialize_copy", rb_cs_initialize_copy, 1);
2569
+
2570
+ rb_define_method(rb_cCAStride, "strides", rb_cs_strides, 0);
2571
+ rb_define_method(rb_cCAStride, "byte_offset", rb_cs_byte_offset, 0);
2572
+
2573
+ rb_define_method(rb_cCArray, "as_strided", rb_ca_as_strided, -1);
2574
+ rb_define_method(rb_cCArray, "sliding_windows",
2575
+ rb_ca_sliding_windows, -1);
2576
+ rb_define_method(rb_cCArray, "unfold", rb_ca_unfold, -1);
2577
+ rb_define_method(rb_cCArray, "block_view", rb_ca_block_view, -1);
2578
+ rb_define_method(rb_cCArray, "dim_view", rb_ca_dim_view, -1);
2579
+ rb_define_method(rb_cCArray, "flip", rb_ca_flip, -1);
2580
+ /* `reverse` = `flip` no-arg form (= all-axis reversed view). Direct CAStride
2581
+ construction with negative strides, no indexer / attach detour. */
2582
+ rb_define_alias(rb_cCArray, "reverse", "flip");
2583
+ rb_define_method(rb_cCArray, "diagonal", rb_ca_diagonal, -1);
2584
+ }