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/ca_obj_window.c CHANGED
@@ -1,36 +1,111 @@
1
1
  /* ---------------------------------------------------------------------------
2
2
 
3
- ca_obj_window.c
3
+ CAWindow: a sliding rectangular view of the parent whose cells may fall
4
+ outside it. Each axis carries a start / count and a bounds policy that says
5
+ what an out-of-range (OOB) cell means: FILL / MASK give it `ca->fill` (or
6
+ mask it), NEAREST / RUBY / STRICT normalise or reject the index.
4
7
 
5
- This file is part of Ruby/CArray extension library.
8
+ CAShift is a typedef of this struct (ca_obj_shift.c) and shares the whole
9
+ operation table, so every path here serves both.
6
10
 
7
- Copyright (C) 2005-2020 Hiroki Motoyoshi
11
+ Two internal models coexist, chosen per view by ca_window_recompute_embed:
8
12
 
9
- ---------------------------------------------------------------------------- */
13
+ embed model (FILL / MASK on every axis, embed_eligible)
14
+ The view decomposes into one alias rectangle (the part of the parent it
15
+ actually addresses, embed_*) plus its fill complement (the OOB part).
16
+ Attach / xfer are then "1 typed fill + 1 strided memcpy", with no
17
+ per-cell bound check. A window whose inner axes are full and which sits
18
+ entirely inside the parent (embed_alias_eligible) skips even that and
19
+ aliases the parent's buffer.
20
+
21
+ descriptor engine (any other bounds policy)
22
+ ca_window_describe_axes emits a per-axis descriptor and the shared engine
23
+ (ca_axis_dispatch.c) applies the policy per cell.
24
+
25
+ Region-copy helpers shared with CATile / CAStack live in
26
+ ca_composite_dispatch.c (included above).
27
+
28
+ --------------------------------------------------------------------------- */
10
29
 
11
30
  #include "carray.h"
31
+ #include "ca_composite_dispatch.h"
32
+ #include "ca_obj_face.h" /* CA_FACE_LIFT_IF_FACE, used by rb_ca_window */
12
33
 
13
34
  /* should not be static variable as used by CAIteratorWindow */
14
35
 
36
+ static size_t
37
+ ca_window_dsize (const void *ap)
38
+ {
39
+ const CAWindow *ca = (const CAWindow *) ap;
40
+ /* dim points to count; start, count, size0 are each ALLOC_N(ndim);
41
+ bounds is ALLOC_N(uint8_t, ndim) (per-axis);
42
+ fill is ALLOC_N(bytes);
43
+ embed_{parent_start,count,output_offset} are each ALLOC_N(ndim). */
44
+ return sizeof(CAWindow) + 6 * ca->ndim * sizeof(ca_size_t)
45
+ + ca->ndim * sizeof(uint8_t) + ca->bytes;
46
+ }
47
+
48
+ /* Pool framework hooks for CAWindow. CAWindow owns eight variable-size tail
49
+ fields; seven of them are ndim-sized and move into a single _pool buffer:
50
+
51
+ 6 * ndim ca_size_t : start, count, size0,
52
+ embed_parent_start, embed_count, embed_output_offset
53
+ 1 * ndim uint8_t : bounds
54
+
55
+ `dim` aliases `count` (no allocation). The eighth field, `fill`, is
56
+ `bytes`-sized (element width, not ndim) and the ndim-only pool_bytes/
57
+ pool_init signature cannot size it; it stays on its own ALLOC_N inside
58
+ ca_window_setup. CAShift shares this struct and reuses these hooks via
59
+ the ca_window_func copy in Init_ca_obj_shift. */
60
+ static size_t
61
+ ca_window_pool_bytes (int8_t ndim)
62
+ {
63
+ ca_size_t n = (ndim > 0) ? ndim : 1;
64
+ return 6 * (size_t) n * sizeof(ca_size_t) + (size_t) n * sizeof(uint8_t);
65
+ }
66
+
67
+ static void
68
+ ca_window_pool_init (void *ap, int8_t ndim)
69
+ {
70
+ CAWindow *ca = (CAWindow *) ap;
71
+ ca_size_t n = (ndim > 0) ? ndim : 1;
72
+ ca_size_t *base = (ca_size_t *) ca->_pool;
73
+ /* six ca_size_t arrays first (8-byte aligned), then the uint8_t bounds
74
+ array after them. */
75
+ ca->start = base + 0 * n;
76
+ ca->count = base + 1 * n;
77
+ ca->size0 = base + 2 * n;
78
+ ca->embed_parent_start = base + 3 * n;
79
+ ca->embed_count = base + 4 * n;
80
+ ca->embed_output_offset = base + 5 * n;
81
+ ca->bounds = (uint8_t *) (base + 6 * n);
82
+ ca->dim = ca->count; /* alias; ca_window_setup re-sets */
83
+ }
84
+
15
85
  const rb_data_type_t cawindow_data_type = {
16
- .parent = &cavirtual_data_type,
86
+ .parent = &caview_data_type,
17
87
  .wrap_struct_name = "CAWindow",
18
88
  .function = {
19
89
  .dmark = ca_mark,
20
90
  .dfree = ca_free,
21
- .dsize = NULL,
91
+ .dsize = ca_window_dsize,
22
92
  .dcompact = NULL
23
93
  },
24
94
  .flags = RUBY_TYPED_FREE_IMMEDIATELY
25
95
  };
26
96
 
