carray 2.0.0 → 3.0.0

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