carray 2.0.0 → 3.0.0

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