carray 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +5 -25
  3. data/CHANGELOG.md +16 -0
  4. data/LICENSE +1 -1
  5. data/NEWS.md +3 -0
  6. data/README.md +128 -44
  7. data/carray.gemspec +22 -24
  8. data/ext/ca_array_pool.c +91 -0
  9. data/ext/ca_axis_descriptor.h +186 -0
  10. data/ext/ca_axis_dispatch.c +924 -0
  11. data/ext/ca_axis_group.c +1208 -0
  12. data/ext/ca_bincmp_dispatch.c +76 -0
  13. data/ext/ca_bincmp_dispatch.h +85 -0
  14. data/ext/ca_binop_dispatch.c +125 -0
  15. data/ext/ca_binop_dispatch.h +159 -0
  16. data/ext/ca_categorical_iterator.c +1375 -0
  17. data/ext/ca_compare.c +94 -0
  18. data/ext/ca_compare.h +26 -0
  19. data/ext/ca_composite_dispatch.c +414 -0
  20. data/ext/ca_composite_dispatch.h +116 -0
  21. data/ext/ca_for_buffer.h +96 -0
  22. data/ext/ca_for_each_element.h +241 -0
  23. data/ext/ca_group_iter.c +304 -0
  24. data/ext/ca_iter_substrate.h +325 -0
  25. data/ext/ca_kernel_iterator.c +4321 -0
  26. data/ext/ca_kernel_iterator.h +2603 -0
  27. data/ext/ca_moncmp_dispatch.c +37 -0
  28. data/ext/ca_moncmp_dispatch.h +62 -0
  29. data/ext/ca_monop_dispatch.c +200 -0
  30. data/ext/ca_monop_dispatch.h +235 -0
  31. data/ext/ca_obj_array.c +355 -359
  32. data/ext/ca_obj_bincmp.c +809 -0
  33. data/ext/ca_obj_binop.c +892 -0
  34. data/ext/ca_obj_bitarray.c +369 -164
  35. data/ext/ca_obj_bitfield.c +294 -234
  36. data/ext/ca_obj_block.c +189 -711
  37. data/ext/ca_obj_byte_swap.c +766 -0
  38. data/ext/ca_obj_const_string.c +965 -0
  39. data/ext/ca_obj_face.c +670 -0
  40. data/ext/ca_obj_face.h +247 -0
  41. data/ext/ca_obj_fake.c +228 -100
  42. data/ext/ca_obj_farray.c +54 -441
  43. data/ext/ca_obj_field.c +82 -529
  44. data/ext/ca_obj_fixlen_string.c +306 -0
  45. data/ext/ca_obj_grid.c +858 -440
  46. data/ext/ca_obj_meld.c +1034 -0
  47. data/ext/ca_obj_moncmp.c +569 -0
  48. data/ext/ca_obj_monop.c +1111 -0
  49. data/ext/ca_obj_object.c +774 -298
  50. data/ext/ca_obj_record.c +468 -0
  51. data/ext/ca_obj_reduce.c +97 -82
  52. data/ext/ca_obj_refer.c +569 -459
  53. data/ext/ca_obj_remap.c +475 -0
  54. data/ext/ca_obj_repeat.c +92 -477
  55. data/ext/ca_obj_roll.c +616 -0
  56. data/ext/ca_obj_select.c +344 -296
  57. data/ext/ca_obj_select_axis.c +1296 -0
  58. data/ext/ca_obj_shift.c +230 -792
  59. data/ext/ca_obj_source.c +78 -0
  60. data/ext/ca_obj_stack.c +1173 -0
  61. data/ext/ca_obj_stride.c +2501 -0
  62. data/ext/ca_obj_string.c +268 -0
  63. data/ext/ca_obj_tile.c +614 -0
  64. data/ext/ca_obj_time.c +546 -0
  65. data/ext/ca_obj_timedelta.c +435 -0
  66. data/ext/ca_obj_transpose.c +62 -516
  67. data/ext/ca_obj_triop.c +746 -0
  68. data/ext/ca_obj_unbound_repeat.c +208 -241
  69. data/ext/ca_obj_window.c +1131 -563
  70. data/ext/ca_op_byte_swap.c +175 -0
  71. data/ext/ca_op_ipower.c +319 -0
  72. data/ext/ca_op_powi.h +88 -0
  73. data/ext/ca_sort_kernels.h +132 -0
  74. data/ext/ca_sweep_engine.c +430 -0
  75. data/ext/ca_sweep_engine.h +157 -0
  76. data/ext/ca_transform_common.c +228 -0
  77. data/ext/ca_triop_dispatch.c +55 -0
  78. data/ext/ca_triop_dispatch.h +62 -0
  79. data/ext/carray.h +795 -402
  80. data/ext/carray_access.c +831 -711
  81. data/ext/carray_attribute.c +98 -330
  82. data/ext/carray_bincount.c +255 -0
  83. data/ext/carray_broadcast.c +283 -0
  84. data/ext/carray_call_cfunc.c +1360 -828
  85. data/ext/carray_call_cfunc.h +160 -0
  86. data/ext/carray_cast.c +1212 -301
  87. data/ext/carray_cast_func.rb +81 -40
  88. data/ext/carray_class.c +53 -63
  89. data/ext/carray_config.h +28 -0
  90. data/ext/carray_conversion.c +350 -346
  91. data/ext/carray_copy.c +156 -268
  92. data/ext/carray_core.c +1342 -199
  93. data/ext/carray_count.c +312 -0
  94. data/ext/carray_data_type.c +43 -19
  95. data/ext/carray_element.c +585 -213
  96. data/ext/carray_factorize.c +2542 -0
  97. data/ext/carray_generate.c +230 -559
  98. data/ext/carray_histogram.c +490 -0
  99. data/ext/carray_hold.c +228 -0
  100. data/ext/carray_index_classifier.c +1035 -0
  101. data/ext/carray_index_classifier.h +27 -0
  102. data/ext/carray_internal.h +120 -0
  103. data/ext/carray_kernels_bincmp.c +4445 -0
  104. data/ext/carray_kernels_binop.c +10979 -0
  105. data/ext/carray_kernels_init.c +36 -0
  106. data/ext/carray_kernels_map.c +3466 -0
  107. data/ext/carray_kernels_moncmp.c +2096 -0
  108. data/ext/carray_kernels_monop.c +18312 -0
  109. data/ext/carray_kernels_reduce_aggregate.c +25836 -0
  110. data/ext/carray_kernels_reduce_boolean.c +329 -0
  111. data/ext/carray_kernels_reduce_cumulative.c +14592 -0
  112. data/ext/carray_kernels_reduce_extreme.c +16947 -0
  113. data/ext/carray_kernels_reduce_variance.c +3909 -0
  114. data/ext/carray_kernels_scan.c +3692 -0
  115. data/ext/carray_kernels_search.c +32137 -0
  116. data/ext/carray_kernels_sort.c +10625 -0
  117. data/ext/carray_kernels_triop.c +1391 -0
  118. data/ext/carray_lazy.c +567 -0
  119. data/ext/carray_loop.c +88 -200
  120. data/ext/carray_mask.c +848 -154
  121. data/ext/carray_math_kernel.h +120 -0
  122. data/ext/carray_mathfunc.c +10 -241
  123. data/ext/carray_median_percentile.c +1257 -0
  124. data/ext/carray_memory_view.c +1625 -0
  125. data/ext/carray_operator.c +1526 -318
  126. data/ext/carray_order.c +664 -1394
  127. data/ext/carray_partition.c +416 -0
  128. data/ext/carray_random.c +518 -0
  129. data/ext/carray_scatter.c +357 -0
  130. data/ext/carray_slab.c +1219 -0
  131. data/ext/carray_slab.h +84 -0
  132. data/ext/carray_sort.c +829 -0
  133. data/ext/carray_sort_kernel.c +620 -0
  134. data/ext/carray_struct.c +695 -0
  135. data/ext/carray_test.c +343 -229
  136. data/ext/carray_undef.c +34 -17
  137. data/ext/carray_utils.c +175 -74
  138. data/ext/extconf.rb +216 -55
  139. data/ext/mk_call_cfunc.rb +480 -0
  140. data/ext/mkkernel.rb +8842 -0
  141. data/ext/ruby_carray.c +202 -101
  142. data/ext/version.h +4 -14
  143. data/ext/version.rb +5 -13
  144. data/lib/carray/arrow_tensor.rb +401 -0
  145. data/lib/carray/attribute.rb +166 -0
  146. data/lib/carray/autoload_carray.rb +220 -0
  147. data/lib/carray/autoload_method_extension.rb +44 -0
  148. data/lib/carray/axis_group.rb +711 -0
  149. data/lib/carray/basics.rb +481 -0
  150. data/lib/carray/bincount_nd.rb +358 -0
  151. data/lib/carray/block_iterator.rb +604 -0
  152. data/lib/carray/boolean_reduce.rb +109 -0
  153. data/lib/carray/categorical.rb +561 -0
  154. data/lib/carray/categorical_iterator.rb +1062 -0
  155. data/lib/carray/complex.rb +150 -0
  156. data/lib/carray/conditional.rb +216 -0
  157. data/lib/carray/const_string.rb +228 -0
  158. data/lib/carray/construct.rb +139 -328
  159. data/lib/carray/core_extensions.rb +240 -0
  160. data/lib/carray/data_type_extension.rb +233 -0
  161. data/lib/carray/fixlen_string.rb +95 -0
  162. data/lib/carray/frame/concat.rb +132 -0
  163. data/lib/carray/frame/convert.rb +95 -0
  164. data/lib/carray/frame/csv_parser.rb +211 -0
  165. data/lib/carray/frame/frame.rb +649 -0
  166. data/lib/carray/frame/group.rb +186 -0
  167. data/lib/carray/frame/io.rb +164 -0
  168. data/lib/carray/frame/join.rb +248 -0
  169. data/lib/carray/frame/records.rb +99 -0
  170. data/lib/carray/frame/sort.rb +113 -0
  171. data/lib/carray/frame/verbs.rb +299 -0
  172. data/lib/carray/frame.rb +16 -0
  173. data/lib/carray/histogram.rb +512 -0
  174. data/lib/carray/inspect.rb +37 -20
  175. data/lib/carray/iterator.rb +57 -349
  176. data/lib/carray/lazy.rb +889 -0
  177. data/lib/carray/mask_gap_fill.rb +200 -0
  178. data/lib/carray/math.rb +78 -342
  179. data/lib/carray/meld_reduce.rb +289 -0
  180. data/lib/carray/methods/align_addr.rb +116 -0
  181. data/lib/carray/methods/bin.rb +128 -0
  182. data/lib/carray/methods/bincount.rb +87 -0
  183. data/lib/carray/methods/bit_string.rb +92 -0
  184. data/lib/carray/methods/broadcast.rb +63 -0
  185. data/lib/carray/methods/choose.rb +39 -0
  186. data/lib/carray/methods/composition.rb +280 -0
  187. data/lib/carray/methods/gather_nd.rb +206 -0
  188. data/lib/carray/methods/index.rb +39 -0
  189. data/lib/carray/methods/insert_block.rb +99 -0
  190. data/lib/carray/methods/is_in.rb +141 -0
  191. data/lib/carray/methods/join.rb +90 -0
  192. data/lib/carray/methods/locate_addr.rb +47 -0
  193. data/lib/carray/methods/mask_duplicates.rb +41 -0
  194. data/lib/carray/methods/meshgrid.rb +91 -0
  195. data/lib/carray/methods/mode.rb +126 -0
  196. data/lib/carray/methods/nunique.rb +46 -0
  197. data/lib/carray/methods/resize.rb +56 -0
  198. data/lib/carray/methods/snap.rb +156 -0
  199. data/lib/carray/methods/string_format.rb +57 -0
  200. data/lib/carray/methods/unique.rb +47 -0
  201. data/lib/carray/methods/value_counts.rb +71 -0
  202. data/lib/carray/mkmf.rb +124 -101
  203. data/lib/carray/runtime.rb +108 -0
  204. data/lib/carray/serialize.rb +478 -167
  205. data/lib/carray/slab_iterator.rb +292 -0
  206. data/lib/carray/stack.rb +291 -0
  207. data/lib/carray/string.rb +56 -180
  208. data/lib/carray/string_operation_extension.rb +289 -0
  209. data/lib/carray/struct.rb +335 -323
  210. data/lib/carray/struct_builder.rb +697 -0
  211. data/lib/carray/table.rb +41 -2
  212. data/lib/carray/time.rb +2255 -38
  213. data/lib/carray/window_iterator.rb +655 -0
  214. data/lib/carray.rb +55 -57
  215. metadata +163 -130
  216. data/Rakefile +0 -51
  217. data/TODO.md +0 -18
  218. data/ext/ca_iter_block.c +0 -257
  219. data/ext/ca_iter_dimension.c +0 -299
  220. data/ext/ca_iter_window.c +0 -214
  221. data/ext/ca_obj_mapping.c +0 -644
  222. data/ext/carray_iterator.c +0 -641
  223. data/ext/carray_math.rb +0 -850
  224. data/ext/carray_numeric.c +0 -259
  225. data/ext/carray_sort_addr.c +0 -254
  226. data/ext/carray_stat.c +0 -2100
  227. data/ext/carray_stat_proc.rb +0 -1999
  228. data/ext/mkmath.rb +0 -741
  229. data/ext/ruby_ccomplex.c +0 -509
  230. data/ext/ruby_float_func.c +0 -86
  231. data/lib/carray/array.rb +0 -8
  232. data/lib/carray/autoload/autoload_base.rb +0 -19
  233. data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
  234. data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
  235. data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
  236. data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
  237. data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
  238. data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
  239. data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
  240. data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
  241. data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
  242. data/lib/carray/autoload/autoload_gem_random.rb +0 -8
  243. data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
  244. data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
  245. data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
  246. data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
  247. data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
  248. data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
  249. data/lib/carray/autoload/autoload_object_link.rb +0 -1
  250. data/lib/carray/autoload/autoload_object_pack.rb +0 -2
  251. data/lib/carray/autoload.rb +0 -141
  252. data/lib/carray/basic.rb +0 -191
  253. data/lib/carray/broadcast.rb +0 -101
  254. data/lib/carray/compose.rb +0 -315
  255. data/lib/carray/convert.rb +0 -115
  256. data/lib/carray/info.rb +0 -110
  257. data/lib/carray/io/imagemagick.rb +0 -235
  258. data/lib/carray/mask.rb +0 -102
  259. data/lib/carray/math/histogram.rb +0 -177
  260. data/lib/carray/math/recurrence.rb +0 -93
  261. data/lib/carray/object/ca_obj_iterator.rb +0 -50
  262. data/lib/carray/object/ca_obj_link.rb +0 -50
  263. data/lib/carray/object/ca_obj_pack.rb +0 -99
  264. data/lib/carray/obsolete.rb +0 -256
  265. data/lib/carray/ordering.rb +0 -181
  266. data/lib/carray/testing.rb +0 -51
  267. data/lib/carray/transform.rb +0 -109
  268. data/misc/Methods.ja.md +0 -182
  269. data/misc/NOTE +0 -51
  270. data/spec/Classes/CABitfield_spec.rb +0 -58
  271. data/spec/Classes/CABlockIterator_spec.rb +0 -114
  272. data/spec/Classes/CABlock_spec.rb +0 -205
  273. data/spec/Classes/CAField_spec.rb +0 -39
  274. data/spec/Classes/CAGrid_spec.rb +0 -75
  275. data/spec/Classes/CAMap_spec.rb +0 -0
  276. data/spec/Classes/CAMapping_spec.rb +0 -105
  277. data/spec/Classes/CAObject_attribute_spec.rb +0 -33
  278. data/spec/Classes/CAObject_spec.rb +0 -33
  279. data/spec/Classes/CARefer_spec.rb +0 -93
  280. data/spec/Classes/CARepeat_spec.rb +0 -65
  281. data/spec/Classes/CASelect_spec.rb +0 -22
  282. data/spec/Classes/CAShift_spec.rb +0 -16
  283. data/spec/Classes/CAStruct_spec.rb +0 -71
  284. data/spec/Classes/CATranspose_spec.rb +0 -60
  285. data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
  286. data/spec/Classes/CAWindow_spec.rb +0 -54
  287. data/spec/Classes/CAWrap_spec.rb +0 -8
  288. data/spec/Classes/CArray_spec.rb +0 -184
  289. data/spec/Classes/CScalar_spec.rb +0 -55
  290. data/spec/Classes/ex1.rb +0 -46
  291. data/spec/Features/feature_130_spec.rb +0 -19
  292. data/spec/Features/feature_attributes_spec.rb +0 -280
  293. data/spec/Features/feature_boolean_spec.rb +0 -98
  294. data/spec/Features/feature_broadcast.rb +0 -116
  295. data/spec/Features/feature_cast_function.rb +0 -19
  296. data/spec/Features/feature_cast_spec.rb +0 -33
  297. data/spec/Features/feature_class_spec.rb +0 -84
  298. data/spec/Features/feature_complex_spec.rb +0 -42
  299. data/spec/Features/feature_composite_spec.rb +0 -124
  300. data/spec/Features/feature_convert_spec.rb +0 -46
  301. data/spec/Features/feature_copy_spec.rb +0 -123
  302. data/spec/Features/feature_creation_spec.rb +0 -84
  303. data/spec/Features/feature_element_spec.rb +0 -144
  304. data/spec/Features/feature_extream_spec.rb +0 -54
  305. data/spec/Features/feature_generate_spec.rb +0 -74
  306. data/spec/Features/feature_index_spec.rb +0 -69
  307. data/spec/Features/feature_mask_spec.rb +0 -580
  308. data/spec/Features/feature_math_spec.rb +0 -97
  309. data/spec/Features/feature_order_spec.rb +0 -146
  310. data/spec/Features/feature_ref_store_spec.rb +0 -209
  311. data/spec/Features/feature_serialization_spec.rb +0 -125
  312. data/spec/Features/feature_stat_spec.rb +0 -397
  313. data/spec/Features/feature_virtual_spec.rb +0 -48
  314. data/spec/Features/method_eq_spec.rb +0 -81
  315. data/spec/Features/method_is_nan_spec.rb +0 -12
  316. data/spec/Features/method_map_spec.rb +0 -54
  317. data/spec/Features/method_max_with.rb +0 -20
  318. data/spec/Features/method_min_with.rb +0 -19
  319. data/spec/Features/method_ne_spec.rb +0 -18
  320. data/spec/Features/method_project_spec.rb +0 -188
  321. data/spec/Features/method_ref_spec.rb +0 -27
  322. data/spec/Features/method_round_spec.rb +0 -11
  323. data/spec/Features/method_s_linspace_spec.rb +0 -48
  324. data/spec/Features/method_s_span_spec.rb +0 -14
  325. data/spec/Features/method_seq_spec.rb +0 -47
  326. data/spec/Features/method_sort_with.rb +0 -43
  327. data/spec/Features/method_sorted_with.rb +0 -29
  328. data/spec/Features/method_span_spec.rb +0 -42
  329. data/spec/Features/method_wrap_readonly_spec.rb +0 -43
  330. data/spec/UnitTest/test_CAVirtual.rb +0 -214
  331. data/spec/spec_all.rb +0 -10
  332. data/utils/ca_ase.rb +0 -21
  333. data/utils/ca_methods.rb +0 -15
  334. data/utils/cast_checker.rb +0 -30
  335. data/utils/convert_test.rb +0 -73
  336. data/utils/extract_yard.rb +0 -22
  337. data/utils/guess_shape.rb +0 -76
  338. data/utils/monkey_patch_methods.rb +0 -62
  339. data/utils/remove_resource_fork.sh +0 -5