97
+ /* CAREFUL: the mask TypedData must keep dfree = ca_free_nop. The mask CArray
98
+ is owned by the parent CAWindow's `ca->mask` field and freed by
99
+ free_ca_window's ca_free(ca->mask); if the wrapped Ruby VALUE (from the
100
+ `ca.mask` accessor / rb_ca_mask_array) freed it as well, GC stress would
101
+ double-free it. */
27
102
  const rb_data_type_t cawindow_mask_data_type = {
28
103
  .parent = &cawindow_data_type,
29
104
  .wrap_struct_name = "CAWindowMask",
30
105
  .function = {
31
106
  .dmark = ca_mark,
32
- .dfree = ca_free,
33
- .dsize = NULL,
107
+ .dfree = ca_free_nop,
108
+ .dsize = ca_window_dsize,
34
109
  .dcompact = NULL
35
110
  },
36
111
  .flags = RUBY_TYPED_FREE_IMMEDIATELY
@@ -38,26 +113,552 @@ const rb_data_type_t cawindow_mask_data_type = {
38
113
 
39
114
  int8_t CA_OBJ_WINDOW;
40
115
 
41
- VALUE rb_cCAWindow;
42
- VALUE rb_cCAWindowMask;
116
+ VALUE rb_cCAWindow;
117
+ VALUE rb_cCAWindowMask;
43
118
 
44
119
 
45
- /* yard:
46
- class CAWindow < CAVirtual # :nodoc:
47
- end
48
- */
120
+ /* ------------------------------------------------------------------- */
49
121
 
122
+ /* Computes the embed descriptor for a CAWindow / CAShift view. The
123
+ descriptor decomposes the view into "1 alias region (the part of parent
124
+ that is actually addressable) + 1 fill complement (the OOB part)".
125
+
126
+ Inputs: parent_dim[ndim], start[ndim], count[ndim]. step is
127
+ implicitly 1 for CAWindow (enforced in rb_ca_window).
128
+ bounds[] is not read here — the embed shape is policy-independent
129
+ (policy decides what to put in the fill complement, not which cells
130
+ are in the alias).
131
+
132
+ Outputs (written into ca->embed_*):
133
+ embed_parent_start[k] = max(0, start[k])
134
+ embed_count[k] = min(start[k]+count[k], parent_dim[k])
135
+ - embed_parent_start[k] (clamped to >= 0)
136
+ embed_output_offset[k]= embed_parent_start[k] - start[k]
137
+ embed_is_empty = 1 if any axis has embed_count[k] <= 0
138
+ embed_covers_all = 1 if every axis has start[k] >= 0
139
+ AND start[k]+count[k] <= parent_dim[k]
140
+
141
+ Called only through ca_window_recompute_embed, which also derives the
142
+ eligibility flags from the result. */
143
+ static void
144
+ ca_compute_embed_descriptor (int8_t ndim,
145
+ ca_size_t *parent_dim,
146
+ ca_size_t *start,
147
+ ca_size_t *count,
148
+ ca_size_t *embed_parent_start,
149
+ ca_size_t *embed_count,
150
+ ca_size_t *embed_output_offset,
151
+ uint8_t *embed_is_empty,
152
+ uint8_t *embed_covers_all)
153
+ {
154
+ int8_t k;
155
+ uint8_t any_empty = 0;
156
+ uint8_t all_interior = 1;
157
+
158
+ for ( k = 0; k < ndim; k++ ) {
159
+ ca_size_t s = start[k];
160
+ ca_size_t c = count[k];
161
+ ca_size_t pd = parent_dim[k];
162
+ ca_size_t ps, pe, oo, ec;
163
+
164
+ ps = (s > 0) ? s : 0; /* clamp low */
165
+ pe = (s + c < pd) ? (s + c) : pd;/* clamp high */
166
+ ec = pe - ps; /* alias count this axis */
167
+ if ( ec < 0 ) ec = 0; /* fully outside */
168
+ oo = ps - s; /* output-side offset */
169
+
170
+ embed_parent_start[k] = ps;
171
+ embed_count[k] = ec;
172
+ embed_output_offset[k] = oo;
173
+
174
+ if ( ec <= 0 ) any_empty = 1;
175
+ if ( s < 0 || s + c > pd ) all_interior = 0;
176
+ }
177
+
178
+ *embed_is_empty = any_empty;
179
+ *embed_covers_all = all_interior;
180
+ }
50
181
 
51
182
  /* ------------------------------------------------------------------- */
52
183
 
184
+ /* [MOVED] ca_fill_typed / ca_composite_region_gather / _scatter /
185
+ ca_composite_fill_complement -> ca_composite_dispatch.c (shared with
186
+ CATile / CAStack); reached via the include at the top of this file. */
187
+
188
+ /* Exposes CAWindow as a synthetic CAStride for compose-fold purposes. Used
189
+ by ca_stride_compose_to_root to walk *through* an interior-only CAWindow
190
+ without materialising it, enabling a full zero-copy chain when CAStride
191
+ family children wrap an interior-only CAWindow.
192
+
193
+ On success (interior-only + embed_eligible):
194
+ - synth_strides[k] = row-major byte stride of window->parent
195
+ (= bytes * Π window->parent->dim[k+1..ndim-1])
196
+ - synth_base = Σ embed_parent_start[k] * synth_strides[k]
197
+ (byte offset of embedded region start in
198
+ window->parent's byte space)
199
+ - synth_dim = pointer to window->dim (window's logical shape)
200
+ - synth_bytes = window->bytes
201
+ - synth_ndim = window->ndim
202
+ - next_parent = window->parent
203
+ Returns 1.
204
+
205
+ Caller assembles a CAStride struct from these fields and feeds it to
206
+ ca_stride_compose_through; the loop continues with next_parent.
207
+
208
+ Returns 0 if not foldable (= not a CAWindow / not embed_eligible /
209
+ not embed_covers_all). Callers must then break out of the
210
+ compose-fold loop and accept window as the root (= materialise via
211
+ ca_attach).
212
+
213
+ ndim invariant: synth_ndim equals window->ndim equals the leaf's
214
+ ndim by construction (CAStride children inherit ndim from parent).
215
+ No reshaping. */
216
+ int
217
+ ca_window_compose_fold (void *win_ap,
218
+ ca_size_t *synth_strides,
219
+ ca_size_t *synth_base,
220
+ ca_size_t **synth_dim,
221
+ ca_size_t *synth_bytes,
222
+ int8_t *synth_ndim,
223
+ CArray **next_parent)
224
+ {
225
+ CAWindow *w = (CAWindow *) win_ap;
226
+ ca_size_t s;
227
+ int8_t k;
228
+
229
+ if ( ! w->embed_eligible ) return 0;
230
+ if ( ! w->embed_covers_all ) return 0;
231
+
232
+ /* synth_strides = row-major byte stride over window->parent->dim */
233
+ s = w->bytes;
234
+ for ( k = w->ndim - 1; k >= 0; k-- ) {
235
+ synth_strides[k] = s;
236
+ s *= w->parent->dim[k];
237
+ }
238
+
239
+ /* synth_base = embedded region origin in window->parent's byte space */
240
+ *synth_base = 0;
241
+ for ( k = 0; k < w->ndim; k++ ) {
242
+ *synth_base += w->embed_parent_start[k] * synth_strides[k];
243
+ }
244
+
245
+ *synth_dim = w->dim;
246
+ *synth_bytes = w->bytes;
247
+ *synth_ndim = w->ndim;
248
+ *next_parent = w->parent;
249
+ return 1;
250
+ }
251
+
252
+ /* fold_stride slot: compose the fold state f (leaf coords in this window's
253
+ byte space) through the interior
254
+ window into window->parent's byte space. Synthesises a CAStride layer
255
+ for the window-over-parent mapping (ca_window_compose_fold) and composes
256
+ f through it (ca_stride_compose_through). Declines (returns 0) when the
257
+ window is not interior-only, making the window the fold boundary. */
258
+ static int
259
+ ca_window_func_fold_stride (void *ap, ca_fold_t *f, void **next_parent)
260
+ {
261
+ CAWindow *w = (CAWindow *) ap;
262
+ ca_size_t synth_strides[CA_RANK_MAX];
263
+ ca_size_t synth_base;
264
+ ca_size_t *synth_dim;
265
+ ca_size_t synth_bytes;
266
+ int8_t synth_ndim;
267
+ CArray *win_parent;
268
+ CAStride tmp, synth;
269
+ ca_size_t next_strides[CA_RANK_MAX];
270
+ ca_size_t next_base;
271
+ int8_t k;
272
+
273
+ if (!ca_window_compose_fold(w, synth_strides, &synth_base, &synth_dim,
274
+ &synth_bytes, &synth_ndim, &win_parent)) {
275
+ return 0;
276
+ }
277
+
278
+ tmp.ndim = f->ndim;
279
+ tmp.bytes = synth_bytes;
280
+ tmp.dim = f->counts; /* leaf extent in this window's space */
281
+ tmp.strides = f->strides;
282
+ tmp.base_offset = f->base;
283
+
284
+ synth.ndim = synth_ndim;
285
+ synth.bytes = synth_bytes;
286
+ synth.dim = synth_dim;
287
+ synth.strides = synth_strides;
288
+ synth.base_offset = synth_base;
289
+
290
+ if (!ca_stride_compose_through(&tmp, &synth, next_strides, &next_base)) {
291
+ return 0;
292
+ }
293
+
294
+ for (k = 0; k < f->ndim; k++) f->strides[k] = next_strides[k];
295
+ f->base = next_base;
296
+ *next_parent = win_parent;
297
+ return 1;
298
+ }
299
+
300
+ static void ca_window_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir);
301
+
302
+ /* xfer_stride: structural region delivery when a boundary-crossing CAWindow is
303
+ the (declining) fold boundary. The window fills the OOB cells itself and
304
+ hands the in-bound region to the parent via parent.xfer_stride, so no whole
305
+ view is materialised.
306
+
307
+ Structural path requires: FILL/MASK bounds (so OOB is a contiguous edge),
308
+ axis-aligned access, and unit src step (the natural window region / a
309
+ contiguous window slice). Other cases (PERIODIC/REFLECT/NEAREST bounds,
310
+ transposed / sub-sampled leaf) fall back to per-cell delivery (correct,
311
+ no whole-view attach). data is contiguous (semantics b). The wiring
312
+ guards ndim == window ndim. window.bytes == parent.bytes (no reinterpret). */
313
+ static void
314
+ ca_window_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
315
+ ca_size_t *strides, void *data, int dir)
316
+ {
317
+ CAWindow *w = (CAWindow *) ap;
318
+ CArray *parent = w->parent;
319
+ int8_t ndim = w->ndim;
320
+ int8_t inner = ndim - 1;
321
+ ca_size_t pnative[CA_RANK_MAX], wnative[CA_RANK_MAX], dstride[CA_RANK_MAX];
322
+ ca_size_t base_pos[CA_RANK_MAX]; /* window.start[k] + starts[k] (parent pos at o=0) */
323
+ ca_size_t lo[CA_RANK_MAX], hi[CA_RANK_MAX], o[CA_RANK_MAX];
324
+ ca_size_t s, n = 1, i;
325
+ int8_t k;
326
+ int structural = 1;
327
+ char *d = (char *) data;
328
+
329
+ s = parent->bytes;
330
+ for (k = ndim - 1; k >= 0; k--) { pnative[k] = s; s *= parent->dim[k]; }
331
+ s = w->bytes;
332
+ for (k = ndim - 1; k >= 0; k--) { wnative[k] = s; s *= w->dim[k]; }
333
+ s = w->bytes;
334
+ for (k = ndim - 1; k >= 0; k--) { dstride[k] = s; s *= counts[k]; }
335
+ for (k = 0; k < ndim; k++) n *= counts[k];
336
+
337
+ /* The request is over the view's addresses, so a transposed / flat request
338
+ is legal and must not be composed axis-by-axis; see
339
+ ca_xfer_stride_request_is_axis_box (carray.h). */
340
+ if ( ! ca_xfer_stride_request_is_axis_box(w, starts, counts, strides) ) {
341
+ structural = 0;
342
+ }
343
+ else {
344
+ for (k = 0; k < ndim; k++) {
345
+ if ( (w->bounds[k] != CA_BOUNDS_FILL && w->bounds[k] != CA_BOUNDS_MASK)
346
+ || strides[k] % wnative[k] != 0
347
+ || strides[k] / wnative[k] != 1 ) {
348
+ structural = 0;
349
+ break;
350
+ }
351
+ }
352
+ }
353
+
354
+ if (!structural) {
355
+ ca_size_t idx[CA_RANK_MAX], doff = 0, base = 0;
356
+ for (k = 0; k < ndim; k++) base += starts[k] * wnative[k];
357
+ for (k = 0; k < ndim; k++) idx[k] = 0;
358
+ while (1) {
359
+ ca_size_t woff = base, widx[CA_RANK_MAX];
360
+ for (k = 0; k < ndim; k++) woff += idx[k] * strides[k];
361
+ ca_addr2index((CArray *) w, woff / w->bytes, widx);
362
+ ca_window_func_xfer_index(w, widx, d + doff, dir);
363
+ doff += w->bytes;
364
+ k = ndim - 1;
365
+ while (k >= 0) { if (++idx[k] < counts[k]) break; idx[k] = 0; k--; }
366
+ if (k < 0) break;
367
+ }
368
+ return;
369
+ }
370
+
371
+ /* Intersect sub-region [starts, starts+counts) with the in-bound region.
372
+ lo[k]/hi[k] are offsets within the sub-region (= output-coordinate)
373
+ where the parent is in-bound. Same algebra as the per-row loop below,
374
+ hoisted up so the embed-based path can share it. */
375
+ for (k = 0; k < ndim; k++) {
376
+ ca_size_t l, h;
377
+ base_pos[k] = w->start[k] + starts[k];
378
+ l = -base_pos[k]; if (l < 0) l = 0;
379
+ h = w->size0[k] - base_pos[k]; if (h > counts[k]) h = counts[k];
380
+ if (h < 0) h = 0;
381
+ if (l > h) l = h;
382
+ lo[k] = l; hi[k] = h;
383
+ }
384
+
385
+ /* An unattached parent that ca_attach_is_alias accepts has memory to lend
386
+ and the attach neither allocates nor copies, so borrow it and re-enter
387
+ on the batched branch below. The recursion runs at most once: the
388
+ second entry sees parent->ptr != NULL. A parent with nothing to lend is
389
+ left alone — the per-row loop is the region protocol and must not be
390
+ traded for a whole-parent attach. */
391
+ if ( ! parent->ptr && ca_attach_is_alias(parent) ) {
392
+ ca_attach(parent);
393
+ if ( parent->ptr ) {
394
+ ca_window_func_xfer_stride(ap, starts, counts, strides, data, dir);
395
+ if ( dir == CA_XFER_PUT ) {
396
+ ca_sync(parent);
397
+ }
398
+ ca_detach(parent);
399
+ return;
400
+ }
401
+ ca_detach(parent);
402
+ }
403
+
404
+ /* Fast path: when the parent is attached (ptr != NULL), drive
405
+ ca_composite_region_* + fill_complement directly with the sub-region
406
+ intersection geometry. This collapses the outer per-row dispatch loop
407
+ (one ca_xfer_stride per inner row) into a single batched routine. An
408
+ unattached parent with nothing to lend falls through to the per-row loop
409
+ below, which is equivalent but slower. */
410
+ if ( parent->ptr ) {
411
+ ca_size_t any_empty = 0;
412
+ ca_size_t alias_parent_start[CA_RANK_MAX];
413
+ ca_size_t alias_count[CA_RANK_MAX];
414
+ ca_size_t alias_output_offset[CA_RANK_MAX];
415
+ ca_size_t parent_strides[CA_RANK_MAX];
416
+
417
+ for (k = 0; k < ndim; k++) {
418
+ if (lo[k] >= hi[k]) { any_empty = 1; break; }
419
+ alias_output_offset[k] = lo[k];
420
+ alias_count[k] = hi[k] - lo[k];
421
+ alias_parent_start[k] = base_pos[k] + lo[k];
422
+ }
423
+
424
+ if (dir == CA_XFER_GET) {
425
+ if (any_empty) {
426
+ ca_fill_typed(d, w->fill, w->bytes, n);
427
+ } else {
428
+ ca_composite_fill_complement(d, dstride, counts,
429
+ alias_output_offset, alias_count,
430
+ w->fill, w->bytes, ndim);
431
+ /* parent_strides = row-major byte stride over parent->dim */
432
+ s = parent->bytes;
433
+ for (k = ndim - 1; k >= 0; k--) { parent_strides[k] = s; s *= parent->dim[k]; }
434
+ ca_composite_region_gather(parent->ptr, parent_strides,
435
+ alias_parent_start,
436
+ d, dstride, alias_output_offset,
437
+ alias_count, ndim, w->bytes);
438
+ }
439
+ } else { /* CA_XFER_PUT */
440
+ /* complement silent skip (no parent storage for fill region) */
441
+ if (!any_empty) {
442
+ s = parent->bytes;
443
+ for (k = ndim - 1; k >= 0; k--) { parent_strides[k] = s; s *= parent->dim[k]; }
444
+ ca_composite_region_scatter(parent->ptr, parent_strides,
445
+ alias_parent_start,
446
+ d, dstride, alias_output_offset,
447
+ alias_count, ndim, w->bytes);
448
+ }
449
+ }
450
+ return;
451
+ }
452
+
453
+ /* Parent unattached: per-row outer loop + ca_xfer_stride(parent)
454
+ recursion. */
455
+ if (dir == CA_XFER_GET) {
456
+ for (i = 0; i < n; i++) memcpy(d + i * w->bytes, w->fill, w->bytes);
457
+ }
458
+
459
+ for (k = 0; k < ndim; k++) {
460
+ if (lo[k] >= hi[k]) return; /* no in-bound cells (GET: filled; PUT: skip) */
461
+ }
462
+
463
+ for (k = 0; k < ndim; k++) o[k] = lo[k];
464
+ while (1) {
465
+ ca_size_t pbase = 0, doff = 0;
466
+ ca_size_t pstarts[CA_RANK_MAX], pcounts[CA_RANK_MAX], pstrides[CA_RANK_MAX];
467
+ ca_size_t inner_pbase;
468
+ for (k = 0; k < inner; k++) {
469
+ pbase += (base_pos[k] + o[k]) * pnative[k];
470
+ doff += o[k] * dstride[k];
471
+ }
472
+ inner_pbase = (base_pos[inner] + lo[inner]) * pnative[inner];
473
+ ca_addr2index((CArray *) parent, (pbase + inner_pbase) / parent->bytes, pstarts);
474
+ for (k = 0; k < ndim; k++) { pcounts[k] = 1; pstrides[k] = 0; }
475
+ pcounts[inner] = hi[inner] - lo[inner];
476
+ pstrides[inner] = pnative[inner];
477
+ ca_xfer_stride(parent, pstarts, pcounts, pstrides,
478
+ d + doff + lo[inner] * dstride[inner], dir);
479
+
480
+ k = inner - 1;
481
+ while (k >= 0) { o[k]++; if (o[k] < hi[k]) break; o[k] = lo[k]; k--; }
482
+ if (k < 0) break;
483
+ }
484
+ }
485
+
486
+ /* Executes the "1 typed fill + 1 strided memcpy" embed path on a
487
+ pre-allocated output buffer. Requires the parent to be attached (the
488
+ caller's responsibility).
489
+
490
+ Pre-conditions (must hold; not validated here):
491
+ - ca->embed_eligible == 1 (all axes FILL or MASK)
492
+ - ca->parent->ptr != NULL (parent attached)
493
+ - out_ptr points to ca->elements * ca->bytes writable bytes
494
+
495
+ Behaviour:
496
+ - if embed_covers_all: skip fill (alias overwrites every cell)
497
+ - else: fill the entire output with ca->fill (typed loop)
498
+ - if embed_is_empty: skip alias copy
499
+ - else: strided memcpy from parent[embed_parent_*] rectangle to
500
+ out_ptr[embed_output_*] rectangle. Inner contig run =
501
+ embed_count[ndim-1] * bytes. Outer axes iterate row-major
502
+ via a multi-dim cursor; per-axis offsets are precomputed in
503
+ bytes for both parent and output.
504
+
505
+ The strided copy itself is ca_composite_region_gather
506
+ (ca_composite_dispatch.c), shared with CATile / CAStack. */
507
+ static void
508
+ ca_window_attach_embed (CAWindow *ca, char *out_ptr)
509
+ {
510
+ int8_t ndim = ca->ndim;
511
+ ca_size_t bytes = ca->bytes;
512
+ ca_size_t parent_strides[CA_RANK_MAX];
513
+ ca_size_t output_strides[CA_RANK_MAX];
514
+ ca_size_t s;
515
+ int8_t k;
516
+
517
+ /* Row-major byte strides for output (used by fill-complement and
518
+ region-gather both). */
519
+ s = bytes;
520
+ for ( k = ndim - 1; k >= 0; k-- ) {
521
+ output_strides[k] = s;
522
+ s *= ca->count[k];
523
+ }
524
+
525
+ /* Step 1: fill the complement of the alias rectangle only — filling the
526
+ whole output first would write the alias area twice. For
527
+ embed_is_empty (= alias is empty, whole output is fill) short-circuit
528
+ to a full-output typed fill since the complement is the whole output;
529
+ for embed_covers_all (= alias is the whole output, no fill) skip. */
530
+ if ( ! ca->embed_covers_all ) {
531
+ if ( ca->embed_is_empty ) {
532
+ ca_fill_typed(out_ptr, ca->fill, bytes, ca->elements);
533
+ } else {
534
+ ca_composite_fill_complement(out_ptr, output_strides, ca->count,
535
+ ca->embed_output_offset,
536
+ ca->embed_count,
537
+ ca->fill, bytes, ndim);
538
+ }
539
+ }
540
+
541
+ /* Step 2: strided memcpy from parent alias rectangle to output rectangle. */
542
+ if ( ca->embed_is_empty ) return;
543
+
544
+ s = bytes;
545
+ for ( k = ndim - 1; k >= 0; k-- ) {
546
+ parent_strides[k] = s;
547
+ s *= ca->parent->dim[k];
548
+ }
549
+
550
+ ca_composite_region_gather(ca->parent->ptr, parent_strides,
551
+ ca->embed_parent_start,
552
+ out_ptr, output_strides,
553
+ ca->embed_output_offset,
554
+ ca->embed_count, ndim, bytes);
555
+ }
556
+
557
+ /* Executes the reverse of ca_window_attach_embed on a caller-provided source
558
+ buffer. Strided memcpy from the alias sub-rectangle of in_ptr (= the
559
+ view's data layout) back into the parent's alias rectangle. The fill
560
+ region is ignored: writes in the OOB area have no parent cell to land in
561
+ and are dropped, matching ca_axis_dispatch_scatter's SHIFT-kind OOB.
562
+
563
+ Pre-conditions:
564
+ - ca->embed_eligible == 1
565
+ - ca->parent->ptr != NULL (parent attached)
566
+ - in_ptr points to ca->elements * ca->bytes valid bytes
567
+
568
+ The reverse strided copy is ca_composite_region_scatter
569
+ (ca_composite_dispatch.c), shared with the CATile / CAStack sync paths. */
570
+ static void
571
+ ca_window_sync_embed (CAWindow *ca, char *in_ptr)
572
+ {
573
+ int8_t ndim = ca->ndim;
574
+ ca_size_t bytes = ca->bytes;
575
+ ca_size_t parent_strides[CA_RANK_MAX];
576
+ ca_size_t input_strides[CA_RANK_MAX];
577
+ ca_size_t s;
578
+ int8_t k;
579
+
580
+ if ( ca->embed_is_empty ) return; /* nothing of in_ptr maps back */
581
+
582
+ /* Row-major byte strides for parent and the view-shaped source. */
583
+ s = bytes;
584
+ for ( k = ndim - 1; k >= 0; k-- ) {
585
+ parent_strides[k] = s;
586
+ s *= ca->parent->dim[k];
587
+ }
588
+ s = bytes;
589
+ for ( k = ndim - 1; k >= 0; k-- ) {
590
+ input_strides[k] = s;
591
+ s *= ca->count[k];
592
+ }
593
+
594
+ ca_composite_region_scatter(ca->parent->ptr, parent_strides,
595
+ ca->embed_parent_start,
596
+ in_ptr, input_strides,
597
+ ca->embed_output_offset,
598
+ ca->embed_count, ndim, bytes);
599
+ }
600
+
601
+ /* ------------------------------------------------------------------- */
602
+
603
+ /* Recomputes the embed descriptor + eligibility flags from the current
604
+ (start, count, bounds). The recompute is unconditional — no eligibility
605
+ pre-check — so callers never have to reason about which flags are stale.
606
+ Called by ca_window_setup.
607
+ *
608
+ * CAREFUL: any code path that mutates ca->start[] in place (e.g.
609
+ * ca_window_move, which is why this is not static) must call this
610
+ * afterwards. Otherwise embed_* keeps describing the OLD start and the
611
+ * embed-model attach / copy_data reads the wrong parent rectangle — wrong
612
+ * data, no error. */
613
+ void
614
+ ca_window_recompute_embed (CAWindow *ca)
615
+ {
616
+ int8_t ndim = ca->ndim;
617
+ int8_t i;
618
+
619
+ ca_compute_embed_descriptor(ndim, ca->parent->dim, ca->start, ca->count,
620
+ ca->embed_parent_start,
621
+ ca->embed_count,
622
+ ca->embed_output_offset,
623
+ &ca->embed_is_empty,
624
+ &ca->embed_covers_all);
625
+
626
+ /* embed path eligibility = all axes use FILL or MASK bounds. */
627
+ ca->embed_eligible = 1;
628
+ for (i = 0; i < ndim; i++) {
629
+ if ( ca->bounds[i] != CA_BOUNDS_FILL
630
+ && ca->bounds[i] != CA_BOUNDS_MASK ) {
631
+ ca->embed_eligible = 0;
632
+ break;
633
+ }
634
+ }
635
+
636
+ /* direct-attach alias eligibility (see ca_window_func_attach). */
637
+ ca->embed_alias_eligible = 0;
638
+ if ( ca->embed_eligible && ca->embed_covers_all ) {
639
+ int alias_ok = 1;
640
+ for (i = 1; i < ndim; i++) {
641
+ if ( ca->start[i] != 0 || ca->count[i] != ca->parent->dim[i] ) {
642
+ alias_ok = 0;
643
+ break;
644
+ }
645
+ }
646
+ if ( alias_ok ) ca->embed_alias_eligible = 1;
647
+ }
648
+ }
649
+
650
+ /* `bounds` is a per-axis uint8_t array. CAWindow's Ruby surface
651
+ (rb_ca_window) receives a single scalar policy and fans it out to all axes;
652
+ the per-axis form is what lets CAShift be a CAWindow specialisation. */
53
653
  int
