carray 2.0.1 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (386) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -25
  3. data/CHANGELOG.md +338 -0
  4. data/{NEWS.md → CHANGELOG.v1.md} +3 -0
  5. data/LICENSE +1 -1
  6. data/README.md +120 -36
  7. data/carray.gemspec +32 -30
  8. data/ext/ca_array_pool.c +91 -0
  9. data/ext/ca_axis_descriptor.h +186 -0
  10. data/ext/ca_axis_dispatch.c +924 -0
  11. data/ext/ca_axis_group.c +1244 -0
  12. data/ext/ca_bincmp_dispatch.c +76 -0
  13. data/ext/ca_bincmp_dispatch.h +85 -0
  14. data/ext/ca_binop_dispatch.c +124 -0
  15. data/ext/ca_binop_dispatch.h +152 -0
  16. data/ext/ca_categorical_iterator.c +1375 -0
  17. data/ext/ca_compare.c +94 -0
  18. data/ext/ca_compare.h +26 -0
  19. data/ext/ca_composite_dispatch.c +414 -0
  20. data/ext/ca_composite_dispatch.h +116 -0
  21. data/ext/ca_for_buffer.h +96 -0
  22. data/ext/ca_for_each_element.h +239 -0
  23. data/ext/ca_group_iter.c +304 -0
  24. data/ext/ca_iter_substrate.h +325 -0
  25. data/ext/ca_kernel_iterator.c +4367 -0
  26. data/ext/ca_kernel_iterator.h +2596 -0
  27. data/ext/ca_moncmp_dispatch.c +37 -0
  28. data/ext/ca_moncmp_dispatch.h +62 -0
  29. data/ext/ca_monop_dispatch.c +200 -0
  30. data/ext/ca_monop_dispatch.h +235 -0
  31. data/ext/ca_obj_array.c +355 -359
  32. data/ext/ca_obj_bincmp.c +839 -0
  33. data/ext/ca_obj_binop.c +948 -0
  34. data/ext/ca_obj_bitarray.c +369 -164
  35. data/ext/ca_obj_bitfield.c +294 -234
  36. data/ext/ca_obj_block.c +189 -711
  37. data/ext/ca_obj_byte_swap.c +766 -0
  38. data/ext/ca_obj_const_string.c +967 -0
  39. data/ext/ca_obj_face.c +750 -0
  40. data/ext/ca_obj_face.h +279 -0
  41. data/ext/ca_obj_fake.c +239 -100
  42. data/ext/ca_obj_farray.c +54 -441
  43. data/ext/ca_obj_field.c +82 -529
  44. data/ext/ca_obj_fixlen_string.c +308 -0
  45. data/ext/ca_obj_grid.c +866 -440
  46. data/ext/ca_obj_meld.c +1039 -0
  47. data/ext/ca_obj_moncmp.c +588 -0
  48. data/ext/ca_obj_monop.c +1123 -0
  49. data/ext/ca_obj_object.c +866 -296
  50. data/ext/ca_obj_record.c +470 -0
  51. data/ext/ca_obj_reduce.c +97 -82
  52. data/ext/ca_obj_refer.c +593 -459
  53. data/ext/ca_obj_remap.c +475 -0
  54. data/ext/ca_obj_repeat.c +92 -477
  55. data/ext/ca_obj_roll.c +624 -0
  56. data/ext/ca_obj_select.c +344 -296
  57. data/ext/ca_obj_select_axis.c +1306 -0
  58. data/ext/ca_obj_shift.c +231 -793
  59. data/ext/ca_obj_source.c +78 -0
  60. data/ext/ca_obj_stack.c +1173 -0
  61. data/ext/ca_obj_stride.c +2584 -0
  62. data/ext/ca_obj_string.c +270 -0
  63. data/ext/ca_obj_tile.c +622 -0
  64. data/ext/ca_obj_time.c +548 -0
  65. data/ext/ca_obj_timedelta.c +437 -0
  66. data/ext/ca_obj_transpose.c +62 -516
  67. data/ext/ca_obj_triop.c +785 -0
  68. data/ext/ca_obj_window.c +1202 -565
  69. data/ext/ca_op_byte_swap.c +175 -0
  70. data/ext/ca_op_cmplx64.h +123 -0
  71. data/ext/ca_op_ipower.c +316 -0
  72. data/ext/ca_op_powi.h +88 -0
  73. data/ext/ca_sort_kernels.h +132 -0
  74. data/ext/ca_sweep_engine.c +473 -0
  75. data/ext/ca_sweep_engine.h +166 -0
  76. data/ext/ca_transform_common.c +235 -0
  77. data/ext/ca_triop_dispatch.c +55 -0
  78. data/ext/ca_triop_dispatch.h +62 -0
  79. data/ext/carray.h +810 -420
  80. data/ext/carray_access.c +873 -731
  81. data/ext/carray_attribute.c +98 -329
  82. data/ext/carray_bincount.c +255 -0
  83. data/ext/carray_broadcast.c +376 -0
  84. data/ext/carray_build_flags.h +3 -0
  85. data/ext/carray_call_cfunc.c +2897 -874
  86. data/ext/carray_call_cfunc.h +313 -0
  87. data/ext/carray_cast.c +1264 -315
  88. data/ext/carray_cast_func.rb +81 -40
  89. data/ext/carray_class.c +53 -63
  90. data/ext/carray_config.h +28 -0
  91. data/ext/carray_conversion.c +350 -346
  92. data/ext/carray_copy.c +168 -270
  93. data/ext/carray_core.c +1396 -206
  94. data/ext/carray_count.c +312 -0
  95. data/ext/carray_data_type.c +43 -19
  96. data/ext/carray_element.c +585 -213
  97. data/ext/carray_factorize.c +2542 -0
  98. data/ext/carray_generate.c +230 -559
  99. data/ext/carray_histogram.c +490 -0
  100. data/ext/carray_hold.c +228 -0
  101. data/ext/carray_index_classifier.c +1021 -0
  102. data/ext/carray_index_classifier.h +27 -0
  103. data/ext/carray_internal.h +136 -0
  104. data/ext/carray_kernels_bincmp.c +4446 -0
  105. data/ext/carray_kernels_binop.c +11001 -0
  106. data/ext/carray_kernels_init.c +1131 -0
  107. data/ext/carray_kernels_map.c +3467 -0
  108. data/ext/carray_kernels_moncmp.c +2097 -0
  109. data/ext/carray_kernels_monop.c +18313 -0
  110. data/ext/carray_kernels_reduce_aggregate.c +25837 -0
  111. data/ext/carray_kernels_reduce_boolean.c +330 -0
  112. data/ext/carray_kernels_reduce_cumulative.c +14593 -0
  113. data/ext/carray_kernels_reduce_extreme.c +16948 -0
  114. data/ext/carray_kernels_reduce_variance.c +3910 -0
  115. data/ext/carray_kernels_scan.c +3693 -0
  116. data/ext/carray_kernels_search.c +32138 -0
  117. data/ext/carray_kernels_sort.c +10626 -0
  118. data/ext/carray_kernels_triop.c +1392 -0
  119. data/ext/carray_lazy.c +737 -0
  120. data/ext/carray_loop.c +88 -200
  121. data/ext/carray_mask.c +853 -158
  122. data/ext/carray_math_kernel.h +120 -0
  123. data/ext/carray_mathfunc.c +10 -241
  124. data/ext/carray_median_percentile.c +1257 -0
  125. data/ext/carray_memory_view.c +1650 -0
  126. data/ext/carray_operator.c +1525 -320
  127. data/ext/carray_order.c +664 -1394
  128. data/ext/carray_partition.c +416 -0
  129. data/ext/carray_random.c +518 -0
  130. data/ext/carray_scatter.c +357 -0
  131. data/ext/carray_slab.c +1219 -0
  132. data/ext/carray_slab.h +84 -0
  133. data/ext/carray_sort.c +829 -0
  134. data/ext/carray_sort_kernel.c +620 -0
  135. data/ext/carray_struct.c +695 -0
  136. data/ext/carray_test.c +343 -229
  137. data/ext/carray_undef.c +34 -17
  138. data/ext/carray_utils.c +175 -74
  139. data/ext/extconf.rb +234 -55
  140. data/ext/mk_call_cfunc.rb +671 -0
  141. data/ext/mkkernel.rb +9096 -0
  142. data/ext/ruby_carray.c +211 -108
  143. data/ext/version.h +4 -14
  144. data/ext/version.rb +5 -13
  145. data/lib/carray/arrow_tensor.rb +401 -0
  146. data/lib/carray/attribute.rb +166 -0
  147. data/lib/carray/autoload_carray.rb +239 -0
  148. data/lib/carray/autoload_method_extension.rb +45 -0
  149. data/lib/carray/axis_group.rb +711 -0
  150. data/lib/carray/basics.rb +481 -0
  151. data/lib/carray/bincount_nd.rb +358 -0
  152. data/lib/carray/block_iterator.rb +614 -0
  153. data/lib/carray/boolean_reduce.rb +109 -0
  154. data/lib/carray/categorical.rb +561 -0
  155. data/lib/carray/categorical_iterator.rb +1084 -0
  156. data/lib/carray/complex.rb +150 -0
  157. data/lib/carray/conditional.rb +216 -0
  158. data/lib/carray/const_string.rb +228 -0
  159. data/lib/carray/construct.rb +160 -328
  160. data/lib/carray/core_extensions.rb +297 -0
  161. data/lib/carray/data_type_extension.rb +250 -0
  162. data/lib/carray/fixlen_string.rb +95 -0
  163. data/lib/carray/frame/concat.rb +132 -0
  164. data/lib/carray/frame/convert.rb +95 -0
  165. data/lib/carray/frame/csv_parser.rb +211 -0
  166. data/lib/carray/frame/frame.rb +642 -0
  167. data/lib/carray/frame/group.rb +186 -0
  168. data/lib/carray/frame/io.rb +321 -0
  169. data/lib/carray/frame/join.rb +248 -0
  170. data/lib/carray/frame/records.rb +99 -0
  171. data/lib/carray/frame/sort.rb +113 -0
  172. data/lib/carray/frame/verbs.rb +316 -0
  173. data/lib/carray/frame.rb +16 -0
  174. data/lib/carray/fuse_source.rb +123 -0
  175. data/lib/carray/fusion.rb +218 -0
  176. data/lib/carray/histogram.rb +512 -0
  177. data/lib/carray/inspect.rb +37 -26
  178. data/lib/carray/iterator.rb +58 -349
  179. data/lib/carray/lazy.rb +941 -0
  180. data/lib/carray/mask_gap_fill.rb +200 -0
  181. data/lib/carray/math.rb +78 -342
  182. data/lib/carray/meld_reduce.rb +289 -0
  183. data/lib/carray/methods/align_addr.rb +116 -0
  184. data/lib/carray/methods/bin.rb +128 -0
  185. data/lib/carray/methods/bincount.rb +87 -0
  186. data/lib/carray/methods/bit_string.rb +92 -0
  187. data/lib/carray/methods/broadcast.rb +63 -0
  188. data/lib/carray/methods/choose.rb +39 -0
  189. data/lib/carray/methods/composition.rb +280 -0
  190. data/lib/carray/methods/gather_nd.rb +206 -0
  191. data/lib/carray/methods/index.rb +39 -0
  192. data/lib/carray/methods/insert_block.rb +99 -0
  193. data/lib/carray/methods/is_in.rb +141 -0
  194. data/lib/carray/methods/join.rb +90 -0
  195. data/lib/carray/methods/locate_addr.rb +52 -0
  196. data/lib/carray/methods/mask_duplicates.rb +41 -0
  197. data/lib/carray/methods/meshgrid.rb +90 -0
  198. data/lib/carray/methods/mode.rb +126 -0
  199. data/lib/carray/methods/nunique.rb +46 -0
  200. data/lib/carray/methods/resize.rb +56 -0
  201. data/lib/carray/methods/snap.rb +161 -0
  202. data/lib/carray/methods/string_format.rb +57 -0
  203. data/lib/carray/methods/unique.rb +47 -0
  204. data/lib/carray/methods/value_counts.rb +71 -0
  205. data/lib/carray/mkmf.rb +124 -101
  206. data/lib/carray/runtime.rb +89 -0
  207. data/lib/carray/serialize.rb +478 -167
  208. data/lib/carray/slab_iterator.rb +305 -0
  209. data/lib/carray/stack.rb +291 -0
  210. data/lib/carray/string.rb +56 -180
  211. data/lib/carray/string_operation_extension.rb +289 -0
  212. data/lib/carray/struct.rb +335 -323
  213. data/lib/carray/struct_builder.rb +697 -0
  214. data/lib/carray/table.rb +41 -2
  215. data/lib/carray/time.rb +2654 -38
  216. data/lib/carray/window_iterator.rb +927 -0
  217. data/lib/carray.rb +55 -57
  218. data/yard-stubs/ca_obj_array.rb +385 -0
  219. data/yard-stubs/ca_obj_bitarray.rb +38 -0
  220. data/yard-stubs/ca_obj_bitfield.rb +43 -0
  221. data/yard-stubs/ca_obj_block.rb +73 -0
  222. data/yard-stubs/ca_obj_byte_swap.rb +56 -0
  223. data/yard-stubs/ca_obj_fake.rb +31 -0
  224. data/yard-stubs/ca_obj_farray.rb +32 -0
  225. data/yard-stubs/ca_obj_field.rb +45 -0
  226. data/yard-stubs/ca_obj_grid.rb +35 -0
  227. data/yard-stubs/ca_obj_refer.rb +72 -0
  228. data/yard-stubs/ca_obj_roll.rb +45 -0
  229. data/yard-stubs/ca_obj_shift.rb +43 -0
  230. data/yard-stubs/ca_obj_stride.rb +181 -0
  231. data/yard-stubs/ca_obj_tile.rb +29 -0
  232. data/yard-stubs/ca_obj_transpose.rb +40 -0
  233. data/yard-stubs/ca_obj_window.rb +49 -0
  234. data/yard-stubs/carray_access.rb +131 -0
  235. data/yard-stubs/carray_attribute.rb +246 -0
  236. data/yard-stubs/carray_broadcast.rb +37 -0
  237. data/yard-stubs/carray_cast.rb +489 -0
  238. data/yard-stubs/carray_class.rb +65 -0
  239. data/yard-stubs/carray_conversion.rb +76 -0
  240. data/yard-stubs/carray_copy.rb +79 -0
  241. data/yard-stubs/carray_core.rb +114 -0
  242. data/yard-stubs/carray_count.rb +79 -0
  243. data/yard-stubs/carray_element.rb +108 -0
  244. data/yard-stubs/carray_generate.rb +66 -0
  245. data/yard-stubs/carray_lazy.rb +23 -0
  246. data/yard-stubs/carray_loop.rb +140 -0
  247. data/yard-stubs/carray_mask.rb +259 -0
  248. data/yard-stubs/carray_math.rb +132 -0
  249. data/yard-stubs/carray_mathfunc.rb +45 -0
  250. data/yard-stubs/carray_median_percentile.rb +89 -0
  251. data/yard-stubs/carray_memory_view.rb +163 -0
  252. data/yard-stubs/carray_order.rb +312 -0
  253. data/yard-stubs/carray_random.rb +89 -0
  254. data/yard-stubs/carray_scatter.rb +106 -0
  255. data/yard-stubs/carray_slab.rb +57 -0
  256. data/yard-stubs/carray_sort.rb +163 -0
  257. data/yard-stubs/carray_test.rb +85 -0
  258. data/yard-stubs/carray_undef.rb +64 -0
  259. data/yard-stubs/carray_utils.rb +97 -0
  260. data/yard-stubs/ruby_carray.rb +193 -0
  261. metadata +220 -138
  262. data/Rakefile +0 -51
  263. data/TODO.md +0 -18
  264. data/ext/ca_iter_block.c +0 -257
  265. data/ext/ca_iter_dimension.c +0 -299
  266. data/ext/ca_iter_window.c +0 -214
  267. data/ext/ca_obj_mapping.c +0 -644
  268. data/ext/ca_obj_unbound_repeat.c +0 -529
  269. data/ext/carray_iterator.c +0 -641
  270. data/ext/carray_math.rb +0 -850
  271. data/ext/carray_numeric.c +0 -259
  272. data/ext/carray_sort_addr.c +0 -254
  273. data/ext/carray_stat.c +0 -2100
  274. data/ext/carray_stat_proc.rb +0 -1999
  275. data/ext/mkmath.rb +0 -741
  276. data/ext/ruby_ccomplex.c +0 -509
  277. data/ext/ruby_float_func.c +0 -86
  278. data/lib/carray/array.rb +0 -8
  279. data/lib/carray/autoload/autoload_base.rb +0 -19
  280. data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
  281. data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
  282. data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
  283. data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
  284. data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
  285. data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
  286. data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
  287. data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
  288. data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
  289. data/lib/carray/autoload/autoload_gem_random.rb +0 -8
  290. data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
  291. data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
  292. data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
  293. data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
  294. data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
  295. data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
  296. data/lib/carray/autoload/autoload_object_link.rb +0 -1
  297. data/lib/carray/autoload/autoload_object_pack.rb +0 -2
  298. data/lib/carray/autoload.rb +0 -141
  299. data/lib/carray/basic.rb +0 -191
  300. data/lib/carray/broadcast.rb +0 -101
  301. data/lib/carray/compose.rb +0 -315
  302. data/lib/carray/convert.rb +0 -115
  303. data/lib/carray/info.rb +0 -110
  304. data/lib/carray/io/imagemagick.rb +0 -235
  305. data/lib/carray/mask.rb +0 -102
  306. data/lib/carray/math/histogram.rb +0 -177
  307. data/lib/carray/math/recurrence.rb +0 -93
  308. data/lib/carray/object/ca_obj_iterator.rb +0 -50
  309. data/lib/carray/object/ca_obj_link.rb +0 -50
  310. data/lib/carray/object/ca_obj_pack.rb +0 -99
  311. data/lib/carray/obsolete.rb +0 -256
  312. data/lib/carray/ordering.rb +0 -181
  313. data/lib/carray/testing.rb +0 -51
  314. data/lib/carray/transform.rb +0 -109
  315. data/mailmap +0 -1
  316. data/misc/Methods.ja.md +0 -182
  317. data/misc/NOTE +0 -51
  318. data/spec/Classes/CABitfield_spec.rb +0 -58
  319. data/spec/Classes/CABlockIterator_spec.rb +0 -114
  320. data/spec/Classes/CABlock_spec.rb +0 -205
  321. data/spec/Classes/CAField_spec.rb +0 -39
  322. data/spec/Classes/CAGrid_spec.rb +0 -75
  323. data/spec/Classes/CAMap_spec.rb +0 -0
  324. data/spec/Classes/CAMapping_spec.rb +0 -105
  325. data/spec/Classes/CAObject_attribute_spec.rb +0 -33
  326. data/spec/Classes/CAObject_spec.rb +0 -33
  327. data/spec/Classes/CARefer_spec.rb +0 -93
  328. data/spec/Classes/CARepeat_spec.rb +0 -65
  329. data/spec/Classes/CASelect_spec.rb +0 -22
  330. data/spec/Classes/CAShift_spec.rb +0 -16
  331. data/spec/Classes/CAStruct_spec.rb +0 -71
  332. data/spec/Classes/CATranspose_spec.rb +0 -60
  333. data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
  334. data/spec/Classes/CAWindow_spec.rb +0 -54
  335. data/spec/Classes/CAWrap_spec.rb +0 -8
  336. data/spec/Classes/CArray_spec.rb +0 -184
  337. data/spec/Classes/CScalar_spec.rb +0 -55
  338. data/spec/Features/feature_130_spec.rb +0 -19
  339. data/spec/Features/feature_attributes_spec.rb +0 -280
  340. data/spec/Features/feature_boolean_spec.rb +0 -98
  341. data/spec/Features/feature_broadcast.rb +0 -116
  342. data/spec/Features/feature_cast_function.rb +0 -19
  343. data/spec/Features/feature_cast_spec.rb +0 -33
  344. data/spec/Features/feature_class_spec.rb +0 -84
  345. data/spec/Features/feature_complex_spec.rb +0 -42
  346. data/spec/Features/feature_composite_spec.rb +0 -124
  347. data/spec/Features/feature_convert_spec.rb +0 -46
  348. data/spec/Features/feature_copy_spec.rb +0 -123
  349. data/spec/Features/feature_creation_spec.rb +0 -84
  350. data/spec/Features/feature_element_spec.rb +0 -144
  351. data/spec/Features/feature_extream_spec.rb +0 -54
  352. data/spec/Features/feature_generate_spec.rb +0 -74
  353. data/spec/Features/feature_index_spec.rb +0 -69
  354. data/spec/Features/feature_mask_spec.rb +0 -580
  355. data/spec/Features/feature_math_spec.rb +0 -97
  356. data/spec/Features/feature_order_spec.rb +0 -146
  357. data/spec/Features/feature_ref_store_spec.rb +0 -209
  358. data/spec/Features/feature_serialization_spec.rb +0 -125
  359. data/spec/Features/feature_stat_spec.rb +0 -397
  360. data/spec/Features/feature_virtual_spec.rb +0 -48
  361. data/spec/Features/method_eq_spec.rb +0 -81
  362. data/spec/Features/method_is_nan_spec.rb +0 -12
  363. data/spec/Features/method_map_spec.rb +0 -54
  364. data/spec/Features/method_max_with.rb +0 -20
  365. data/spec/Features/method_min_with.rb +0 -19
  366. data/spec/Features/method_ne_spec.rb +0 -18
  367. data/spec/Features/method_project_spec.rb +0 -188
  368. data/spec/Features/method_ref_spec.rb +0 -27
  369. data/spec/Features/method_round_spec.rb +0 -11
  370. data/spec/Features/method_s_linspace_spec.rb +0 -48
  371. data/spec/Features/method_s_span_spec.rb +0 -14
  372. data/spec/Features/method_seq_spec.rb +0 -47
  373. data/spec/Features/method_sort_with.rb +0 -43
  374. data/spec/Features/method_sorted_with.rb +0 -29
  375. data/spec/Features/method_span_spec.rb +0 -42
  376. data/spec/Features/method_wrap_readonly_spec.rb +0 -43
  377. data/spec/UnitTest/test_CAVirtual.rb +0 -214
  378. data/spec/spec_all.rb +0 -10
  379. data/utils/ca_ase.rb +0 -21
  380. data/utils/ca_methods.rb +0 -15
  381. data/utils/cast_checker.rb +0 -30
  382. data/utils/convert_test.rb +0 -73
  383. data/utils/extract_yard.rb +0 -22
  384. data/utils/guess_shape.rb +0 -76
  385. data/utils/monkey_patch_methods.rb +0 -62
  386. data/utils/remove_resource_fork.sh +0 -5