data/ext/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,525 @@ 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;
118
+
119
+
120
+ /* ------------------------------------------------------------------- */
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
+ }
181
+
182
+ /* ------------------------------------------------------------------- */
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
+ for (k = 0; k < ndim; k++) {
338
+ if ( (w->bounds[k] != CA_BOUNDS_FILL && w->bounds[k] != CA_BOUNDS_MASK)
339
+ || strides[k] % wnative[k] != 0
340
+ || strides[k] / wnative[k] != 1 ) {
341
+ structural = 0;
342
+ break;
343
+ }
344
+ }
345
+
346
+ if (!structural) {
347
+ ca_size_t idx[CA_RANK_MAX], doff = 0, base = 0;
348
+ for (k = 0; k < ndim; k++) base += starts[k] * wnative[k];
349
+ for (k = 0; k < ndim; k++) idx[k] = 0;
350
+ while (1) {
351
+ ca_size_t woff = base, widx[CA_RANK_MAX];
352
+ for (k = 0; k < ndim; k++) woff += idx[k] * strides[k];
353
+ ca_addr2index((CArray *) w, woff / w->bytes, widx);
354
+ ca_window_func_xfer_index(w, widx, d + doff, dir);
355
+ doff += w->bytes;
356
+ k = ndim - 1;
357
+ while (k >= 0) { if (++idx[k] < counts[k]) break; idx[k] = 0; k--; }
358
+ if (k < 0) break;
359
+ }
360
+ return;
361
+ }
362
+
363
+ /* Intersect sub-region [starts, starts+counts) with the in-bound region.
364
+ lo[k]/hi[k] are offsets within the sub-region (= output-coordinate)
365
+ where the parent is in-bound. Same algebra as the per-row loop below,
366
+ hoisted up so the embed-based path can share it. */
367
+ for (k = 0; k < ndim; k++) {
368
+ ca_size_t l, h;
369
+ base_pos[k] = w->start[k] + starts[k];
370
+ l = -base_pos[k]; if (l < 0) l = 0;
371
+ h = w->size0[k] - base_pos[k]; if (h > counts[k]) h = counts[k];
372
+ if (h < 0) h = 0;
373
+ if (l > h) l = h;
374
+ lo[k] = l; hi[k] = h;
375
+ }
376
+
377
+ /* Fast path: when the parent is attached (ptr != NULL), drive
378
+ ca_composite_region_* + fill_complement directly with the sub-region
379
+ intersection geometry. This collapses the outer per-row dispatch loop
380
+ (one ca_xfer_stride per inner row) into a single batched routine. An
381
+ unattached parent falls through to the per-row loop below, which is
382
+ equivalent but slower. */
383
+ if ( parent->ptr ) {
384
+ ca_size_t any_empty = 0;
385
+ ca_size_t alias_parent_start[CA_RANK_MAX];
386
+ ca_size_t alias_count[CA_RANK_MAX];
387
+ ca_size_t alias_output_offset[CA_RANK_MAX];
388
+ ca_size_t parent_strides[CA_RANK_MAX];
389
+
390
+ for (k = 0; k < ndim; k++) {
391
+ if (lo[k] >= hi[k]) { any_empty = 1; break; }
392
+ alias_output_offset[k] = lo[k];
393
+ alias_count[k] = hi[k] - lo[k];
394
+ alias_parent_start[k] = base_pos[k] + lo[k];
395
+ }
396
+
397
+ if (dir == CA_XFER_GET) {
398
+ if (any_empty) {
399
+ ca_fill_typed(d, w->fill, w->bytes, n);
400
+ } else {
401
+ ca_composite_fill_complement(d, dstride, counts,
402
+ alias_output_offset, alias_count,
403
+ w->fill, w->bytes, ndim);
404
+ /* parent_strides = row-major byte stride over parent->dim */
405
+ s = parent->bytes;
406
+ for (k = ndim - 1; k >= 0; k--) { parent_strides[k] = s; s *= parent->dim[k]; }
407
+ ca_composite_region_gather(parent->ptr, parent_strides,
408
+ alias_parent_start,
409
+ d, dstride, alias_output_offset,
410
+ alias_count, ndim, w->bytes);
411
+ }
412
+ } else { /* CA_XFER_PUT */
413
+ /* complement silent skip (no parent storage for fill region) */
414
+ if (!any_empty) {
415
+ s = parent->bytes;
416
+ for (k = ndim - 1; k >= 0; k--) { parent_strides[k] = s; s *= parent->dim[k]; }
417
+ ca_composite_region_scatter(parent->ptr, parent_strides,
418
+ alias_parent_start,
419
+ d, dstride, alias_output_offset,
420
+ alias_count, ndim, w->bytes);
421
+ }
422
+ }
423
+ return;
424
+ }
425
+
426
+ /* Parent unattached: per-row outer loop + ca_xfer_stride(parent)
427
+ recursion. */
428
+ if (dir == CA_XFER_GET) {
429
+ for (i = 0; i < n; i++) memcpy(d + i * w->bytes, w->fill, w->bytes);
430
+ }
431
+
432
+ for (k = 0; k < ndim; k++) {
433
+ if (lo[k] >= hi[k]) return; /* no in-bound cells (GET: filled; PUT: skip) */
434
+ }
435
+
436
+ for (k = 0; k < ndim; k++) o[k] = lo[k];
437
+ while (1) {
438
+ ca_size_t pbase = 0, doff = 0;
439
+ ca_size_t pstarts[CA_RANK_MAX], pcounts[CA_RANK_MAX], pstrides[CA_RANK_MAX];
440
+ ca_size_t inner_pbase;
441
+ for (k = 0; k < inner; k++) {
442
+ pbase += (base_pos[k] + o[k]) * pnative[k];
443
+ doff += o[k] * dstride[k];
444
+ }
445
+ inner_pbase = (base_pos[inner] + lo[inner]) * pnative[inner];
446
+ ca_addr2index((CArray *) parent, (pbase + inner_pbase) / parent->bytes, pstarts);
447
+ for (k = 0; k < ndim; k++) { pcounts[k] = 1; pstrides[k] = 0; }
448
+ pcounts[inner] = hi[inner] - lo[inner];
449
+ pstrides[inner] = pnative[inner];
450
+ ca_xfer_stride(parent, pstarts, pcounts, pstrides,
451
+ d + doff + lo[inner] * dstride[inner], dir);
452
+
453
+ k = inner - 1;
454
+ while (k >= 0) { o[k]++; if (o[k] < hi[k]) break; o[k] = lo[k]; k--; }
455
+ if (k < 0) break;
456
+ }
457
+ }
458
+
459
+ /* Executes the "1 typed fill + 1 strided memcpy" embed path on a
460
+ pre-allocated output buffer. Requires the parent to be attached (the
461
+ caller's responsibility).
462
+
463
+ Pre-conditions (must hold; not validated here):
464
+ - ca->embed_eligible == 1 (all axes FILL or MASK)
465
+ - ca->parent->ptr != NULL (parent attached)
466
+ - out_ptr points to ca->elements * ca->bytes writable bytes
467
+
468
+ Behaviour:
469
+ - if embed_covers_all: skip fill (alias overwrites every cell)
470
+ - else: fill the entire output with ca->fill (typed loop)
471
+ - if embed_is_empty: skip alias copy
472
+ - else: strided memcpy from parent[embed_parent_*] rectangle to
473
+ out_ptr[embed_output_*] rectangle. Inner contig run =
474
+ embed_count[ndim-1] * bytes. Outer axes iterate row-major
475
+ via a multi-dim cursor; per-axis offsets are precomputed in
476
+ bytes for both parent and output.
477
+
478
+ The strided copy itself is ca_composite_region_gather
479
+ (ca_composite_dispatch.c), shared with CATile / CAStack. */
480
+ static void
481
+ ca_window_attach_embed (CAWindow *ca, char *out_ptr)
482
+ {
483
+ int8_t ndim = ca->ndim;
484
+ ca_size_t bytes = ca->bytes;
485
+ ca_size_t parent_strides[CA_RANK_MAX];
486
+ ca_size_t output_strides[CA_RANK_MAX];
487
+ ca_size_t s;
488
+ int8_t k;
489
+
490
+ /* Row-major byte strides for output (used by fill-complement and
491
+ region-gather both). */
492
+ s = bytes;
493
+ for ( k = ndim - 1; k >= 0; k-- ) {
494
+ output_strides[k] = s;
495
+ s *= ca->count[k];
496
+ }
497
+
498
+ /* Step 1: fill the complement of the alias rectangle only — filling the
499
+ whole output first would write the alias area twice. For
500
+ embed_is_empty (= alias is empty, whole output is fill) short-circuit
501
+ to a full-output typed fill since the complement is the whole output;
502
+ for embed_covers_all (= alias is the whole output, no fill) skip. */
503
+ if ( ! ca->embed_covers_all ) {
504
+ if ( ca->embed_is_empty ) {
505
+ ca_fill_typed(out_ptr, ca->fill, bytes, ca->elements);
506
+ } else {
507
+ ca_composite_fill_complement(out_ptr, output_strides, ca->count,
508
+ ca->embed_output_offset,
509
+ ca->embed_count,
510
+ ca->fill, bytes, ndim);
511
+ }
512
+ }
513
+
514
+ /* Step 2: strided memcpy from parent alias rectangle to output rectangle. */
515
+ if ( ca->embed_is_empty ) return;
516
+
517
+ s = bytes;
518
+ for ( k = ndim - 1; k >= 0; k-- ) {
519
+ parent_strides[k] = s;
520
+ s *= ca->parent->dim[k];
521
+ }
522
+
523
+ ca_composite_region_gather(ca->parent->ptr, parent_strides,
524
+ ca->embed_parent_start,
525
+ out_ptr, output_strides,
526
+ ca->embed_output_offset,
527
+ ca->embed_count, ndim, bytes);
528
+ }
529
+
530
+ /* Executes the reverse of ca_window_attach_embed on a caller-provided source
531
+ buffer. Strided memcpy from the alias sub-rectangle of in_ptr (= the
532
+ view's data layout) back into the parent's alias rectangle. The fill
533
+ region is ignored: writes in the OOB area have no parent cell to land in
534
+ and are dropped, matching ca_axis_dispatch_scatter's SHIFT-kind OOB.
43
535
 
536
+ Pre-conditions:
537
+ - ca->embed_eligible == 1
538
+ - ca->parent->ptr != NULL (parent attached)
539
+ - in_ptr points to ca->elements * ca->bytes valid bytes
44
540
 
45
- /* yard:
46
- class CAWindow < CAVirtual # :nodoc:
47
- end
48
- */
541
+ The reverse strided copy is ca_composite_region_scatter
542
+ (ca_composite_dispatch.c), shared with the CATile / CAStack sync paths. */
543
+ static void
544
+ ca_window_sync_embed (CAWindow *ca, char *in_ptr)
545
+ {
546
+ int8_t ndim = ca->ndim;
547
+ ca_size_t bytes = ca->bytes;
548
+ ca_size_t parent_strides[CA_RANK_MAX];
549
+ ca_size_t input_strides[CA_RANK_MAX];
550
+ ca_size_t s;
551
+ int8_t k;
552
+
553
+ if ( ca->embed_is_empty ) return; /* nothing of in_ptr maps back */
554
+
555
+ /* Row-major byte strides for parent and the view-shaped source. */
556
+ s = bytes;
557
+ for ( k = ndim - 1; k >= 0; k-- ) {
558
+ parent_strides[k] = s;
559
+ s *= ca->parent->dim[k];
560
+ }
561
+ s = bytes;
562
+ for ( k = ndim - 1; k >= 0; k-- ) {
563
+ input_strides[k] = s;
564
+ s *= ca->count[k];
565
+ }
49
566
 
567
+ ca_composite_region_scatter(ca->parent->ptr, parent_strides,
568
+ ca->embed_parent_start,
569
+ in_ptr, input_strides,
570
+ ca->embed_output_offset,
571
+ ca->embed_count, ndim, bytes);
572
+ }
50
573
 