54
654
  ca_window_setup (CAWindow *ca, CArray *parent,
55
- ca_size_t *start, ca_size_t *count, int8_t bounds, char *fill)
655
+ ca_size_t *start, ca_size_t *count, uint8_t *bounds, char *fill)
56
656
  {
57
657
  int8_t data_type, ndim;
58
658
  ca_size_t *dim;
59
659
  ca_size_t bytes, elements;
60
660
  int i;
661
+ int any_mask;
61
662
 
62
663
  data_type = parent->data_type;
63
664
  ndim = parent->ndim;
@@ -86,18 +687,34 @@ ca_window_setup (CAWindow *ca, CArray *parent,
86
687
  ca->parent = parent;
87
688
  ca->attach = 0;
88
689
  ca->nosync = 0;
89
- ca->bounds = bounds;
90
- ca->start = ALLOC_N(ca_size_t, ndim);
91
- ca->count = ALLOC_N(ca_size_t, ndim);
92
- ca->size0 = ALLOC_N(ca_size_t, ndim);
690
+ /* Pool path: bounds/start/count/size0/embed_* are already wired into
691
+ ca->_pool by ca_window_pool_init. Legacy path: ALLOC_N each. `fill`
692
+ is bytes-sized (not ndim) so it stays on its own ALLOC_N in both
693
+ branches. */
694
+ if ( ! ca->_pool ) {
695
+ ca->bounds = ALLOC_N(uint8_t, ndim);
696
+ ca->start = ALLOC_N(ca_size_t, ndim);
697
+ ca->count = ALLOC_N(ca_size_t, ndim);
698
+ ca->size0 = ALLOC_N(ca_size_t, ndim);
699
+ ca->embed_parent_start = ALLOC_N(ca_size_t, ndim);
700
+ ca->embed_count = ALLOC_N(ca_size_t, ndim);
701
+ ca->embed_output_offset = ALLOC_N(ca_size_t, ndim);
702
+ }
93
703
  ca->fill = ALLOC_N(char, ca->bytes);
704
+ ca->embed_is_empty = 0;
705
+ ca->embed_covers_all = 0;
94
706
 
95
707
  ca->dim = ca->count;
96
708
 
709
+ memcpy(ca->bounds, bounds, ndim * sizeof(uint8_t));
97
710
  memcpy(ca->start, start, ndim * sizeof(ca_size_t));
98
711
  memcpy(ca->count, count, ndim * sizeof(ca_size_t));
99
712
  memcpy(ca->size0, dim, ndim * sizeof(ca_size_t));
100
713
 
714
+ /* Compute the embed descriptor + eligibility flags from the current
715
+ (start, count, bounds). See ca_window_recompute_embed below. */
716
+ ca_window_recompute_embed(ca);
717
+
101
718
  if ( fill ) {
102
719
  memcpy(ca->fill, fill, ca->bytes);
103
720
  }
@@ -110,7 +727,15 @@ ca_window_setup (CAWindow *ca, CArray *parent,
110
727
  }
111
728
  }
112
729
 
113
- if ( ca->bounds == CA_BOUNDS_MASK ) {
730
+ /* Mask is needed if ANY axis uses MASK policy (per-axis). */
731
+ any_mask = 0;
732
+ for (i=0; i<ndim; i++) {
733
+ if ( ca->bounds[i] == CA_BOUNDS_MASK ) {
734
+ any_mask = 1;
735
+ break;
736
+ }
737
+ }
738
+ if ( any_mask ) {
114
739
  ca_create_mask(ca);
115
740
  }
116
741
 
@@ -119,9 +744,9 @@ ca_window_setup (CAWindow *ca, CArray *parent,
119
744
 
120
745
  CAWindow *
121
746
  ca_window_new (CArray *parent,
122
- ca_size_t *start, ca_size_t *count, int8_t bounds, char *fill)
747
+ ca_size_t *start, ca_size_t *count, uint8_t *bounds, char *fill)
123
748
  {
124
- CAWindow *ca = ALLOC(CAWindow);
749
+ CAWindow *ca = (CAWindow *) ca_array_alloc(CA_OBJ_WINDOW, parent->ndim);
125
750
  ca_window_setup(ca, parent, start, count, bounds, fill);
126
751
  return ca;
127
752
  }
@@ -132,18 +757,38 @@ free_ca_window (void *ap)
132
757
  CAWindow *ca = (CAWindow *) ap;
133
758
  if ( ca != NULL ) {
134
759
  ca_free(ca->mask);
135
- xfree(ca->fill);
136
- xfree(ca->start);
137
- xfree(ca->count);
138
- xfree(ca->size0);
139
- /* xfree(ca->dim); */
140
- xfree(ca);
760
+ xfree(ca->fill); /* always separate (bytes-sized) */
761
+ if ( ca->_pool ) {
762
+ /* bounds/start/count/size0/embed_* all live in ca->_pool. */
763
+ ca_array_free(ca); /* one xfree pool + one xfree struct */
764
+ }
765
+ else {
766
+ xfree(ca->bounds);
767
+ xfree(ca->start);
768
+ xfree(ca->count);
769
+ xfree(ca->size0);
770
+ xfree(ca->embed_parent_start);
771
+ xfree(ca->embed_count);
772
+ xfree(ca->embed_output_offset);
773
+ /* xfree(ca->dim); */
774
+ xfree(ca);
775
+ }
141
776
  }
142
777
  }
143
778
 
144
- static void ca_window_attach (CAWindow *ca);
145
- static void ca_window_sync (CAWindow *ca);
146
- static void ca_window_fill (CAWindow *ca, char *ptr);
779
+ /* Path selection for attach / sync / copy_data / sync_data below.
780
+
781
+ embed_eligible (= all axes use FILL or MASK bounds) takes the embed path:
782
+ "1 typed fill + 1 strided memcpy" with no per-cell bound check (see
783
+ ca_window_attach_embed / ca_window_sync_embed above).
784
+
785
+ Everything else (PERIODIC — still reached through CAShift's roll form —
786
+ REFLECT / NEAREST / RUBY / STRICT) goes through the descriptor engine,
787
+ ca_axis_dispatch_* fed by ca_window_describe_axes (defined below), which
788
+ still promotes interior axes to STRIDE kind.
789
+
790
+ fill_data is the exception: it has its own split (see
791
+ ca_window_func_fill_data). */
147
792
 
148
793
  /* ------------------------------------------------------------------- */
149
794
 
@@ -154,57 +799,11 @@ ca_window_func_clone (void *ap)
154
799
  return ca_window_new(ca->parent, ca->start, ca->count, ca->bounds, ca->fill);
155
800
  }