data/ext/carray_core.c CHANGED
@@ -1,14 +1,15 @@
1
1
  /* ---------------------------------------------------------------------------
2
2
 
3
- carray_core.c
4
-
5
- This file is part of Ruby/CArray extension library.
6
-
7
- Copyright (C) 2005-2020 Hiroki Motoyoshi
3
+ Core runtime: obj_type registration (ca_install_obj_type), the
4
+ ca_func / ca_class / ca_typeddata dispatch tables, TypedData mark /
5
+ free, the attach lifecycle, and the per-cell / per-region / whole-view
6
+ transfer primitives (ca_xfer_index / _addrs / _stride / _all).
8
7
 
9
8
  ---------------------------------------------------------------------------- */
10
9
 
11
10
  #include "carray.h"
11
+ #include "carray_internal.h" /* ca_lazy_arena_enter / _exit */
12
+ #include "ca_iter_substrate.h"
12
13
  #include <stdarg.h>
13
14
 
14
15
  /* definition of ca_endian */
@@ -92,11 +93,7 @@ ca_valid[CA_NTYPE] = {
92
93
  #else
93
94
  0,
94
95
  #endif
95
- #ifdef HAVE_TYPE_FLOAT128_T
96
- 1,
97
- #else
98
- 0,
99
- #endif
96
+ 0, /* CA_FLOAT128 (not built) */
100
97
  #ifdef HAVE_TYPE_CMPLX64_T
101
98
  1,
102
99
  #else
@@ -107,11 +104,7 @@ ca_valid[CA_NTYPE] = {
107
104
  #else
108
105
  0,
109
106
  #endif
110
- #ifdef HAVE_TYPE_CMPLX256_T
111
- 1,
112
- #else
113
- 0,
114
- #endif
107
+ 0, /* CA_CMPLX256 (not built) */
115
108
  1
116
109
  };
117
110
 
@@ -131,10 +124,10 @@ ca_sizeof[CA_NTYPE] = {
131
124
  sizeof(uint64_t),
132
125
  sizeof(float32_t),
133
126
  sizeof(float64_t),
134
- sizeof(float128_t),
127
+ 0, /* float128_t (not built) */
135
128
  sizeof(cmplx64_t),
136
129
  sizeof(cmplx128_t),
137
- sizeof(cmplx256_t),
130
+ 0, /* cmplx256_t (not built) */
138
131
  sizeof(VALUE),
139
132
  };
140
133
 
@@ -154,10 +147,10 @@ ca_type_name[CA_NTYPE] = {
154
147
  "uint64",
155
148
  "float32",
156
149
  "float64",
157
- "float128",
150
+ "(retired:float128)",
158
151
  "cmplx64",
159
152
  "cmplx128",
160
- "cmplx256",
153
+ "(retired:cmplx256)",
161
154
  "object",
162
155
  };
163
156
 
@@ -238,21 +231,17 @@ ca_cast_table2[CA_NTYPE][CA_NTYPE] = {
238
231
  * CASelect
239
232
  * CAObject
240
233
  * CARepeat
241
- * CAUnboundRepeat
242
234
  */
243
235
 
244
236
  void
245
- ca_init_obj_type ()
237
+ ca_init_obj_type (void)
246
238
  {
247
239
  extern ca_operation_function_t ca_array_func;
248
240
  extern ca_operation_function_t ca_wrap_func;
249
241
  extern ca_operation_function_t ca_scalar_func;
250
- extern ca_operation_function_t ca_refer_func;
251
- extern ca_operation_function_t ca_block_func;
252
242
  extern ca_operation_function_t ca_select_func;
253
243
  extern ca_operation_function_t ca_object_func;
254
- extern ca_operation_function_t ca_repeat_func;
255
- extern ca_operation_function_t ca_ubrep_func;
244
+ extern ca_operation_function_t ca_stride_func;
256
245
 
257
246
  /* CArray */
258
247
  ca_func[CA_OBJ_ARRAY] = ca_array_func;
@@ -275,15 +264,22 @@ ca_init_obj_type ()
275
264
  ca_mask_class[CA_OBJ_SCALAR] = rb_cCArrayMask;
276
265
  ca_mask_typeddata[CA_OBJ_SCALAR] = &carray_mask_data_type;
277
266
 
278
- /* CARefer */
279
- ca_func[CA_OBJ_REFER] = ca_refer_func;
267
+ /* CARefer (CAStride subclass). The function table is
268
+ installed as ca_stride_func here as a baseline; Init_ca_obj_refer
269
+ overrides ca_func[CA_OBJ_REFER] with a copy that has custom
270
+ free_object (frees the mask0 tail) and custom create_mask
271
+ (handles byte-reinterpret cases). */
272
+ ca_func[CA_OBJ_REFER] = ca_stride_func;
280
273
  ca_class[CA_OBJ_REFER] = rb_cCARefer;
281
274
  ca_typeddata[CA_OBJ_REFER] = &carefer_data_type;
282
275
  ca_mask_class[CA_OBJ_REFER] = rb_cCAReferMask;
283
276
  ca_mask_typeddata[CA_OBJ_REFER] = &carefer_mask_data_type;
284
277
 
285
- /* CABlock */
286
- ca_func[CA_OBJ_BLOCK] = ca_block_func;
278
+ /* CABlock (CAStride subclass). Baseline registered to
279
+ ca_stride_func; Init_ca_obj_block overrides with a copy that has
280
+ custom free_object (frees the tail arrays) and custom create_mask
281
+ (builds the mask as a CABlock with matching block parameters). */
282
+ ca_func[CA_OBJ_BLOCK] = ca_stride_func;
287
283
  ca_class[CA_OBJ_BLOCK] = rb_cCABlock;
288
284
  ca_typeddata[CA_OBJ_BLOCK] = &cablock_data_type;
289
285
  ca_mask_class[CA_OBJ_BLOCK] = rb_cCABlockMask;
@@ -303,19 +299,14 @@ ca_init_obj_type ()
303
299
  ca_mask_class[CA_OBJ_OBJECT] = rb_cCArrayMask;
304
300
  ca_mask_typeddata[CA_OBJ_OBJECT] = &carray_mask_data_type;
305
301
 
306
- /* CARepeat */
307
- ca_func[CA_OBJ_REPEAT] = ca_repeat_func;
302
+ /* CARepeat (subclass of CAStride; shares its function table and
303
+ TypedData entirely). */
304
+ ca_func[CA_OBJ_REPEAT] = ca_stride_func;
308
305
  ca_class[CA_OBJ_REPEAT] = rb_cCARepeat;
309
- ca_typeddata[CA_OBJ_REPEAT] = &carepeat_data_type;
306
+ ca_typeddata[CA_OBJ_REPEAT] = &castride_data_type;
310
307
  ca_mask_class[CA_OBJ_REPEAT] = rb_cCARepeatMask;
311
- ca_mask_typeddata[CA_OBJ_REPEAT] = &carepeat_mask_data_type;
308
+ ca_mask_typeddata[CA_OBJ_REPEAT] = &castride_mask_data_type;
312
309
 
313
- /* CAUnboundRepeat */
314
- ca_func[CA_OBJ_UNBOUND_REPEAT] = ca_ubrep_func;
315
- ca_class[CA_OBJ_UNBOUND_REPEAT] = rb_cCAUnboundRepeat;
316
- ca_typeddata[CA_OBJ_UNBOUND_REPEAT] = &caunboundrepeat_data_type;
317
- ca_mask_class[CA_OBJ_UNBOUND_REPEAT] = rb_cCAUnboundRepeatMask;
318
- ca_mask_typeddata[CA_OBJ_UNBOUND_REPEAT] = &caunboundrepeat_mask_data_type;
319
310
 
320
311
  ca_obj_num = 9;
321
312
  }
@@ -325,11 +316,12 @@ ca_init_obj_type ()
325
316
  */
326
317
 
327
318
  int
328
- ca_install_obj_type (VALUE klass,
329
- const rb_data_type_t *typeddata,
330
- VALUE mask_klass,
331
- const rb_data_type_t *mask_typeddata,
332
- ca_operation_function_t func)
319
+ ca_install_obj_type (VALUE klass,
320
+ const rb_data_type_t *typeddata,
321
+ VALUE mask_klass,
322
+ const rb_data_type_t *mask_typeddata,
323
+ const ca_operation_function_t *func,
324
+ size_t func_size)
333
325
  {
334
326
  int obj_type = ca_obj_num++;
335
327
 
@@ -339,10 +331,31 @@ ca_install_obj_type (VALUE klass,
339
331
  CA_OBJ_TYPE_MAX);
340
332
  }
341
333
 
342
- func.obj_type = obj_type;
334
+ if ( func_size > sizeof(ca_operation_function_t) ) {
335
+ rb_raise(rb_eRuntimeError,
336
+ "operation table is larger than this carray's (%zu > %zu); "
337
+ "the caller was built against a newer carray",
338
+ func_size, sizeof(ca_operation_function_t));
339
+ }
340
+
341
+ /* Everything from xfer_index on was appended after the table's first
342
+ shape, so a caller may legitimately stop short of it. Anything shorter
343
+ than that cannot dispatch at all. */
344
+ if ( func_size < offsetof(ca_operation_function_t, xfer_index) ) {
345
+ rb_raise(rb_eRuntimeError,
346
+ "operation table is too small to dispatch (%zu < %zu)",
347
+ func_size,
348
+ (size_t) offsetof(ca_operation_function_t, xfer_index));
349
+ }
350
+
351
+ /* Copy by the caller's length and zero the rest: slots this build knows
352
+ about but the caller does not are NULL, which every dispatcher already
353
+ reads as "not provided". */
354
+ MEMZERO(&ca_func[obj_type], ca_operation_function_t, 1);
355
+ memcpy(&ca_func[obj_type], func, func_size);
356
+ ca_func[obj_type].obj_type = obj_type;
343
357
 
344
358
  ca_class[obj_type] = klass;
345
- ca_func[obj_type] = func;
346
359
  ca_typeddata[obj_type] = typeddata;
347
360
  ca_mask_class[obj_type] = mask_klass;
348
361
  ca_mask_typeddata[obj_type] = mask_typeddata;
@@ -396,47 +409,412 @@ ca_free_nop (void *ap)
396
409
 
397
410
  /* ------------------------------------------------------------------- */
398
411
 
412
+ /* Returns true iff ca_attach(ca) is essentially O(1) (= no malloc /
413
+ copy):
414
+ - entity arrays (CA_REAL_ARRAY: already attached, ca->ptr valid)
415
+ - CAStride-family views whose composed strides are row-major
416
+ contiguous (the alias path takes parent->ptr + base_offset
417
+ without allocating)
418
+ Used by kernel_iterator's L1 alias decision (ca_iter_can_alias level 1)
419
+ and by overlay view dispatch. The name says "is_alias" (a structural
420
+ property) rather than "is_cheap" (a cost claim): the predicate checks
421
+ whether ca can be aliased without materialise. */
422
+ int
423
+ ca_attach_is_alias (void *ap)
424
+ {
425
+ CArray *ca = (CArray *) ap;
426
+ extern ca_operation_function_t ca_stride_func;
427
+ extern ca_operation_function_t ca_lazy_marker_func;
428
+ extern int ca_stride_is_contiguous (CAStride *ca);
429
+ extern int ca_stride_attach_aliases_root (CAStride *ca);
430
+
431
+ if ( ca == NULL ) return 0;
432
+ if ( ca_is_entity(ca) ) return 1;
433
+
434
+ /* CALazyMarker's attach is literally `ca->ptr = ca->parent->ptr` after
435
+ attaching the parent — it adds no layout of its own — so it aliases
436
+ exactly when its parent does. Without this a marker looks expensive
437
+ to every caller and views built on it fall onto materialising paths,
438
+ even though there is nothing between the marker and real memory. */
439
+ if ( ca_func[ca->obj_type].attach == ca_lazy_marker_func.attach ) {
440
+ return ca_attach_is_alias(((CAView *) ca)->parent);
441
+ }
442
+
443
+ /* CAStride family share ca_stride_func.attach (= ca_stride_func_attach).
444
+ The alias-attach fast path is taken iff composed strides are
445
+ row-major contiguous. ca_stride_is_contiguous checks the leaf
446
+ view's own strides, which is what we want — the compose-fold to
447
+ root happens during attach itself; if leaf is contig and parent
448
+ chain is too (transitively, since each CAStride's strides are
449
+ composed against parent's), the alias path fires.
450
+
451
+ ...and iff there is parent memory to alias at the end of that fold.
452
+ A non-entity root has none to lend, so attach builds its own buffer
453
+ and writes through ca->ptr reach the root only via ca_sync. Saying
454
+ "alias" there would let a caller write and skip the sync. */
455
+ if ( ca_func[ca->obj_type].attach == ca_stride_func.attach ) {
456
+ return ca_stride_is_contiguous((CAStride *) ca)
457
+ && ca_stride_attach_aliases_root((CAStride *) ca);
458
+ }
459
+ return 0;
460
+ }
461
+
462
+ /* ------------------------------------------------------------------- */
463
+
464
+ /* Allocate a reduction-output CArray for kernel_iterator authors.
465
+
466
+ Arguments:
467
+ self Ruby VALUE wrapping the input CArray (source of the reduction).
468
+ slab_axes sort-ascending list of axis indices that the kernel
469
+ will walk per slab (= the axes removed from the output).
470
+ May contain any K in [1..self.ndim]; the helper
471
+ validates range and uniqueness.
472
+ naxes length of slab_axes. Must satisfy 0 < naxes <= self.ndim.
473
+ data_type output data_type (CA_INT32 / CA_FLOAT64 / ... — any numeric
474
+ data_type with non-zero ca_sizeof[]). May differ from
475
+ self's data_type (e.g. mean of int32 → float64).
476
+
477
+ Output shape:
478
+ - Partial reduction (naxes < self.ndim): self.dim with slab axes
479
+ removed in ascending order, ndim = self.ndim - naxes.
480
+ - Full reduction (naxes == self.ndim): shape [1] 1-D CArray
481
+ (kernel writes op[0] and the author wraps the result to a Ruby
482
+ Float / CScalar at their own discretion).
483
+
484
+ Same axis-validation rules as init_l2 CA_SLAB_AXES (= duplicates and
485
+ out-of-range raise ArgumentError so author input bugs surface here
486
+ rather than at the slab walk). Mask is NULL on the output (=
487
+ reduction kernels populate it themselves if needed). */
488
+ VALUE
489
+ rb_ca_new_reduced_bytes (VALUE self, int8_t *slab_axes, int8_t naxes,
490
+ int32_t data_type, ca_size_t bytes, int keep_axis)
491
+ {
492
+ CArray *ca;
493
+ int8_t k;
494
+
495
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
496
+
497
+ if ( naxes <= 0 || naxes > ca->ndim ) {
498
+ rb_raise(rb_eArgError,
499
+ "rb_ca_new_reduced: naxes=%d invalid for ndim=%d",
500
+ (int) naxes, (int) ca->ndim);
501
+ }
502
+
503
+ int8_t in_slab[CA_RANK_MAX];
504
+ for ( k = 0; k < CA_RANK_MAX; k++ ) in_slab[k] = 0;
505
+ for ( k = 0; k < naxes; k++ ) {
506
+ int8_t ax = slab_axes[k];
507
+ if ( ax < 0 || ax >= ca->ndim ) {
508
+ rb_raise(rb_eArgError,
509
+ "rb_ca_new_reduced: slab_axes[%d]=%d out of range [0, %d)",
510
+ (int) k, (int) ax, (int) ca->ndim);
511
+ }
512
+ if ( in_slab[ax] ) {
513
+ rb_raise(rb_eArgError,
514
+ "rb_ca_new_reduced: duplicate axis %d in slab_axes", (int) ax);
515
+ }
516
+ in_slab[ax] = 1;
517
+ }
518
+
519
+ ca_size_t out_dim[CA_RANK_MAX];
520
+ int8_t out_ndim = 0;
521
+ for ( k = 0; k < ca->ndim; k++ ) {
522
+ if ( !in_slab[k] ) {
523
+ out_dim[out_ndim++] = ca->dim[k];
524
+ }
525
+ else if ( keep_axis ) {
526
+ /* keep_axis: retain each reduced axis as a length-1 axis instead
527
+ of dropping it (= automation of view[..., :_]).
528
+ Element count and row-major order are unchanged, so the kernel
529
+ slab walk writes the output identically. */
530
+ out_dim[out_ndim++] = 1;
531
+ }
532
+ }
533
+ if ( out_ndim == 0 ) {
534
+ /* Full reduction without keep_axis: collapse to a 1-element array.
535
+ (With keep_axis, full reduction already produced [1, 1, ..., 1]
536
+ above so out_ndim == ca->ndim and this branch is not taken.) */
537
+ out_dim[0] = 1;
538
+ out_ndim = 1;
539
+ }
540
+
541
+ if ( data_type < 0 || data_type >= CA_NTYPE ) {
542
+ rb_raise(rb_eArgError,
543
+ "rb_ca_new_reduced: data_type=%d out of range", (int) data_type);
544
+ }
545
+ if ( bytes <= 0 ) {
546
+ rb_raise(rb_eArgError,
547
+ "rb_ca_new_reduced: bytes=%ld invalid for data_type=%d",
548
+ (long) bytes, (int) data_type);
549
+ }
550
+
551
+ return rb_carray_new(data_type, out_ndim, out_dim, bytes, NULL);
552
+ }
553
+
554
+ /* Fixed-element-size wrapper: the byte width is looked up from ca_sizeof,
555
+ which rejects CA_FIXLEN / CA_OBJECT (runtime-width / VALUE cells). A
556
+ reduction whose output is a runtime-width data_type (fixlen min / max)
557
+ must call rb_ca_new_reduced_bytes with the source's own byte width. */
558
+ VALUE
559
+ rb_ca_new_reduced (VALUE self, int8_t *slab_axes, int8_t naxes, int32_t data_type,
560
+ int keep_axis)
561
+ {
562
+ if ( data_type < 0 || data_type >= CA_NTYPE ) {
563
+ rb_raise(rb_eArgError,
564
+ "rb_ca_new_reduced: data_type=%d out of range", (int) data_type);
565
+ }
566
+ ca_size_t bytes = ca_sizeof[data_type];
567
+ if ( bytes <= 0 ) {
568
+ rb_raise(rb_eArgError,
569
+ "rb_ca_new_reduced: data_type=%d unsupported (CA_FIXLEN/OBJECT not supported)",
570
+ (int) data_type);
571
+ }
572
+ return rb_ca_new_reduced_bytes(self, slab_axes, naxes, data_type, bytes, keep_axis);
573
+ }
574
+
575
+ /* ------------------------------------------------------------------- */
576
+
577
+ /* Parse the variadic axis argument of a reduction kernel into a
578
+ sort-ascending
579
+ int8_t array, with full validation.
580
+
581
+ Accepts:
582
+ - Integer args: kernel(0, 2, 3) → axes = {0, 2, 3}
583
+ - Single Array arg: kernel([0, 2, 3]) → axes = {0, 2, 3}
584
+ - Negative axes (Python-style): -1 = innermost, normalised to
585
+ positive in-range indices before validation
586
+
587
+ Validation (raises ArgumentError on failure):
588
+ - argc == 0 → no axes given
589
+ - naxes > ca->ndim → too many axes
590
+ - any axis out of [0, ca->ndim) → range
591
+ - duplicate axes → duplicate
592
+
593
+ Returns the validated naxes (= count of axes written to out_axes[]).
594
+ out_axes[] is filled with the parsed axes in *input order* (= NOT
595
+ pre-sorted; canonicalisation to ascending order happens inside
596
+ init_l2 CA_SLAB_AXES, so callers can pass the user's order directly). */
597
+ /* Core validation: takes raw items[] (each must be Integer / Symbol-able
598
+ to NUM2SIZE) of length `count`, normalises + range-checks + duplicate-
599
+ checks, fills out_axes[] in input order. Shared between the legacy
600
+ variadic entry (rb_ca_parse_reduce_axes) and the kwarg entry
601
+ (rb_ca_parse_reduce_axes_kw). ctx is a short label embedded in error
602
+ messages so callers can disambiguate which entry raised. */
603
+ static int8_t
604
+ parse_axes_items (const VALUE *items, int count, CArray *ca,
605
+ int8_t *out_axes, const char *ctx)
606
+ {
607
+ int i;
608
+ int8_t seen[CA_RANK_MAX];
609
+
610
+ if ( count <= 0 ) {
611
+ rb_raise(rb_eArgError, "%s: empty axes array", ctx);
612
+ }
613
+ if ( count > CA_RANK_MAX ) {
614
+ rb_raise(rb_eArgError,
615
+ "%s: too many axes (%d > CA_RANK_MAX=%d)",
616
+ ctx, count, CA_RANK_MAX);
617
+ }
618
+ if ( count > ca->ndim ) {
619
+ rb_raise(rb_eArgError,
620
+ "%s: too many axes (%d > ndim=%d)",
621
+ ctx, count, (int) ca->ndim);
622
+ }
623
+
624
+ for ( i = 0; i < CA_RANK_MAX; i++ ) seen[i] = 0;
625
+ for ( i = 0; i < count; i++ ) {
626
+ ca_size_t a = NUM2SIZE(items[i]);
627
+ if ( a < 0 ) a += ca->ndim;
628
+ if ( a < 0 || a >= ca->ndim ) {
629
+ rb_raise(rb_eIndexError,
630
+ "%s: axis %ld out of range [0, %d)",
631
+ ctx, (long) a, (int) ca->ndim);
632
+ }
633
+ if ( seen[a] ) {
634
+ rb_raise(rb_eArgError,
635
+ "%s: duplicate axis %ld", ctx, (long) a);
636
+ }
637
+ seen[a] = 1;
638
+ out_axes[i] = (int8_t) a;
639
+ }
640
+
641
+ return (int8_t) count;
642
+ }
643
+
644
+ int8_t
645
+ rb_ca_parse_reduce_axes (int argc, VALUE *argv, CArray *ca, int8_t *out_axes)
646
+ {
647
+ int i;
648
+
649
+ /* argc == 0 means "full reduction over all axes" -- matches legacy
650
+ CArray#sum etc. This contract keeps the ki kernels drop-in
651
+ replacements for the legacy stat dispatchers. */
652
+ if ( argc <= 0 ) {
653
+ for ( i = 0; i < ca->ndim; i++ ) {
654
+ out_axes[i] = (int8_t) i;
655
+ }
656
+ return (int8_t) ca->ndim;
657
+ }
658
+
659
+ /* Detect single-Array call form: foo([0, 2]) */
660
+ const VALUE *items = (const VALUE *) argv;
661
+ int count = argc;
662
+ if ( argc == 1 && TYPE(argv[0]) == T_ARRAY ) {
663
+ VALUE arr = argv[0];
664
+ count = (int) RARRAY_LEN(arr);
665
+ items = (const VALUE *) RARRAY_CONST_PTR(arr);
666
+ }
667
+
668
+ return parse_axes_items(items, count, ca, out_axes,
669
+ "rb_ca_parse_reduce_axes");
670
+ }
671
+
672
+ /* Kwarg form of rb_ca_parse_reduce_axes — accepts the `axis:` value as
673
+ extracted by the caller via rb_scan_args(..., "0:", &kw_hash) +
674
+ rb_get_kwargs (or equivalent), and dispatches:
675
+
676
+ axis_val == Qnil or Qundef → full reduction (= all axes)
677
+ axis_val Integer → single axis (negative normalised)
678
+ axis_val Array of Integer → multiple axes in input order
679
+ anything else → TypeError
680
+
681
+ Validation (range / duplicates / overflow) is identical to the
682
+ variadic entry. out_axes[] receives axes in input order. Returns
683
+ the validated naxes. */
684
+ int8_t
685
+ rb_ca_parse_reduce_axes_kw_ctx (VALUE axis_val, CArray *ca, int8_t *out_axes,
686
+ const char *ctx)
687
+ {
688
+ int i;
689
+
690
+ if ( axis_val == Qnil || axis_val == Qundef ) {
691
+ for ( i = 0; i < ca->ndim; i++ ) {
692
+ out_axes[i] = (int8_t) i;
693
+ }
694
+ return (int8_t) ca->ndim;
695
+ }
696
+
697
+ if ( TYPE(axis_val) == T_ARRAY ) {
698
+ int count = (int) RARRAY_LEN(axis_val);
699
+ const VALUE *items = (const VALUE *) RARRAY_CONST_PTR(axis_val);
700
+ return parse_axes_items(items, count, ca, out_axes, ctx);
701
+ }
702
+
703
+ if ( rb_obj_is_kind_of(axis_val, rb_cInteger) ) {
704
+ return parse_axes_items(&axis_val, 1, ca, out_axes, ctx);
705
+ }
706
+
707
+ rb_raise(rb_eTypeError,
708
+ "%s: axis: must be nil, Integer, or "
709
+ "Array of Integer (got %"PRIsVALUE")",
710
+ ctx, rb_obj_class(axis_val));
711
+ }
712
+
713
+ int8_t
714
+ rb_ca_parse_reduce_axes_kw (VALUE axis_val, CArray *ca, int8_t *out_axes)
715
+ {
716
+ return rb_ca_parse_reduce_axes_kw_ctx(axis_val, ca, out_axes,
717
+ "rb_ca_parse_reduce_axes_kw");
718
+ }
719
+
720
+ /* ------------------------------------------------------------------- */
721
+
722
+ /* api: ca_wrap_struct_as
723
+ wraps CArray struct in C -> Ruby's object, with the Ruby class chosen
724
+ by the caller instead of taken from ca_class[obj_type].
725
+
726
+ The TypedData tag still comes from obj_type. Only the class is the
727
+ caller's; the tag is what GetCArray and every dispatch path look at,
728
+ so decoupling the two leaves those paths untouched. klass must be a
729
+ subclass of the class registered for obj_type -- that is the caller's
730
+ responsibility, not checked here.
731
+ */
732
+
733
+ VALUE
734
+ ca_wrap_struct_as (void *ap, VALUE klass)
735
+ {
736
+ CArray *ca = (CArray *) ap;
737
+ return TypedData_Wrap_Struct(klass, ca_typeddata[ca->obj_type], ca);
738
+ }
739
+
399
740
  /* api: ca_wrap_struct
400
- wraps CArray struct in C -> Ruby's object
741
+ wraps CArray struct in C -> Ruby's object
401
742
  */
402
743
 
403
744
  VALUE
404
745
  ca_wrap_struct (void *ap)
405
746
  {
406
747
  CArray *ca = (CArray *) ap;
407
- return TypedData_Wrap_Struct(ca_class[ca->obj_type], ca_typeddata[ca->obj_type], ca);
748
+ return ca_wrap_struct_as(ap, ca_class[ca->obj_type]);
408
749
  }
409
750
 
410
751
  /* ------------------------------------------------------------------- */
411
752
 
412
- /* calculate index from address */
753
+ /* calculate index from address.
754
+ Hot path: called per fetch_addr dispatch when the view has no
755
+ dedicated fetch_addr slot (= CAStride family and others that only
756
+ implement fetch_index). 1-D / 2-D / 3-D fast paths skip the
757
+ generic divmod loop; higher-ndim falls through. */
413
758
 
414
759
  void
415
760
  ca_addr2index (void *ap, ca_size_t addr, ca_size_t *idx)
416
761
  {
417
762
  CArray *ca = (CArray *) ap;
418
763
  ca_size_t *dim = ca->dim;
764
+ int8_t ndim = ca->ndim;
419
765
  int8_t i;
420
- for (i=ca->ndim-1; i>=0; i--) {
421
- idx[i] = addr % dim[i];
422
- addr /= dim[i];
766
+ switch (ndim) {
767
+ case 1:
768
+ idx[0] = addr;
769
+ return;
770
+ case 2: {
771
+ ca_size_t d1 = dim[1];
772
+ idx[1] = addr % d1;
773
+ idx[0] = addr / d1;
774
+ return;
775
+ }
776
+ case 3: {
777
+ ca_size_t d1 = dim[1], d2 = dim[2];
778
+ idx[2] = addr % d2;
779
+ addr /= d2;
780
+ idx[1] = addr % d1;
781
+ idx[0] = addr / d1;
782
+ return;
783
+ }
784
+ default:
785
+ for (i = ndim - 1; i >= 0; i--) {
786
+ idx[i] = addr % dim[i];
787
+ addr /= dim[i];
788
+ }
423
789
  }
424
790
  }
425
791
 
426
- /* calculate address from index */
792
+ /* calculate address from index. 1-D / 2-D / 3-D fast paths skip the
793
+ loop entirely. Compiler typically inlines the generic loop fine
794
+ but the fast paths help where this function is called via pointer. */
427
795
 
428
796
  ca_size_t
429
797
  ca_index2addr (void *ap, ca_size_t *idx)
430
798
  {
431
799
  CArray *ca = (CArray *) ap;
432
800
  ca_size_t *dim = ca->dim;
433
- int8_t i;
434
- ca_size_t n;
435
- n = idx[0];
436
- for (i=1; i<ca->ndim; i++) {
437
- n = dim[i]*n+idx[i];
801
+ int8_t ndim = ca->ndim;
802
+ int8_t i;
803
+ ca_size_t n;
804
+ switch (ndim) {
805
+ case 1:
806
+ return idx[0];
807
+ case 2:
808
+ return dim[1] * idx[0] + idx[1];
809
+ case 3:
810
+ return (dim[1] * idx[0] + idx[1]) * dim[2] + idx[2];
811
+ default:
812
+ n = idx[0];
813
+ for (i = 1; i < ndim; i++) {
814
+ n = dim[i] * n + idx[i];
815
+ }
816
+ return n;
438
817
  }
439
- return n;
440
818
  }
441
819
 
442
820
  /* ------------------------------------------------------------------- */
@@ -489,169 +867,750 @@ ca_test_cyclic_check(void *ap, void *ptr)
489
867
  }
490
868
 
491
869
  /* ------------------------------------------------------------------- */
870
+ /* CArray offers no per-cell ptr accessor (ca_ptr_at_addr /
871
+ ca_ptr_at_index): such a slot is structurally unsafe -- CABitarray /
872
+ CABitfield have no byte-addressable cell, and CAByteSwap / CAFake would
873
+ hand back bytes in the wrong data_type / endian. Internal code that
874
+ already holds an attached view uses direct `ca->ptr + ca->bytes * addr`
875
+ arithmetic; external ext gems use ca_fetch_addr / ca_fetch_index
876
+ (data_type-correct via the xfer_addrs / xfer_index dispatch) for
877
+ per-cell access. */
878
+
879
+ /* fetch / store at a single linear address: thin wrappers over
880
+ ca_xfer_addrs. */
492
881
 
493
- /* return pointer of the element at given address */
882
+ void
883
+ ca_fetch_addr (void *ap, ca_size_t addr, void *pval)
884
+ {
885
+ ca_xfer_addrs(ap, 1, &addr, pval, CA_XFER_GET);
886
+ }
494
887
 
495
- void *
496
- ca_ptr_at_addr (void *ap, ca_size_t addr)
888
+ void
889
+ ca_store_addr (void *ap, ca_size_t addr, void *pval)
497
890
  {
498
- CArray *ca = (CArray *) ap;
891
+ ca_xfer_addrs(ap, 1, &addr, pval, CA_XFER_PUT);
892
+ }
499
893
 
500
- if ( ca->ptr ) {
501
- switch ( ca->obj_type ) {
502
- case CA_OBJ_SCALAR:
503
- return ca->ptr;
504
- case CA_OBJ_REFER:
505
- return ((CARefer*)ca)->parent->ptr + ca->bytes * addr;
506
- default:
507
- return ca->ptr + ca->bytes * addr;
508
- }
509
- }
894
+ /* per-cell transfer by multi-dim index. ca_xfer_index is the primary
895
+ entry; ca_fetch_index / ca_store_index are thin wrappers (kept as
896
+ public C-API for external ext gems). Every view supplies an
897
+ xfer_index slot. */
510
898
 
511
- return ca_func[ca->obj_type].ptr_at_addr(ap, addr);
899
+ static inline void
900
+ ca_xfer_index_dispatch (CArray *ca, ca_size_t *idx, void *data, int dir)
901
+ {
902
+ if ( ! ca_func[ca->obj_type].xfer_index ) {
903
+ rb_raise(rb_eRuntimeError,
904
+ "[BUG] xfer_index not defined for object type <%i>",
905
+ ca->obj_type);
906
+ }
907
+ ca_func[ca->obj_type].xfer_index(ca, idx, data, dir);
512
908
  }
513
909
 
514
- /* return pointer of the element at given index */
910
+ struct ca_xfer_index_args {
911
+ CArray *ca;
912
+ ca_size_t *idx;
913
+ char *ptr;
914
+ };
515
915
 
516
- void *
517
- ca_ptr_at_index (void *ap, ca_size_t *idx)
916
+ static VALUE
917
+ ca_xfer_index_get_body (VALUE arg)
518
918
  {
519
- CArray *ca = (CArray *) ap;
520
- return ca_func[ca->obj_type].ptr_at_index(ca, idx);
919
+ struct ca_xfer_index_args *d = (struct ca_xfer_index_args *) arg;
920
+ ca_xfer_index_dispatch(d->ca, d->idx, d->ptr, CA_XFER_GET);
921
+ ca_test_cyclic_check(d->ca, d->ptr);
922
+ return Qnil;
521
923
  }
522
924
 
523
- /* fetch data of the element at given address to memory pointed by pval */
524
-
525
925
  void
526
- ca_fetch_addr (void *ap, ca_size_t addr, void *pval)
926
+ ca_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
527
927
  {
528
928
  CArray *ca = (CArray *) ap;
529
- char *ptr = (char *)pval;
530
-
531
- ca_set_cyclic_check(ca);
532
929
 
533
- if ( ca->ptr ) {
534
- memcpy(ptr, ca->ptr + ca->bytes * addr, ca->bytes);
930
+ if ( dir == CA_XFER_PUT && ca_is_readonly(ca) ) {
931
+ rb_raise(rb_eRuntimeError, "can not store data to read-only array");
535
932
  }
536
- else if ( ca_func[ca->obj_type].fetch_addr ) {
537
- ca_func[ca->obj_type].fetch_addr(ca, addr, ptr);
933
+
934
+ /* Fast path: non-CA_OBJECT element type needs no GC protection. */
935
+ if ( ca->data_type != CA_OBJECT ) {
936
+ ca_xfer_index_dispatch(ca, idx, data, dir);
937
+ return;
538
938
  }
539
- else if ( ca_func[ca->obj_type].fetch_index ) { /* delegate -> fetch_index */
540
- ca_size_t idx[CA_RANK_MAX];
541
- ca_addr2index(ca, addr, idx);
542
- ca_func[ca->obj_type].fetch_index(ca, idx, ptr);
939
+
940
+ /* CA_OBJECT slow path: cyclic check (+ rb_protect on GET). */
941
+ if ( dir == CA_XFER_GET ) {
942
+ struct ca_xfer_index_args args;
943
+ int state = 0;
944
+ args.ca = ca;
945
+ args.idx = idx;
946
+ args.ptr = (char *) data;
947
+ ca_set_cyclic_check(ca);
948
+ rb_protect(ca_xfer_index_get_body, (VALUE) &args, &state);
949
+ ca_clear_cyclic_check(ca);
950
+ if ( state ) {
951
+ rb_jump_tag(state);
952
+ }
543
953
  }
544
954
  else {
545
- rb_raise(rb_eRuntimeError,
546
- "[BUG] fetch_addr or fetch_index " \
547
- "are not defined for object type <%i>",
548
- ca->obj_type);
955
+ ca_set_cyclic_check(ca);
956
+ ca_xfer_index_dispatch(ca, idx, data, CA_XFER_PUT);
957
+ ca_clear_cyclic_check(ca);
549
958
  }
959
+ }
550
960
 
551
- ca_test_cyclic_check(ca, ptr);
961
+ /* fetch data of the element at given index to memory pointed by pval */
552
962
 
553
- ca_clear_cyclic_check(ca);
963
+ void
964
+ ca_fetch_index (void *ap, ca_size_t *idx, void *pval)
965
+ {
966
+ ca_xfer_index(ap, idx, pval, CA_XFER_GET);
554
967
  }
555
968
 
556
- /* store value pointed by pval to the element at given address */
969
+ /* store value pointed by pval to the element at given index */
557
970
 
558
971
  void
559
- ca_store_addr (void *ap, ca_size_t addr, void *pval)
972
+ ca_store_index (void *ap, ca_size_t *idx, void *pval)
560
973
  {
561
- CArray *ca = (CArray *) ap;
562
- char *ptr = (char *)pval;
974
+ ca_xfer_index(ap, idx, pval, CA_XFER_PUT);
975
+ }
563
976
 
564
- if ( ca_is_readonly(ca) ) { /* read only array */
977
+ /* gather / scatter over a list of linear addresses. ca_xfer_addrs is the
978
+ primary addr entry; ca_fetch_addr / ca_store_addr are thin wrappers
979
+ (kept as public C-API for external ext gems).
980
+
981
+ Dispatch core (no GC protection; caller handles CA_OBJECT):
982
+ 1. ca->ptr present (entity / attached / alias) -> direct memcpy at addr.
983
+ 2. xfer_addrs slot -> use it (every view supplies one). */
984
+
985
+ /* Detect a sequential addr run (addrs[i] == addrs[0] + i for all i) so a
986
+ single bulk memcpy replaces the per-cell loop. O(n) integer compare
987
+ with early-exit on first mismatch. This fires for dominant-true mask
988
+ workloads (ca[:is_not_masked] += v etc.): when the boolean is mostly
989
+ TRUE, the view->parent addr remap degenerates to [0..n-1]. */
990
+ int
991
+ ca_xfer_addrs_is_sequential_run (ca_size_t n, ca_size_t *addrs,
992
+ ca_size_t *base_out)
993
+ {
994
+ ca_size_t base, i;
995
+ if ( n == 0 ) { *base_out = 0; return 1; }
996
+ base = addrs[0];
997
+ for ( i = 1; i < n; i++ ) {
998
+ if ( addrs[i] != base + i ) return 0;
999
+ }
1000
+ *base_out = base;
1001
+ return 1;
1002
+ }
1003
+
1004
+ static void
1005
+ ca_xfer_addrs_dispatch (CArray *ca, ca_size_t n, ca_size_t *addrs,
1006
+ void *data, int dir)
1007
+ {
1008
+ char *d = (char *) data;
1009
+ ca_size_t i, base;
1010
+
1011
+ if ( ca->ptr ) { /* attached / entity / alias: fast path */
1012
+ if ( ca_xfer_addrs_is_sequential_run(n, addrs, &base) ) {
1013
+ /* Sequential-run fast path: single bulk memcpy. Triggered by any
1014
+ sub-region run ([k..k+m-1] form), not whole-view limited. Safe:
1015
+ detection is view-structural (the addr shape, not a workload tag)
1016
+ and does not call xfer_all. */
1017
+ char *p = ca->ptr + ca->bytes * base;
1018
+ ca_size_t nbytes = n * ca->bytes;
1019
+ if ( dir == CA_XFER_GET ) memcpy(d, p, nbytes);
1020
+ else memcpy(p, d, nbytes);
1021
+ return;
1022
+ }
1023
+ /* Per-cell loop for arbitrary (non-sequential) addrs:
1024
+ fancy gather/scatter from CASelect 2-D mapper, CSA sparse mask, etc. */
1025
+ for ( i = 0; i < n; i++ ) {
1026
+ char *p = ca->ptr + ca->bytes * addrs[i];
1027
+ if ( dir == CA_XFER_GET ) memcpy(d + i * ca->bytes, p, ca->bytes);
1028
+ else memcpy(p, d + i * ca->bytes, ca->bytes);
1029
+ }
1030
+ return;
1031
+ }
1032
+
1033
+ if ( ! ca_func[ca->obj_type].xfer_addrs ) {
565
1034
  rb_raise(rb_eRuntimeError,
566
- "can not store data to read-only array");
1035
+ "[BUG] xfer_addrs not defined for object type <%i>",
1036
+ ca->obj_type);
567
1037
  }
1038
+ ca_func[ca->obj_type].xfer_addrs(ca, n, addrs, data, dir);
1039
+ }
568
1040
 
569
- ca_set_cyclic_check(ca);
1041
+ struct ca_xfer_addrs_args {
1042
+ CArray *ca;
1043
+ ca_size_t n;
1044
+ ca_size_t *addrs;
1045
+ char *data;
1046
+ };
570
1047
 
571
- if ( ca->ptr ) {
572
- memcpy(ca->ptr + ca->bytes * addr, ptr, ca->bytes);
1048
+ static VALUE
1049
+ ca_xfer_addrs_get_body (VALUE arg)
1050
+ {
1051
+ struct ca_xfer_addrs_args *d = (struct ca_xfer_addrs_args *) arg;
1052
+ ca_size_t i;
1053
+ ca_xfer_addrs_dispatch(d->ca, d->n, d->addrs, d->data, CA_XFER_GET);
1054
+ for ( i = 0; i < d->n; i++ ) {
1055
+ ca_test_cyclic_check(d->ca, d->data + i * d->ca->bytes);
573
1056
  }
574
- else if ( ca_func[ca->obj_type].store_addr ) {
575
- ca_func[ca->obj_type].store_addr(ca, addr, ptr);
1057
+ return Qnil;
1058
+ }
1059
+
1060
+ void
1061
+ ca_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs, void *data, int dir)
1062
+ {
1063
+ CArray *ca = (CArray *) ap;
1064
+
1065
+ if ( dir == CA_XFER_PUT && ca_is_readonly(ca) ) {
1066
+ rb_raise(rb_eRuntimeError, "can not store data to read-only array");
576
1067
  }
577
- else if ( ca_func[ca->obj_type].store_index ) { /* delegate -> store_index */
578
- ca_size_t idx[CA_RANK_MAX];
579
- ca_addr2index(ca, addr, idx);
580
- ca_func[ca->obj_type].store_index(ca, idx, ptr);
1068
+
1069
+ /* Fast path: non-CA_OBJECT element type needs no GC protection. */
1070
+ if ( ca->data_type != CA_OBJECT ) {
1071
+ ca_xfer_addrs_dispatch(ca, n, addrs, data, dir);
1072
+ return;
1073
+ }
1074
+
1075
+ /* CA_OBJECT slow path: cyclic check (+ rb_protect on GET). */
1076
+ if ( dir == CA_XFER_GET ) {
1077
+ struct ca_xfer_addrs_args args;
1078
+ int state = 0;
1079
+ args.ca = ca;
1080
+ args.n = n;
1081
+ args.addrs = addrs;
1082
+ args.data = (char *) data;
1083
+ ca_set_cyclic_check(ca);
1084
+ rb_protect(ca_xfer_addrs_get_body, (VALUE) &args, &state);
1085
+ ca_clear_cyclic_check(ca);
1086
+ if ( state ) {
1087
+ rb_jump_tag(state);
1088
+ }
581
1089
  }
582
1090
  else {
1091
+ ca_set_cyclic_check(ca);
1092
+ ca_xfer_addrs_dispatch(ca, n, addrs, data, CA_XFER_PUT);
583
1093
  ca_clear_cyclic_check(ca);
584
- rb_raise(rb_eRuntimeError,
585
- "[BUG] store_addr or store_index "\
586
- "are not defined for object type <%i>",
587
- ca->obj_type);
588
1094
  }
589
-
590
- ca_clear_cyclic_check(ca);
591
-
592
1095
  }
593
1096
 
594
- /* fetch data of the element at given index to memory pointed by pval */
595
-
1097
+ /* gather / scatter over a STRIDED region of the view. The region is
1098
+ described in the view's own byte space:
1099
+
1100
+ base = Σ starts[k] * native_byte_stride[k] (the region's first cell,
1101
+ starts[] given as a per-axis index into the view's row-major layout)
1102
+ cell(idx) byte offset = base + Σ idx[k] * strides[k] for idx in [0,counts)
1103
+ strides[] = SRC access byte strides into the view (NOT a contiguous region;
1104
+ carries sub-sampling / transpose from the composed leaf access)
1105
+
1106
+ data is a CONTIGUOUS caller buffer in row-major order over counts[]. Local
1107
+ materialise of the requested region only -- never the whole view.
1108
+
1109
+ One example is CASelect (step = strides[0]/bytes is a view
1110
+ access step, data delivered contiguously to the parent). The boundary wiring
1111
+ passes a CAStride leaf's composed access (composed_base, composed_strides,
1112
+ leaf->dim) straight through.
1113
+
1114
+ Dispatch core (no GC protection; caller handles CA_OBJECT):
1115
+ 1. ca->ptr present (entity / attached / alias) -> strided memcpy.
1116
+ 2. xfer_stride slot -> the view delivers its own region (recurse / cast /
1117
+ gather-translate).
1118
+ 3. else per cell: byte offset -> flat addr -> index -> ca_xfer_index_dispatch
1119
+ (universal fallback, no whole-view attach). */
1120
+
1121
+ /* Cache-tiled 2-D transpose fast path (helper for ca_xfer_stride_dispatch).
1122
+
1123
+ ------------------------------------------------------------------------
1124
+ PROBLEM
1125
+ ------------------------------------------------------------------------
1126
+ When the dispatcher detects that slab merge fails (innermost stride is
1127
+ not contig at ca->bytes) AND the access pattern looks like a 2-D
1128
+ transpose (outer view axis IS source-contig: strides[0] == bytes, inner
1129
+ view axis is non-contig: strides[1] != bytes), the naive prefix
1130
+ odometer issues counts[0] * counts[1] independent memcpy(_,_,bytes)
1131
+ calls. Each call reads one cell of `bytes` from ca->ptr at a different
1132
+ row of source -- with strides[1] huge (e.g. N * 8 for f64), every read
1133
+ touches a different cache line and often a different 4 KB page.
1134
+
1135
+ At N=2000 / bytes=8 (parent data_type = float64), this is 4M random
1136
+ reads. DRAM random-access bandwidth bottoms out around 1-2 GB/s, so
1137
+ the per-cell loop is dominated by cache-line / page misses on a large
1138
+ working set.
1139
+
1140
+ ------------------------------------------------------------------------
1141
+ TECHNIQUE: cache-tiled transpose with L1-resident scratch
1142
+ ------------------------------------------------------------------------
1143
+ Process the iteration space in 32x32 tiles, staging each tile through
1144
+ a stack-allocated `scratch` buffer that fits in L1 (32 * 32 * 16 =
1145
+ 16 KB; bytes <= 16 ceiling).
1146
+
1147
+ Load pass:
1148
+ Read `Tj` rows of source contiguously, each `Ti * bytes` long, into
1149
+ scratch[j_t * Ti + i_t]. Per-tile DRAM traffic: Tj sequential
1150
+ reads of small (256 B at bytes=8) runs. Outer loop carries source
1151
+ row band [sr0..sr0+Tj) -- those rows stay resident in L2 across
1152
+ the inner sc0 sweep (32 rows * N * bytes = 512 KB at N=2000, fits
1153
+ in any modern L2).
1154
+
1155
+ Store pass:
1156
+ For each output row (sc0+i_t), write `Tj` cells contiguously to
1157
+ the data buffer. The source side is the L1-resident scratch read
1158
+ at byte stride `Ti * bytes` -- a small constant stride into a
1159
+ 16 KB region, effectively free.
1160
+
1161
+ The key invariant: BOTH DRAM-facing transfers (the load-pass source
1162
+ read and the store-pass data write) are sequential. Random access is
1163
+ confined to the L1 scratch.
1164
+
1165
+ ------------------------------------------------------------------------
1166
+ bytes specialisation
1167
+ ------------------------------------------------------------------------
1168
+ The store pass's inner loop is the hottest path (Ti * Tj memcpy calls
1169
+ per tile). memcpy(_,_,bytes) with a runtime `bytes` defeats the
1170
+ compiler's small-constant inlining heuristic, so we dispatch on
1171
+ bytes ∈ {1, 2, 4, 8} to a TILED_*_TYPED macro that uses typed pointer
1172
+ arithmetic and explicit stores. At bytes=8 (float64 / int64 -- the
1173
+ dominant case for large 2-D workloads) Clang / gcc generate vectorised
1174
+ loads/stores for the strided scratch reads.
1175
+
1176
+ bytes=16 (cmplx128) and other unusual widths fall through to a generic
1177
+ memcpy loop; correctness is preserved, only the typed-store benefit
1178
+ is lost.
1179
+
1180
+ ------------------------------------------------------------------------
1181
+ Why not always tile?
1182
+ ------------------------------------------------------------------------
1183
+ When strides[1] IS contig (== bytes), the slab merge already collapses
1184
+ the iteration to a single bulk memcpy. When strides[0] is also non-
1185
+ contig (e.g. strided sub-sampling on BOTH axes), tiling still helps
1186
+ but the gains are smaller; we conservatively limit the trigger to
1187
+ strides[0] == bytes to keep the fast-path predicate cheap and the
1188
+ guarantees unambiguous.
1189
+
1190
+ ------------------------------------------------------------------------
1191
+ No attach inside xfer_stride
1192
+ ------------------------------------------------------------------------
1193
+ CAREFUL: xfer_stride is a per-region delivery primitive; it must not
1194
+ invoke ca_attach on `ca` itself or any ancestor. Doing so would
1195
+ short-circuit CAStack's multi-parent design and the general "partial
1196
+ materialise instead of whole-view attach" goal. This helper operates
1197
+ strictly on ca->ptr in place. */
1198
+
1199
+ #define CA_TILED_TRANSPOSE_2D_TILE 32
1200
+
1201
+ /* Non-static so cross-file callers (ca_obj_stride.c) can reuse the same
1202
+ tile-block algorithm. `src_base` points at the strided side's [0,0]
1203
+ cell; `dst` is the row-major contig side (M x N over bytes). `strides[0]`
1204
+ = source-contig stride (must equal `bytes`), `strides[1]` = source-strided
1205
+ stride. dir = CA_XFER_GET (strided->contig) / CA_XFER_PUT (contig->strided).
1206
+
1207
+ 3 caller sites:
1208
+ - ca_xfer_stride_dispatch ca->ptr path (this file)
1209
+ - ca_stride_func_xfer_stride root-direct (ca_obj_stride.c)
1210
+ - ca_stride_xfer_with_layout general driver (ca_obj_stride.c) */
596
1211
  void
597
- ca_fetch_index (void *ap, ca_size_t *idx, void *pval)
1212
+ ca_xfer_stride_tiled_transpose_2d (char *src_base,
1213
+ ca_size_t bytes_,
1214
+ ca_size_t *counts,
1215
+ ca_size_t *strides,
1216
+ char *data,
1217
+ int dir)
598
1218
  {
599
- CArray *ca = (CArray *) ap;
600
- char *ptr = (char *)pval;
1219
+ enum { TILE = CA_TILED_TRANSPOSE_2D_TILE };
1220
+ char scratch[TILE * TILE * 16]; /* L1-resident, max bytes = 16 */
1221
+ ca_size_t M = counts[0]; /* view outer = source contig direction */
1222
+ ca_size_t N = counts[1]; /* view inner = source non-contig direction */
1223
+ ca_size_t sr0, sc0, i_t, j_t;
1224
+
1225
+ for ( sr0 = 0; sr0 < N; sr0 += TILE ) {
1226
+ ca_size_t Tj = (N - sr0 < TILE) ? (N - sr0) : TILE;
1227
+ for ( sc0 = 0; sc0 < M; sc0 += TILE ) {
1228
+ ca_size_t Ti = (M - sc0 < TILE) ? (M - sc0) : TILE;
1229
+
1230
+ if ( dir == CA_XFER_GET ) {
1231
+ /* Load pass: contig source reads -> scratch[j_t * Ti + i_t]. */
1232
+ for ( j_t = 0; j_t < Tj; j_t++ ) {
1233
+ char *src_row = src_base
1234
+ + (sr0 + j_t) * strides[1]
1235
+ + sc0 * bytes_;
1236
+ memcpy(scratch + j_t * Ti * bytes_, src_row, Ti * bytes_);
1237
+ }
1238
+ /* Store pass: strided read from L1 scratch + contig write to data
1239
+ buffer, dispatched by element width. bytes={1,2,4,8} use
1240
+ typed pointer arithmetic so the compiler can vectorise. */
1241
+ #define TILED_GET_TYPED(T) \
1242
+ do { \
1243
+ T *scr = (T *) scratch; \
1244
+ for ( i_t = 0; i_t < Ti; i_t++ ) { \
1245
+ T *out = (T *) (data + ((sc0 + i_t) * N + sr0) * sizeof(T)); \
1246
+ for ( j_t = 0; j_t < Tj; j_t++ ) { \
1247
+ out[j_t] = scr[j_t * Ti + i_t]; \
1248
+ } \
1249
+ } \
1250
+ } while (0)
1251
+ switch ( bytes_ ) {
1252
+ case 1: TILED_GET_TYPED(uint8_t); break;
1253
+ case 2: TILED_GET_TYPED(uint16_t); break;
1254
+ case 4: TILED_GET_TYPED(uint32_t); break;
1255
+ case 8: TILED_GET_TYPED(uint64_t); break;
1256
+ default:
1257
+ for ( i_t = 0; i_t < Ti; i_t++ ) {
1258
+ char *out_row = data + ((sc0 + i_t) * N + sr0) * bytes_;
1259
+ for ( j_t = 0; j_t < Tj; j_t++ ) {
1260
+ memcpy(out_row + j_t * bytes_,
1261
+ scratch + (j_t * Ti + i_t) * bytes_, bytes_);
1262
+ }
1263
+ }
1264
+ break;
1265
+ }
1266
+ #undef TILED_GET_TYPED
1267
+ }
1268
+ else { /* CA_XFER_PUT: mirror of GET, data drives writes to ca->ptr. */
1269
+ /* Load pass: contig data reads -> scratch (transposed layout). */
1270
+ #define TILED_PUT_TYPED(T) \
1271
+ do { \
1272
+ T *scr = (T *) scratch; \
1273
+ for ( i_t = 0; i_t < Ti; i_t++ ) { \
1274
+ T *in = (T *) (data + ((sc0 + i_t) * N + sr0) * sizeof(T)); \
1275
+ for ( j_t = 0; j_t < Tj; j_t++ ) { \
1276
+ scr[j_t * Ti + i_t] = in[j_t]; \
1277
+ } \
1278
+ } \
1279
+ } while (0)
1280
+ switch ( bytes_ ) {
1281
+ case 1: TILED_PUT_TYPED(uint8_t); break;
1282
+ case 2: TILED_PUT_TYPED(uint16_t); break;
1283
+ case 4: TILED_PUT_TYPED(uint32_t); break;
1284
+ case 8: TILED_PUT_TYPED(uint64_t); break;
1285
+ default:
1286
+ for ( i_t = 0; i_t < Ti; i_t++ ) {
1287
+ char *data_row = data + ((sc0 + i_t) * N + sr0) * bytes_;
1288
+ for ( j_t = 0; j_t < Tj; j_t++ ) {
1289
+ memcpy(scratch + (j_t * Ti + i_t) * bytes_,
1290
+ data_row + j_t * bytes_, bytes_);
1291
+ }
1292
+ }
1293
+ break;
1294
+ }
1295
+ #undef TILED_PUT_TYPED
1296
+ /* Store pass: contig source writes from scratch. */
1297
+ for ( j_t = 0; j_t < Tj; j_t++ ) {
1298
+ char *src_row = src_base
1299
+ + (sr0 + j_t) * strides[1]
1300
+ + sc0 * bytes_;
1301
+ memcpy(src_row, scratch + j_t * Ti * bytes_, Ti * bytes_);
1302
+ }
1303
+ }
1304
+ }
1305
+ }
1306
+ }
601
1307
 
602
- ca_set_cyclic_check(ca);
1308
+ /* Shared strided-region walker for the dispatcher (this file) and the CAStride
1309
+ root-direct path (ca_obj_stride.c::ca_stride_func_xfer_stride). This
1310
+ helper consolidates the slab-merge + tile-block + general-driver logic
1311
+ both paths use.
1312
+
1313
+ Callers responsibility: compute `src_base` to already include any per-axis
1314
+ base offset, supply `src_strides[]` as byte strides matching `counts[]`,
1315
+ and provide `data` as a row-major contig buffer over counts in `bytes`-
1316
+ per-cell layout. `dir` is CA_XFER_GET (src -> data) or CA_XFER_PUT
1317
+ (data -> src).
1318
+
1319
+ Inner-loop strategy: slab merge (innermost contig run) + 2-D tile-block
1320
+ transpose at the inner pair + outer-prefix odometer with per-iter memcpy.
1321
+ Inner-loop strategy is NOT shared with ca_stride_xfer_with_layout, which
1322
+ uses ca_stride_gather_run / scatter_run typed runs -- intentionally kept
1323
+ separate to avoid abstraction over two structurally distinct inner
1324
+ strategies. */
1325
+ void
1326
+ ca_xfer_strided_walk (char *src_base,
1327
+ ca_size_t bytes,
1328
+ int8_t ndim,
1329
+ const ca_size_t *counts,
1330
+ const ca_size_t *src_strides,
1331
+ char *data,
1332
+ int dir)
1333
+ {
1334
+ ca_size_t idx[CA_RANK_MAX];
1335
+ ca_size_t doff = 0;
1336
+ int8_t k;
1337
+
1338
+ /* slab merge -- scan innermost contig run (src_strides[k] equals the
1339
+ accumulated slab byte size). This collapses per-cell memcpy(_,_,
1340
+ bytes) loops into per-slab memcpy when the source region is row-major
1341
+ contig. Critical for transform views (CAFake / CAByteSwap / CATile)
1342
+ calling parent.xfer_stride on (N-2)x(N-2)-style interior regions: the
1343
+ inner axis is parent-contig and merges into a single row memcpy. */
1344
+ int8_t slab_start = ndim;
1345
+ ca_size_t slab_bytes = bytes;
1346
+ for ( k = ndim - 1; k >= 0; k-- ) {
1347
+ if ( src_strides[k] != slab_bytes ) break;
1348
+ slab_bytes *= counts[k];
1349
+ slab_start = k;
1350
+ }
1351
+
1352
+ if ( slab_start == 0 ) { /* whole region is one contig slab */
1353
+ if ( dir == CA_XFER_GET ) memcpy(data, src_base, slab_bytes);
1354
+ else memcpy(src_base, data, slab_bytes);
1355
+ return;
1356
+ }
603
1357
 
604
- if ( ca_func[ca->obj_type].fetch_index ) {
605
- ca_func[ca->obj_type].fetch_index(ca, idx, ptr);
1358
+ /* Innermost-2-axis tile-block transpose (ndim >= 2 generalisation).
1359
+ When slab merge cannot
1360
+ collapse the innermost axis but the innermost-1 axis is source-contig
1361
+ (= transpose-like at the inner pair), iterate the outer (ndim-2) axes
1362
+ on an odometer and apply the 2-D cache-tiled helper to each inner
1363
+ (counts[ndim-2] x counts[ndim-1]) block. ndim == 2 reduces to
1364
+ outer_n == 0 -- the odometer runs exactly once with soff == 0 (relative
1365
+ to src_base) -- so it is byte-equivalent to the 2-D-only case.
1366
+ Helper operates in place on src_base; no attach is invoked. */
1367
+ if ( ndim >= 2 && bytes <= 16 &&
1368
+ slab_start == ndim &&
1369
+ src_strides[ndim-2] == bytes && src_strides[ndim-1] != bytes ) {
1370
+ int8_t outer_n = ndim - 2;
1371
+ ca_size_t inner_counts[2] = { counts[ndim-2], counts[ndim-1] };
1372
+ ca_size_t inner_strides[2] = { src_strides[ndim-2], src_strides[ndim-1] };
1373
+ ca_size_t inner_dst_bytes = counts[ndim-2] * counts[ndim-1] * bytes;
1374
+
1375
+ for ( k = 0; k < outer_n; k++ ) idx[k] = 0;
1376
+ while ( 1 ) {
1377
+ ca_size_t soff = 0;
1378
+ for ( k = 0; k < outer_n; k++ ) soff += idx[k] * src_strides[k];
1379
+ ca_xfer_stride_tiled_transpose_2d(src_base + soff, bytes,
1380
+ inner_counts, inner_strides,
1381
+ data + doff, dir);
1382
+ doff += inner_dst_bytes;
1383
+ if ( outer_n == 0 ) break;
1384
+ k = outer_n - 1;
1385
+ while ( k >= 0 ) { if ( ++idx[k] < counts[k] ) break; idx[k] = 0; k--; }
1386
+ if ( k < 0 ) break;
1387
+ }
1388
+ return;
606
1389
  }
607
- else if ( ca_func[ca->obj_type].fetch_addr ) { /* delegate -> fetch_addr */
608
- ca_size_t addr = ca_index2addr(ca, idx);
609
- ca_func[ca->obj_type].fetch_addr(ca, addr, ptr);
1390
+
1391
+ /* prefix axes [0..slab_start-1] on odometer, slab-sized memcpy per iter. */
1392
+ for ( k = 0; k < slab_start; k++ ) idx[k] = 0;
1393
+ while ( 1 ) {
1394
+ ca_size_t soff = 0;
1395
+ for ( k = 0; k < slab_start; k++ ) soff += idx[k] * src_strides[k];
1396
+ if ( dir == CA_XFER_GET ) memcpy(data + doff, src_base + soff, slab_bytes);
1397
+ else memcpy(src_base + soff, data + doff, slab_bytes);
1398
+ doff += slab_bytes;
1399
+ k = slab_start - 1;
1400
+ while ( k >= 0 ) { if ( ++idx[k] < counts[k] ) break; idx[k] = 0; k--; }
1401
+ if ( k < 0 ) break;
610
1402
  }
611
- else {
612
- ca_clear_cyclic_check(ca);
613
- rb_raise(rb_eRuntimeError,
614
- "[BUG] fetch_addr or fetch_index " \
615
- "are not defined for object type <%i>",
616
- ca->obj_type);
1403
+ }
1404
+
1405
+ /* See the comment on the prototype in carray.h. */
1406
+ int
1407
+ ca_xfer_stride_request_is_axis_box (void *ap, ca_size_t *starts,
1408
+ ca_size_t *counts, ca_size_t *strides)
1409
+ {
1410
+ CArray *ca = (CArray *) ap;
1411
+ ca_size_t native[CA_RANK_MAX];
1412
+ ca_size_t s = ca->bytes;
1413
+ int8_t ndim = ca->ndim, k;
1414
+
1415
+ for ( k = ndim - 1; k >= 0; k-- ) { native[k] = s; s *= ca->dim[k]; }
1416
+
1417
+ for ( k = 0; k < ndim; k++ ) {
1418
+ ca_size_t q;
1419
+ if ( counts[k] <= 1 ) continue; /* moves nothing */
1420
+ if ( strides[k] <= 0 ) return 0; /* zero / negative: not an axis walk */
1421
+ if ( strides[k] % native[k] != 0 ) return 0;
1422
+ q = strides[k] / native[k];
1423
+ if ( q < 1 ) return 0;
1424
+ if ( starts[k] + (counts[k] - 1) * q >= ca->dim[k] ) return 0; /* runs off axis k */
1425
+ }
1426
+ return 1;
1427
+ }
1428
+
1429
+ static void
1430
+ ca_xfer_stride_dispatch (CArray *ca, ca_size_t *starts, ca_size_t *counts,
1431
+ ca_size_t *strides, void *data, int dir)
1432
+ {
1433
+ char *d = (char *) data;
1434
+ int8_t ndim = ca->ndim;
1435
+ ca_size_t native[CA_RANK_MAX];
1436
+ ca_size_t base = 0;
1437
+ ca_size_t doff = 0;
1438
+ ca_size_t s;
1439
+ int8_t k;
1440
+ ca_size_t idx[CA_RANK_MAX];
1441
+
1442
+ s = ca->bytes;
1443
+ for ( k = ndim - 1; k >= 0; k-- ) { native[k] = s; s *= ca->dim[k]; }
1444
+ for ( k = 0; k < ndim; k++ ) base += starts[k] * native[k];
1445
+
1446
+ if ( ca->ptr && d != (char *)ca->ptr + base ) {
1447
+ /* attached / entity / alias: strided memcpy.
1448
+ CAREFUL: the `d != (char *)ca->ptr + base` guard in the branch
1449
+ condition above is load-bearing. It blocks the lazy-view self-fill
1450
+ pattern where data == ca->ptr + base would degenerate into a
1451
+ self-memcpy and leave the buffer garbage. Lazy-view attach funcs
1452
+ bypass this dispatcher, but the guard catches any future caller that
1453
+ re-introduces the same category error. */
1454
+ ca_xfer_strided_walk(ca->ptr + base, ca->bytes, ndim, counts, strides,
1455
+ d, dir);
1456
+ return;
617
1457
  }
618
1458
 
619
- ca_test_cyclic_check(ca, ptr);
1459
+ if ( ca_func[ca->obj_type].xfer_stride ) {
1460
+ ca_func[ca->obj_type].xfer_stride(ca, starts, counts, strides, data, dir);
1461
+ return;
1462
+ }
620
1463
 
621
- ca_clear_cyclic_check(ca);
1464
+ /* fallback: per-cell via byte offset -> flat addr -> index -> xfer_index. */
1465
+ for ( k = 0; k < ndim; k++ ) idx[k] = 0;
1466
+ while ( 1 ) {
1467
+ ca_size_t soff = base, vidx[CA_RANK_MAX];
1468
+ for ( k = 0; k < ndim; k++ ) soff += idx[k] * strides[k];
1469
+ ca_addr2index(ca, soff / ca->bytes, vidx);
1470
+ ca_xfer_index_dispatch(ca, vidx, d + doff, dir);
1471
+ doff += ca->bytes;
1472
+ k = ndim - 1;
1473
+ while ( k >= 0 ) { if ( ++idx[k] < counts[k] ) break; idx[k] = 0; k--; }
1474
+ if ( k < 0 ) break;
1475
+ }
622
1476
  }
623
1477
 
624
- /* store value pointed by pval to the element at given index */
1478
+ struct ca_xfer_stride_args {
1479
+ CArray *ca;
1480
+ ca_size_t *starts;
1481
+ ca_size_t *counts;
1482
+ ca_size_t *strides;
1483
+ char *data;
1484
+ };
1485
+
1486
+ static VALUE
1487
+ ca_xfer_stride_get_body (VALUE arg)
1488
+ {
1489
+ struct ca_xfer_stride_args *a = (struct ca_xfer_stride_args *) arg;
1490
+ ca_size_t n = 1, i;
1491
+ int8_t k;
1492
+ ca_xfer_stride_dispatch(a->ca, a->starts, a->counts, a->strides, a->data,
1493
+ CA_XFER_GET);
1494
+ /* cyclic check over the delivered cells (CA_OBJECT only). dst is contiguous
1495
+ row-major over counts (semantics b), so cell i is at data + i*bytes. */
1496
+ for ( k = 0; k < a->ca->ndim; k++ ) n *= a->counts[k];
1497
+ for ( i = 0; i < n; i++ ) {
1498
+ ca_test_cyclic_check(a->ca, a->data + i * a->ca->bytes);
1499
+ }
1500
+ return Qnil;
1501
+ }
625
1502
 
626
1503
  void
627
- ca_store_index (void *ap, ca_size_t *idx, void *pval)
1504
+ ca_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
1505
+ ca_size_t *strides, void *data, int dir)
628
1506
  {
629
1507
  CArray *ca = (CArray *) ap;
630
- char *ptr = (char *) pval;
631
1508
 
632
- if ( ca_is_readonly(ca) ) { /* read only array */
633
- rb_raise(rb_eRuntimeError,
634
- "can not store data to read-only array");
1509
+ if ( dir == CA_XFER_PUT && ca_is_readonly(ca) ) {
1510
+ rb_raise(rb_eRuntimeError, "can not store data to read-only array");
635
1511
  }
636
1512
 
637
- ca_set_cyclic_check(ca);
638
-
639
- if ( ca_func[ca->obj_type].store_index ) {
640
- ca_func[ca->obj_type].store_index(ca, idx, ptr);
1513
+ if ( ca->data_type != CA_OBJECT ) {
1514
+ ca_xfer_stride_dispatch(ca, starts, counts, strides, data, dir);
1515
+ return;
641
1516
  }
642
- else if ( ca_func[ca->obj_type].store_addr ) { /* delegate -> store_addr */
643
- ca_size_t addr = ca_index2addr(ca, idx);
644
- ca_func[ca->obj_type].store_addr(ca, addr, ptr);
1517
+
1518
+ if ( dir == CA_XFER_GET ) {
1519
+ struct ca_xfer_stride_args args;
1520
+ int state = 0;
1521
+ args.ca = ca; args.starts = starts; args.counts = counts;
1522
+ args.strides = strides; args.data = (char *) data;
1523
+ ca_set_cyclic_check(ca);
1524
+ rb_protect(ca_xfer_stride_get_body, (VALUE) &args, &state);
1525
+ ca_clear_cyclic_check(ca);
1526
+ if ( state ) rb_jump_tag(state);
645
1527
  }
646
1528
  else {
647
- rb_raise(rb_eRuntimeError,
648
- "[BUG] store_addr or store_index "\
649
- "are not defined for object type <%i>",
650
- ca->obj_type);
1529
+ ca_set_cyclic_check(ca);
1530
+ ca_xfer_stride_dispatch(ca, starts, counts, strides, data, CA_XFER_PUT);
1531
+ ca_clear_cyclic_check(ca);
651
1532
  }
1533
+ }
1534
+
1535
+ /* whole-view transfer: direction-unified replacement of copy_data /
1536
+ sync_data. Pure dispatch to the view's xfer_all slot. Readonly /
1537
+ nosync policy lives in ca_sync_data (the PUT entry), not here -- this
1538
+ is the raw dispatcher.
652
1539
 
653
- ca_clear_cyclic_check(ca);
1540
+ CAREFUL: the dispatcher is a thin wrapper (no ca_attach here), and each
1541
+ view's xfer_all slot must not ca_attach(parent) either. That is what
1542
+ gives ca_xfer_all and all internal callers (ca_update / ca_copy_data /
1543
+ ca_sync_data / kernel_iterator SRC_ATTACH path) their cheap-attach
1544
+ semantics; re-adding an attach into a slot silently reintroduces a
1545
+ whole-parent materialise. External ext gems calling ca_xfer_all should
1546
+ likewise expect a thin dispatcher. */
654
1547
 
1548
+ typedef struct {
1549
+ CArray *ca;
1550
+ void *data;
1551
+ int dir;
1552
+ } ca_xfer_all_args_t;
1553
+
1554
+ static VALUE
1555
+ ca_xfer_all_body (VALUE arg)
1556
+ {
1557
+ ca_xfer_all_args_t *a = (ca_xfer_all_args_t *) arg;
1558
+ ca_func[a->ca->obj_type].xfer_all(a->ca, a->data, a->dir);
1559
+ return Qnil;
1560
+ }
1561
+
1562
+ /* `arg` carries the hold depth to unwind to, so nested ca_xfer_all calls
1563
+ each drop their own hold rather than the whole stack. */
1564
+ static VALUE
1565
+ ca_xfer_all_ensure (VALUE arg)
1566
+ {
1567
+ ca_gc_hold_pop_to(NUM2INT(arg));
1568
+ ca_lazy_arena_exit();
1569
+ return Qnil;
1570
+ }
1571
+
1572
+ void
1573
+ ca_xfer_all (void *ap, void *data, int dir)
1574
+ {
1575
+ CArray *ca = (CArray *) ap;
1576
+ ca_xfer_all_args_t args;
1577
+ int guard = -1;
1578
+ if ( ! ca_func[ca->obj_type].xfer_all ) {
1579
+ rb_raise(rb_eRuntimeError,
1580
+ "[BUG] xfer_all not defined for object type <%i>",
1581
+ ca->obj_type);
1582
+ }
1583
+ /* Universal arena lifetime hook. ca_xfer_all is the single universal
1584
+ entry for materialise (to_ca -> ca_copy -> ca_copy_data ->
1585
+ ca_xfer_all), so wrapping here covers every outermost view type,
1586
+ including an affine view wrapping a lazy view ((a.lazy+b).transpose).
1587
+
1588
+ CAREFUL: the arena _exit must run under rb_ensure. If an exception
1589
+ skips it, the arena depth stays stuck at +1 and the reset trigger
1590
+ (a depth==0 entry) never fires -- a silent failure. With rb_ensure
1591
+ the depth returns to 0 on exit and the reset fires correctly at the
1592
+ next entry. */
1593
+ ca_lazy_arena_enter();
1594
+ args.ca = ca; args.data = data; args.dir = dir;
1595
+
1596
+ /* A CA_OBJECT cell is a VALUE, and this buffer belongs to no Ruby
1597
+ object yet -- it is the destination `copy` will hand out, or a
1598
+ view's own freshly allocated one. The object lane calls rb_funcall
1599
+ per cell, so a collection partway through would free what has been
1600
+ written so far. ca_xfer_all is the whole-view entry, so the window
1601
+ is exactly ca->elements contiguous cells: the one place where the
1602
+ extent is known without trusting a caller's strides. Sub-windows
1603
+ written by nested transfers land inside it. */
1604
+ if ( ca->data_type == CA_OBJECT && dir == CA_XFER_GET ) {
1605
+ if ( data != (void *) ca->ptr ) {
1606
+ VALUE *p = (VALUE *) data;
1607
+ ca_size_t i;
1608
+ for ( i = 0; i < ca->elements; i++ ) *p++ = Qnil;
1609
+ }
1610
+ guard = ca_gc_hold_push(data, ca->elements);
1611
+ }
1612
+ rb_ensure(ca_xfer_all_body, (VALUE) &args,
1613
+ ca_xfer_all_ensure, INT2NUM(guard));
655
1614
  }
656
1615
 
657
1616
  /* ------------------------------------------------------------------- */
@@ -667,12 +1626,12 @@ ca_allocate (void *ap)
667
1626
  return;
668
1627
  }
669
1628
 
670
- if ( ca_is_virtual(ca) ) { /* virtual array */
1629
+ if ( ca_is_view(ca) ) { /* view array */
671
1630
 
672
- CAVIRTUAL(ca)->attach += 1; /* increments attach level */
673
- if ( CAVIRTUAL(ca)->attach > CA_ATTACH_MAX ) {
1631
+ CAVIEW(ca)->attach += 1; /* increments attach level */
1632
+ if ( CAVIEW(ca)->attach > CA_ATTACH_MAX ) {
674
1633
  rb_raise(rb_eRuntimeError,
675
- "too large attach count of virtual array");
1634
+ "too large attach count of view array");
676
1635
  }
677
1636
 
678
1637
  if ( ! ca->ptr ) {
@@ -707,12 +1666,12 @@ ca_attach (void *ap)
707
1666
  return;
708
1667
  }
709
1668
 
710
- if ( ca_is_virtual(ca) ) { /* virtual array */
1669
+ if ( ca_is_view(ca) ) { /* view array */
711
1670
 
712
- CAVIRTUAL(ca)->attach += 1; /* increments attach level */
713
- if ( CAVIRTUAL(ca)->attach > CA_ATTACH_MAX ) {
1671
+ CAVIEW(ca)->attach += 1; /* increments attach level */
1672
+ if ( CAVIEW(ca)->attach > CA_ATTACH_MAX ) {
714
1673
  rb_raise(rb_eRuntimeError,
715
- "too large attach count of virtual array");
1674
+ "too large attach count of view array");
716
1675
  }
717
1676
 
718
1677
  if ( ! ca->ptr ) {
@@ -738,13 +1697,13 @@ ca_update (void *ap)
738
1697
  return;
739
1698
  }
740
1699
 
741
- if ( ca_is_virtual(ca) ) { /* virtual array */
1700
+ if ( ca_is_view(ca) ) { /* view array */
742
1701
 
743
1702
  if ( ca->ptr ) {
744
- ca_func[ca->obj_type].copy_data(ca, ca->ptr);
1703
+ ca_xfer_all(ca, ca->ptr, CA_XFER_GET); /* re-gather into own ptr (step 4) */
745
1704
  }
746
1705
  else {
747
- rb_raise(rb_eRuntimeError,
1706
+ rb_raise(rb_eRuntimeError,
748
1707
  "[BUG] ca_update() called for not-attached virtal array");
749
1708
  }
750
1709
 
@@ -778,8 +1737,8 @@ ca_sync (void *ap)
778
1737
  ca_update_mask(ca);
779
1738
  ca_sync(ca->mask);
780
1739
 
781
- if ( ca_is_virtual(ca) ) { /* virtual array */
782
- if ( ! CAVIRTUAL(ca)->nosync ) { /* FIXME : */
1740
+ if ( ca_is_view(ca) ) { /* view array */
1741
+ if ( ! CAVIEW(ca)->nosync ) { /* FIXME : */
783
1742
  ca_func[ca->obj_type].sync(ap);
784
1743
  }
785
1744
  }
@@ -805,11 +1764,11 @@ ca_detach (void *ap)
805
1764
  "[BUG] tried to detach a detached array");
806
1765
  }
807
1766
 
808
- if ( ca_is_virtual(ca) ) { /* virtual array */
809
- if ( CAVIRTUAL(ca)->attach == 1 ) {
1767
+ if ( ca_is_view(ca) ) { /* view array */
1768
+ if ( CAVIEW(ca)->attach == 1 ) {
810
1769
  ca_func[ca->obj_type].detach(ap);
811
1770
  }
812
- CAVIRTUAL(ca)->attach -= 1;
1771
+ CAVIEW(ca)->attach -= 1;
813
1772
  }
814
1773
  else { /* entity array */
815
1774
  ca_func[ca->obj_type].detach(ap);
@@ -882,8 +1841,7 @@ ca_detach_n (int n, ...)
882
1841
  void
883
1842
  ca_copy_data (void *ap, char *ptr)
884
1843
  {
885
- CArray *ca = (CArray *) ap;
886
- ca_func[ca->obj_type].copy_data(ap, ptr); /* delegate */
1844
+ ca_xfer_all(ap, ptr, CA_XFER_GET); /* whole-view gather (step 4) */
887
1845
  }
888
1846
 
889
1847
  /* synchronize the data pointed by given pointer to parent's data */
@@ -898,16 +1856,12 @@ ca_sync_data (void *ap, char *ptr)
898
1856
  "can not sync data to read-only array");
899
1857
  }
900
1858
 
901
- if ( ca_is_virtual(ca) ) { /* virtual array */
902
- if ( CAVIRTUAL(ca)->nosync ) { /* ca is to be attached */
903
- ca_func[CA_OBJ_ARRAY].sync_data(ap, ptr);
904
- }
905
- else {
906
- ca_func[ca->obj_type].sync_data(ap, ptr);
907
- }
1859
+ if ( ca_is_view(ca) && CAVIEW(ca)->nosync ) {
1860
+ /* ca is to be attached: treat ca->ptr as an owned entity buffer */
1861
+ ca_array_func_xfer_all(ap, ptr, CA_XFER_PUT);
908
1862
  }
909
- else { /* entity array */
910
- ca_func[ca->obj_type].sync_data(ap, ptr);
1863
+ else {
1864
+ ca_xfer_all(ap, ptr, CA_XFER_PUT); /* whole-view scatter (step 4) */
911
1865
  }
912
1866
  }
913
1867
 
@@ -924,7 +1878,7 @@ ca_fill_data (void *ap, void *aptr)
924
1878
  "can not fill data to read-only array");
925
1879
  }
926
1880
 
927
- if ( ca_is_virtual(ca) ) { /* virtual array */
1881
+ if ( ca_is_view(ca) ) { /* view array */
928
1882
  if ( ca_is_attached(ca) ) { /* ca is to be attached */
929
1883
  ca_func[CA_OBJ_ARRAY].fill_data(ap, ptr);
930
1884
  }
@@ -937,6 +1891,174 @@ ca_fill_data (void *ap, void *aptr)
937
1891
  }
938
1892
  }
939
1893
 
1894
+ /* Write one value into part of a view.
1895
+
1896
+ The default walks the region and hands each cell to xfer_index, which
1897
+ composes one hop and delegates to the parent. It is per-cell, so it is the
1898
+ floor rather than the path: a view that can pass the region on fills in the
1899
+ slot and the walk never happens. What the default guarantees is that a view
1900
+ with no slot still touches only the region. */
1901
+
1902
+ void
1903
+ ca_fill_stride_default (void *ap, ca_size_t base, int8_t ndim,
1904
+ ca_size_t *counts, ca_size_t *steps, void *ptr)
1905
+ {
1906
+ CArray *ca = (CArray *) ap;
1907
+ ca_size_t idx[CA_RANK_MAX];
1908
+ int8_t k;
1909
+
1910
+ for ( k = 0; k < ndim; k++ ) idx[k] = 0;
1911
+ while ( 1 ) {
1912
+ ca_size_t addr = base, vidx[CA_RANK_MAX];
1913
+ for ( k = 0; k < ndim; k++ ) addr += idx[k] * steps[k];
1914
+ ca_addr2index(ca, addr, vidx);
1915
+ ca_xfer_index_dispatch(ca, vidx, ptr, CA_XFER_PUT);
1916
+ k = ndim - 1;
1917
+ while ( k >= 0 ) { if ( ++idx[k] < counts[k] ) break; idx[k] = 0; k--; }
1918
+ if ( k < 0 ) break;
1919
+ }
1920
+ }
1921
+
1922
+ void
1923
+ ca_fill_stride (void *ap, ca_size_t base, int8_t ndim,
1924
+ ca_size_t *counts, ca_size_t *steps, void *ptr)
1925
+ {
1926
+ CArray *ca = (CArray *) ap;
1927
+ int8_t k;
1928
+
1929
+ if ( ca_is_readonly(ca) ) {
1930
+ rb_raise(rb_eRuntimeError, "can not fill data to read-only array");
1931
+ }
1932
+
1933
+ for ( k = 0; k < ndim; k++ ) {
1934
+ if ( counts[k] <= 0 ) return;
1935
+ }
1936
+
1937
+ if ( ca_func[ca->obj_type].fill_stride ) {
1938
+ ca_func[ca->obj_type].fill_stride(ap, base, ndim, counts, steps, ptr);
1939
+ return;
1940
+ }
1941
+
1942
+ ca_fill_stride_default(ap, base, ndim, counts, steps, ptr);
1943
+ }
1944
+
1945
+ /* True if the region is exactly `ca`'s own extent in row-major order. A view
1946
+ that composes its axes into its parent's space can only do so for the whole
1947
+ of itself: a sub-box arrives as addresses, and recovering which axis each
1948
+ step belongs to is not something addresses can answer once the view has
1949
+ reordered or dropped axes. In practice that is the only region a view is
1950
+ asked for -- ca_fill_stride_whole is the caller -- so the check is a
1951
+ precondition rather than a fast path. */
1952
+
1953
+ int
1954
+ ca_fill_stride_is_whole (void *ap, ca_size_t base, int8_t ndim,
1955
+ ca_size_t *counts, ca_size_t *steps)
1956
+ {
1957
+ CArray *ca = (CArray *) ap;
1958
+ ca_size_t s = 1;
1959
+ int8_t k;
1960
+
1961
+ if ( base != 0 || ndim != ca->ndim ) return 0;
1962
+ for ( k = ndim - 1; k >= 0; k-- ) {
1963
+ if ( counts[k] != ca->dim[k] || steps[k] != s ) return 0;
1964
+ s *= ca->dim[k];
1965
+ }
1966
+ return 1;
1967
+ }
1968
+
1969
+ /* "All of me" as a region: the whole extent in row-major order. */
1970
+
1971
+ void
1972
+ ca_fill_stride_whole (void *ap, void *ptr)
1973
+ {
1974
+ CArray *ca = (CArray *) ap;
1975
+ ca_size_t counts[CA_RANK_MAX], steps[CA_RANK_MAX];
1976
+ ca_size_t s = 1;
1977
+ int8_t k;
1978
+
1979
+ for ( k = ca->ndim - 1; k >= 0; k-- ) {
1980
+ counts[k] = ca->dim[k];
1981
+ steps[k] = s;
1982
+ s *= ca->dim[k];
1983
+ }
1984
+ ca_fill_stride(ap, 0, ca->ndim, counts, steps, ptr);
1985
+ }
1986
+
1987
+ void
1988
+ ca_fill_addrs_default (void *ap, ca_size_t n, ca_size_t *addrs, void *ptr)
1989
+ {
1990
+ CArray *ca = (CArray *) ap;
1991
+ ca_size_t i;
1992
+
1993
+ for ( i = 0; i < n; i++ ) {
1994
+ ca_size_t vidx[CA_RANK_MAX];
1995
+ ca_addr2index(ca, addrs[i], vidx);
1996
+ ca_xfer_index_dispatch(ca, vidx, ptr, CA_XFER_PUT);
1997
+ }
1998
+ }
1999
+
2000
+ /* Walk a region and hand its addresses on in windows.
2001
+
2002
+ For a view whose fill is a read-modify-write of the parent -- the sub-byte
2003
+ ones, where a cell carries bits the fill must leave alone -- there is no
2004
+ region to pass down: the parent has to be read before it can be written.
2005
+ What there is to save is being asked for it one cell at a time, each cell
2006
+ descending the chain on its own. The batched address slot already does the
2007
+ read and the write in one call each; this only feeds it.
2008
+
2009
+ The window is fixed so the scratch does not follow the region's size. */
2010
+
2011
+ #define CA_FILL_ADDR_WINDOW 1024
2012
+
2013
+ void
2014
+ ca_fill_stride_via_addrs (void *ap, ca_size_t base, int8_t ndim,
2015
+ ca_size_t *counts, ca_size_t *steps, void *ptr)
2016
+ {
2017
+ CArray *ca = (CArray *) ap;
2018
+ ca_size_t idx[CA_RANK_MAX];
2019
+ ca_size_t window[CA_FILL_ADDR_WINDOW];
2020
+ ca_size_t n = 0;
2021
+ int8_t k;
2022
+
2023
+ for ( k = 0; k < ndim; k++ ) {
2024
+ if ( counts[k] <= 0 ) return;
2025
+ idx[k] = 0;
2026
+ }
2027
+
2028
+ while ( 1 ) {
2029
+ ca_size_t addr = base;
2030
+ for ( k = 0; k < ndim; k++ ) addr += idx[k] * steps[k];
2031
+ window[n++] = addr;
2032
+ if ( n == CA_FILL_ADDR_WINDOW ) {
2033
+ ca_fill_addrs(ca, n, window, ptr);
2034
+ n = 0;
2035
+ }
2036
+ k = ndim - 1;
2037
+ while ( k >= 0 ) { if ( ++idx[k] < counts[k] ) break; idx[k] = 0; k--; }
2038
+ if ( k < 0 ) break;
2039
+ }
2040
+ if ( n ) {
2041
+ ca_fill_addrs(ca, n, window, ptr);
2042
+ }
2043
+ }
2044
+
2045
+ void
2046
+ ca_fill_addrs (void *ap, ca_size_t n, ca_size_t *addrs, void *ptr)
2047
+ {
2048
+ CArray *ca = (CArray *) ap;
2049
+
2050
+ if ( ca_is_readonly(ca) ) {
2051
+ rb_raise(rb_eRuntimeError, "can not fill data to read-only array");
2052
+ }
2053
+
2054
+ if ( ca_func[ca->obj_type].fill_addrs ) {
2055
+ ca_func[ca->obj_type].fill_addrs(ap, n, addrs, ptr);
2056
+ return;
2057
+ }
2058
+
2059
+ ca_fill_addrs_default(ap, n, addrs, ptr);
2060
+ }
2061
+
940
2062
  /* ------------------------------------------------------------------- */
941
2063
 
942
2064
  /* clone CArray struct */
@@ -975,9 +2097,9 @@ rb_ca_attach_i (VALUE self)
975
2097
  if ( rb_obj_is_carray(self) ) {
976
2098
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
977
2099
  ca_attach(ca);
978
- if ( ca_is_virtual(ca) ) {
979
- CAVIRTUAL(ca)->nosync += 1;
980
- if ( CAVIRTUAL(ca)->nosync > 64 ) {
2100
+ if ( ca_is_view(ca) ) {
2101
+ CAVIEW(ca)->nosync += 1;
2102
+ if ( CAVIEW(ca)->nosync > 64 ) {
981
2103
  rb_raise(rb_eRuntimeError, "nosync count exceeds 64");
982
2104
  }
983
2105
  }
@@ -990,10 +2112,10 @@ rb_ca_sync_i (VALUE self)
990
2112
  CArray *ca;
991
2113
  if ( rb_obj_is_carray(self) ) {
992
2114
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
993
- if ( ca_is_virtual(ca) ) {
994
- CAVIRTUAL(ca)->nosync -= 1;
2115
+ if ( ca_is_view(ca) ) {
2116
+ CAVIEW(ca)->nosync -= 1;
995
2117
  ca_sync(ca);
996
- CAVIRTUAL(ca)->nosync += 1;
2118
+ CAVIEW(ca)->nosync += 1;
997
2119
  }
998
2120
  else {
999
2121
  ca_sync(ca);
@@ -1007,8 +2129,8 @@ rb_ca_detach_i (VALUE self)
1007
2129
  CArray *ca;
1008
2130
  if ( rb_obj_is_carray(self) ) {
1009
2131
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
1010
- if ( ca_is_virtual(ca) ) { /* virtual array */
1011
- CAVIRTUAL(ca)->nosync -= 1;
2132
+ if ( ca_is_view(ca) ) { /* view array */
2133
+ CAVIEW(ca)->nosync -= 1;
1012
2134
  ca_detach(ca);
1013
2135
  }
1014
2136
  else { /* entity array */
@@ -1092,7 +2214,7 @@ rb_ca_s_attach_bang (int argc, VALUE *argv, VALUE self)
1092
2214
 
1093
2215
  for (i=0; i<RARRAY_LEN(list); i++) {
1094
2216
  obj = rb_ary_entry(list, i);
1095
- rb_ca_modify(obj);
2217
+ rb_check_frozen(obj);
1096
2218
  rb_ca_attach_i(obj);
1097
2219
  }
1098
2220
 
@@ -1142,7 +2264,7 @@ It is ensured the syncing the memory block at the end of the block evaluation.
1142
2264
  static VALUE
1143
2265
  rb_ca_attach_bang (VALUE self)
1144
2266
  {
1145
- rb_ca_modify(self);
2267
+ rb_check_frozen(self);
1146
2268
  rb_ca_attach_i(self);
1147
2269
  return rb_ensure(rb_yield, self, rb_ca_ensure_sync_detach, self);
1148
2270
  }
@@ -1168,7 +2290,7 @@ rb_ca__attach__ (VALUE self)
1168
2290
  static VALUE
1169
2291
  rb_ca__sync__ (VALUE self)
1170
2292
  {
1171
- rb_ca_modify(self);
2293
+ rb_check_frozen(self);
1172
2294
  rb_ca_sync_i(self);
1173
2295
  return self;
1174
2296
  }
@@ -1232,23 +2354,39 @@ rb_ca_members (VALUE self)
1232
2354
  }
1233
2355
  }
1234
2356
 
2357
+ /* Projects a struct member of `self` to its CAField view. data_class
2358
+ lives only on a Face, so this accepts both a Face and a plain FIXLEN
2359
+ entity. For a Face, the field-view receiver is swapped to the parent
2360
+ (= Face strip): the @member cache lives on self (the Face) while the
2361
+ actual field view is the CAField on the parent. A plain FIXLEN entity
2362
+ keeps self == receiver. */
1235
2363
  VALUE
1236
- rb_ca_field_as_member (VALUE self, VALUE sym)
2364
+ rb_ca_face_field (VALUE self, VALUE sym)
1237
2365
  {
1238
2366
  volatile VALUE data_class = rb_ca_data_class(self);
1239
2367
  volatile VALUE member;
1240
2368
  volatile VALUE obj;
2369
+ volatile VALUE receiver;
2370
+ CArray *ca;
1241
2371
 
1242
2372
  if ( NIL_P(data_class) ) {
1243
2373
  rb_raise(rb_eRuntimeError, "carray doesn't have data class");
1244
2374
  }
1245
2375
 
2376
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
2377
+ receiver = ca_is_face(ca) ? rb_ca_parent(self) : self;
2378
+
1246
2379
  member = rb_ivar_get(self, rb_intern("member"));
1247
2380
 
1248
2381
  if ( NIL_P(member) ) {
1249
- rb_raise(rb_eRuntimeError,
1250
- "[BUG] instance variable member doesn't defined "\
1251
- "for data_class array");
2382
+ /* Derived CARecord views (= arr[range] / arr.transpose etc.) inherit
2383
+ data_class via inherit_data_class but not @member, since only
2384
+ ca_record_build initialises it. Lazy-init here so chain field
2385
+ projection (arr[range]["lat"]) works without a [BUG] raise. Cache
2386
+ is local to the derived view; field views are still on its parent
2387
+ (which itself is on the entity). */
2388
+ member = rb_hash_new();
2389
+ rb_ivar_set(self, rb_intern("member"), member);
1252
2390
  }
1253
2391
 
1254
2392
  if ( rb_obj_is_kind_of(sym, rb_cInteger) ) {
@@ -1278,7 +2416,45 @@ rb_ca_field_as_member (VALUE self, VALUE sym)
1278
2416
  "can't find data_member named <%s>", StringValuePtr(sym));
1279
2417
  }
1280
2418
  Check_Type(info, T_ARRAY);
1281
- obj = rb_apply(self, rb_intern("field"), info);
2419
+ /* Bit-typed members route through a CAField power-of-2 byte
2420
+ projection + CABitfield, mirroring the
2421
+ per-record dispatch in CAStruct#[]. MEMBER_TABLE entry shape
2422
+ for bits is `[byte_offset, :bitfield, {bits:, bit_offset:}]`
2423
+ where bit_offset is the struct-relative *bit* offset. Plain
2424
+ byte-typed members fall through to the original `.field(...)`
2425
+ path. */
2426
+ {
2427
+ volatile VALUE type_val = rb_ary_entry(info, 1);
2428
+ if ( SYMBOL_P(type_val) &&
2429
+ SYM2ID(type_val) == rb_intern("bitfield") ) {
2430
+ volatile VALUE opts = rb_ary_entry(info, 2);
2431
+ volatile VALUE word_view, range, vtype_sym;
2432
+ ca_size_t bit_offset = NUM2SIZE(rb_hash_aref(opts,
2433
+ ID2SYM(rb_intern("bit_offset"))));
2434
+ ca_size_t bits = NUM2SIZE(rb_hash_aref(opts,
2435
+ ID2SYM(rb_intern("bits"))));
2436
+ ca_size_t start_byte = bit_offset / 8;
2437
+ int bit_in_word = (int)(bit_offset % 8);
2438
+ ca_size_t end_byte_excl = (bit_offset + bits + 7) / 8;
2439
+ ca_size_t span = end_byte_excl - start_byte;
2440
+ int view_bytes;
2441
+ const char *vtype_name;
2442
+ if (span <= 1) { view_bytes = 1; vtype_name = "uint8"; }
2443
+ else if (span <= 2) { view_bytes = 2; vtype_name = "uint16"; }
2444
+ else if (span <= 4) { view_bytes = 4; vtype_name = "uint32"; }
2445
+ else { view_bytes = 8; vtype_name = "uint64"; }
2446
+ (void) view_bytes;
2447
+ vtype_sym = ID2SYM(rb_intern(vtype_name));
2448
+ word_view = rb_funcall(receiver, rb_intern("field"), 2,
2449
+ SIZE2NUM(start_byte), vtype_sym);
2450
+ range = rb_range_new(LONG2NUM(bit_in_word),
2451
+ SIZE2NUM(bit_in_word + bits - 1), 0);
2452
+ obj = rb_funcall(word_view, rb_intern("bitfield"), 1, range);
2453
+ rb_hash_aset(member, sym, obj);
2454
+ return obj;
2455
+ }
2456
+ }
2457
+ obj = rb_apply(receiver, rb_intern("field"), info);
1282
2458
  rb_hash_aset(member, sym, obj);
1283
2459
  return obj;
1284
2460
  }
@@ -1302,7 +2478,7 @@ rb_ca_fields (VALUE self)
1302
2478
  list = rb_ary_new2(RARRAY_LEN(member_names));
1303
2479
  for (i=0; i<RARRAY_LEN(member_names); i++) {
1304
2480
  VALUE name = rb_ary_entry(member_names, i);
1305
- rb_ary_store(list, i, rb_ca_field_as_member(self, name));
2481
+ rb_ary_store(list, i, rb_ca_face_field(self, name));
1306
2482
  }
1307
2483
  return list;
1308
2484
  }
@@ -1325,15 +2501,14 @@ rb_ca_fields_at (int argc, VALUE *argv, VALUE self)
1325
2501
  list = rb_ary_new2(RARRAY_LEN(member_names));
1326
2502
  for (i=0; i<RARRAY_LEN(member_names); i++) {
1327
2503
  VALUE name = rb_ary_entry(member_names, i);
1328
- rb_ary_store(list, i, rb_ca_field_as_member(self, name));
2504
+ rb_ary_store(list, i, rb_ca_face_field(self, name));
1329
2505
  }
1330
2506
  return list;
1331
2507
  }
1332
2508
 
1333
- /* ------------------------------------------------------------------- */
1334
2509
 
1335
2510
  void
1336
- Init_carray_core ()
2511
+ Init_carray_core (void)
1337
2512
  {
1338
2513
  id_decode = rb_intern("decode");
1339
2514
  id_encode = rb_intern("encode");
@@ -1354,5 +2529,20 @@ Init_carray_core ()
1354
2529
 
1355
2530
  rb_define_method(rb_cCArray, "fields", rb_ca_fields, 0);
1356
2531
  rb_define_method(rb_cCArray, "fields_at", rb_ca_fields_at, -1);
2532
+
1357
2533
  }
1358
2534
 
2535
+
2536
+ /* ------------------------------------------------------------------- */
2537
+
2538
+ /* The out-of-build form of the ca_is_entity macro (see carray.h). Defined
2539
+ last so the macro stays in force for the rest of this file. */
2540
+
2541
+ #undef ca_is_entity
2542
+
2543
+ int
2544
+ ca_is_entity (const void *ap)
2545
+ {
2546
+ const CArray *ca = (const CArray *) ap;
2547
+ return ( ca_func[ca->obj_type].entity_type == CA_REAL_ARRAY );
2548
+ }