51
574
  /* ------------------------------------------------------------------- */
52
575
 
576
+ /* Recomputes the embed descriptor + eligibility flags from the current
577
+ (start, count, bounds). The recompute is unconditional — no eligibility
578
+ pre-check — so callers never have to reason about which flags are stale.
579
+ Called by ca_window_setup.
580
+ *
581
+ * CAREFUL: any code path that mutates ca->start[] in place (e.g.
582
+ * ca_window_move, which is why this is not static) must call this
583
+ * afterwards. Otherwise embed_* keeps describing the OLD start and the
584
+ * embed-model attach / copy_data reads the wrong parent rectangle — wrong
585
+ * data, no error. */
586
+ void
587
+ ca_window_recompute_embed (CAWindow *ca)
588
+ {
589
+ int8_t ndim = ca->ndim;
590
+ int8_t i;
591
+
592
+ ca_compute_embed_descriptor(ndim, ca->parent->dim, ca->start, ca->count,
593
+ ca->embed_parent_start,
594
+ ca->embed_count,
595
+ ca->embed_output_offset,
596
+ &ca->embed_is_empty,
597
+ &ca->embed_covers_all);
598
+
599
+ /* embed path eligibility = all axes use FILL or MASK bounds. */
600
+ ca->embed_eligible = 1;
601
+ for (i = 0; i < ndim; i++) {
602
+ if ( ca->bounds[i] != CA_BOUNDS_FILL
603
+ && ca->bounds[i] != CA_BOUNDS_MASK ) {
604
+ ca->embed_eligible = 0;
605
+ break;
606
+ }
607
+ }
608
+
609
+ /* direct-attach alias eligibility (see ca_window_func_attach). */
610
+ ca->embed_alias_eligible = 0;
611
+ if ( ca->embed_eligible && ca->embed_covers_all ) {
612
+ int alias_ok = 1;
613
+ for (i = 1; i < ndim; i++) {
614
+ if ( ca->start[i] != 0 || ca->count[i] != ca->parent->dim[i] ) {
615
+ alias_ok = 0;
616
+ break;
617
+ }
618
+ }
619
+ if ( alias_ok ) ca->embed_alias_eligible = 1;
620
+ }
621
+ }
622
+
623
+ /* `bounds` is a per-axis uint8_t array. CAWindow's Ruby surface
624
+ (rb_ca_window) receives a single scalar policy and fans it out to all axes;
625
+ the per-axis form is what lets CAShift be a CAWindow specialisation. */
53
626
  int