156
801
 
157
- static char *
158
- ca_window_func_ptr_at_index (void *ap, ca_size_t *idx) ;
159
-
160
- static char *
161
- ca_window_func_ptr_at_addr (void *ap, ca_size_t addr)
162
- {
163
- CAWindow *ca = (CAWindow *) ap;
164
- if ( ca->ptr ) {
165
- return ca->ptr + ca->bytes * addr;
166
- }
167
- else {
168
- ca_size_t idx[CA_RANK_MAX];
169
- ca_addr2index((CArray *)ca, addr, idx);
170
- return ca_window_func_ptr_at_index(ca, idx);
171
- }
172
- }
173
-
174
- static char *
175
- ca_window_func_ptr_at_index (void *ap, ca_size_t *idx)
176
- {
177
- ca_size_t k;
178
- CAWindow *ca = (CAWindow *) ap;
179
- if ( ! ca->ptr ) {
180
- ca_size_t *start = ca->start;
181
- ca_size_t *size0 = ca->size0;
182
- int8_t i;
183
- ca_size_t n;
184
- n = 0;
185
- for (i=0; i<ca->ndim; i++) {
186
- k = start[i] + idx[i];
187
- k = ca_bounds_normalize_index(ca->bounds, size0[i], k);
188
- if ( k < 0 || k >= size0[i] ) {
189
- return ca->fill;
190
- }
191
- n = size0[i] * n + k;
192
- }
193
-
194
- if ( ! ca->parent->ptr ) {
195
- return ca_ptr_at_addr(ca->parent, n);
196
- }
197
- else {
198
- return ca->parent->ptr + ca->bytes * n;
199
- }
200
- }
201
- else {
202
- return ca_func[CA_OBJ_ARRAY].ptr_at_index(ca, idx);
203
- }
204
- }
205
-
802
+ /* Per-cell access. GET and PUT share the bound-normalised index walk; an OOB
803
+ cell is filled on GET and skipped on PUT. CAShift inherits this via the
804
+ ca_shift_func copy of the operation table. */
206
805
  static void
207
- ca_window_func_fetch_index (void *ap, ca_size_t *idx, void *ptr)
806
+ ca_window_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
208
807
  {
209
808
  CAWindow *ca = (CAWindow *) ap;
210
809
  ca_size_t *start = ca->start;
@@ -214,34 +813,143 @@ ca_window_func_fetch_index (void *ap, ca_size_t *idx, void *ptr)
214
813
  ca_size_t k;
215
814
  for (i=0; i<ca->ndim; i++) {
216
815
  k = start[i] + idx[i];
217
- k = ca_bounds_normalize_index(ca->bounds, size0[i], k);
816
+ k = ca_bounds_normalize_index(ca->bounds[i], size0[i], k);
218
817
  if ( k < 0 || k >= size0[i] ) {
219
- memcpy(ptr, ca->fill, ca->bytes);
220
- return;
818
+ if ( dir == CA_XFER_GET ) memcpy(data, ca->fill, ca->bytes);
819
+ return; /* PUT to out-of-bounds cell: skip */
221
820
  }
222
821
  idx0[i] = k;
223
822
  }
224
- ca_fetch_index(ca->parent, idx0, ptr);
823
+ ca_xfer_index(ca->parent, idx0, data, dir);
225
824
  }
226
825
 
826
+ /* Batched address gather/scatter. Bound-normalises each view addr; OOB cells are handled inline (GET fills,
827
+ PUT skips) and the in-bounds cells are delivered to the parent in ONE
828
+ ca_xfer_addrs call. When some cells are OOB the in-bounds set is packed
829
+ into a contiguous temp (the parent's contig-buf contract), gathered/
830
+ scattered, then unpacked. No whole-view attach.
831
+ CAShift inherits via ca_shift_func copy. */
227
832
  static void