54
627
  ca_window_setup (CAWindow *ca, CArray *parent,
55
- ca_size_t *start, ca_size_t *count, int8_t bounds, char *fill)
628
+ ca_size_t *start, ca_size_t *count, uint8_t *bounds, char *fill)
56
629
  {
57
630
  int8_t data_type, ndim;
58
631
  ca_size_t *dim;
59
632
  ca_size_t bytes, elements;
60
633
  int i;
634
+ int any_mask;
61
635
 
62
636
  data_type = parent->data_type;
63
637
  ndim = parent->ndim;
@@ -86,18 +660,34 @@ ca_window_setup (CAWindow *ca, CArray *parent,
86
660
  ca->parent = parent;
87
661
  ca->attach = 0;
88
662
  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);
663
+ /* Pool path: bounds/start/count/size0/embed_* are already wired into
664
+ ca->_pool by ca_window_pool_init. Legacy path: ALLOC_N each. `fill`
665
+ is bytes-sized (not ndim) so it stays on its own ALLOC_N in both
666
+ branches. */
667
+ if ( ! ca->_pool ) {
668
+ ca->bounds = ALLOC_N(uint8_t, ndim);
669
+ ca->start = ALLOC_N(ca_size_t, ndim);
670
+ ca->count = ALLOC_N(ca_size_t, ndim);
671
+ ca->size0 = ALLOC_N(ca_size_t, ndim);
672
+ ca->embed_parent_start = ALLOC_N(ca_size_t, ndim);
673
+ ca->embed_count = ALLOC_N(ca_size_t, ndim);
674
+ ca->embed_output_offset = ALLOC_N(ca_size_t, ndim);
675
+ }
93
676
  ca->fill = ALLOC_N(char, ca->bytes);
677
+ ca->embed_is_empty = 0;
678
+ ca->embed_covers_all = 0;
94
679
 
95
680
  ca->dim = ca->count;
96
681
 
682
+ memcpy(ca->bounds, bounds, ndim * sizeof(uint8_t));
97
683
  memcpy(ca->start, start, ndim * sizeof(ca_size_t));
98
684
  memcpy(ca->count, count, ndim * sizeof(ca_size_t));
99
685
  memcpy(ca->size0, dim, ndim * sizeof(ca_size_t));
100
686
 
687
+ /* Compute the embed descriptor + eligibility flags from the current
688
+ (start, count, bounds). See ca_window_recompute_embed below. */
689
+ ca_window_recompute_embed(ca);
690
+
101
691
  if ( fill ) {
102
692
  memcpy(ca->fill, fill, ca->bytes);
103
693
  }
@@ -110,7 +700,15 @@ ca_window_setup (CAWindow *ca, CArray *parent,
110
700
  }
111
701
  }
112
702
 
113
- if ( ca->bounds == CA_BOUNDS_MASK ) {
703
+ /* Mask is needed if ANY axis uses MASK policy (per-axis). */
704
+ any_mask = 0;
705
+ for (i=0; i<ndim; i++) {
706
+ if ( ca->bounds[i] == CA_BOUNDS_MASK ) {
707
+ any_mask = 1;
708
+ break;
709
+ }
710
+ }
711
+ if ( any_mask ) {
114
712
  ca_create_mask(ca);
115
713
  }
116
714
 
@@ -119,9 +717,9 @@ ca_window_setup (CAWindow *ca, CArray *parent,
119
717
 
120
718
  CAWindow *
121
719
  ca_window_new (CArray *parent,
122
- ca_size_t *start, ca_size_t *count, int8_t bounds, char *fill)
720
+ ca_size_t *start, ca_size_t *count, uint8_t *bounds, char *fill)
123
721
  {
124
- CAWindow *ca = ALLOC(CAWindow);
722
+ CAWindow *ca = (CAWindow *) ca_array_alloc(CA_OBJ_WINDOW, parent->ndim);
125
723
  ca_window_setup(ca, parent, start, count, bounds, fill);
126
724
  return ca;
127
725
  }
@@ -132,18 +730,38 @@ free_ca_window (void *ap)
132
730
  CAWindow *ca = (CAWindow *) ap;
133
731
  if ( ca != NULL ) {
134
732
  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);
733
+ xfree(ca->fill); /* always separate (bytes-sized) */
734
+ if ( ca->_pool ) {
735
+ /* bounds/start/count/size0/embed_* all live in ca->_pool. */
736
+ ca_array_free(ca); /* one xfree pool + one xfree struct */
737
+ }
738
+ else {
739
+ xfree(ca->bounds);
740
+ xfree(ca->start);
741
+ xfree(ca->count);
742
+ xfree(ca->size0);
743
+ xfree(ca->embed_parent_start);
744
+ xfree(ca->embed_count);
745
+ xfree(ca->embed_output_offset);
746
+ /* xfree(ca->dim); */
747
+ xfree(ca);
748
+ }
141
749
  }
142
750
  }
143
751
 
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);
752
+ /* Path selection for attach / sync / copy_data / sync_data below.
753
+
754
+ embed_eligible (= all axes use FILL or MASK bounds) takes the embed path:
755
+ "1 typed fill + 1 strided memcpy" with no per-cell bound check (see
756
+ ca_window_attach_embed / ca_window_sync_embed above).
757
+
758
+ Everything else (PERIODIC — still reached through CAShift's roll form —
759
+ REFLECT / NEAREST / RUBY / STRICT) goes through the descriptor engine,
760
+ ca_axis_dispatch_* fed by ca_window_describe_axes (defined below), which
761
+ still promotes interior axes to STRIDE kind.
762
+
763
+ fill_data is the exception: it has its own split (see
764
+ ca_window_func_fill_data). */
147
765
 
148
766
  /* ------------------------------------------------------------------- */
149
767
 
@@ -154,57 +772,11 @@ ca_window_func_clone (void *ap)
154
772
  return ca_window_new(ca->parent, ca->start, ca->count, ca->bounds, ca->fill);
155
773
  }
156
774
 
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
-
775
+ /* Per-cell access. GET and PUT share the bound-normalised index walk; an OOB
776
+ cell is filled on GET and skipped on PUT. CAShift inherits this via the
777
+ ca_shift_func copy of the operation table. */
206
778
  static void
207
- ca_window_func_fetch_index (void *ap, ca_size_t *idx, void *ptr)
779
+ ca_window_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
208
780
  {
209
781
  CAWindow *ca = (CAWindow *) ap;
210
782
  ca_size_t *start = ca->start;
@@ -214,34 +786,143 @@ ca_window_func_fetch_index (void *ap, ca_size_t *idx, void *ptr)
214
786
  ca_size_t k;
215
787
  for (i=0; i<ca->ndim; i++) {
216
788
  k = start[i] + idx[i];
217
- k = ca_bounds_normalize_index(ca->bounds, size0[i], k);
789
+ k = ca_bounds_normalize_index(ca->bounds[i], size0[i], k);
218
790
  if ( k < 0 || k >= size0[i] ) {
219
- memcpy(ptr, ca->fill, ca->bytes);
220
- return;
791
+ if ( dir == CA_XFER_GET ) memcpy(data, ca->fill, ca->bytes);
792
+ return; /* PUT to out-of-bounds cell: skip */
221
793
  }
222
794
  idx0[i] = k;
223
795
  }
224
- ca_fetch_index(ca->parent, idx0, ptr);
796
+ ca_xfer_index(ca->parent, idx0, data, dir);
225
797
  }
226
798
 
799
+ /* Batched address gather/scatter. Bound-normalises each view addr; OOB cells are handled inline (GET fills,
800
+ PUT skips) and the in-bounds cells are delivered to the parent in ONE
801
+ ca_xfer_addrs call. When some cells are OOB the in-bounds set is packed
802
+ into a contiguous temp (the parent's contig-buf contract), gathered/
803
+ scattered, then unpacked. No whole-view attach.
804
+ CAShift inherits via ca_shift_func copy. */
227
805
  static void
228
- ca_window_func_store_index (void *ap, ca_size_t *idx, void *ptr)
806
+ ca_window_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
807
+ void *data, int dir)
229
808
  {
230
809
  CAWindow *ca = (CAWindow *) ap;
231
810
  ca_size_t *start = ca->start;
232
811
  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] ) {
812
+ char *d = (char *) data;
813
+ ca_size_t *paddrs;
814
+ ca_size_t *pos;
815
+ ca_size_t m = 0, i, base;
816
+ int8_t k;
817
+ volatile VALUE h1, h2;
818
+
819
+ /* Fast path: an embed-eligible window (interior alias rectangle + OOB
820
+ strips for FILL mode) over a parent that resolves to a ptr-bearing root.
821
+ Whole-view sequential addrs let us drive ca_composite_region_gather /
822
+ ca_composite_fill_complement directly (= the same helpers
823
+ ca_window_attach_embed / _sync_embed use), skipping the per-cell
824
+ bounds-normalise + OOB-pack two-pass. Covers both interior-only windows
825
+ and boundary-crossing ones (CAShift included): the OOB strip fill is
826
+ batched through ca_composite_fill_complement. */
827
+ if ( ca->embed_eligible
828
+ && n == ca->elements
829
+ && ca_xfer_addrs_is_sequential_run(n, addrs, &base) && base == 0 ) {
830
+ CArray *eff_parent = ca_resolve_attached_root_via_identity(ca->parent);
831
+ if ( eff_parent->ptr ) {
832
+ ca_size_t output_strides[CA_RANK_MAX];
833
+ ca_size_t parent_strides[CA_RANK_MAX];
834
+ ca_size_t s;
835
+ s = ca->bytes;
836
+ for ( k = ca->ndim - 1; k >= 0; k-- ) {
837
+ output_strides[k] = s;
838
+ s *= ca->count[k];
839
+ }
840
+ s = eff_parent->bytes;
841
+ for ( k = ca->ndim - 1; k >= 0; k-- ) {
842
+ parent_strides[k] = s;
843
+ s *= ca->parent->dim[k]; /* logical shape from immediate parent */
844
+ }
845
+ if ( dir == CA_XFER_GET ) {
846
+ /* Step 1: fill OOB complement (no-op when covers_all). */
847
+ if ( ! ca->embed_covers_all ) {
848
+ if ( ca->embed_is_empty ) {
849
+ ca_fill_typed((char *) data, ca->fill, ca->bytes, ca->elements);
850
+ } else {
851
+ ca_composite_fill_complement((char *) data, output_strides, ca->count,
852
+ ca->embed_output_offset,
853
+ ca->embed_count,
854
+ ca->fill, ca->bytes, ca->ndim);
855
+ }
856
+ }
857
+ /* Step 2: gather interior alias rectangle from parent. */
858
+ if ( ! ca->embed_is_empty ) {
859
+ ca_composite_region_gather(eff_parent->ptr, parent_strides,
860
+ ca->embed_parent_start,
861
+ (char *) data, output_strides,
862
+ ca->embed_output_offset,
863
+ ca->embed_count, ca->ndim, ca->bytes);
864
+ }
865
+ } else { /* CA_XFER_PUT */
866
+ /* Scatter the input rectangle back to the parent's alias
867
+ rectangle. Writes to OOB cells are dropped — same as
868
+ ca_window_sync_embed and the engine's SHIFT-kind scatter. */
869
+ if ( ! ca->embed_is_empty ) {
870
+ ca_composite_region_scatter(eff_parent->ptr, parent_strides,
871
+ ca->embed_parent_start,
872
+ (char *) data, output_strides,
873
+ ca->embed_output_offset,
874
+ ca->embed_count, ca->ndim, ca->bytes);
875
+ }
876
+ }
240
877
  return;
241
878
  }
242
- idx0[i] = k;
243
879
  }
244
- ca_store_index(ca->parent, idx0, ptr);
880
+
881
+ paddrs = ALLOCV_N(ca_size_t, h1, n);
882
+ pos = ALLOCV_N(ca_size_t, h2, n);
883
+
884
+ for (i = 0; i < n; i++) {
885
+ ca_size_t vidx[CA_RANK_MAX], pidx[CA_RANK_MAX];
886
+ int oob = 0;
887
+ ca_addr2index((CArray *) ca, addrs[i], vidx);
888
+ for (k = 0; k < ca->ndim; k++) {
889
+ ca_size_t kk = start[k] + vidx[k];
890
+ kk = ca_bounds_normalize_index(ca->bounds[k], size0[k], kk);
891
+ if (kk < 0 || kk >= size0[k]) { oob = 1; break; }
892
+ pidx[k] = kk;
893
+ }
894
+ if (oob) {
895
+ if (dir == CA_XFER_GET) memcpy(d + i * ca->bytes, ca->fill, ca->bytes);
896
+ /* PUT to OOB cell: skip */
897
+ }
898
+ else {
899
+ paddrs[m] = ca_index2addr(ca->parent, pidx);
900
+ pos[m] = i;
901
+ m++;
902
+ }
903
+ }
904
+
905
+ if (m == n) { /* no OOB: deliver in place, contiguous */
906
+ ca_xfer_addrs(ca->parent, n, paddrs, data, dir);
907
+ }
908
+ else if (m > 0) { /* some OOB: pack the in-bounds set */
909
+ volatile VALUE h3;
910
+ char *packed = ALLOCV_N(char, h3, m * ca->bytes);
911
+ if (dir == CA_XFER_GET) {
912
+ ca_xfer_addrs(ca->parent, m, paddrs, packed, CA_XFER_GET);
913
+ for (i = 0; i < m; i++)
914
+ memcpy(d + pos[i] * ca->bytes, packed + i * ca->bytes, ca->bytes);
915
+ }
916
+ else {
917
+ for (i = 0; i < m; i++)
918
+ memcpy(packed + i * ca->bytes, d + pos[i] * ca->bytes, ca->bytes);
919
+ ca_xfer_addrs(ca->parent, m, paddrs, packed, CA_XFER_PUT);
920
+ }
921
+ ALLOCV_END(h3);
922
+ }
923
+
924
+ ALLOCV_END(h2);
925
+ ALLOCV_END(h1);
245
926
  }
246
927
 
247
928
  static void
@@ -250,26 +931,66 @@ ca_window_func_allocate (void *ap)
250
931
  CAWindow *ca = (CAWindow *) ap;
251
932
  ca_attach(ca->parent);
252
933
  /* ca->ptr = ALLOC_N(char, ca_length(ca)); */
253
- ca->ptr = malloc_with_check(ca_length(ca));
934
+ ca->ptr = xmalloc(ca_length(ca));
254
935
  }
255
936
 
937
+ /* The engine paths below emit a per-axis descriptor (ca_window_describe_axes)
938
+ and let the shared engine's SHIFT-kind handling apply the boundary policy
939
+ and the OOB cell fill. CAShift inherits all of it through
940
+ ca_shift_func = a copy of ca_window_func. */
941
+
942
+ void ca_window_describe_axes (void *ap, ca_axis_desc_t *out,
943
+ ca_size_t *out_parent_dims);
944
+
256
945
  static void
257
946
  ca_window_func_attach (void *ap)
258
947
  {
259
- void ca_window_attach (CAWindow *cb);
260
-
261
948
  CAWindow *ca = (CAWindow *) ap;
262
949
  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);