228
- ca_window_func_store_index (void *ap, ca_size_t *idx, void *ptr)
833
+ ca_window_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
834
+ void *data, int dir)
229
835
  {
230
836
  CAWindow *ca = (CAWindow *) ap;
231
837
  ca_size_t *start = ca->start;
232
838
  ca_size_t *size0 = ca->size0;
233
- ca_size_t idx0[CA_RANK_MAX];
234
- int8_t i;
235
- ca_size_t k;
236
- for (i=0; i<ca->ndim; i++) {
237
- k = start[i] + idx[i];
238
- k = ca_bounds_normalize_index(ca->bounds, size0[i], k);
239
- if ( k < 0 || k >= size0[i] ) {
839
+ char *d = (char *) data;
840
+ ca_size_t *paddrs;
841
+ ca_size_t *pos;
842
+ ca_size_t m = 0, i, base;
843
+ int8_t k;
844
+ volatile VALUE h1, h2;
845
+
846
+ /* Fast path: an embed-eligible window (interior alias rectangle + OOB
847
+ strips for FILL mode) over a parent that resolves to a ptr-bearing root.
848
+ Whole-view sequential addrs let us drive ca_composite_region_gather /
849
+ ca_composite_fill_complement directly (= the same helpers
850
+ ca_window_attach_embed / _sync_embed use), skipping the per-cell
851
+ bounds-normalise + OOB-pack two-pass. Covers both interior-only windows
852
+ and boundary-crossing ones (CAShift included): the OOB strip fill is
853
+ batched through ca_composite_fill_complement. */
854
+ if ( ca->embed_eligible
855
+ && n == ca->elements
856
+ && ca_xfer_addrs_is_sequential_run(n, addrs, &base) && base == 0 ) {
857
+ CArray *eff_parent = ca_resolve_attached_root_via_identity(ca->parent);
858
+ if ( eff_parent->ptr ) {
859
+ ca_size_t output_strides[CA_RANK_MAX];
860
+ ca_size_t parent_strides[CA_RANK_MAX];
861
+ ca_size_t s;
862
+ s = ca->bytes;
863
+ for ( k = ca->ndim - 1; k >= 0; k-- ) {
864
+ output_strides[k] = s;
865
+ s *= ca->count[k];
866
+ }
867
+ s = eff_parent->bytes;
868
+ for ( k = ca->ndim - 1; k >= 0; k-- ) {
869
+ parent_strides[k] = s;
870
+ s *= ca->parent->dim[k]; /* logical shape from immediate parent */
871
+ }
872
+ if ( dir == CA_XFER_GET ) {
873
+ /* Step 1: fill OOB complement (no-op when covers_all). */
874
+ if ( ! ca->embed_covers_all ) {
875
+ if ( ca->embed_is_empty ) {
876
+ ca_fill_typed((char *) data, ca->fill, ca->bytes, ca->elements);
877
+ } else {
878
+ ca_composite_fill_complement((char *) data, output_strides, ca->count,
879
+ ca->embed_output_offset,
880
+ ca->embed_count,
881
+ ca->fill, ca->bytes, ca->ndim);
882
+ }
883
+ }
884
+ /* Step 2: gather interior alias rectangle from parent. */
885
+ if ( ! ca->embed_is_empty ) {
886
+ ca_composite_region_gather(eff_parent->ptr, parent_strides,
887
+ ca->embed_parent_start,
888
+ (char *) data, output_strides,
889
+ ca->embed_output_offset,
890
+ ca->embed_count, ca->ndim, ca->bytes);
891
+ }
892
+ } else { /* CA_XFER_PUT */
893
+ /* Scatter the input rectangle back to the parent's alias
894
+ rectangle. Writes to OOB cells are dropped — same as
895
+ ca_window_sync_embed and the engine's SHIFT-kind scatter. */
896
+ if ( ! ca->embed_is_empty ) {
897
+ ca_composite_region_scatter(eff_parent->ptr, parent_strides,
898
+ ca->embed_parent_start,
899
+ (char *) data, output_strides,
900
+ ca->embed_output_offset,
901
+ ca->embed_count, ca->ndim, ca->bytes);
902
+ }
903
+ }
240
904
  return;
241
905
  }
242
- idx0[i] = k;
243
906
  }
244
- ca_store_index(ca->parent, idx0, ptr);
907
+
908
+ paddrs = ALLOCV_N(ca_size_t, h1, n);
909
+ pos = ALLOCV_N(ca_size_t, h2, n);
910
+
911
+ for (i = 0; i < n; i++) {
912
+ ca_size_t vidx[CA_RANK_MAX], pidx[CA_RANK_MAX];
913
+ int oob = 0;
914
+ ca_addr2index((CArray *) ca, addrs[i], vidx);
915
+ for (k = 0; k < ca->ndim; k++) {
916
+ ca_size_t kk = start[k] + vidx[k];
917
+ kk = ca_bounds_normalize_index(ca->bounds[k], size0[k], kk);
918
+ if (kk < 0 || kk >= size0[k]) { oob = 1; break; }
919
+ pidx[k] = kk;
920
+ }
921
+ if (oob) {
922
+ if (dir == CA_XFER_GET) memcpy(d + i * ca->bytes, ca->fill, ca->bytes);
923
+ /* PUT to OOB cell: skip */
924
+ }
925
+ else {
926
+ paddrs[m] = ca_index2addr(ca->parent, pidx);
927
+ pos[m] = i;
928
+ m++;
929
+ }
930
+ }
931
+
932
+ if (m == n) { /* no OOB: deliver in place, contiguous */
933
+ ca_xfer_addrs(ca->parent, n, paddrs, data, dir);
934
+ }
935
+ else if (m > 0) { /* some OOB: pack the in-bounds set */
936
+ volatile VALUE h3;
937
+ char *packed = ALLOCV_N(char, h3, m * ca->bytes);
938
+ if (dir == CA_XFER_GET) {
939
+ ca_xfer_addrs(ca->parent, m, paddrs, packed, CA_XFER_GET);
940
+ for (i = 0; i < m; i++)
941
+ memcpy(d + pos[i] * ca->bytes, packed + i * ca->bytes, ca->bytes);
942
+ }
943
+ else {
944
+ for (i = 0; i < m; i++)
945
+ memcpy(packed + i * ca->bytes, d + pos[i] * ca->bytes, ca->bytes);
946
+ ca_xfer_addrs(ca->parent, m, paddrs, packed, CA_XFER_PUT);
947
+ }
948
+ ALLOCV_END(h3);
949
+ }
950
+
951
+ ALLOCV_END(h2);
952
+ ALLOCV_END(h1);
245
953
  }
246
954
 
247
955
  static void
@@ -250,26 +958,66 @@ ca_window_func_allocate (void *ap)
250
958
  CAWindow *ca = (CAWindow *) ap;
251
959
  ca_attach(ca->parent);
252
960
  /* ca->ptr = ALLOC_N(char, ca_length(ca)); */
253
- ca->ptr = malloc_with_check(ca_length(ca));
961
+ ca->ptr = xmalloc(ca_length(ca));
254
962
  }
255
963
 
964
+ /* The engine paths below emit a per-axis descriptor (ca_window_describe_axes)
965
+ and let the shared engine's SHIFT-kind handling apply the boundary policy
966
+ and the OOB cell fill. CAShift inherits all of it through
967
+ ca_shift_func = a copy of ca_window_func. */
968
+
969
+ void ca_window_describe_axes (void *ap, ca_axis_desc_t *out,
970
+ ca_size_t *out_parent_dims);
971
+
256
972
  static void
257
973
  ca_window_func_attach (void *ap)
258
974
  {
259
- void ca_window_attach (CAWindow *cb);
260
-
261
975
  CAWindow *ca = (CAWindow *) ap;
262
976
  ca_attach(ca->parent);
263
- /* ca->ptr = ALLOC_N(char, ca_length(ca)); */
264
- ca->ptr = malloc_with_check(ca_length(ca));
265
- ca_window_attach(ca);
977
+ if ( ca->embed_alias_eligible ) {
978
+ /* Alias path: inner axes full + interior, so the embedded region is a
979
+ contiguous run of parent storage. Skip malloc/memcpy and point
980
+ ca->ptr into the parent's buffer.
981
+ CAREFUL: sync and detach must agree with this — sync has nothing to
982
+ scatter back (the writes already landed in the parent) and detach must
983
+ not xfree a pointer it does not own. */
984
+ ca_size_t parent_row_stride = ca->bytes;
985
+ int8_t k;
986
+ for (k = ca->ndim - 1; k >= 1; k--) parent_row_stride *= ca->parent->dim[k];
987
+ ca->ptr = ca->parent->ptr + ca->start[0] * parent_row_stride;
988
+ } else if ( ca->embed_eligible ) {
989
+ /* Embed path: allocate, then 1 fill + 1 strided memcpy. */
990
+ ca_size_t out_len = ca->elements * ca->bytes;
991
+ ca->ptr = xmalloc(out_len > 0 ? out_len : 1);
992
+ ca_window_attach_embed(ca, ca->ptr);
993
+ } else {
994
+ /* Fallback: PERIODIC / REFLECT / NEAREST / RUBY / STRICT go through
995
+ the descriptor engine. */
996
+ ca_axis_desc_t desc[CA_RANK_MAX];
997
+ ca_size_t pdims[CA_RANK_MAX];
998
+ ca_window_describe_axes(ca, desc, pdims);
999
+ ca->ptr = ca_axis_dispatch_attach(ca->parent, pdims, desc, ca->ndim,
1000
+ ca->bytes, ca->elements, ca->fill);
1001
+ }
266
1002
  }
267
1003
 
268
1004
  static void
269
1005
  ca_window_func_sync (void *ap)
270
1006
  {
271
1007
  CAWindow *ca = (CAWindow *) ap;
272
- ca_window_sync(ca);
1008
+ if ( ca->embed_alias_eligible ) {
1009
+ /* Alias path: ca->ptr aliases the parent, so the writes already landed
1010
+ in parent storage. Nothing to scatter back. */
1011
+ } else if ( ca->embed_eligible ) {
1012
+ /* Embed path: write back the alias region only. */
1013
+ ca_window_sync_embed(ca, ca->ptr);
1014
+ } else {
1015
+ ca_axis_desc_t desc[CA_RANK_MAX];
1016
+ ca_size_t pdims[CA_RANK_MAX];
1017
+ ca_window_describe_axes(ca, desc, pdims);
1018
+ ca_axis_dispatch_scatter(ca->parent, pdims, desc, ca->ndim, ca->bytes,
1019
+ ca->elements, ca->ptr);
1020
+ }
273
1021
  ca_sync(ca->parent);
274
1022
  }
275
1023
 
@@ -277,42 +1025,219 @@ static void
277
1025
  ca_window_func_detach (void *ap)
278
1026
  {
279
1027
  CAWindow *ca = (CAWindow *) ap;
280
- free(ca->ptr);
1028
+ /* Alias path: ca->ptr aliases parent storage; it is not ours to xfree. */
1029
+ if ( ! ca->embed_alias_eligible ) {
1030
+ xfree(ca->ptr);
1031
+ }
281
1032
  ca->ptr = NULL;
282
1033
  ca_detach(ca->parent);
283
1034
  }
284
1035
 
1036
+ /* Fast path body for xfer_all, shared by the warm and the cold-parent case.
1037
+ Both the embed path and the engine dispatch read ca->parent->ptr directly,
1038
+ so the caller must have made it available.
1039
+
1040
+ CAREFUL: neither this nor ca_window_func_xfer_all may call
1041
+ ca_attach(parent) on a parent that would have to materialise to answer.
1042
+ A transfer slot that silently does that duplicates the whole parent
1043
+ behind the caller's back — the cold case below instead materialises a
1044
+ parent-shaped scratch through ca_xfer_all, which recurses under the
1045
+ same rule.
1046
+
1047
+ The exception is a parent that ca_attach_is_alias accepts: there the
1048
+ attach hands back parent (or root) memory that already exists, so
1049
+ nothing is allocated or copied and the rule has nothing to protect.
1050
+ ca_window_func_fill_data draws the same line. */
285
1051
  static void
286
- ca_window_func_copy_data (void *ap, void *ptr)
1052
+ ca_window_func_run_fast_path (CAWindow *ca, char *data, int dir)
287
1053
  {
288
- CAWindow *ca = (CAWindow *) ap;
289
- char *ptr0 = ca->ptr;
290
- ca_attach(ca->parent);
291
- ca->ptr = ptr;
292
- ca_window_attach(ca);
293
- ca->ptr = ptr0;
294
- ca_detach(ca->parent);
1054
+ if ( dir == CA_XFER_GET ) {
1055
+ if ( ca->embed_eligible ) {
1056
+ ca_window_attach_embed(ca, data);
1057
+ } else {
1058
+ ca_axis_desc_t desc[CA_RANK_MAX];
1059
+ ca_size_t pdims[CA_RANK_MAX];
1060
+ ca_window_describe_axes(ca, desc, pdims);
1061
+ ca_axis_dispatch_gather(ca->parent, pdims, desc, ca->ndim, ca->bytes,
1062
+ ca->elements, ca->fill, data);
1063
+ }
1064
+ } else {
1065
+ if ( ca->embed_eligible ) {
1066
+ ca_window_sync_embed(ca, data);
1067
+ } else {
1068
+ ca_axis_desc_t desc[CA_RANK_MAX];
1069
+ ca_size_t pdims[CA_RANK_MAX];
1070
+ ca_window_describe_axes(ca, desc, pdims);
1071
+ ca_axis_dispatch_scatter(ca->parent, pdims, desc, ca->ndim, ca->bytes,
1072
+ ca->elements, data);
1073
+ }
1074
+ }
295
1075
  }
296
1076
 
297
1077
  static void
298
- ca_window_func_sync_data (void *ap, void *ptr)
1078
+ ca_window_func_xfer_all (void *ap, void *data, int dir)
299
1079
  {
300
1080
  CAWindow *ca = (CAWindow *) ap;
301
- char *ptr0 = ca->ptr;
302
- ca_attach(ca->parent);
303
- ca->ptr = ptr;
304
- ca_window_sync(ca);
305
- ca->ptr = ptr0;
306
- ca_sync(ca->parent);
307
- ca_detach(ca->parent);
1081
+ if ( ca->parent->ptr ) {
1082
+ ca_window_func_run_fast_path(ca, (char *) data, dir);
1083
+ return;
1084
+ }
1085
+ /* Cold parent that has memory to lend (entity, or a CAStride-family view
1086
+ whose composed strides alias a ptr-bearing root): borrow it. The attach
1087
+ costs no allocation and no copy, and on the alias path ca->parent->ptr
1088
+ points into the root, so a PUT lands where the scratch path would have
1089
+ had to copy it back. Without this the window duplicates the entire
1090
+ parent on every transfer, which is what an ordinary `a[nil, nil]`,
1091
+ `a.refer` or `a.transpose.transpose` parent used to pay. */
1092
+ if ( ca_attach_is_alias(ca->parent) ) {
1093
+ ca_attach(ca->parent);
1094
+ ca_window_func_run_fast_path(ca, (char *) data, dir);
1095
+ if ( dir == CA_XFER_PUT ) {
1096
+ ca_sync(ca->parent);
1097
+ }
1098
+ ca_detach(ca->parent);
1099
+ return;
1100
+ }
1101
+ /* Second chance: ca_attach_is_alias reads the leaf view's own strides, so
1102
+ it declines a chain that is only contiguous once composed (a.transpose
1103
+ .transpose is the plain case). ca_resolve_attached_root_via_identity
1104
+ folds the chain and answers the same question about the root, and when
1105
+ it succeeds the parent's flat byte addressing IS the root's — so the
1106
+ root ptr can stand in for the parent's exactly as the scratch does
1107
+ below, with no copy in either direction. ca_window_func_xfer_addrs
1108
+ already resolves its parent this way. */
1109
+ {
1110
+ CArray *root = ca_resolve_attached_root_via_identity(ca->parent);
1111
+ if ( root != ca->parent && root->ptr ) {
1112
+ CArray *parent = ca->parent;
1113
+ parent->ptr = root->ptr;
1114
+ ca_window_func_run_fast_path(ca, (char *) data, dir);
1115
+ parent->ptr = NULL;
1116
+ return;
1117
+ }
1118
+ }
1119
+
1120
+ /* Cold parent with nothing to lend: materialise it into a scratch buffer
1121
+ via ca_xfer_all, then run the normal fast path with the scratch standing
1122
+ in for parent->ptr. */
1123
+ {
1124
+ volatile VALUE holder;
1125
+ CArray *parent = ca->parent;
1126
+ ca_size_t plen = parent->elements * parent->bytes;
1127
+ char *parent_scratch = ALLOCV_N(char, holder, plen);
1128
+ char *parent_ptr_saved = parent->ptr;
1129
+
1130
+ /* GET path needs parent data; PUT path will overwrite parent so we
1131
+ still need to read existing parent state if the view's fast path
1132
+ does partial writes (e.g., embed_sync overwrites only the embed
1133
+ rectangle, OOB cells untouched in parent). Safe default: always
1134
+ GET first. */
1135
+ ca_xfer_all(parent, parent_scratch, CA_XFER_GET);
1136
+ parent->ptr = parent_scratch;
1137
+
1138
+ ca_window_func_run_fast_path(ca, (char *) data, dir);
1139
+
1140
+ if ( dir == CA_XFER_PUT ) {
1141
+ /* Push back scratch (modified by scatter) to parent. */
1142
+ ca_xfer_all(parent, parent_scratch, CA_XFER_PUT);
1143
+ }
1144
+
1145
+ parent->ptr = parent_ptr_saved;
1146
+ ALLOCV_END(holder);
1147
+ }
308
1148
  }
309
1149
 
1150
+ /* fill_data (= view.fill(scalar) / view[] = scalar).
1151
+
1152
+ Wrapping the window in a CAStride and filling that is markedly faster than
1153
+ the engine path, because compose-fold reaches the entity and the inner loop
1154
+ collapses to a per-row memset. So for an embed-eligible window we do the
1155
+ same thing directly: build a transient CAStride header matching
1156
+ ca_window_compose_fold's synthetic strides and dispatch to CAStride's
1157
+ fill_data, which continues compose-fold (covering
1158
+ CAStride-of-CAWindow-of-CAStride chains) and runs the merge + tight-fill
1159
+ inner loop. PERIODIC / REFLECT windows fall through to the engine path. */
310
1160
  static void
311
1161
  ca_window_func_fill_data (void *ap, void *ptr)
312
1162
  {
313
1163
  CAWindow *ca = (CAWindow *) ap;
1164
+
1165
+ /* embed_eligible (= no PERIODIC/REFLECT) covers both interior-only
1166
+ windows (embed_covers_all == 1) and OOB-bearing ones such as a CAShift
1167
+ with a fill_value (embed_covers_all == 0, embed_is_empty == 0). In the
1168
+ OOB case the synth is restricted to the interior region (embed_count
1169
+ cells starting at embed_parent_start in the parent): the view's OOB
1170
+ cells have no parent cell and must be skipped, which the restricted
1171
+ synth does by construction. A wholly-OOB window (embed_is_empty == 1)
1172
+ is a no-op. */
1173
+ if ( ca->embed_eligible && ! ca->embed_is_empty ) {
1174
+ ca_size_t synth_strides[CA_RANK_MAX];
1175
+ ca_size_t synth_base;
1176
+ ca_size_t s;
1177
+ int8_t k;
1178
+ ca_size_t synth_elements;
1179
+
1180
+ /* Row-major byte strides over parent. */
1181
+ s = ca->bytes;
1182
+ for ( k = ca->ndim - 1; k >= 0; k-- ) {
1183
+ synth_strides[k] = s;
1184
+ s *= ca->parent->dim[k];
1185
+ }
1186
+
1187
+ /* Origin: embedded region start in parent's byte space. */
1188
+ synth_base = 0;
1189
+ for ( k = 0; k < ca->ndim; k++ ) {
1190
+ synth_base += ca->embed_parent_start[k] * synth_strides[k];
1191
+ }
1192
+
1193
+ /* synth dims = interior count per axis (= the cells we actually
1194
+ write to; OOB view cells are skipped by construction). */
1195
+ synth_elements = 1;
1196
+ for ( k = 0; k < ca->ndim; k++ ) {
1197
+ synth_elements *= ca->embed_count[k];
1198
+ }
1199
+
1200
+ /* Stack-allocated transient CAStride header. Dispatched via the
1201
+ op table since ca_stride_func_fill_data is static in
1202
+ ca_obj_stride.c. It reads only struct fields (parent, ndim,
1203
+ dim, strides, base_offset, bytes, elements) and never
1204
+ registers / persists this pointer. */
1205
+ CAStride synth;
1206
+ memset(&synth, 0, sizeof(synth));
1207
+ synth.obj_type = CA_OBJ_STRIDE;
1208
+ synth.data_type = ca->data_type;
1209
+ synth.ndim = ca->ndim;
1210
+ synth.bytes = ca->bytes;
1211
+ synth.elements = synth_elements;
1212
+ synth.dim = ca->embed_count;
1213
+ synth.parent = ca->parent;
1214
+ synth.strides = synth_strides;
1215
+ synth.base_offset = synth_base;
1216
+ ca_func[CA_OBJ_STRIDE].fill_data(&synth, ptr);
1217
+ return;
1218
+ }
1219
+
1220
+ /* Engine path: PERIODIC / REFLECT, or pure-OOB window (no-op via
1221
+ engine's OOB-skip). Bound_fill writes to view's OOB cells are
1222
+ not propagated to parent (no cells to write to). */
1223
+ ca_axis_desc_t desc[CA_RANK_MAX];
1224
+ ca_size_t pdims[CA_RANK_MAX];
1225
+
1226
+ ca_window_describe_axes(ca, desc, pdims);
1227
+
1228
+ /* A wrapping window writes the cells it lands on, but a whole-parent attach
1229
+ and sync carries the rest of the parent with it — and over a lossy layer
1230
+ those cells do not come back the same. Hand each slab to the parent as a
1231
+ region instead, as the other views on the descriptor engine do. */
1232
+ if ( !ca_is_attached(ca->parent) && !ca_attach_is_alias(ca->parent) ) {
1233
+ ca_axis_dispatch_fill_value_via_parent(ca->parent, pdims, desc, ca->ndim,
1234
+ ca->bytes, ca->elements, ptr);
1235
+ return;
1236
+ }
1237
+
314
1238
  ca_attach(ca->parent);
315
- ca_window_fill(ca, ptr);
1239
+ ca_axis_dispatch_fill_value(ca->parent, pdims, desc, ca->ndim, ca->bytes,
1240
+ ca->elements, ptr);
316
1241
  ca_sync(ca->parent);
317
1242
  ca_detach(ca->parent);
318
1243
  }
@@ -322,392 +1247,97 @@ ca_window_func_create_mask (void *ap)
322
1247
  {
323
1248
  CAWindow *ca = (CAWindow *) ap;
324
1249
  boolean8_t fill;
325
- ca_size_t bounds = ca->bounds;
1250
+ uint8_t mbounds[CA_RANK_MAX];
1251
+ int8_t i;
1252
+ int any_mask;
326
1253
 
327
1254
  ca_update_mask(ca->parent);
328
1255
  if ( ! ca->parent->mask ) {
329
1256
  ca_create_mask(ca->parent);
330
1257
  }
331
1258
 
332
- if ( bounds == CA_BOUNDS_MASK ) {
333
- bounds = CA_BOUNDS_FILL;
334
- fill = 1;
335
- }
336
- else {
337
- fill = 0;
1259
+ /* Any MASK axis of the view becomes FILL in the mask sub-view, with the
1260
+ mask cell forced to 1 (= "masked"). Other axes keep their policy. When
1261
+ every axis shares MASK this is exactly "OOB cells are masked". */
1262
+ any_mask = 0;
1263
+ for (i = 0; i < ca->ndim; i++) {
1264
+ if ( ca->bounds[i] == CA_BOUNDS_MASK ) {
1265
+ mbounds[i] = CA_BOUNDS_FILL;
1266
+ any_mask = 1;
1267
+ } else {
1268
+ mbounds[i] = ca->bounds[i];
1269
+ }
338
1270
  }
1271
+ fill = any_mask ? 1 : 0;
339
1272
 
340
1273
  ca->mask = (CArray *) ca_window_new(ca->parent->mask,
341
- ca->start, ca->count, bounds, (char*)&fill);
1274
+ ca->start, ca->count, mbounds, (char*)&fill);
342
1275
  }
343
1276
 
344
1277
  ca_operation_function_t ca_window_func = {
345
1278
  -1, /* CA_OBJ_WINDOW */
346
- CA_VIRTUAL_ARRAY,
1279
+ CA_VIEW_ARRAY,
347
1280
  free_ca_window,
348
1281
  ca_window_func_clone,
349
- ca_window_func_ptr_at_addr,
350
- ca_window_func_ptr_at_index,
351
- NULL,
352
- ca_window_func_fetch_index,
353
- NULL,
354
- ca_window_func_store_index,
355
1282
  ca_window_func_allocate,
356
1283
  ca_window_func_attach,
357
1284
  ca_window_func_sync,
358
1285
  ca_window_func_detach,
359
- ca_window_func_copy_data,
360
- ca_window_func_sync_data,
361
1286
  ca_window_func_fill_data,
362
1287
  ca_window_func_create_mask,
1288
+ ca_window_func_xfer_index,
1289
+ ca_window_func_xfer_addrs,
1290
+ ca_window_func_fold_stride,
1291
+ ca_window_func_xfer_stride,
1292
+ ca_window_func_xfer_all,
363
1293
  };
364
1294
 
365
1295
  /* ------------------------------------------------------------------- */
1296
+ /* Producer interface: emits one descriptor per axis. The engine
1297
+ (ca_axis_dispatch.c) consumes these and applies the per-axis bounds policy
1298
+ via ca_bounds_normalize_index, writing ca->fill for cells that resolve
1299
+ out of range.
366
1300
 
367
- #define proc_window_attach_get(type) \
368
- if ( fill ) { \
369
- type *p, *v; \
370
- idx[level] = 0; \
371
- p = ca_ptr_at_index((CArray*)cb, idx); \
372
- v = (type*)cb->fill; \
373
- for (i=0; i<count; i++, p++) { \
374
- *p = *v; \
375
- } \
376
- } \
377
- else { \
378
- CArray *parent = cb->parent; \
379
- ca_size_t start = cb->start[level]; \
380
- ca_size_t size0 = cb->size0[level]; \
381
- type *p, *q, *v; \
382
- idx[level] = 0; \
383
- p = (type*)ca_ptr_at_index((CArray*)cb, idx); \
384
- v = (type*)cb->fill; \
385
- i = 0; \
386
- while ( start+i<0 && i<count ) { \
387
- k = start + i; \
388
- k = ca_bounds_normalize_index(cb->bounds, size0, k); \
389
- if ( k < 0 || k >= size0 ) { \
390
- *p = *v; \
391
- } \
392
- else { \
393
- idx0[level] = k; \
394
- *p = *(type*) ca_ptr_at_index(parent, idx0); \
395
- } \
396
- i++; p++; \
397
- } \
398
- idx0[level] = start+i; \
399
- q = (type*)ca_ptr_at_index(parent, idx0);\
400
- while ( start+i<size0 && i < count ) { \
401
- *p = *q; \
402
- i++, p++, q++; \
403
- } \
404
- while ( i < count ) { \
405
- k = start + i; \
406
- k = ca_bounds_normalize_index(cb->bounds, size0, k); \
407
- if ( k < 0 || k >= size0 ) { \
408
- *p = *v; \
409
- } \
410
- else { \
411
- idx0[level] = k; \
412
- *p = *(type*) ca_ptr_at_index(parent, idx0); \
413
- } \
414
- i++, p++; \
415
- } \
416
- }
417
-
418
- static void
419
- ca_window_attach_loop (CAWindow *cb, int8_t level,
420
- ca_size_t *idx, ca_size_t *idx0, int fill)
421
- {
422
- ca_size_t count = cb->count[level];
423
- ca_size_t i, k;
424
-
425
- if ( level == cb->ndim - 1 ) {
426
- switch ( cb->data_type ) {
427
- case CA_BOOLEAN:
428
- case CA_INT8: proc_window_attach_get(int8_t); break;
429
- case CA_UINT8: proc_window_attach_get(uint8_t); break;
430
- case CA_INT16: proc_window_attach_get(int16_t); break;
431
- case CA_UINT16: proc_window_attach_get(uint16_t); break;
432
- case CA_INT32: proc_window_attach_get(int32_t); break;
433
- case CA_UINT32: proc_window_attach_get(uint32_t); break;
434
- case CA_INT64: proc_window_attach_get(int64_t); break;
435
- case CA_UINT64: proc_window_attach_get(uint64_t); break;
436
- case CA_FLOAT32: proc_window_attach_get(float32_t); break;
437
- case CA_FLOAT64: proc_window_attach_get(float64_t); break;
438
- case CA_FLOAT128: proc_window_attach_get(float128_t); break;
439
- #ifdef HAVE_COMPLEX_H
440
- case CA_CMPLX64: proc_window_attach_get(cmplx64_t); break;
441
- case CA_CMPLX128: proc_window_attach_get(cmplx128_t); break;
442
- case CA_CMPLX256: proc_window_attach_get(cmplx256_t); break;
443
- #endif
444
- default:
445
- if ( fill ) {
446
- for (i=0; i<count; i++) {
447
- idx[level] = i;
448
- memcpy(ca_ptr_at_index((CArray*)cb, idx), cb->fill, cb->bytes);
449
- }
450
- }
451
- else {
452
- ca_size_t start = cb->start[level];
453
- ca_size_t size0 = cb->size0[level];
454
- for (i=0; i<count; i++) {
455
- idx[level] = i;
456
- k = start + i;
457
- if ( k < 0 || k >= size0 ) {
458
- k = ca_bounds_normalize_index(cb->bounds, size0, k);
459
- if ( k < 0 || k >= size0 ) {
460
- memcpy(ca_ptr_at_index((CArray*)cb, idx), cb->fill, cb->bytes);
461
- continue;
462
- }
463
- }
464
- idx0[level] = k;
465
- memcpy(ca_ptr_at_index((CArray*)cb, idx), ca_ptr_at_index(cb->parent, idx0), cb->bytes);
466
- }
467
- }
468
- }
469
- }
470
- else {
471
- if ( fill ) {
472
- for (i=0; i<count; i++) {
473
- idx[level] = i;
474
- ca_window_attach_loop(cb, level+1, idx, idx0, 1);
475
- }
476
- }
477
- else {
478
- ca_size_t start = cb->start[level];
479
- ca_size_t size0 = cb->size0[level];
480
- for (i=0; i<count; i++) {
481
- idx[level] = i;
482
- k = start + i;
483
- if ( k < 0 || k >= size0 ) {
484
- k = ca_bounds_normalize_index(cb->bounds, size0, k);
485
- if ( k < 0 || k >= size0 ) {
486
- ca_window_attach_loop(cb, level+1, idx, idx0, 1); /* fill */
487
- continue;
488
- }
489
- }
490
- idx0[level] = k;
491
- ca_window_attach_loop(cb, level+1, idx, idx0, 0); /* not-fill */
492
- }
493
- }
494
- }
495
- }
1301
+ CAWindow's step is implicitly 1 (count cells starting at start, sliding by
1302
+ 1); the engine's SHIFT-axis offset computation is parent_index = start + i,
1303
+ with bounds normalisation per policy.
496
1304
 