950
+ if ( ca->embed_alias_eligible ) {
951
+ /* Alias path: inner axes full + interior, so the embedded region is a
952
+ contiguous run of parent storage. Skip malloc/memcpy and point
953
+ ca->ptr into the parent's buffer.
954
+ CAREFUL: sync and detach must agree with this — sync has nothing to
955
+ scatter back (the writes already landed in the parent) and detach must
956
+ not xfree a pointer it does not own. */
957
+ ca_size_t parent_row_stride = ca->bytes;
958
+ int8_t k;
959
+ for (k = ca->ndim - 1; k >= 1; k--) parent_row_stride *= ca->parent->dim[k];
960
+ ca->ptr = ca->parent->ptr + ca->start[0] * parent_row_stride;
961
+ } else if ( ca->embed_eligible ) {
962
+ /* Embed path: allocate, then 1 fill + 1 strided memcpy. */
963
+ ca_size_t out_len = ca->elements * ca->bytes;
964
+ ca->ptr = xmalloc(out_len > 0 ? out_len : 1);
965
+ ca_window_attach_embed(ca, ca->ptr);
966
+ } else {
967
+ /* Fallback: PERIODIC / REFLECT / NEAREST / RUBY / STRICT go through
968
+ the descriptor engine. */
969
+ ca_axis_desc_t desc[CA_RANK_MAX];
970
+ ca_size_t pdims[CA_RANK_MAX];
971
+ ca_window_describe_axes(ca, desc, pdims);
972
+ ca->ptr = ca_axis_dispatch_attach(ca->parent, pdims, desc, ca->ndim,
973
+ ca->bytes, ca->elements, ca->fill);
974
+ }
266
975
  }
267
976
 
268
977
  static void
269
978
  ca_window_func_sync (void *ap)
270
979
  {
271
980
  CAWindow *ca = (CAWindow *) ap;
272
- ca_window_sync(ca);
981
+ if ( ca->embed_alias_eligible ) {
982
+ /* Alias path: ca->ptr aliases the parent, so the writes already landed
983
+ in parent storage. Nothing to scatter back. */
984
+ } else if ( ca->embed_eligible ) {
985
+ /* Embed path: write back the alias region only. */
986
+ ca_window_sync_embed(ca, ca->ptr);
987
+ } else {
988
+ ca_axis_desc_t desc[CA_RANK_MAX];
989
+ ca_size_t pdims[CA_RANK_MAX];
990
+ ca_window_describe_axes(ca, desc, pdims);
991
+ ca_axis_dispatch_scatter(ca->parent, pdims, desc, ca->ndim, ca->bytes,
992
+ ca->elements, ca->ptr);
993
+ }
273
994
  ca_sync(ca->parent);
274
995
  }
275
996
 
@@ -277,42 +998,177 @@ static void
277
998
  ca_window_func_detach (void *ap)
278
999
  {
279
1000
  CAWindow *ca = (CAWindow *) ap;
280
- free(ca->ptr);
1001
+ /* Alias path: ca->ptr aliases parent storage; it is not ours to xfree. */
1002
+ if ( ! ca->embed_alias_eligible ) {
1003
+ xfree(ca->ptr);
1004
+ }
281
1005
  ca->ptr = NULL;
282
1006
  ca_detach(ca->parent);
283
1007
  }
284
1008
 
1009
+ /* Fast path body for xfer_all, shared by the warm and the cold-parent case.
1010
+ Both the embed path and the engine dispatch read ca->parent->ptr directly,
1011
+ so the caller must have made it available.
1012
+
1013
+ CAREFUL: neither this nor ca_window_func_xfer_all may call
1014
+ ca_attach(parent). A transfer slot that silently attaches its parent
1015
+ materialises the whole parent behind the caller's back — the cold case
1016
+ below instead materialises a parent-shaped scratch through ca_xfer_all,
1017
+ which recurses under the same rule. */
285
1018
  static void
286
- ca_window_func_copy_data (void *ap, void *ptr)
1019
+ ca_window_func_run_fast_path (CAWindow *ca, char *data, int dir)
287
1020
  {
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);
1021
+ if ( dir == CA_XFER_GET ) {
1022
+ if ( ca->embed_eligible ) {
1023
+ ca_window_attach_embed(ca, data);
1024
+ } else {
1025
+ ca_axis_desc_t desc[CA_RANK_MAX];
1026
+ ca_size_t pdims[CA_RANK_MAX];
1027
+ ca_window_describe_axes(ca, desc, pdims);
1028
+ ca_axis_dispatch_gather(ca->parent, pdims, desc, ca->ndim, ca->bytes,
1029
+ ca->elements, ca->fill, data);
1030
+ }
1031
+ } else {
1032
+ if ( ca->embed_eligible ) {
1033
+ ca_window_sync_embed(ca, data);
1034
+ } else {
1035
+ ca_axis_desc_t desc[CA_RANK_MAX];
1036
+ ca_size_t pdims[CA_RANK_MAX];
1037
+ ca_window_describe_axes(ca, desc, pdims);
1038
+ ca_axis_dispatch_scatter(ca->parent, pdims, desc, ca->ndim, ca->bytes,
1039
+ ca->elements, data);
1040
+ }
1041
+ }
295
1042
  }
296
1043
 
297
1044
  static void
298
- ca_window_func_sync_data (void *ap, void *ptr)
1045
+ ca_window_func_xfer_all (void *ap, void *data, int dir)
299
1046
  {
300
1047
  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);
1048
+ if ( ca->parent->ptr ) {
1049
+ ca_window_func_run_fast_path(ca, (char *) data, dir);
1050
+ return;
1051
+ }
1052
+ /* Cold parent: materialise it into a scratch buffer via ca_xfer_all, then
1053
+ run the normal fast path with the scratch standing in for parent->ptr. */
1054
+ {
1055
+ volatile VALUE holder;
1056
+ CArray *parent = ca->parent;
1057
+ ca_size_t plen = parent->elements * parent->bytes;
1058
+ char *parent_scratch = ALLOCV_N(char, holder, plen);
1059
+ char *parent_ptr_saved = parent->ptr;
1060
+
1061
+ /* GET path needs parent data; PUT path will overwrite parent so we
1062
+ still need to read existing parent state if the view's fast path
1063
+ does partial writes (e.g., embed_sync overwrites only the embed
1064
+ rectangle, OOB cells untouched in parent). Safe default: always
1065
+ GET first. */
1066
+ ca_xfer_all(parent, parent_scratch, CA_XFER_GET);
1067
+ parent->ptr = parent_scratch;
1068
+
1069
+ ca_window_func_run_fast_path(ca, (char *) data, dir);
1070
+
1071
+ if ( dir == CA_XFER_PUT ) {
1072
+ /* Push back scratch (modified by scatter) to parent. */
1073
+ ca_xfer_all(parent, parent_scratch, CA_XFER_PUT);
1074
+ }
1075
+
1076
+ parent->ptr = parent_ptr_saved;
1077
+ ALLOCV_END(holder);
1078
+ }
308
1079
  }
309
1080
 
1081
+ /* fill_data (= view.fill(scalar) / view[] = scalar).
1082
+
1083
+ Wrapping the window in a CAStride and filling that is markedly faster than
1084
+ the engine path, because compose-fold reaches the entity and the inner loop
1085
+ collapses to a per-row memset. So for an embed-eligible window we do the
1086
+ same thing directly: build a transient CAStride header matching
1087
+ ca_window_compose_fold's synthetic strides and dispatch to CAStride's
1088
+ fill_data, which continues compose-fold (covering
1089
+ CAStride-of-CAWindow-of-CAStride chains) and runs the merge + tight-fill
1090
+ inner loop. PERIODIC / REFLECT windows fall through to the engine path. */
310
1091
  static void
311
1092
  ca_window_func_fill_data (void *ap, void *ptr)
312
1093
  {
313
1094
  CAWindow *ca = (CAWindow *) ap;
1095
+
1096
+ /* embed_eligible (= no PERIODIC/REFLECT) covers both interior-only
1097
+ windows (embed_covers_all == 1) and OOB-bearing ones such as a CAShift
1098
+ with a fill_value (embed_covers_all == 0, embed_is_empty == 0). In the
1099
+ OOB case the synth is restricted to the interior region (embed_count
1100
+ cells starting at embed_parent_start in the parent): the view's OOB
1101
+ cells have no parent cell and must be skipped, which the restricted
1102
+ synth does by construction. A wholly-OOB window (embed_is_empty == 1)
1103
+ is a no-op. */
1104
+ if ( ca->embed_eligible && ! ca->embed_is_empty ) {
1105
+ ca_size_t synth_strides[CA_RANK_MAX];
1106
+ ca_size_t synth_base;
1107
+ ca_size_t s;
1108
+ int8_t k;
1109
+ ca_size_t synth_elements;
1110
+
1111
+ /* Row-major byte strides over parent. */
1112
+ s = ca->bytes;
1113
+ for ( k = ca->ndim - 1; k >= 0; k-- ) {
1114
+ synth_strides[k] = s;
1115
+ s *= ca->parent->dim[k];
1116
+ }
1117
+
1118
+ /* Origin: embedded region start in parent's byte space. */
1119
+ synth_base = 0;
1120
+ for ( k = 0; k < ca->ndim; k++ ) {
1121
+ synth_base += ca->embed_parent_start[k] * synth_strides[k];
1122
+ }
1123
+
1124
+ /* synth dims = interior count per axis (= the cells we actually
1125
+ write to; OOB view cells are skipped by construction). */
1126
+ synth_elements = 1;
1127
+ for ( k = 0; k < ca->ndim; k++ ) {
1128
+ synth_elements *= ca->embed_count[k];
1129
+ }
1130
+
1131
+ /* Stack-allocated transient CAStride header. Dispatched via the
1132
+ op table since ca_stride_func_fill_data is static in
1133
+ ca_obj_stride.c. It reads only struct fields (parent, ndim,
1134
+ dim, strides, base_offset, bytes, elements) and never
1135
+ registers / persists this pointer. */
1136
+ CAStride synth;
1137
+ memset(&synth, 0, sizeof(synth));
1138
+ synth.obj_type = CA_OBJ_STRIDE;
1139
+ synth.data_type = ca->data_type;
1140
+ synth.ndim = ca->ndim;
1141
+ synth.bytes = ca->bytes;
1142
+ synth.elements = synth_elements;
1143
+ synth.dim = ca->embed_count;
1144
+ synth.parent = ca->parent;
1145
+ synth.strides = synth_strides;
1146
+ synth.base_offset = synth_base;
1147
+ ca_func[CA_OBJ_STRIDE].fill_data(&synth, ptr);
1148
+ return;
1149
+ }
1150
+
1151
+ /* Engine path: PERIODIC / REFLECT, or pure-OOB window (no-op via
1152
+ engine's OOB-skip). Bound_fill writes to view's OOB cells are
1153
+ not propagated to parent (no cells to write to). */
1154
+ ca_axis_desc_t desc[CA_RANK_MAX];
1155
+ ca_size_t pdims[CA_RANK_MAX];
1156
+
1157
+ ca_window_describe_axes(ca, desc, pdims);
1158
+
1159
+ /* A wrapping window writes the cells it lands on, but a whole-parent attach
1160
+ and sync carries the rest of the parent with it — and over a lossy layer
1161
+ those cells do not come back the same. Hand each slab to the parent as a
1162
+ region instead, as the other views on the descriptor engine do. */
1163
+ if ( !ca_is_attached(ca->parent) && !ca_attach_is_alias(ca->parent) ) {
1164
+ ca_axis_dispatch_fill_value_via_parent(ca->parent, pdims, desc, ca->ndim,
1165
+ ca->bytes, ca->elements, ptr);
1166
+ return;
1167
+ }
1168
+
314
1169
  ca_attach(ca->parent);
315
- ca_window_fill(ca, ptr);
1170
+ ca_axis_dispatch_fill_value(ca->parent, pdims, desc, ca->ndim, ca->bytes,
1171
+ ca->elements, ptr);
316
1172
  ca_sync(ca->parent);
317
1173
  ca_detach(ca->parent);
318
1174
  }
@@ -322,392 +1178,97 @@ ca_window_func_create_mask (void *ap)
322
1178
  {
323
1179
  CAWindow *ca = (CAWindow *) ap;
324
1180
  boolean8_t fill;
325
- ca_size_t bounds = ca->bounds;
1181
+ uint8_t mbounds[CA_RANK_MAX];
1182
+ int8_t i;
1183
+ int any_mask;
326
1184
 
327
1185
  ca_update_mask(ca->parent);
328
1186
  if ( ! ca->parent->mask ) {
329
1187
  ca_create_mask(ca->parent);
330
1188
  }
331
1189
 
332
- if ( bounds == CA_BOUNDS_MASK ) {
333
- bounds = CA_BOUNDS_FILL;
334
- fill = 1;
335
- }
336
- else {
337
- fill = 0;
1190
+ /* Any MASK axis of the view becomes FILL in the mask sub-view, with the
1191
+ mask cell forced to 1 (= "masked"). Other axes keep their policy. When
1192
+ every axis shares MASK this is exactly "OOB cells are masked". */
1193
+ any_mask = 0;
1194
+ for (i = 0; i < ca->ndim; i++) {
1195
+ if ( ca->bounds[i] == CA_BOUNDS_MASK ) {
1196
+ mbounds[i] = CA_BOUNDS_FILL;
1197
+ any_mask = 1;
1198
+ } else {
1199
+ mbounds[i] = ca->bounds[i];
1200
+ }
338
1201
  }
1202
+ fill = any_mask ? 1 : 0;
339
1203
 
340
1204
  ca->mask = (CArray *) ca_window_new(ca->parent->mask,
341
- ca->start, ca->count, bounds, (char*)&fill);
1205
+ ca->start, ca->count, mbounds, (char*)&fill);
342
1206
  }
343
1207
 
344
1208
  ca_operation_function_t ca_window_func = {
345
1209
  -1, /* CA_OBJ_WINDOW */
346
- CA_VIRTUAL_ARRAY,
1210
+ CA_VIEW_ARRAY,
347
1211
  free_ca_window,
348
1212
  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
1213
  ca_window_func_allocate,
356
1214
  ca_window_func_attach,
357
1215
  ca_window_func_sync,
358
1216
  ca_window_func_detach,
359
- ca_window_func_copy_data,
360
- ca_window_func_sync_data,
361
1217
  ca_window_func_fill_data,
362
1218
  ca_window_func_create_mask,
1219
+ ca_window_func_xfer_index,
1220
+ ca_window_func_xfer_addrs,
1221
+ ca_window_func_fold_stride,
1222
+ ca_window_func_xfer_stride,
1223
+ ca_window_func_xfer_all,
363
1224
  };
364
1225
 
365
1226
  /* ------------------------------------------------------------------- */
1227
+ /* Producer interface: emits one descriptor per axis. The engine
1228
+ (ca_axis_dispatch.c) consumes these and applies the per-axis bounds policy
1229
+ via ca_bounds_normalize_index, writing ca->fill for cells that resolve
1230
+ out of range.
366
1231
 
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
- }
1232
+ CAWindow's step is implicitly 1 (count cells starting at start, sliding by
1233
+ 1); the engine's SHIFT-axis offset computation is parent_index = start + i,
1234
+ with bounds normalisation per policy.
496
1235
 
1236
+ Reached only from the non-embed paths: an embed_eligible view attaches and
1237
+ syncs through ca_window_attach_embed / _sync_embed and never gets here. */
497
1238
  void
498
- ca_window_attach (CAWindow *cb)
1239
+ ca_window_describe_axes (void *ap, ca_axis_desc_t *out,
1240
+ ca_size_t *out_parent_dims)
499
1241
  {
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);
1242
+ CAWindow *ca = (CAWindow *) ap;
1243
+ int8_t k;
1244
+ for ( k = 0; k < ca->ndim; k++ ) {
1245
+ out_parent_dims[k] = ca->parent->dim[k];
1246
+ /* Interior-only axes are promoted to STRIDE kind. An axis touches no
1247
+ boundary cell iff start[k] >= 0 and start[k] + count[k] <=
1248
+ parent->dim[k] (step is implicitly 1); it is then a pure strided slice
1249
+ of the parent, so emitting STRIDE lets the engine take its strided fast
1250
+ paths (slab fusion, axis-merge, alias) and skip the per-cell
1251
+ ca_bounds_normalize_index + OOB check that SHIFT costs. */
1252
+ if ( ca->start[k] >= 0
1253
+ && ca->start[k] + ca->count[k] <= ca->parent->dim[k] ) {
1254
+ out[k].kind = CA_AXIS_KIND_STRIDE;
1255
+ out[k].count = ca->count[k];
1256
+ out[k].start = ca->start[k];
1257
+ out[k].step = 1;
1258
+ out[k].indices = NULL;
1259
+ /* size0 / policy unused for STRIDE — set defaults for hygiene. */
1260
+ out[k].size0 = ca->size0[k];
1261
+ out[k].policy = ca->bounds[k];
1262
+ } else {
1263
+ out[k].kind = CA_AXIS_KIND_SHIFT;
1264
+ out[k].count = ca->count[k];
1265
+ out[k].start = ca->start[k];
1266
+ out[k].step = 1;
1267
+ out[k].indices = NULL;
1268
+ out[k].size0 = ca->size0[k];
1269
+ out[k].policy = ca->bounds[k];
701
1270
  }
702
1271
  }
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
1272
  }
712
1273
 
713
1274
  /* ------------------------------------------------------------------- */
@@ -716,25 +1277,22 @@ VALUE
716
1277
  rb_ca_window_new (VALUE cary,
717
1278
  ca_size_t *start, ca_size_t *count, int8_t bounds, char *fill)
718
1279
  {
1280
+ /* Scalar-bounds wrapper for the Ruby surface: fan the single policy out
1281
+ to a per-axis array before calling ca_window_new. */
719
1282
  volatile VALUE obj;
720
1283
  CArray *parent;
721
1284
  CAWindow *ca;
1285
+ uint8_t bounds_arr[CA_RANK_MAX];
1286
+ int8_t i;
722
1287
  rb_check_carray_object(cary);
723
1288
  TypedData_Get_Struct(cary, CArray, &carray_data_type, parent);
724
- ca = ca_window_new(parent, start, count, bounds, fill);
1289
+ for (i = 0; i < parent->ndim; i++) bounds_arr[i] = (uint8_t) bounds;
1290
+ ca = ca_window_new(parent, start, count, bounds_arr, fill);
725
1291
  obj = ca_wrap_struct(ca);
726
1292
  rb_ca_set_parent(obj, cary);
727
- rb_ca_data_type_inherit(obj, cary);
728
1293
  return obj;
729
1294
  }
730
1295
 
731
- /* yard:
732
- class CArray
733
- def window (*argv)
734
- end
735
- end
736
- */
737
-
738
1296
  VALUE
739
1297
  rb_ca_window (int argc, VALUE *argv, VALUE self)
740
1298
  {
@@ -770,13 +1328,10 @@ rb_ca_window (int argc, VALUE *argv, VALUE self)
770
1328
  count[i] = len;
771
1329
  }
772
1330
 
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."); */
1331
+ if ( rb_block_given_p() ) {
1332
+ rb_raise(rb_eArgError,
1333
+ "window: block form for fill value removed in 3.0; "
1334
+ "use fill_value: kwarg (e.g. window(-1..1, fill_value: UNDEF))");
780
1335
  }
781
1336
 
782
1337
  if ( rfval == CA_NIL ) {
@@ -809,14 +1364,19 @@ rb_ca_window (int argc, VALUE *argv, VALUE self)
809
1364
  bounds = CA_BOUNDS_NEAREST;
810
1365
  }
811
1366
  else if ( ! strncmp(cbounds, "periodic", 8) ) {
812
- bounds = CA_BOUNDS_PERIODIC;
1367
+ rb_raise(rb_eArgError,
1368
+ "bounds=>'periodic' removed in 3.0; "
1369
+ "use CArray#roll(...) for cyclic shift "
1370
+ "(returns a CARoll view)");
813
1371
  }
814
1372
  else if ( ! strncmp(cbounds, "reflect", 7) ) {
815
- bounds = CA_BOUNDS_REFLECT;
1373
+ rb_raise(rb_eArgError,
1374
+ "bounds=>'reflect' removed in 3.0; "
1375
+ "there is no view-based alternative");
816
1376
  }
817
1377
  else if ( ! strncmp(cbounds, "mask", 4) ) {
818
1378
  rb_warn("CAWindow option :bounds=>\"mask\" will be obsolete");
819
- rb_warn("use ca.window(...) { UNDEF }");
1379
+ rb_warn("use ca.window(..., fill_value: UNDEF)");
820
1380
  bounds = CA_BOUNDS_MASK;
821
1381
  }