1305
+ Reached only from the non-embed paths: an embed_eligible view attaches and
1306
+ syncs through ca_window_attach_embed / _sync_embed and never gets here. */
497
1307
  void
498
- ca_window_attach (CAWindow *cb)
1308
+ ca_window_describe_axes (void *ap, ca_axis_desc_t *out,
1309
+ ca_size_t *out_parent_dims)
499
1310
  {
500
- ca_size_t idx[CA_RANK_MAX];
501
- ca_size_t idx0[CA_RANK_MAX];
502
- ca_window_attach_loop(cb, (int8_t) 0, idx, idx0, 0);
503
- }
504
-
505
- #define proc_window_sync_set(type) \
506
- { \
507
- CArray *parent = cb->parent; \
508
- type *p, *q; \
509
- idx[level] = 0; \
510
- p = (type*)ca_ptr_at_index((CArray*)cb, idx); \
511
- i = 0; \
512
- while ( start+i<0 && i<count ) { \
513
- k = start + i; \
514
- k = ca_bounds_normalize_index(cb->bounds, size0, k); \
515
- if ( k < 0 || k >= size0 ) { \
516
- ; \
517
- } \
518
- else { \
519
- idx0[level] = k; \
520
- *(type *)ca_ptr_at_index(parent, idx0) = *p; \
521
- } \
522
- i++; p++; \
523
- }\
524
- idx0[level] = start + i; \
525
- q = (type*)ca_ptr_at_index(parent, idx0);\
526
- while ( start+i < size0 && i<count ) { \
527
- *q = *p; \
528
- i++; p++; q++; \
529
- } \
530
- while ( i<count ) { \
531
- k = start + i; \
532
- k = ca_bounds_normalize_index(cb->bounds, size0, k); \
533
- if ( k < 0 || k >= size0 ) { \
534
- ; \
535
- } \
536
- else { \
537
- idx0[level] = k; \
538
- *(type*)ca_ptr_at_index(parent, idx0) = *p; \
539
- } \
540
- i++; p++; \
541
- } \
542
- }
543
-
544
- static void
545
- ca_window_sync_loop (CAWindow *cb, int8_t level,
546
- ca_size_t *idx, ca_size_t *idx0)
547
- {
548
- ca_size_t count = cb->count[level];
549
- ca_size_t start = cb->start[level];
550
- ca_size_t size0 = cb->size0[level];
551
- ca_size_t i, k;
552
-
553
- if ( level == cb->ndim - 1 ) {
554
- switch ( cb->data_type ) {
555
- case CA_BOOLEAN:
556
- case CA_INT8: proc_window_sync_set(int8_t); break;
557
- case CA_UINT8: proc_window_sync_set(uint8_t); break;
558
- case CA_INT16: proc_window_sync_set(int16_t); break;
559
- case CA_UINT16: proc_window_sync_set(uint16_t); break;
560
- case CA_INT32: proc_window_sync_set(int32_t); break;
561
- case CA_UINT32: proc_window_sync_set(uint32_t); break;
562
- case CA_INT64: proc_window_sync_set(int64_t); break;
563
- case CA_UINT64: proc_window_sync_set(uint64_t); break;
564
- case CA_FLOAT32: proc_window_sync_set(float32_t); break;
565
- case CA_FLOAT64: proc_window_sync_set(float64_t); break;
566
- case CA_FLOAT128: proc_window_sync_set(float128_t); break;
567
- #ifdef HAVE_COMPLEX_H
568
- case CA_CMPLX64: proc_window_sync_set(cmplx64_t); break;
569
- case CA_CMPLX128: proc_window_sync_set(cmplx128_t); break;
570
- case CA_CMPLX256: proc_window_sync_set(cmplx256_t); break;
571
- #endif
572
- default:
573
- for (i=0; i<count; i++) {
574
- idx[level] = i;
575
- k = start + i;
576
- if ( k < 0 || k >= size0 ) {
577
- k = ca_bounds_normalize_index(cb->bounds, size0, k);
578
- if ( k < 0 || k >= size0 ) {
579
- continue;
580
- }
581
- }
582
- idx0[level] = k;
583
- memcpy(ca_ptr_at_index(cb->parent, idx0), ca_ptr_at_index((CArray*)cb, idx), cb->bytes);
584
- }
585
- }
586
- }
587
- else {
588
- for (i=0; i<count; i++) {
589
- idx[level] = i;
590
- k = start + i;
591
- if ( k < 0 || k >= size0 ) {
592
- k = ca_bounds_normalize_index(cb->bounds, size0, k);
593
- if ( k < 0 || k >= size0 ) {
594
- continue;
595
- }
596
- }
597
- idx0[level] = k;
598
- ca_window_sync_loop(cb, level+1, idx, idx0);
599
- }
600
- }
601
- }
602
-
603
- void
604
- ca_window_sync (CAWindow *cb)
605
- {
606
- ca_size_t idx[CA_RANK_MAX];
607
- ca_size_t idx0[CA_RANK_MAX];
608
- ca_window_sync_loop(cb, (int8_t) 0, idx, idx0);
609
- }
610
-
611
- #define proc_window_fill_set(type) \
612
- { \
613
- CArray *parent = cb->parent; \
614
- type *q; \
615
- i = 0; \
616
- while ( start+i<0 && i<count ) { \
617
- k = start + i; \
618
- k = ca_bounds_normalize_index(cb->bounds, size0, k); \
619
- if ( k < 0 || k >= size0 ) { \
620
- ; \
621
- } \
622
- else { \
623
- idx0[level] = k; \
624
- *(type *)ca_ptr_at_index(parent, idx0) = *ptr; \
625
- } \
626
- i++; \
627
- }\
628
- idx0[level] = start + i; \
629
- q = (type*)ca_ptr_at_index(parent, idx0);\
630
- while ( start+i < size0 && i<count ) { \
631
- *q = *ptr; \
632
- i++; q++; \
633
- } \
634
- while ( i<count ) { \
635
- k = start + i; \
636
- k = ca_bounds_normalize_index(cb->bounds, size0, k); \
637
- if ( k < 0 || k >= size0 ) { \
638
- ; \
639
- } \
640
- else { \
641
- idx0[level] = k; \
642
- *(type*)ca_ptr_at_index(parent, idx0) = *ptr; \
643
- } \
644
- i++; \
645
- } \
646
- }
647
-
648
- static void
649
- ca_window_fill_loop (CAWindow *cb, char *ptr,
650
- int8_t level, ca_size_t *idx0)
651
- {
652
- ca_size_t count = cb->count[level];
653
- ca_size_t start = cb->start[level];
654
- ca_size_t size0 = cb->size0[level];
655
- ca_size_t i, k;
656
-
657
- if ( level == cb->ndim - 1 ) {
658
- switch ( cb->data_type ) {
659
- case CA_BOOLEAN:
660
- case CA_INT8: proc_window_fill_set(int8_t); break;
661
- case CA_UINT8: proc_window_fill_set(uint8_t); break;
662
- case CA_INT16: proc_window_fill_set(int16_t); break;
663
- case CA_UINT16: proc_window_fill_set(uint16_t); break;
664
- case CA_INT32: proc_window_fill_set(int32_t); break;
665
- case CA_UINT32: proc_window_fill_set(uint32_t); break;
666
- case CA_INT64: proc_window_fill_set(int64_t); break;
667
- case CA_UINT64: proc_window_fill_set(uint64_t); break;
668
- case CA_FLOAT32: proc_window_fill_set(float32_t); break;
669
- case CA_FLOAT64: proc_window_fill_set(float64_t); break;
670
- case CA_FLOAT128: proc_window_fill_set(float128_t); break;
671
- #ifdef HAVE_COMPLEX_H
672
- case CA_CMPLX64: proc_window_fill_set(cmplx64_t); break;
673
- case CA_CMPLX128: proc_window_fill_set(cmplx128_t); break;
674
- case CA_CMPLX256: proc_window_fill_set(cmplx256_t); break;
675
- #endif
676
- default:
677
- for (i=0; i<count; i++) {
678
- k = start + i;
679
- if ( k < 0 || k >= size0 ) {
680
- k = ca_bounds_normalize_index(cb->bounds, size0, k);
681
- if ( k < 0 || k >= size0 ) {
682
- continue;
683
- }
684
- }
685
- idx0[level] = k;
686
- memcpy(ca_ptr_at_index(cb->parent, idx0), ptr, cb->bytes);
687
- }
688
- }
689
- }
690
- else {
691
- for (i=0; i<count; i++) {
692
- k = start + i;
693
- if ( k < 0 || k >= size0 ) {
694
- k = ca_bounds_normalize_index(cb->bounds, size0, k);
695
- if ( k < 0 || k >= size0 ) {
696
- continue;
697
- }
698
- }
699
- idx0[level] = k;
700
- ca_window_fill_loop(cb, ptr, level+1, idx0);
1311
+ CAWindow *ca = (CAWindow *) ap;
1312
+ int8_t k;
1313
+ for ( k = 0; k < ca->ndim; k++ ) {
1314
+ out_parent_dims[k] = ca->parent->dim[k];
1315
+ /* Interior-only axes are promoted to STRIDE kind. An axis touches no
1316
+ boundary cell iff start[k] >= 0 and start[k] + count[k] <=
1317
+ parent->dim[k] (step is implicitly 1); it is then a pure strided slice
1318
+ of the parent, so emitting STRIDE lets the engine take its strided fast
1319
+ paths (slab fusion, axis-merge, alias) and skip the per-cell
1320
+ ca_bounds_normalize_index + OOB check that SHIFT costs. */
1321
+ if ( ca->start[k] >= 0
1322
+ && ca->start[k] + ca->count[k] <= ca->parent->dim[k] ) {
1323
+ out[k].kind = CA_AXIS_KIND_STRIDE;
1324
+ out[k].count = ca->count[k];
1325
+ out[k].start = ca->start[k];
1326
+ out[k].step = 1;
1327
+ out[k].indices = NULL;
1328
+ /* size0 / policy unused for STRIDE — set defaults for hygiene. */
1329
+ out[k].size0 = ca->size0[k];
1330
+ out[k].policy = ca->bounds[k];
1331
+ } else {
1332
+ out[k].kind = CA_AXIS_KIND_SHIFT;
1333
+ out[k].count = ca->count[k];
1334
+ out[k].start = ca->start[k];
1335
+ out[k].step = 1;
1336
+ out[k].indices = NULL;
1337
+ out[k].size0 = ca->size0[k];
1338
+ out[k].policy = ca->bounds[k];
701
1339
  }
702
1340
  }
703
-
704
- }
705
-
706
- void
707
- ca_window_fill (CAWindow *cb, char *ptr)
708
- {
709
- ca_size_t idx0[CA_RANK_MAX];
710
- ca_window_fill_loop(cb, ptr, (int8_t) 0, idx0);
711
1341
  }
712
1342
 
713
1343
  /* ------------------------------------------------------------------- */
@@ -716,29 +1346,26 @@ VALUE
716
1346
  rb_ca_window_new (VALUE cary,
717
1347
  ca_size_t *start, ca_size_t *count, int8_t bounds, char *fill)
718
1348
  {
1349
+ /* Scalar-bounds wrapper for the Ruby surface: fan the single policy out
1350
+ to a per-axis array before calling ca_window_new. */
719
1351
  volatile VALUE obj;
720
1352
  CArray *parent;
721
1353
  CAWindow *ca;
1354
+ uint8_t bounds_arr[CA_RANK_MAX];
1355
+ int8_t i;
722
1356
  rb_check_carray_object(cary);
723
1357
  TypedData_Get_Struct(cary, CArray, &carray_data_type, parent);
724
- ca = ca_window_new(parent, start, count, bounds, fill);
1358
+ for (i = 0; i < parent->ndim; i++) bounds_arr[i] = (uint8_t) bounds;
1359
+ ca = ca_window_new(parent, start, count, bounds_arr, fill);
725
1360
  obj = ca_wrap_struct(ca);
726
1361
  rb_ca_set_parent(obj, cary);
727
- rb_ca_data_type_inherit(obj, cary);
728
1362
  return obj;
729
1363
  }
730
1364
 
731
- /* yard:
732
- class CArray
733
- def window (*argv)
734
- end
735
- end
736
- */
737
-
738
1365
  VALUE
739
1366
  rb_ca_window (int argc, VALUE *argv, VALUE self)
740
1367
  {
741
- volatile VALUE obj, ropt, rfval = CA_NIL, rbounds = Qnil, rcs;
1368
+ volatile VALUE obj, ropt, rfval = CA_UNSPECIFIED, rbounds = Qnil, rcs;
742
1369
  CArray *ca;
743
1370
  CScalar *cs;
744
1371
  ca_size_t start[CA_RANK_MAX];
@@ -770,16 +1397,13 @@ rb_ca_window (int argc, VALUE *argv, VALUE self)
770
1397
  count[i] = len;
771
1398
  }
772
1399
 
773
- if ( rfval == CA_NIL ) {
774
- if ( rb_block_given_p() ) {
775
- rfval = rb_yield(self);
776
- }
777
- }
778
- else {
779
- /* rb_warn(":fill_value option for CArray#window will be obsoleted."); */
1400
+ if ( rb_block_given_p() ) {
1401
+ rb_raise(rb_eArgError,
1402
+ "window: block form for fill value removed in 3.0; "
1403
+ "use fill_value: kwarg (e.g. window(-1..1, fill_value: UNDEF))");
780
1404
  }
781
1405
 
782
- if ( rfval == CA_NIL ) {
1406
+ if ( rfval == CA_UNSPECIFIED ) {
783
1407
  ;
784
1408
  }
785
1409
  else if ( rfval == CA_UNDEF ) {
@@ -809,14 +1433,19 @@ rb_ca_window (int argc, VALUE *argv, VALUE self)
809
1433
  bounds = CA_BOUNDS_NEAREST;
810
1434
  }
811
1435
  else if ( ! strncmp(cbounds, "periodic", 8) ) {
812
- bounds = CA_BOUNDS_PERIODIC;
1436
+ rb_raise(rb_eArgError,
1437
+ "bounds=>'periodic' removed in 3.0; "
1438
+ "use CArray#roll(...) for cyclic shift "
1439
+ "(returns a CARoll view)");
813
1440
  }
814
1441
  else if ( ! strncmp(cbounds, "reflect", 7) ) {
815
- bounds = CA_BOUNDS_REFLECT;
1442
+ rb_raise(rb_eArgError,
1443
+ "bounds=>'reflect' removed in 3.0; "
1444
+ "there is no view-based alternative");
816
1445
  }
817
1446
  else if ( ! strncmp(cbounds, "mask", 4) ) {
818
1447
  rb_warn("CAWindow option :bounds=>\"mask\" will be obsolete");
819
- rb_warn("use ca.window(...) { UNDEF }");
1448
+ rb_warn("use ca.window(..., fill_value: UNDEF)");
820
1449
  bounds = CA_BOUNDS_MASK;
821
1450
  }
822
1451
  else if ( ! strncmp(cbounds, "fill", 4) ) {
@@ -837,6 +1466,7 @@ rb_ca_window (int argc, VALUE *argv, VALUE self)
837
1466
 
838
1467
  obj = rb_ca_window_new(self, start, count, bounds, fill);
839
1468
 
1469
+ CA_WRAPPER_LIFT(obj, self, ca);
840
1470
  return obj;
841
1471
  }
842
1472
 
@@ -857,18 +1487,17 @@ rb_ca_window_initialize_copy (VALUE self, VALUE other)
857
1487
  TypedData_Get_Struct(self, CAWindow, &cawindow_data_type, ca);
858
1488
  TypedData_Get_Struct(other, CAWindow, &cawindow_data_type, cs);
859
1489
 
1490
+ /* `self` came from rb_ca_window_s_allocate (TypedData_Make_Struct,
1491
+ _pool == NULL). Attach the pool before setup so the ndim-sized
1492
+ tail fields skip ALLOC_N. */
1493
+ if ( ca_func[CA_OBJ_WINDOW].pool_init ) {
1494
+ ca_array_pool_alloc(ca, CA_OBJ_WINDOW, cs->ndim);
1495
+ }
860
1496
  ca_window_setup(ca, cs->parent, cs->start, cs->count, cs->bounds, cs->fill);
861
1497
 
862
1498
  return self;
863
1499
  }
864
1500
 
865
- /* yard:
866
- class CAWindow
867
- def index2addr0 (idx)
868
- end
869
- end
870
- */
871
-
872
1501
  static VALUE
873
1502
  rb_ca_window_idx2addr0 (int argc, VALUE *argv, VALUE self)
874
1503
  {
@@ -899,13 +1528,6 @@ rb_ca_window_idx2addr0 (int argc, VALUE *argv, VALUE self)
899
1528
  }
900
1529
  }
901
1530
 
902
- /* yard:
903
- class CAWindow
904
- def addr2addr0 (addr)
905
- end
906
- end
907
- */
908
-
909
1531
  static VALUE
910
1532
  rb_ca_window_addr2addr0 (VALUE self, VALUE raddr)
911
1533
  {
@@ -924,43 +1546,13 @@ rb_ca_window_addr2addr0 (VALUE self, VALUE raddr)
924
1546
  addr += cw->start[i] + idx[i];
925
1547
  }
926
1548
 
927
- return SIZE2NUM(addr);
928
- }
929
-
930
-
931
- static VALUE
932
- rb_ca_window_move (int argc, VALUE *argv, VALUE self)
933
- {
934
- CAWindow *cw;
935
- ca_size_t start;
936
- int8_t i;
937
-
938
- TypedData_Get_Struct(self, CAWindow, &cawindow_data_type, cw);
939
-
940
- if ( argc != cw->ndim ) {
941
- rb_raise(rb_eArgError, "invalid # of arguments");
942
- }
943
-
944
- ca_update_mask(cw);
945
- for (i=0; i<cw->ndim; i++) {
946
- start = NUM2SIZE(argv[i]);
947
- cw->start[i] = start;
948
- if ( cw->mask ) {
949
- ((CAWindow*)(cw->mask))->start[i] = start;
950
- }
1549
+ if ( addr < 0 || addr >= cw->parent->elements ) {
1550
+ return Qnil;
951
1551
  }
952
1552
 
953
- return self;
1553
+ return SIZE2NUM(addr);
954
1554
  }
955
1555
 
956
- /* yard:
957
- class CAWindow
958
- def fill_value
959
- end
960
- def fill_value= (val)
961
- end
962
- end
963
- */
964
1556
 
965
1557
  static VALUE
966
1558
  rb_ca_window_set_fill_value (VALUE self, VALUE rfval)
@@ -982,9 +1574,12 @@ rb_ca_window_get_fill_value (VALUE self)
982
1574
  static VALUE
983
1575
  rb_ca_window_get_bounds (VALUE self)
984
1576
  {
1577
+ /* bounds is per-axis, but the Ruby surface always constructs with a single
1578
+ scalar policy fanned out across all axes, so axis 0 reproduces the value
1579
+ that was passed in. */
985
1580
  CAWindow *cw;
986
1581
  TypedData_Get_Struct(self, CAWindow, &cawindow_data_type, cw);
987
- return SIZE2NUM(cw->bounds);
1582
+ return SIZE2NUM(cw->bounds[0]);
988
1583
  }
989
1584
 
990
1585
  #define rb_cw_get_attr_ary(name) \
@@ -1001,36 +1596,73 @@ rb_ca_window_get_bounds (VALUE self)
1001
1596
  return ary; \
1002
1597
  }
1003
1598
 
1004
- /* yard:
1005
- class CAWindow
1006
- def size0
1007
- end
1008
- def start
1009
- end
1010
- def step
1011
- end
1012
- def count
1013
- end
1014
- def offset
1015
- end
1016
- end
1017
- */
1018
-
1019
1599
  static VALUE rb_cw_get_attr_ary(start);
1020
1600
  static VALUE rb_cw_get_attr_ary(count);
1021
1601
  static VALUE rb_cw_get_attr_ary(size0);
1022
1602
 
1603
+ #ifdef CARRAY_DEV_BUILD
1604
+ /* Debug accessor (dev-only, stripped in release), returning the embed
1605
+ descriptor as a Hash. The descriptor is an internal implementation detail
1606
+ with no user-facing meaning; this exists so spec_ai can pin its geometry,
1607
+ which nothing else can observe (a stale descriptor produces wrong data, not
1608
+ an error). Gated by CARRAY_DEV_BUILD, enabled via
1609
+ `extconf.rb --enable-dev-build` or `CARRAY_DEV=1 rake build_ext`.
1610
+
1611
+ Polymorphic over CAWindow / CAShift: both share the same C struct layout
1612
+ (CAShift is a typedef of CAWindow) but use distinct TypedData types.
1613
+ DATA_PTR is safe here because Ruby method dispatch has already restricted
1614
+ self to one of those two classes. */
1615
+ VALUE
1616
+ rb_ca_window_embed_descriptor (VALUE self)
1617
+ {
1618
+ CAWindow *ca = (CAWindow *) DATA_PTR(self);
1619
+ VALUE hash, ps_ary, ec_ary, oo_ary;
1620
+ int8_t k;
1621
+
1622
+ ps_ary = rb_ary_new_capa(ca->ndim);
1623
+ ec_ary = rb_ary_new_capa(ca->ndim);
1624
+ oo_ary = rb_ary_new_capa(ca->ndim);
1625
+ for ( k = 0; k < ca->ndim; k++ ) {
1626
+ rb_ary_push(ps_ary, SIZE2NUM(ca->embed_parent_start[k]));
1627
+ rb_ary_push(ec_ary, SIZE2NUM(ca->embed_count[k]));
1628
+ rb_ary_push(oo_ary, SIZE2NUM(ca->embed_output_offset[k]));
1629
+ }
1630
+
1631
+ hash = rb_hash_new();
1632
+ rb_hash_aset(hash, ID2SYM(rb_intern("parent_start")), ps_ary);
1633
+ rb_hash_aset(hash, ID2SYM(rb_intern("count")), ec_ary);
1634
+ rb_hash_aset(hash, ID2SYM(rb_intern("output_offset")), oo_ary);
1635
+ rb_hash_aset(hash, ID2SYM(rb_intern("is_empty")),
1636
+ ca->embed_is_empty ? Qtrue : Qfalse);
1637
+ rb_hash_aset(hash, ID2SYM(rb_intern("covers_all")),
1638
+ ca->embed_covers_all ? Qtrue : Qfalse);
1639
+ rb_hash_aset(hash, ID2SYM(rb_intern("eligible")),
1640
+ ca->embed_eligible ? Qtrue : Qfalse);
1641
+ rb_hash_aset(hash, ID2SYM(rb_intern("alias_eligible")),
1642
+ ca->embed_alias_eligible ? Qtrue : Qfalse);
1643
+ return hash;
1644
+ }
1645
+ #endif /* CARRAY_DEV_BUILD */
1646
+
1023
1647
  void
1024
- Init_ca_obj_window ()
1648
+ Init_ca_obj_window (void)
1025
1649
  {
1026
1650
 
1027
- rb_cCAWindow = rb_define_class("CAWindow", rb_cCAVirtual);
1651
+ rb_cCAWindow = rb_define_class("CAWindow", rb_cCAView);
1028
1652
  rb_cCAWindowMask = rb_define_class("CAWindowMask", rb_cCAWindow);
1029
1653
 
1030
- CA_OBJ_WINDOW = ca_install_obj_type(rb_cCAWindow,
1654
+ /* Pool framework: seven ndim-sized tail fields live in one _pool buffer
1655
+ (fill stays separate, bytes-sized). Set on the global ca_window_func
1656
+ before ca_install_obj_type copies it into ca_func[], and before
1657
+ Init_ca_obj_shift copies ca_window_func into ca_shift_func. */
1658
+ ca_window_func.struct_size = sizeof(CAWindow);
1659
+ ca_window_func.pool_bytes = ca_window_pool_bytes;
1660
+ ca_window_func.pool_init = ca_window_pool_init;
1661
+
1662
+ CA_OBJ_WINDOW = ca_install_obj_type(rb_cCAWindow,
1031
1663
  &cawindow_data_type,
1032
1664
  rb_cCAWindowMask,
1033
- &cawindow_mask_data_type, ca_window_func);
1665
+ &cawindow_mask_data_type, &ca_window_func, sizeof(ca_window_func));
1034
1666
  rb_define_const(rb_cObject, "CA_OBJ_WINDOW", INT2NUM(CA_OBJ_WINDOW));
1035
1667
 
1036
1668
  rb_define_method(rb_cCArray, "window", rb_ca_window, -1);
@@ -1039,7 +1671,6 @@ Init_ca_obj_window ()
1039
1671
  rb_define_method(rb_cCAWindow, "initialize_copy",
1040
1672
  rb_ca_window_initialize_copy, 1);
1041
1673
 
1042
- rb_define_method(rb_cCAWindow, "move", rb_ca_window_move, -1);
1043
1674
 
1044
1675
  rb_define_method(rb_cCAWindow, "index2addr0", rb_ca_window_idx2addr0, -1);
1045
1676
  rb_define_method(rb_cCAWindow, "addr2addr0", rb_ca_window_addr2addr0, 1);
@@ -1053,5 +1684,11 @@ Init_ca_obj_window ()
1053
1684
  rb_define_method(rb_cCAWindow, "count", rb_cw_count, 0);
1054
1685
  rb_define_method(rb_cCAWindow, "size0", rb_cw_size0, 0);
1055
1686
 
1687
+ #ifdef CARRAY_DEV_BUILD
1688
+ /* debug accessor (dev-only, stripped in release) */
1689
+ rb_define_method(rb_cCAWindow, "_embed_descriptor",
1690
+ rb_ca_window_embed_descriptor, 0);
1691
+ #endif
1692
+
1056
1693
  }
1057
1694