822
1382
  else if ( ! strncmp(cbounds, "fill", 4) ) {
@@ -837,6 +1397,7 @@ rb_ca_window (int argc, VALUE *argv, VALUE self)
837
1397
 
838
1398
  obj = rb_ca_window_new(self, start, count, bounds, fill);
839
1399
 
1400
+ CA_FACE_LIFT_IF_FACE(obj, self, ca);
840
1401
  return obj;
841
1402
  }
842
1403
 
@@ -857,18 +1418,17 @@ rb_ca_window_initialize_copy (VALUE self, VALUE other)
857
1418
  TypedData_Get_Struct(self, CAWindow, &cawindow_data_type, ca);
858
1419
  TypedData_Get_Struct(other, CAWindow, &cawindow_data_type, cs);
859
1420
 
1421
+ /* `self` came from rb_ca_window_s_allocate (TypedData_Make_Struct,
1422
+ _pool == NULL). Attach the pool before setup so the ndim-sized
1423
+ tail fields skip ALLOC_N. */
1424
+ if ( ca_func[CA_OBJ_WINDOW].pool_init ) {
1425
+ ca_array_pool_alloc(ca, CA_OBJ_WINDOW, cs->ndim);
1426
+ }
860
1427
  ca_window_setup(ca, cs->parent, cs->start, cs->count, cs->bounds, cs->fill);
861
1428
 
862
1429
  return self;
863
1430
  }
864
1431
 
865
- /* yard:
866
- class CAWindow
867
- def index2addr0 (idx)
868
- end
869
- end
870
- */
871
-
872
1432
  static VALUE
873
1433
  rb_ca_window_idx2addr0 (int argc, VALUE *argv, VALUE self)
874
1434
  {
@@ -899,13 +1459,6 @@ rb_ca_window_idx2addr0 (int argc, VALUE *argv, VALUE self)
899
1459
  }
900
1460
  }
901
1461
 
902
- /* yard:
903
- class CAWindow
904
- def addr2addr0 (addr)
905
- end
906
- end
907
- */
908
-
909
1462
  static VALUE
910
1463
  rb_ca_window_addr2addr0 (VALUE self, VALUE raddr)
911
1464
  {
@@ -924,43 +1477,13 @@ rb_ca_window_addr2addr0 (VALUE self, VALUE raddr)
924
1477
  addr += cw->start[i] + idx[i];
925
1478
  }
926
1479
 
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
- }
1480
+ if ( addr < 0 || addr >= cw->parent->elements ) {
1481
+ return Qnil;
951
1482
  }
952
1483
 
953
- return self;
1484
+ return SIZE2NUM(addr);
954
1485
  }
955
1486
 
956
- /* yard:
957
- class CAWindow
958
- def fill_value
959
- end
960
- def fill_value= (val)
961
- end
962
- end
963
- */
964
1487
 
965
1488
  static VALUE
966
1489
  rb_ca_window_set_fill_value (VALUE self, VALUE rfval)
@@ -982,9 +1505,12 @@ rb_ca_window_get_fill_value (VALUE self)
982
1505
  static VALUE
983
1506
  rb_ca_window_get_bounds (VALUE self)
984
1507
  {
1508
+ /* bounds is per-axis, but the Ruby surface always constructs with a single
1509
+ scalar policy fanned out across all axes, so axis 0 reproduces the value
1510
+ that was passed in. */
985
1511
  CAWindow *cw;
986
1512
  TypedData_Get_Struct(self, CAWindow, &cawindow_data_type, cw);
987
- return SIZE2NUM(cw->bounds);
1513
+ return SIZE2NUM(cw->bounds[0]);
988
1514
  }
989
1515
 
990
1516
  #define rb_cw_get_attr_ary(name) \
@@ -1001,36 +1527,73 @@ rb_ca_window_get_bounds (VALUE self)
1001
1527
  return ary; \
1002
1528
  }
1003
1529
 
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
1530
  static VALUE rb_cw_get_attr_ary(start);
1020
1531
  static VALUE rb_cw_get_attr_ary(count);
1021
1532
  static VALUE rb_cw_get_attr_ary(size0);
1022
1533
 
1534
+ #ifdef CARRAY_DEV_BUILD
1535
+ /* Debug accessor (dev-only, stripped in release), returning the embed
1536
+ descriptor as a Hash. The descriptor is an internal implementation detail
1537
+ with no user-facing meaning; this exists so spec_ai can pin its geometry,
1538
+ which nothing else can observe (a stale descriptor produces wrong data, not
1539
+ an error). Gated by CARRAY_DEV_BUILD, enabled via
1540
+ `extconf.rb --enable-dev-build` or `CARRAY_DEV=1 rake build_ext`.
1541
+
1542
+ Polymorphic over CAWindow / CAShift: both share the same C struct layout
1543
+ (CAShift is a typedef of CAWindow) but use distinct TypedData types.
1544
+ DATA_PTR is safe here because Ruby method dispatch has already restricted
1545
+ self to one of those two classes. */
1546
+ VALUE
1547
+ rb_ca_window_embed_descriptor (VALUE self)
1548
+ {
1549
+ CAWindow *ca = (CAWindow *) DATA_PTR(self);
1550
+ VALUE hash, ps_ary, ec_ary, oo_ary;
1551
+ int8_t k;
1552
+
1553
+ ps_ary = rb_ary_new_capa(ca->ndim);
1554
+ ec_ary = rb_ary_new_capa(ca->ndim);
1555
+ oo_ary = rb_ary_new_capa(ca->ndim);
1556
+ for ( k = 0; k < ca->ndim; k++ ) {
1557
+ rb_ary_push(ps_ary, SIZE2NUM(ca->embed_parent_start[k]));
1558
+ rb_ary_push(ec_ary, SIZE2NUM(ca->embed_count[k]));
1559
+ rb_ary_push(oo_ary, SIZE2NUM(ca->embed_output_offset[k]));
1560
+ }
1561
+
1562
+ hash = rb_hash_new();
1563
+ rb_hash_aset(hash, ID2SYM(rb_intern("parent_start")), ps_ary);
1564
+ rb_hash_aset(hash, ID2SYM(rb_intern("count")), ec_ary);
1565
+ rb_hash_aset(hash, ID2SYM(rb_intern("output_offset")), oo_ary);
1566
+ rb_hash_aset(hash, ID2SYM(rb_intern("is_empty")),
1567
+ ca->embed_is_empty ? Qtrue : Qfalse);
1568
+ rb_hash_aset(hash, ID2SYM(rb_intern("covers_all")),
1569
+ ca->embed_covers_all ? Qtrue : Qfalse);
1570
+ rb_hash_aset(hash, ID2SYM(rb_intern("eligible")),
1571
+ ca->embed_eligible ? Qtrue : Qfalse);
1572
+ rb_hash_aset(hash, ID2SYM(rb_intern("alias_eligible")),
1573
+ ca->embed_alias_eligible ? Qtrue : Qfalse);
1574
+ return hash;
1575
+ }
1576
+ #endif /* CARRAY_DEV_BUILD */
1577
+
1023
1578
  void
1024
- Init_ca_obj_window ()
1579
+ Init_ca_obj_window (void)
1025
1580
  {
1026
1581
 
1027
- rb_cCAWindow = rb_define_class("CAWindow", rb_cCAVirtual);
1582
+ rb_cCAWindow = rb_define_class("CAWindow", rb_cCAView);
1028
1583
  rb_cCAWindowMask = rb_define_class("CAWindowMask", rb_cCAWindow);
1029
1584
 
1030
- CA_OBJ_WINDOW = ca_install_obj_type(rb_cCAWindow,
1585
+ /* Pool framework: seven ndim-sized tail fields live in one _pool buffer
1586
+ (fill stays separate, bytes-sized). Set on the global ca_window_func
1587
+ before ca_install_obj_type copies it into ca_func[], and before
1588
+ Init_ca_obj_shift copies ca_window_func into ca_shift_func. */
1589
+ ca_window_func.struct_size = sizeof(CAWindow);
1590
+ ca_window_func.pool_bytes = ca_window_pool_bytes;
1591
+ ca_window_func.pool_init = ca_window_pool_init;
1592
+
1593
+ CA_OBJ_WINDOW = ca_install_obj_type(rb_cCAWindow,
1031
1594
  &cawindow_data_type,
1032
1595
  rb_cCAWindowMask,
1033
- &cawindow_mask_data_type, ca_window_func);
1596
+ &cawindow_mask_data_type, &ca_window_func, sizeof(ca_window_func));
1034
1597
  rb_define_const(rb_cObject, "CA_OBJ_WINDOW", INT2NUM(CA_OBJ_WINDOW));
1035
1598
 
1036
1599
  rb_define_method(rb_cCArray, "window", rb_ca_window, -1);
@@ -1039,7 +1602,6 @@ Init_ca_obj_window ()
1039
1602
  rb_define_method(rb_cCAWindow, "initialize_copy",
1040
1603
  rb_ca_window_initialize_copy, 1);
1041
1604
 
1042
- rb_define_method(rb_cCAWindow, "move", rb_ca_window_move, -1);
1043
1605
 
1044
1606
  rb_define_method(rb_cCAWindow, "index2addr0", rb_ca_window_idx2addr0, -1);
1045
1607
  rb_define_method(rb_cCAWindow, "addr2addr0", rb_ca_window_addr2addr0, 1);
@@ -1053,5 +1615,11 @@ Init_ca_obj_window ()
1053
1615
  rb_define_method(rb_cCAWindow, "count", rb_cw_count, 0);
1054
1616
  rb_define_method(rb_cCAWindow, "size0", rb_cw_size0, 0);
1055
1617
 
1618
+ #ifdef CARRAY_DEV_BUILD
1619
+ /* debug accessor (dev-only, stripped in release) */
1620
+ rb_define_method(rb_cCAWindow, "_embed_descriptor",
1621
+ rb_ca_window_embed_descriptor, 0);
1622
+ #endif
1623
+
1056
1624
  }
1057
1625