carray 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +5 -25
  3. data/CHANGELOG.md +16 -0
  4. data/LICENSE +1 -1
  5. data/NEWS.md +3 -0
  6. data/README.md +128 -44
  7. data/carray.gemspec +22 -24
  8. data/ext/ca_array_pool.c +91 -0
  9. data/ext/ca_axis_descriptor.h +186 -0
  10. data/ext/ca_axis_dispatch.c +924 -0
  11. data/ext/ca_axis_group.c +1208 -0
  12. data/ext/ca_bincmp_dispatch.c +76 -0
  13. data/ext/ca_bincmp_dispatch.h +85 -0
  14. data/ext/ca_binop_dispatch.c +125 -0
  15. data/ext/ca_binop_dispatch.h +159 -0
  16. data/ext/ca_categorical_iterator.c +1375 -0
  17. data/ext/ca_compare.c +94 -0
  18. data/ext/ca_compare.h +26 -0
  19. data/ext/ca_composite_dispatch.c +414 -0
  20. data/ext/ca_composite_dispatch.h +116 -0
  21. data/ext/ca_for_buffer.h +96 -0
  22. data/ext/ca_for_each_element.h +241 -0
  23. data/ext/ca_group_iter.c +304 -0
  24. data/ext/ca_iter_substrate.h +325 -0
  25. data/ext/ca_kernel_iterator.c +4321 -0
  26. data/ext/ca_kernel_iterator.h +2603 -0
  27. data/ext/ca_moncmp_dispatch.c +37 -0
  28. data/ext/ca_moncmp_dispatch.h +62 -0
  29. data/ext/ca_monop_dispatch.c +200 -0
  30. data/ext/ca_monop_dispatch.h +235 -0
  31. data/ext/ca_obj_array.c +355 -359
  32. data/ext/ca_obj_bincmp.c +809 -0
  33. data/ext/ca_obj_binop.c +892 -0
  34. data/ext/ca_obj_bitarray.c +369 -164
  35. data/ext/ca_obj_bitfield.c +294 -234
  36. data/ext/ca_obj_block.c +189 -711
  37. data/ext/ca_obj_byte_swap.c +766 -0
  38. data/ext/ca_obj_const_string.c +965 -0
  39. data/ext/ca_obj_face.c +670 -0
  40. data/ext/ca_obj_face.h +247 -0
  41. data/ext/ca_obj_fake.c +228 -100
  42. data/ext/ca_obj_farray.c +54 -441
  43. data/ext/ca_obj_field.c +82 -529
  44. data/ext/ca_obj_fixlen_string.c +306 -0
  45. data/ext/ca_obj_grid.c +858 -440
  46. data/ext/ca_obj_meld.c +1034 -0
  47. data/ext/ca_obj_moncmp.c +569 -0
  48. data/ext/ca_obj_monop.c +1111 -0
  49. data/ext/ca_obj_object.c +774 -298
  50. data/ext/ca_obj_record.c +468 -0
  51. data/ext/ca_obj_reduce.c +97 -82
  52. data/ext/ca_obj_refer.c +569 -459
  53. data/ext/ca_obj_remap.c +475 -0
  54. data/ext/ca_obj_repeat.c +92 -477
  55. data/ext/ca_obj_roll.c +616 -0
  56. data/ext/ca_obj_select.c +344 -296
  57. data/ext/ca_obj_select_axis.c +1296 -0
  58. data/ext/ca_obj_shift.c +230 -792
  59. data/ext/ca_obj_source.c +78 -0
  60. data/ext/ca_obj_stack.c +1173 -0
  61. data/ext/ca_obj_stride.c +2501 -0
  62. data/ext/ca_obj_string.c +268 -0
  63. data/ext/ca_obj_tile.c +614 -0
  64. data/ext/ca_obj_time.c +546 -0
  65. data/ext/ca_obj_timedelta.c +435 -0
  66. data/ext/ca_obj_transpose.c +62 -516
  67. data/ext/ca_obj_triop.c +746 -0
  68. data/ext/ca_obj_unbound_repeat.c +208 -241
  69. data/ext/ca_obj_window.c +1131 -563
  70. data/ext/ca_op_byte_swap.c +175 -0
  71. data/ext/ca_op_ipower.c +319 -0
  72. data/ext/ca_op_powi.h +88 -0
  73. data/ext/ca_sort_kernels.h +132 -0
  74. data/ext/ca_sweep_engine.c +430 -0
  75. data/ext/ca_sweep_engine.h +157 -0
  76. data/ext/ca_transform_common.c +228 -0
  77. data/ext/ca_triop_dispatch.c +55 -0
  78. data/ext/ca_triop_dispatch.h +62 -0
  79. data/ext/carray.h +795 -402
  80. data/ext/carray_access.c +831 -711
  81. data/ext/carray_attribute.c +98 -330
  82. data/ext/carray_bincount.c +255 -0
  83. data/ext/carray_broadcast.c +283 -0
  84. data/ext/carray_call_cfunc.c +1360 -828
  85. data/ext/carray_call_cfunc.h +160 -0
  86. data/ext/carray_cast.c +1212 -301
  87. data/ext/carray_cast_func.rb +81 -40
  88. data/ext/carray_class.c +53 -63
  89. data/ext/carray_config.h +28 -0
  90. data/ext/carray_conversion.c +350 -346
  91. data/ext/carray_copy.c +156 -268
  92. data/ext/carray_core.c +1342 -199
  93. data/ext/carray_count.c +312 -0
  94. data/ext/carray_data_type.c +43 -19
  95. data/ext/carray_element.c +585 -213
  96. data/ext/carray_factorize.c +2542 -0
  97. data/ext/carray_generate.c +230 -559
  98. data/ext/carray_histogram.c +490 -0
  99. data/ext/carray_hold.c +228 -0
  100. data/ext/carray_index_classifier.c +1035 -0
  101. data/ext/carray_index_classifier.h +27 -0
  102. data/ext/carray_internal.h +120 -0
  103. data/ext/carray_kernels_bincmp.c +4445 -0
  104. data/ext/carray_kernels_binop.c +10979 -0
  105. data/ext/carray_kernels_init.c +36 -0
  106. data/ext/carray_kernels_map.c +3466 -0
  107. data/ext/carray_kernels_moncmp.c +2096 -0
  108. data/ext/carray_kernels_monop.c +18312 -0
  109. data/ext/carray_kernels_reduce_aggregate.c +25836 -0
  110. data/ext/carray_kernels_reduce_boolean.c +329 -0
  111. data/ext/carray_kernels_reduce_cumulative.c +14592 -0
  112. data/ext/carray_kernels_reduce_extreme.c +16947 -0
  113. data/ext/carray_kernels_reduce_variance.c +3909 -0
  114. data/ext/carray_kernels_scan.c +3692 -0
  115. data/ext/carray_kernels_search.c +32137 -0
  116. data/ext/carray_kernels_sort.c +10625 -0
  117. data/ext/carray_kernels_triop.c +1391 -0
  118. data/ext/carray_lazy.c +567 -0
  119. data/ext/carray_loop.c +88 -200
  120. data/ext/carray_mask.c +848 -154
  121. data/ext/carray_math_kernel.h +120 -0
  122. data/ext/carray_mathfunc.c +10 -241
  123. data/ext/carray_median_percentile.c +1257 -0
  124. data/ext/carray_memory_view.c +1625 -0
  125. data/ext/carray_operator.c +1526 -318
  126. data/ext/carray_order.c +664 -1394
  127. data/ext/carray_partition.c +416 -0
  128. data/ext/carray_random.c +518 -0
  129. data/ext/carray_scatter.c +357 -0
  130. data/ext/carray_slab.c +1219 -0
  131. data/ext/carray_slab.h +84 -0
  132. data/ext/carray_sort.c +829 -0
  133. data/ext/carray_sort_kernel.c +620 -0
  134. data/ext/carray_struct.c +695 -0
  135. data/ext/carray_test.c +343 -229
  136. data/ext/carray_undef.c +34 -17
  137. data/ext/carray_utils.c +175 -74
  138. data/ext/extconf.rb +216 -55
  139. data/ext/mk_call_cfunc.rb +480 -0
  140. data/ext/mkkernel.rb +8842 -0
  141. data/ext/ruby_carray.c +202 -101
  142. data/ext/version.h +4 -14
  143. data/ext/version.rb +5 -13
  144. data/lib/carray/arrow_tensor.rb +401 -0
  145. data/lib/carray/attribute.rb +166 -0
  146. data/lib/carray/autoload_carray.rb +220 -0
  147. data/lib/carray/autoload_method_extension.rb +44 -0
  148. data/lib/carray/axis_group.rb +711 -0
  149. data/lib/carray/basics.rb +481 -0
  150. data/lib/carray/bincount_nd.rb +358 -0
  151. data/lib/carray/block_iterator.rb +604 -0
  152. data/lib/carray/boolean_reduce.rb +109 -0
  153. data/lib/carray/categorical.rb +561 -0
  154. data/lib/carray/categorical_iterator.rb +1062 -0
  155. data/lib/carray/complex.rb +150 -0
  156. data/lib/carray/conditional.rb +216 -0
  157. data/lib/carray/const_string.rb +228 -0
  158. data/lib/carray/construct.rb +139 -328
  159. data/lib/carray/core_extensions.rb +240 -0
  160. data/lib/carray/data_type_extension.rb +233 -0
  161. data/lib/carray/fixlen_string.rb +95 -0
  162. data/lib/carray/frame/concat.rb +132 -0
  163. data/lib/carray/frame/convert.rb +95 -0
  164. data/lib/carray/frame/csv_parser.rb +211 -0
  165. data/lib/carray/frame/frame.rb +649 -0
  166. data/lib/carray/frame/group.rb +186 -0
  167. data/lib/carray/frame/io.rb +164 -0
  168. data/lib/carray/frame/join.rb +248 -0
  169. data/lib/carray/frame/records.rb +99 -0
  170. data/lib/carray/frame/sort.rb +113 -0
  171. data/lib/carray/frame/verbs.rb +299 -0
  172. data/lib/carray/frame.rb +16 -0
  173. data/lib/carray/histogram.rb +512 -0
  174. data/lib/carray/inspect.rb +37 -20
  175. data/lib/carray/iterator.rb +57 -349
  176. data/lib/carray/lazy.rb +889 -0
  177. data/lib/carray/mask_gap_fill.rb +200 -0
  178. data/lib/carray/math.rb +78 -342
  179. data/lib/carray/meld_reduce.rb +289 -0
  180. data/lib/carray/methods/align_addr.rb +116 -0
  181. data/lib/carray/methods/bin.rb +128 -0
  182. data/lib/carray/methods/bincount.rb +87 -0
  183. data/lib/carray/methods/bit_string.rb +92 -0
  184. data/lib/carray/methods/broadcast.rb +63 -0
  185. data/lib/carray/methods/choose.rb +39 -0
  186. data/lib/carray/methods/composition.rb +280 -0
  187. data/lib/carray/methods/gather_nd.rb +206 -0
  188. data/lib/carray/methods/index.rb +39 -0
  189. data/lib/carray/methods/insert_block.rb +99 -0
  190. data/lib/carray/methods/is_in.rb +141 -0
  191. data/lib/carray/methods/join.rb +90 -0
  192. data/lib/carray/methods/locate_addr.rb +47 -0
  193. data/lib/carray/methods/mask_duplicates.rb +41 -0
  194. data/lib/carray/methods/meshgrid.rb +91 -0
  195. data/lib/carray/methods/mode.rb +126 -0
  196. data/lib/carray/methods/nunique.rb +46 -0
  197. data/lib/carray/methods/resize.rb +56 -0
  198. data/lib/carray/methods/snap.rb +156 -0
  199. data/lib/carray/methods/string_format.rb +57 -0
  200. data/lib/carray/methods/unique.rb +47 -0
  201. data/lib/carray/methods/value_counts.rb +71 -0
  202. data/lib/carray/mkmf.rb +124 -101
  203. data/lib/carray/runtime.rb +108 -0
  204. data/lib/carray/serialize.rb +478 -167
  205. data/lib/carray/slab_iterator.rb +292 -0
  206. data/lib/carray/stack.rb +291 -0
  207. data/lib/carray/string.rb +56 -180
  208. data/lib/carray/string_operation_extension.rb +289 -0
  209. data/lib/carray/struct.rb +335 -323
  210. data/lib/carray/struct_builder.rb +697 -0
  211. data/lib/carray/table.rb +41 -2
  212. data/lib/carray/time.rb +2255 -38
  213. data/lib/carray/window_iterator.rb +655 -0
  214. data/lib/carray.rb +55 -57
  215. metadata +163 -130
  216. data/Rakefile +0 -51
  217. data/TODO.md +0 -18
  218. data/ext/ca_iter_block.c +0 -257
  219. data/ext/ca_iter_dimension.c +0 -299
  220. data/ext/ca_iter_window.c +0 -214
  221. data/ext/ca_obj_mapping.c +0 -644
  222. data/ext/carray_iterator.c +0 -641
  223. data/ext/carray_math.rb +0 -850
  224. data/ext/carray_numeric.c +0 -259
  225. data/ext/carray_sort_addr.c +0 -254
  226. data/ext/carray_stat.c +0 -2100
  227. data/ext/carray_stat_proc.rb +0 -1999
  228. data/ext/mkmath.rb +0 -741
  229. data/ext/ruby_ccomplex.c +0 -509
  230. data/ext/ruby_float_func.c +0 -86
  231. data/lib/carray/array.rb +0 -8
  232. data/lib/carray/autoload/autoload_base.rb +0 -19
  233. data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
  234. data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
  235. data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
  236. data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
  237. data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
  238. data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
  239. data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
  240. data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
  241. data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
  242. data/lib/carray/autoload/autoload_gem_random.rb +0 -8
  243. data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
  244. data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
  245. data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
  246. data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
  247. data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
  248. data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
  249. data/lib/carray/autoload/autoload_object_link.rb +0 -1
  250. data/lib/carray/autoload/autoload_object_pack.rb +0 -2
  251. data/lib/carray/autoload.rb +0 -141
  252. data/lib/carray/basic.rb +0 -191
  253. data/lib/carray/broadcast.rb +0 -101
  254. data/lib/carray/compose.rb +0 -315
  255. data/lib/carray/convert.rb +0 -115
  256. data/lib/carray/info.rb +0 -110
  257. data/lib/carray/io/imagemagick.rb +0 -235
  258. data/lib/carray/mask.rb +0 -102
  259. data/lib/carray/math/histogram.rb +0 -177
  260. data/lib/carray/math/recurrence.rb +0 -93
  261. data/lib/carray/object/ca_obj_iterator.rb +0 -50
  262. data/lib/carray/object/ca_obj_link.rb +0 -50
  263. data/lib/carray/object/ca_obj_pack.rb +0 -99
  264. data/lib/carray/obsolete.rb +0 -256
  265. data/lib/carray/ordering.rb +0 -181
  266. data/lib/carray/testing.rb +0 -51
  267. data/lib/carray/transform.rb +0 -109
  268. data/misc/Methods.ja.md +0 -182
  269. data/misc/NOTE +0 -51
  270. data/spec/Classes/CABitfield_spec.rb +0 -58
  271. data/spec/Classes/CABlockIterator_spec.rb +0 -114
  272. data/spec/Classes/CABlock_spec.rb +0 -205
  273. data/spec/Classes/CAField_spec.rb +0 -39
  274. data/spec/Classes/CAGrid_spec.rb +0 -75
  275. data/spec/Classes/CAMap_spec.rb +0 -0
  276. data/spec/Classes/CAMapping_spec.rb +0 -105
  277. data/spec/Classes/CAObject_attribute_spec.rb +0 -33
  278. data/spec/Classes/CAObject_spec.rb +0 -33
  279. data/spec/Classes/CARefer_spec.rb +0 -93
  280. data/spec/Classes/CARepeat_spec.rb +0 -65
  281. data/spec/Classes/CASelect_spec.rb +0 -22
  282. data/spec/Classes/CAShift_spec.rb +0 -16
  283. data/spec/Classes/CAStruct_spec.rb +0 -71
  284. data/spec/Classes/CATranspose_spec.rb +0 -60
  285. data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
  286. data/spec/Classes/CAWindow_spec.rb +0 -54
  287. data/spec/Classes/CAWrap_spec.rb +0 -8
  288. data/spec/Classes/CArray_spec.rb +0 -184
  289. data/spec/Classes/CScalar_spec.rb +0 -55
  290. data/spec/Classes/ex1.rb +0 -46
  291. data/spec/Features/feature_130_spec.rb +0 -19
  292. data/spec/Features/feature_attributes_spec.rb +0 -280
  293. data/spec/Features/feature_boolean_spec.rb +0 -98
  294. data/spec/Features/feature_broadcast.rb +0 -116
  295. data/spec/Features/feature_cast_function.rb +0 -19
  296. data/spec/Features/feature_cast_spec.rb +0 -33
  297. data/spec/Features/feature_class_spec.rb +0 -84
  298. data/spec/Features/feature_complex_spec.rb +0 -42
  299. data/spec/Features/feature_composite_spec.rb +0 -124
  300. data/spec/Features/feature_convert_spec.rb +0 -46
  301. data/spec/Features/feature_copy_spec.rb +0 -123
  302. data/spec/Features/feature_creation_spec.rb +0 -84
  303. data/spec/Features/feature_element_spec.rb +0 -144
  304. data/spec/Features/feature_extream_spec.rb +0 -54
  305. data/spec/Features/feature_generate_spec.rb +0 -74
  306. data/spec/Features/feature_index_spec.rb +0 -69
  307. data/spec/Features/feature_mask_spec.rb +0 -580
  308. data/spec/Features/feature_math_spec.rb +0 -97
  309. data/spec/Features/feature_order_spec.rb +0 -146
  310. data/spec/Features/feature_ref_store_spec.rb +0 -209
  311. data/spec/Features/feature_serialization_spec.rb +0 -125
  312. data/spec/Features/feature_stat_spec.rb +0 -397
  313. data/spec/Features/feature_virtual_spec.rb +0 -48
  314. data/spec/Features/method_eq_spec.rb +0 -81
  315. data/spec/Features/method_is_nan_spec.rb +0 -12
  316. data/spec/Features/method_map_spec.rb +0 -54
  317. data/spec/Features/method_max_with.rb +0 -20
  318. data/spec/Features/method_min_with.rb +0 -19
  319. data/spec/Features/method_ne_spec.rb +0 -18
  320. data/spec/Features/method_project_spec.rb +0 -188
  321. data/spec/Features/method_ref_spec.rb +0 -27
  322. data/spec/Features/method_round_spec.rb +0 -11
  323. data/spec/Features/method_s_linspace_spec.rb +0 -48
  324. data/spec/Features/method_s_span_spec.rb +0 -14
  325. data/spec/Features/method_seq_spec.rb +0 -47
  326. data/spec/Features/method_sort_with.rb +0 -43
  327. data/spec/Features/method_sorted_with.rb +0 -29
  328. data/spec/Features/method_span_spec.rb +0 -42
  329. data/spec/Features/method_wrap_readonly_spec.rb +0 -43
  330. data/spec/UnitTest/test_CAVirtual.rb +0 -214
  331. data/spec/spec_all.rb +0 -10
  332. data/utils/ca_ase.rb +0 -21
  333. data/utils/ca_methods.rb +0 -15
  334. data/utils/cast_checker.rb +0 -30
  335. data/utils/convert_test.rb +0 -73
  336. data/utils/extract_yard.rb +0 -22
  337. data/utils/guess_shape.rb +0 -76
  338. data/utils/monkey_patch_methods.rb +0 -62
  339. data/utils/remove_resource_fork.sh +0 -5
@@ -0,0 +1,1173 @@
1
+ /* ---------------------------------------------------------------------------
2
+
3
+ CAStack view = an outer-axis stack of K uniform-shape parents.
4
+ shape = (K, *parent_shape); the k_axis dimension is the parent selector,
5
+ the remaining axes follow each parent's row-major layout.
6
+
7
+ Design summary:
8
+ - parents must be uniform shape + uniform data_type (= constructor raise)
9
+ - attach materialises via per-parent xfer_all (= K * parent.elements
10
+ bytes peak alloc, caller responsibility)
11
+ - xfer_stride dispatches per-parent over axis-0 range (= partial use →
12
+ partial cost)
13
+ - conditional fold_stride: counts[0] == 1 → fold to that parent;
14
+ multi-parent → fold boundary (decline)
15
+ - create_mask: horizontal propagation (= all parents' roots get
16
+ all-zero mask if any parent has mask) + mask CAStack itself
17
+
18
+ ---------------------------------------------------------------------------- */
19
+
20
+ #include "carray.h"
21
+ #include "ca_composite_dispatch.h"
22
+ #include "ca_obj_face.h"
23
+
24
+ /* ------------------------------------------------------------------- */
25
+ /* TypedData */
26
+ /* ------------------------------------------------------------------- */
27
+
28
+ static size_t
29
+ ca_stack_dsize (const void *ap)
30
+ {
31
+ const CAStack *ca = (const CAStack *) ap;
32
+ return sizeof(CAStack)
33
+ + ca->ndim * sizeof(ca_size_t) /* dim[] */
34
+ + ca->n_parents * sizeof(CArray *); /* parents[] */
35
+ }
36
+
37
+ const rb_data_type_t castack_data_type = {
38
+ .parent = &caview_data_type,
39
+ .wrap_struct_name = "CAStack",
40
+ .function = {
41
+ .dmark = ca_mark,
42
+ .dfree = ca_free,
43
+ .dsize = ca_stack_dsize,
44
+ .dcompact = NULL
45
+ },
46
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
47
+ };
48
+
49
+ const rb_data_type_t castack_mask_data_type = {
50
+ .parent = &castack_data_type,
51
+ .wrap_struct_name = "CAStackMask",
52
+ .function = {
53
+ .dmark = ca_mark,
54
+ .dfree = ca_free_nop,
55
+ .dsize = ca_stack_dsize,
56
+ .dcompact = NULL
57
+ },
58
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
59
+ };
60
+
61
+ int8_t CA_OBJ_STACK;
62
+
63
+ VALUE rb_cCAStack;
64
+ VALUE rb_cCAStackMask;
65
+
66
+ static ID id_parents;
67
+
68
+ /* ------------------------------------------------------------------- */
69
+ /* setup / new / free / clone */
70
+ /* ------------------------------------------------------------------- */
71
+
72
+ /* Validate uniform shape + data_type across parents. Returns 0 on success,
73
+ raises ArgumentError on a uniform violation (hard reject at the
74
+ constructor; broadcast / coerce is not performed). */
75
+ static void
76
+ ca_stack_check_uniform (int32_t n_parents, CArray **parents)
77
+ {
78
+ CArray *ref;
79
+ int32_t i;
80
+ int8_t k;
81
+ if ( n_parents <= 0 ) {
82
+ rb_raise(rb_eArgError, "CAStack requires at least one parent");
83
+ }
84
+ ref = parents[0];
85
+ for ( i = 1; i < n_parents; i++ ) {
86
+ CArray *p = parents[i];
87
+ if ( p->data_type != ref->data_type ) {
88
+ rb_raise(rb_eArgError,
89
+ "CAStack parents must have uniform data_type "
90
+ "(parent[0]=%d, parent[%d]=%d)",
91
+ ref->data_type, i, p->data_type);
92
+ }
93
+ if ( p->ndim != ref->ndim ) {
94
+ rb_raise(rb_eArgError,
95
+ "CAStack parents must have uniform ndim "
96
+ "(parent[0]=%d, parent[%d]=%d)",
97
+ ref->ndim, i, p->ndim);
98
+ }
99
+ for ( k = 0; k < ref->ndim; k++ ) {
100
+ if ( p->dim[k] != ref->dim[k] ) {
101
+ rb_raise(rb_eArgError,
102
+ "CAStack parents must have uniform shape "
103
+ "(mismatch at axis %d: parent[0]=%lld, parent[%d]=%lld)",
104
+ (int) k,
105
+ (long long) ref->dim[k],
106
+ (int) i,
107
+ (long long) p->dim[k]);
108
+ }
109
+ }
110
+ if ( p->bytes != ref->bytes ) {
111
+ rb_raise(rb_eArgError,
112
+ "CAStack parents must have uniform bytes "
113
+ "(parent[0]=%lld, parent[%d]=%lld)",
114
+ (long long) ref->bytes, (int) i, (long long) p->bytes);
115
+ }
116
+ }
117
+ }
118
+
119
+ /* One-level strip of a Face VALUE to its storage-side parent (non-Face as-is).
120
+ Used to keep the @parent GC/`.parent` ivar in step with the pre-stripped C
121
+ parents so the Ruby-visible chain of a stacked Face is single-Face too. */
122
+ static VALUE
123
+ ca_stack_face_parent1 (VALUE v)
124
+ {
125
+ CArray *c;
126
+ TypedData_Get_Struct(v, CArray, &carray_data_type, c);
127
+ return ca_is_face(c) ? rb_ca_parent(v) : v;
128
+ }
129
+
130
+ int
131
+ ca_stack_setup_with_axis (CAStack *ca, int32_t n_parents, CArray **parents,
132
+ int8_t k_axis)
133
+ {
134
+ CArray *ref;
135
+ int32_t i;
136
+ int8_t a;
137
+
138
+ /* §8.3 / MEMO_FACE_DOUBLE_LIFT §15.3: pre-strip Face parents one level to
139
+ storage so a stacked Face lifts to a single-Face chain
140
+ (CATime[CAStack[entity, ...]]) instead of a Face on top of Face parents.
141
+ One level (not a full walk) preserves any distinct Face a parent stacked
142
+ underneath. A Face is storage-transparent, so stacking over storage reads
143
+ the same bytes; the lifted top Face (rb_ca_stack_s_new) carries the
144
+ identity. The @parents accessor keeps the originals (set by the callers). */
145
+ for ( i = 0; i < n_parents; i++ ) {
146
+ if ( ca_is_face(parents[i]) ) {
147
+ parents[i] = CAVIEW(parents[i])->parent;
148
+ }
149
+ }
150
+
151
+ ca_stack_check_uniform(n_parents, parents);
152
+ ref = parents[0];
153
+
154
+ if ( k_axis < 0 || k_axis > ref->ndim ) {
155
+ rb_raise(rb_eArgError,
156
+ "CAStack k_axis %d out of range [0, %d]",
157
+ (int) k_axis, (int) ref->ndim);
158
+ }
159
+
160
+ ca->obj_type = CA_OBJ_STACK;
161
+ ca->data_type = ref->data_type;
162
+ ca->flags = CA_FLAG_MULTI_PARENTS; /* fold-over-parents in generic code */
163
+ ca->ndim = ref->ndim + 1;
164
+ ca->bytes = ref->bytes;
165
+ ca->elements = (ca_size_t) n_parents * ref->elements;
166
+ ca->ptr = NULL;
167
+ ca->mask = NULL;
168
+
169
+ ca->parent = ref; /* CAView base field (= parents[0]) */
170
+ ca->attach = 0;
171
+ ca->nosync = 0;
172
+
173
+ ca->n_parents = n_parents;
174
+ ca->parents = ALLOC_N(CArray *, n_parents);
175
+ for ( i = 0; i < n_parents; i++ ) {
176
+ ca->parents[i] = parents[i];
177
+ }
178
+ ca->k_axis = k_axis;
179
+
180
+ ca->dim = ALLOC_N(ca_size_t, ca->ndim);
181
+ /* dim[a] = parent.dim[a] for a < k_axis
182
+ dim[k_axis] = K (= n_parents)
183
+ dim[a] = parent.dim[a-1] for a > k_axis */
184
+ for ( a = 0; a < k_axis; a++ ) ca->dim[a] = ref->dim[a];
185
+ ca->dim[k_axis] = n_parents;
186
+ for ( a = k_axis + 1; a < ca->ndim; a++ ) ca->dim[a] = ref->dim[a - 1];
187
+
188
+ /* mask: lazy; horizontal propagation triggers on first mask access. */
189
+ return 0;
190
+ }
191
+
192
+ int
193
+ ca_stack_setup (CAStack *ca, int32_t n_parents, CArray **parents)
194
+ {
195
+ return ca_stack_setup_with_axis(ca, n_parents, parents, 0);
196
+ }
197
+
198
+ CAStack *
199
+ ca_stack_new_with_axis (int32_t n_parents, CArray **parents, int8_t k_axis)
200
+ {
201
+ CAStack *ca = ALLOC(CAStack);
202
+ ca_stack_setup_with_axis(ca, n_parents, parents, k_axis);
203
+ return ca;
204
+ }
205
+
206
+ CAStack *
207
+ ca_stack_new (int32_t n_parents, CArray **parents)
208
+ {
209
+ return ca_stack_new_with_axis(n_parents, parents, 0);
210
+ }
211
+
212
+ static void
213
+ free_ca_stack (void *ap)
214
+ {
215
+ CAStack *ca = (CAStack *) ap;
216
+ if ( ca != NULL ) {
217
+ ca_free(ca->mask);
218
+ xfree(ca->parents);
219
+ xfree(ca->dim);
220
+ xfree(ca);
221
+ }
222
+ }
223
+
224
+ static void *
225
+ ca_stack_func_clone (void *ap)
226
+ {
227
+ CAStack *ca = (CAStack *) ap;
228
+ return ca_stack_new_with_axis(ca->n_parents, ca->parents, ca->k_axis);
229
+ }
230
+
231
+ /* ------------------------------------------------------------------- */
232
+ /* xfer_index */
233
+ /* ------------------------------------------------------------------- */
234
+
235
+ /* view[..., k at k_axis, ...] = parents[k][parent_idx]
236
+ where parent_idx = idx[] with the k_axis slot removed. */
237
+ static void
238
+ ca_stack_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
239
+ {
240
+ CAStack *ca = (CAStack *) ap;
241
+ int8_t k_axis = ca->k_axis;
242
+ ca_size_t k = idx[k_axis];
243
+ if ( k < 0 || k >= ca->n_parents ) {
244
+ rb_raise(rb_eIndexError, "CAStack k-axis (axis %d) index %lld out of range [0, %d)",
245
+ (int) k_axis, (long long) k, (int) ca->n_parents);
246
+ }
247
+ if ( k_axis == 0 ) {
248
+ /* Fast path: parent_idx = idx[1..ndim-1]; pass idx+1 directly. */
249
+ ca_xfer_index(ca->parents[k], idx + 1, data, dir);
250
+ return;
251
+ }
252
+ /* General case: build parent_idx by removing the k_axis slot. */
253
+ {
254
+ ca_size_t pidx[CA_RANK_MAX];
255
+ int8_t a, pndim = ca->ndim - 1;
256
+ for ( a = 0; a < k_axis; a++ ) pidx[a] = idx[a];
257
+ for ( a = k_axis; a < pndim; a++ ) pidx[a] = idx[a + 1];
258
+ ca_xfer_index(ca->parents[k], pidx, data, dir);
259
+ }
260
+ }
261
+
262
+ /* ------------------------------------------------------------------- */
263
+ /* xfer_addrs (batched) */
264
+ /* ------------------------------------------------------------------- */
265
+
266
+ /* Per-addr: decompose addr -> (k, intra_addr) where k = addr / parent_elements
267
+ and intra_addr = addr % parent_elements. Group by k for batched parent
268
+ dispatch. Simple K-pass implementation: scan once per parent; OK for
269
+ typical K (= small to medium). Hot K cases can opt into a single-pass
270
+ bucket sort if needed. */
271
+ static void
272
+ ca_stack_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
273
+ void *data, int dir)
274
+ {
275
+ CAStack *ca = (CAStack *) ap;
276
+ int8_t k_axis = ca->k_axis;
277
+ ca_size_t parent_elements = ca->parents[0]->elements;
278
+ ca_size_t bytes = ca->bytes;
279
+ ca_size_t *paddrs;
280
+ char *pdata, *cdata = (char *) data;
281
+ volatile VALUE holder1, holder2;
282
+ ca_size_t i;
283
+ int32_t k;
284
+
285
+ paddrs = ALLOCV_N(ca_size_t, holder1, n);
286
+ pdata = ALLOCV_N(char, holder2, n * bytes);
287
+
288
+ if ( k_axis == 0 ) {
289
+ /* Fast path: addr = k * parent_elements + parent_addr (row-major over
290
+ view shape with K at axis 0). */
291
+ for ( k = 0; k < ca->n_parents; k++ ) {
292
+ ca_size_t m = 0;
293
+ for ( i = 0; i < n; i++ ) {
294
+ if ( addrs[i] / parent_elements == (ca_size_t) k ) {
295
+ paddrs[m] = addrs[i] % parent_elements;
296
+ if ( dir == CA_XFER_PUT ) {
297
+ memcpy(pdata + m * bytes, cdata + i * bytes, bytes);
298
+ }
299
+ m++;
300
+ }
301
+ }
302
+ if ( m > 0 ) {
303
+ ca_xfer_addrs(ca->parents[k], m, paddrs, pdata, dir);
304
+ if ( dir == CA_XFER_GET ) {
305
+ ca_size_t mm = 0;
306
+ for ( i = 0; i < n; i++ ) {
307
+ if ( addrs[i] / parent_elements == (ca_size_t) k ) {
308
+ memcpy(cdata + i * bytes, pdata + mm * bytes, bytes);
309
+ mm++;
310
+ }
311
+ }
312
+ }
313
+ }
314
+ }
315
+ }
316
+ else {
317
+ /* General case: decode addrs[i] in view row-major to extract
318
+ k = vidx[k_axis] and the parent's row-major address from the
319
+ remaining axes. */
320
+ int8_t pndim = ca->ndim - 1;
321
+ ca_size_t view_div[CA_RANK_MAX]; /* row-major divisor per view axis */
322
+ ca_size_t parent_mul[CA_RANK_MAX]; /* row-major multiplier per parent axis */
323
+ ca_size_t s;
324
+ int8_t a;
325
+ s = 1;
326
+ for ( a = ca->ndim - 1; a >= 0; a-- ) { view_div[a] = s; s *= ca->dim[a]; }
327
+ s = 1;
328
+ for ( a = pndim - 1; a >= 0; a-- ) {
329
+ parent_mul[a] = s;
330
+ s *= ca->parents[0]->dim[a];
331
+ }
332
+
333
+ for ( k = 0; k < ca->n_parents; k++ ) {
334
+ ca_size_t m = 0;
335
+ for ( i = 0; i < n; i++ ) {
336
+ ca_size_t addr = addrs[i];
337
+ ca_size_t vidx_k = (addr / view_div[k_axis]) % ca->dim[k_axis];
338
+ if ( vidx_k != (ca_size_t) k ) continue;
339
+ {
340
+ ca_size_t paddr = 0;
341
+ int8_t b;
342
+ for ( b = 0; b < k_axis; b++ ) {
343
+ ca_size_t v = (addr / view_div[b]) % ca->dim[b];
344
+ paddr += v * parent_mul[b];
345
+ }
346
+ for ( b = k_axis + 1; b < ca->ndim; b++ ) {
347
+ ca_size_t v = (addr / view_div[b]) % ca->dim[b];
348
+ paddr += v * parent_mul[b - 1];
349
+ }
350
+ paddrs[m] = paddr;
351
+ }
352
+ if ( dir == CA_XFER_PUT ) {
353
+ memcpy(pdata + m * bytes, cdata + i * bytes, bytes);
354
+ }
355
+ m++;
356
+ }
357
+ if ( m > 0 ) {
358
+ ca_xfer_addrs(ca->parents[k], m, paddrs, pdata, dir);
359
+ if ( dir == CA_XFER_GET ) {
360
+ ca_size_t mm = 0;
361
+ for ( i = 0; i < n; i++ ) {
362
+ ca_size_t addr = addrs[i];
363
+ ca_size_t vidx_k = (addr / view_div[k_axis]) % ca->dim[k_axis];
364
+ if ( vidx_k != (ca_size_t) k ) continue;
365
+ memcpy(cdata + i * bytes, pdata + mm * bytes, bytes);
366
+ mm++;
367
+ }
368
+ }
369
+ }
370
+ }
371
+ (void) parent_elements;
372
+ }
373
+ ALLOCV_END(holder2);
374
+ ALLOCV_END(holder1);
375
+ }
376
+
377
+ /* ------------------------------------------------------------------- */
378
+ /* xfer_stride: K-fold per-parent dispatch over axis-0 range */
379
+ /* ------------------------------------------------------------------- */
380
+
381
+ /* Contract (= CATile pattern): caller's `strides[]` are the chain-composed
382
+ strides through root's byte space (= the result of ca_stride_compose_to_root
383
+ in CAStride.xfer_stride). starts[] is in CAStack-axis order (= addr2index
384
+ of composed_base); counts[]/strides[] are in OUTPUT-axis order.
385
+
386
+ Three paths:
387
+ - Structural (strides[i] == native[i] for all i): per-parent K-fold over
388
+ the request's k_axis range. k_axis=0 = each parent contig in dst (zero
389
+ copy slot); k_axis!=0 = per-parent xfer_stride into a slab buf, then
390
+ scatter/gather between buf and the view-strided dst positions.
391
+ - Reloc (permutation that only moved K from view axis k_axis to output
392
+ position p, non-K axes preserve relative order): per (leading + K)
393
+ position dispatch. K-innermost (p == ndim-1) escapes per-cell via the
394
+ parent's xfer_index; otherwise per-parent inner-block xfer_stride.
395
+ - Per-cell fallback via xfer_index. */
396
+ static void
397
+ ca_stack_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
398
+ ca_size_t *strides, void *data, int dir)
399
+ {
400
+ CAStack *ca = (CAStack *) ap;
401
+ int8_t k_axis = ca->k_axis;
402
+ int8_t pndim = ca->ndim - 1;
403
+ ca_size_t native[CA_RANK_MAX], dstride[CA_RANK_MAX];
404
+ ca_size_t pstarts[CA_RANK_MAX], pcounts[CA_RANK_MAX], pstrides[CA_RANK_MAX];
405
+ ca_size_t k, s;
406
+ int8_t i;
407
+ int structural = 1;
408
+ char *d = (char *) data;
409
+
410
+ /* CAStack's native row-major byte strides over its full shape. */
411
+ s = ca->bytes;
412
+ for ( i = ca->ndim - 1; i >= 0; i-- ) { native[i] = s; s *= ca->dim[i]; }
413
+
414
+ /* Parent's native (source) byte strides over its own row-major shape. */
415
+ s = ca->bytes;
416
+ for ( i = pndim - 1; i >= 0; i-- ) {
417
+ pstrides[i] = s;
418
+ s *= ca->parents[0]->dim[i];
419
+ }
420
+
421
+ /* Structural iff strides[k] == native[k] for all k. */
422
+ for ( i = 0; i < ca->ndim; i++ ) {
423
+ if ( strides[i] != native[i] ) { structural = 0; break; }
424
+ }
425
+
426
+ if ( structural ) {
427
+ ca_size_t k_lo = starts[k_axis];
428
+ ca_size_t k_hi = starts[k_axis] + counts[k_axis];
429
+ if ( k_lo < 0 || k_hi > ca->n_parents ) {
430
+ rb_raise(rb_eIndexError,
431
+ "CAStack xfer_stride k-axis (axis %d) [%lld, %lld) out of range [0, %d)",
432
+ (int) k_axis, (long long) k_lo, (long long) k_hi, (int) ca->n_parents);
433
+ }
434
+
435
+ /* dst row-major byte strides over output counts[]. */
436
+ s = ca->bytes;
437
+ for ( i = ca->ndim - 1; i >= 0; i-- ) { dstride[i] = s; s *= counts[i]; }
438
+
439
+ /* pstarts/pcounts = starts/counts with the k_axis slot removed. */
440
+ for ( i = 0; i < pndim; i++ ) {
441
+ int8_t v = (i < k_axis) ? i : (i + 1);
442
+ pstarts[i] = starts[v];
443
+ pcounts[i] = counts[v];
444
+ }
445
+
446
+ if ( k_axis == 0 ) {
447
+ /* Each parent's row-major output is a contig slot of dstride[0] bytes
448
+ in dst -- zero copy. */
449
+ for ( k = k_lo; k < k_hi; k++ ) {
450
+ char *slot = d + (k - k_lo) * dstride[0];
451
+ ca_xfer_stride(ca->parents[k], pstarts, pcounts, pstrides, slot, dir);
452
+ }
453
+ return;
454
+ }
455
+
456
+ /* k_axis != 0: parent's row-major output of size slab_elements * bytes
457
+ does not fit contig in dst because the K-stride sits in the middle of
458
+ the dst row-major flow. Per-parent: deliver into a slab buf, then
459
+ walk parent_idx row-major to scatter/gather to/from dst at the
460
+ view-strided positions. */
461
+ {
462
+ ca_size_t slab_bytes = ca->bytes;
463
+ ca_size_t inner_dim[CA_RANK_MAX];
464
+ volatile VALUE holder;
465
+ char *buf;
466
+ int8_t a;
467
+
468
+ for ( i = 0; i < pndim; i++ ) {
469
+ inner_dim[i] = pcounts[i];
470
+ slab_bytes *= pcounts[i];
471
+ }
472
+ buf = ALLOCV_N(char, holder, slab_bytes);
473
+
474
+ for ( k = k_lo; k < k_hi; k++ ) {
475
+ ca_size_t k_off = (k - k_lo) * dstride[k_axis];
476
+ if ( dir == CA_XFER_GET ) {
477
+ ca_xfer_stride(ca->parents[k], pstarts, pcounts, pstrides, buf, CA_XFER_GET);
478
+ }
479
+ if ( pndim == 0 ) {
480
+ /* parent is scalar-shaped (1 element). */
481
+ if ( dir == CA_XFER_GET ) memcpy(d + k_off, buf, ca->bytes);
482
+ else memcpy(buf, d + k_off, ca->bytes);
483
+ }
484
+ else {
485
+ ca_size_t pidx[CA_RANK_MAX];
486
+ ca_size_t paddr = 0;
487
+ for ( a = 0; a < pndim; a++ ) pidx[a] = 0;
488
+ while ( 1 ) {
489
+ ca_size_t voff = k_off;
490
+ for ( a = 0; a < k_axis; a++ ) voff += pidx[a] * dstride[a];
491
+ for ( a = k_axis; a < pndim; a++ ) voff += pidx[a] * dstride[a + 1];
492
+ if ( dir == CA_XFER_GET ) memcpy(d + voff, buf + paddr, ca->bytes);
493
+ else memcpy(buf + paddr, d + voff, ca->bytes);
494
+ paddr += ca->bytes;
495
+ i = pndim - 1;
496
+ while ( i >= 0 ) {
497
+ if ( ++pidx[i] < inner_dim[i] ) break;
498
+ pidx[i] = 0;
499
+ i--;
500
+ }
501
+ if ( i < 0 ) break;
502
+ }
503
+ }
504
+ if ( dir == CA_XFER_PUT ) {
505
+ ca_xfer_stride(ca->parents[k], pstarts, pcounts, pstrides, buf, CA_XFER_PUT);
506
+ }
507
+ }
508
+ ALLOCV_END(holder);
509
+ }
510
+ return;
511
+ }
512
+
513
+ /* Non-structural: try reloc fast path. The request is a permutation that
514
+ moved K from view axis k_axis to output position p, with the non-K axes
515
+ preserving their relative order. Detection: native[k_axis] appears in
516
+ strides[] at position p; the remaining strides[] equal native[0..ndim-1]
517
+ with native[k_axis] removed, in order. */
518
+ {
519
+ int8_t p = -1;
520
+ int reloc = 1;
521
+ for ( i = 0; i < ca->ndim; i++ ) {
522
+ if ( strides[i] == native[k_axis] ) { p = i; break; }
523
+ }
524
+ if ( p < 0 ) {
525
+ reloc = 0;
526
+ }
527
+ else {
528
+ /* non_K_native[j] = native of the j-th non-K view axis (in view order)
529
+ = native[j] if j < k_axis else native[j + 1].
530
+ For output position i != p, strides[i] should equal
531
+ non_K_native[i if i < p else i - 1]. */
532
+ for ( i = 0; i < ca->ndim; i++ ) {
533
+ int8_t j, v;
534
+ if ( i == p ) continue;
535
+ j = (i < p) ? i : (i - 1);
536
+ v = (j < k_axis) ? j : (j + 1);
537
+ if ( strides[i] != native[v] ) { reloc = 0; break; }
538
+ }
539
+ }
540
+ if ( reloc ) {
541
+ ca_size_t kk_lo = starts[k_axis];
542
+ ca_size_t kk_hi = starts[k_axis] + counts[p];
543
+ ca_size_t odo[CA_RANK_MAX];
544
+ int8_t a;
545
+ if ( kk_lo < 0 || kk_hi > ca->n_parents ) {
546
+ rb_raise(rb_eIndexError,
547
+ "CAStack xfer_stride k-axis [%lld, %lld) out of range [0, %d)",
548
+ (long long) kk_lo, (long long) kk_hi, (int) ca->n_parents);
549
+ }
550
+ if ( p == ca->ndim - 1 ) {
551
+ /* K innermost: per-cell via the parent's xfer_index directly, escaping
552
+ the CAStack-level addr2index dispatch hop in the per-cell fallback. */
553
+ ca_size_t pidx[CA_RANK_MAX], doff = 0;
554
+ for ( i = 0; i < ca->ndim; i++ ) odo[i] = 0;
555
+ while ( 1 ) {
556
+ ca_size_t k2 = starts[k_axis] + odo[p];
557
+ for ( a = 0; a < pndim; a++ ) {
558
+ int8_t v = (a < k_axis) ? a : (a + 1);
559
+ pidx[a] = starts[v] + odo[a];
560
+ }
561
+ ca_xfer_index(ca->parents[k2], pidx, d + doff, dir);
562
+ doff += ca->bytes;
563
+ i = ca->ndim - 1;
564
+ while ( i >= 0 ) { if ( ++odo[i] < counts[i] ) break; odo[i] = 0; i--; }
565
+ if ( i < 0 ) break;
566
+ }
567
+ return;
568
+ }
569
+ /* dst row-major byte strides over output counts. */
570
+ s = ca->bytes;
571
+ for ( i = ca->ndim - 1; i >= 0; i-- ) { dstride[i] = s; s *= counts[i]; }
572
+ for ( i = 0; i <= p; i++ ) odo[i] = 0;
573
+ while ( 1 ) {
574
+ ca_size_t k2 = starts[k_axis] + odo[p];
575
+ ca_size_t doff = 0;
576
+ for ( i = 0; i <= p; i++ ) doff += odo[i] * dstride[i];
577
+ for ( a = 0; a < pndim; a++ ) {
578
+ int8_t v = (a < k_axis) ? a : (a + 1);
579
+ if ( a < p ) {
580
+ pstarts[a] = starts[v] + odo[a];
581
+ pcounts[a] = 1;
582
+ }
583
+ else {
584
+ pstarts[a] = starts[v];
585
+ pcounts[a] = counts[a + 1];
586
+ }
587
+ }
588
+ ca_xfer_stride(ca->parents[k2], pstarts, pcounts, pstrides, d + doff, dir);
589
+ i = p;
590
+ while ( i >= 0 ) { if ( ++odo[i] < counts[i] ) break; odo[i] = 0; i--; }
591
+ if ( i < 0 ) break;
592
+ }
593
+ return;
594
+ }
595
+ }
596
+
597
+ /* Per-cell fallback via xfer_index. */
598
+ {
599
+ ca_size_t idx[CA_RANK_MAX], doff = 0, base = 0;
600
+ for ( i = 0; i < ca->ndim; i++ ) base += starts[i] * native[i];
601
+ for ( i = 0; i < ca->ndim; i++ ) idx[i] = 0;
602
+ while ( 1 ) {
603
+ ca_size_t toff = base, vidx[CA_RANK_MAX];
604
+ for ( i = 0; i < ca->ndim; i++ ) toff += idx[i] * strides[i];
605
+ ca_addr2index((CArray *) ca, toff / ca->bytes, vidx);
606
+ ca_stack_func_xfer_index(ca, vidx, d + doff, dir);
607
+ doff += ca->bytes;
608
+ i = ca->ndim - 1;
609
+ while ( i >= 0 ) { if ( ++idx[i] < counts[i] ) break; idx[i] = 0; i--; }
610
+ if ( i < 0 ) break;
611
+ }
612
+ }
613
+ }
614
+
615
+ /* ------------------------------------------------------------------- */
616
+ /* xfer_all: K-fold over all parents */
617
+ /* ------------------------------------------------------------------- */
618
+
619
+ /* gather/scatter the entire view buffer (= row-major contig over the
620
+ view's own shape).
621
+ - k_axis == 0: each parent slot occupies parent.elements * bytes
622
+ contiguous bytes in dst; deliver via parent.xfer_all directly.
623
+ - k_axis != 0: per-parent contiguous read into a temp buffer, then
624
+ scattered-write to dst at view row-major positions. This is the
625
+ correctness path (per-parent contig-read / scattered-write), not a
626
+ perf-optimised one. */
627
+ static void
628
+ ca_stack_func_xfer_all (void *ap, void *data, int dir)
629
+ {
630
+ CAStack *ca = (CAStack *) ap;
631
+ int8_t k_axis = ca->k_axis;
632
+ ca_size_t bytes = ca->bytes;
633
+ char *d = (char *) data;
634
+ int32_t k;
635
+
636
+ if ( k_axis == 0 ) {
637
+ ca_size_t parent_bytes_total = ca->parents[0]->elements * bytes;
638
+ for ( k = 0; k < ca->n_parents; k++ ) {
639
+ ca_xfer_all(ca->parents[k], d + k * parent_bytes_total, dir);
640
+ }
641
+ return;
642
+ }
643
+
644
+ {
645
+ int8_t pndim = ca->ndim - 1;
646
+ ca_size_t parent_elements = ca->parents[0]->elements;
647
+ ca_size_t view_stride[CA_RANK_MAX]; /* byte strides over view row-major */
648
+ ca_size_t parent_dim[CA_RANK_MAX];
649
+ volatile VALUE holder;
650
+ char *buf;
651
+ ca_size_t s;
652
+ int8_t a;
653
+
654
+ s = bytes;
655
+ for ( a = ca->ndim - 1; a >= 0; a-- ) {
656
+ view_stride[a] = s;
657
+ s *= ca->dim[a];
658
+ }
659
+ for ( a = 0; a < pndim; a++ ) parent_dim[a] = ca->parents[0]->dim[a];
660
+
661
+ buf = ALLOCV_N(char, holder, parent_elements * bytes);
662
+
663
+ /* Per-parent axis step/back tables for incremental voff update.
664
+ parent axis a maps to view axis v = a if a < k_axis else a + 1;
665
+ step[a] = the dst byte stride contributed by parent axis a;
666
+ back[a] = (parent_dim[a] - 1) * step[a] = amount to subtract on wrap.
667
+ These are independent of k, so hoist outside the per-parent loop. */
668
+ {
669
+ ca_size_t step[CA_RANK_MAX];
670
+ ca_size_t back[CA_RANK_MAX];
671
+ for ( a = 0; a < pndim; a++ ) {
672
+ int8_t v = (a < k_axis) ? a : (a + 1);
673
+ step[a] = view_stride[v];
674
+ back[a] = (parent_dim[a] - 1) * step[a];
675
+ }
676
+
677
+ for ( k = 0; k < ca->n_parents; k++ ) {
678
+ ca_size_t k_off = (ca_size_t) k * view_stride[k_axis];
679
+ ca_size_t pidx[CA_RANK_MAX];
680
+ ca_size_t paddr_bytes = 0;
681
+ ca_size_t voff = k_off;
682
+ int8_t i;
683
+
684
+ if ( dir == CA_XFER_GET ) {
685
+ ca_xfer_all(ca->parents[k], buf, CA_XFER_GET);
686
+ }
687
+
688
+ for ( a = 0; a < pndim; a++ ) pidx[a] = 0;
689
+
690
+ if ( pndim == 0 ) {
691
+ /* parent is scalar-shaped (= 1 element); single cell at k_off. */
692
+ if ( dir == CA_XFER_GET ) memcpy(d + k_off, buf, bytes);
693
+ else memcpy(buf, d + k_off, bytes);
694
+ }
695
+ else if ( dir == CA_XFER_GET ) {
696
+ /* dir-hoisted GET inner loop, incremental voff. */
697
+ while ( 1 ) {
698
+ memcpy(d + voff, buf + paddr_bytes, bytes);
699
+ paddr_bytes += bytes;
700
+ i = pndim - 1;
701
+ while ( i >= 0 ) {
702
+ if ( ++pidx[i] < parent_dim[i] ) { voff += step[i]; break; }
703
+ pidx[i] = 0;
704
+ voff -= back[i];
705
+ i--;
706
+ }
707
+ if ( i < 0 ) break;
708
+ }
709
+ }
710
+ else {
711
+ /* dir-hoisted PUT inner loop, incremental voff. */
712
+ while ( 1 ) {
713
+ memcpy(buf + paddr_bytes, d + voff, bytes);
714
+ paddr_bytes += bytes;
715
+ i = pndim - 1;
716
+ while ( i >= 0 ) {
717
+ if ( ++pidx[i] < parent_dim[i] ) { voff += step[i]; break; }
718
+ pidx[i] = 0;
719
+ voff -= back[i];
720
+ i--;
721
+ }
722
+ if ( i < 0 ) break;
723
+ }
724
+ }
725
+
726
+ if ( dir == CA_XFER_PUT ) {
727
+ ca_xfer_all(ca->parents[k], buf, CA_XFER_PUT);
728
+ }
729
+ }
730
+ }
731
+ ALLOCV_END(holder);
732
+ }
733
+ }
734
+
735
+ /* ------------------------------------------------------------------- */
736
+ /* attach / sync / detach (materialise via xfer_all) */
737
+ /* ------------------------------------------------------------------- */
738
+
739
+ static void
740
+ ca_stack_func_allocate (void *ap)
741
+ {
742
+ CAStack *ca = (CAStack *) ap;
743
+ int32_t k;
744
+ for ( k = 0; k < ca->n_parents; k++ ) {
745
+ ca_attach(ca->parents[k]);
746
+ }
747
+ ca->ptr = xmalloc(ca_length(ca));
748
+ }
749
+
750
+ static void
751
+ ca_stack_func_attach (void *ap)
752
+ {
753
+ CAStack *ca = (CAStack *) ap;
754
+ int32_t k;
755
+ for ( k = 0; k < ca->n_parents; k++ ) {
756
+ ca_attach(ca->parents[k]);
757
+ }
758
+ ca->ptr = xmalloc(ca_length(ca));
759
+ ca_stack_func_xfer_all(ca, ca->ptr, CA_XFER_GET);
760
+ }
761
+
762
+ static void
763
+ ca_stack_func_sync (void *ap)
764
+ {
765
+ CAStack *ca = (CAStack *) ap;
766
+ int32_t k;
767
+ ca_stack_func_xfer_all(ca, ca->ptr, CA_XFER_PUT);
768
+ for ( k = 0; k < ca->n_parents; k++ ) {
769
+ ca_sync(ca->parents[k]);
770
+ }
771
+ }
772
+
773
+ static void
774
+ ca_stack_func_detach (void *ap)
775
+ {
776
+ CAStack *ca = (CAStack *) ap;
777
+ int32_t k;
778
+ xfree(ca->ptr);
779
+ ca->ptr = NULL;
780
+ for ( k = 0; k < ca->n_parents; k++ ) {
781
+ ca_detach(ca->parents[k]);
782
+ }
783
+ }
784
+
785
+ /* ------------------------------------------------------------------- */
786
+ /* fill_data: K-fold per-parent fill */
787
+ /* ------------------------------------------------------------------- */
788
+
789
+ static void
790
+ ca_stack_func_fill_data (void *ap, void *ptr)
791
+ {
792
+ CAStack *ca = (CAStack *) ap;
793
+ int32_t k;
794
+ for ( k = 0; k < ca->n_parents; k++ ) {
795
+ ca_fill(ca->parents[k], ptr);
796
+ }
797
+ }
798
+
799
+ /* ------------------------------------------------------------------- */
800
+ /* create_mask: horizontal propagation */
801
+ /* ------------------------------------------------------------------- */
802
+
803
+ /* All parents' roots gain all-zero mask (if not already), then build
804
+ mask CAStack from the K parent mask CArrays. Self-similar (= CAStack
805
+ re-used as its own mask class). */
806
+ static void
807
+ ca_stack_func_create_mask (void *ap)
808
+ {
809
+ CAStack *ca = (CAStack *) ap;
810
+ CArray **mask_parents;
811
+ volatile VALUE holder;
812
+ int32_t k;
813
+
814
+ mask_parents = ALLOCV_N(CArray *, holder, ca->n_parents);
815
+ for ( k = 0; k < ca->n_parents; k++ ) {
816
+ ca_update_mask(ca->parents[k]);
817
+ if ( ! ca->parents[k]->mask ) {
818
+ ca_create_mask(ca->parents[k]);
819
+ }
820
+ mask_parents[k] = ca->parents[k]->mask;
821
+ }
822
+ /* Build the mask stack with the parent's k_axis from the start so its
823
+ dim[] is laid out for that axis. (Patching ->k_axis after a default
824
+ ca_stack_new leaves dim[] computed for k_axis 0 — an inconsistent
825
+ geometry that mis-shapes arr.mask and breaks the structural
826
+ xfer_stride k-range check when k_axis != 0.) */
827
+ ca->mask = (CArray *) ca_stack_new_with_axis(ca->n_parents, mask_parents,
828
+ ca->k_axis);
829
+ ALLOCV_END(holder);
830
+ }
831
+
832
+ /* ------------------------------------------------------------------- */
833
+ /* fold_stride: conditional participation */
834
+ /* ------------------------------------------------------------------- */
835
+
836
+ /* Fold to a single parent iff the request's byte box lies entirely within one
837
+ parent, then rebase into that parent's space and continue the compose there.
838
+
839
+ Testing only f->counts[0] == 1 is WRONG for a permuting chain: after a
840
+ transpose the K axis (native stride = parent_bytes_total) is no longer at
841
+ axis 0, so a size-1 leading PARENT axis (counts[0] == 1) would spuriously
842
+ fold a genuinely multi-parent request down to one parent -- silent data loss
843
+ (observed: t.transpose(1,0,2)[2..2, 0..3, 2..4]). Box containment is
844
+ order-independent and correct regardless of where the K axis ended up. */
845
+ static int
846
+ ca_stack_func_fold_stride (void *ap, ca_fold_t *f, void **next_parent)
847
+ {
848
+ CAStack *ca = (CAStack *) ap;
849
+ ca_size_t pbt = ca->parents[0]->elements * ca->bytes;
850
+ ca_size_t lo = f->base, hi = f->base;
851
+ ca_size_t k;
852
+ int8_t i;
853
+
854
+ /* k_axis != 0: parent byte blocks are interleaved (not contig in view
855
+ buffer), so the byte-box containment check and the size-1 K-axis
856
+ degeneration below would require detecting the K-axis contribution
857
+ inside an arbitrary stride composition -- not just probing for the
858
+ parent_bytes_total stride at axis 0. Decline fold; the caller falls
859
+ through to CAStack.xfer_stride (k_axis-aware), which still delivers
860
+ correct (if non-folded) per-parent dispatch. */
861
+ if ( ca->k_axis != 0 ) return 0;
862
+
863
+ for ( i = 0; i < f->ndim; i++ ) {
864
+ ca_size_t span = (f->counts[i] - 1) * f->strides[i];
865
+ if ( span >= 0 ) hi += span; else lo += span; /* negative stride extends lo */
866
+ }
867
+ if ( lo < 0 ) return 0;
868
+ k = lo / pbt;
869
+ if ( k < 0 || k >= ca->n_parents ) return 0;
870
+ if ( hi >= (k + 1) * pbt ) return 0; /* box spans >1 parent */
871
+
872
+ f->base -= k * pbt;
873
+ for ( i = 0; i < f->ndim; i++ ) { /* degenerate the (size-1) K axis */
874
+ if ( f->strides[i] == pbt ) f->strides[i] = 0;
875
+ }
876
+ *next_parent = (void *) ca->parents[k];
877
+ return 1;
878
+ }
879
+
880
+ /* ------------------------------------------------------------------- */
881
+ /* operation table */
882
+ /* ------------------------------------------------------------------- */
883
+
884
+ ca_operation_function_t ca_stack_func = {
885
+ -1, /* CA_OBJ_STACK */
886
+ CA_VIEW_ARRAY,
887
+ free_ca_stack,
888
+ ca_stack_func_clone,
889
+ ca_stack_func_allocate,
890
+ ca_stack_func_attach,
891
+ ca_stack_func_sync,
892
+ ca_stack_func_detach,
893
+ ca_stack_func_fill_data,
894
+ ca_stack_func_create_mask,
895
+ ca_stack_func_xfer_index,
896
+ ca_stack_func_xfer_addrs,
897
+ ca_stack_func_fold_stride,
898
+ ca_stack_func_xfer_stride,
899
+ ca_stack_func_xfer_all,
900
+ };
901
+
902
+ /* ------------------------------------------------------------------- */
903
+ /* Ruby surface */
904
+ /* ------------------------------------------------------------------- */
905
+
906
+ /* Build the raw CAStack VALUE from a Ruby array of CArray VALUEs.
907
+
908
+ Storage-level construction: no Face awareness, no promote/check
909
+ beyond ca_stack_check_uniform (data_type + shape uniformity).
910
+ Returns a fresh CAStack VALUE (not a Face-wrapped lift).
911
+
912
+ The Face dance (= homogeneity check + lift) lives in the high-level
913
+ Ruby `CArray.stack` defined in lib/carray/compose.rb, which calls
914
+ `CArray.promote_list` for the homogeneity + state / portability
915
+ verdict and `ca.face_lift(face_parent)` (= rb_ca_face_lift_method)
916
+ for the final re-wrap. Keeping CAStack constructor raw-only means
917
+ `CAStack.new(list, axis:)` follows the Class#new contract (= always
918
+ returns a CAStack), and the lift becomes a transparent Ruby-side
919
+ step rather than a hidden C dispatch surprise. */
920
+ VALUE
921
+ rb_ca_stack_new_with_axis (VALUE parents_ary, int8_t k_axis)
922
+ {
923
+ volatile VALUE obj;
924
+ CAStack *ca;
925
+ CArray **parents;
926
+ volatile VALUE holder;
927
+ long n, i;
928
+
929
+ Check_Type(parents_ary, T_ARRAY);
930
+ n = RARRAY_LEN(parents_ary);
931
+ if ( n <= 0 ) {
932
+ rb_raise(rb_eArgError, "CAStack requires at least one parent");
933
+ }
934
+ parents = ALLOCV_N(CArray *, holder, n);
935
+ for ( i = 0; i < n; i++ ) {
936
+ VALUE p = rb_ary_entry(parents_ary, i);
937
+ rb_check_carray_object(p);
938
+ TypedData_Get_Struct(p, CArray, &carray_data_type, parents[i]);
939
+ }
940
+ ca = ca_stack_new_with_axis((int32_t) n, parents, k_axis);
941
+ obj = ca_wrap_struct(ca);
942
+ rb_ivar_set(obj, id_parents, rb_ary_dup(parents_ary)); /* GC anchor */
943
+ rb_ca_set_parent(obj, ca_stack_face_parent1(rb_ary_entry(parents_ary, 0))); /* CAView base (Face-stripped, matches C ->parent) */
944
+ ALLOCV_END(holder);
945
+ return obj;
946
+ }
947
+
948
+ VALUE
949
+ rb_ca_stack_new (VALUE parents_ary)
950
+ {
951
+ return rb_ca_stack_new_with_axis(parents_ary, 0);
952
+ }
953
+
954
+ /* CArray.stack class method + CArray#stack instance method are defined
955
+ in lib/carray/compose.rb as the high-level Ruby surface that delegates
956
+ to CAStack.new after promote_list + (optional) face_lift. CAStack.new
957
+ is the canonical low-level constructor; promote_list handles Face. */
958
+
959
+ static VALUE
960
+ rb_ca_stack_s_allocate (VALUE klass)
961
+ {
962
+ CAStack *ca;
963
+ return TypedData_Make_Struct(klass, CAStack, &castack_data_type, ca);
964
+ }
965
+
966
+ static VALUE
967
+ rb_ca_stack_initialize_copy (VALUE self, VALUE other)
968
+ {
969
+ CAStack *ca, *cs;
970
+ TypedData_Get_Struct(self, CAStack, &castack_data_type, ca);
971
+ TypedData_Get_Struct(other, CAStack, &castack_data_type, cs);
972
+ ca_stack_setup_with_axis(ca, cs->n_parents, cs->parents, cs->k_axis);
973
+ return self;
974
+ }
975
+
976
+ /* CAStack#initialize(list, axis: 0) -- the OO constructor entry.
977
+
978
+ Class#new chain: allocate -> initialize. Allocate produces an empty
979
+ TypedData-wrapped CAStack struct; we set up the parents[] / k_axis /
980
+ dim[] in this method.
981
+
982
+ Raw-only: no Face homogeneity check, no lift. The lift is performed
983
+ by the high-level CArray.stack Ruby method (= lib/carray/compose.rb)
984
+ which calls CAStack.new then ca.face_lift(face_parent). */
985
+ static VALUE
986
+ rb_ca_stack_initialize (int argc, VALUE *argv, VALUE self)
987
+ {
988
+ CAStack *ca;
989
+ CArray **parents;
990
+ volatile VALUE holder;
991
+ VALUE list, kwargs, axis_val = Qnil;
992
+ int8_t k_axis = 0;
993
+ long n, i;
994
+
995
+ rb_scan_args(argc, argv, "1:", &list, &kwargs);
996
+ Check_Type(list, T_ARRAY);
997
+ rb_scan_options(kwargs, "axis", &axis_val);
998
+ n = RARRAY_LEN(list);
999
+ if ( n <= 0 ) {
1000
+ rb_raise(rb_eArgError, "CAStack.new requires at least one parent");
1001
+ }
1002
+ if ( ! NIL_P(axis_val) ) {
1003
+ /* Resolve k_axis against the first parent's ndim (= insertion position
1004
+ range [0, parent_ndim], i.e. half-open [0, parent_ndim + 1)). */
1005
+ CArray *ref;
1006
+ VALUE first = rb_ary_entry(list, 0);
1007
+ rb_check_carray_object(first);
1008
+ TypedData_Get_Struct(first, CArray, &carray_data_type, ref);
1009
+ k_axis = (int8_t) rb_ca_normalize_axis_for_ndim(
1010
+ NUM2LONG(axis_val), (int) ref->ndim + 1, "CAStack.new");
1011
+ }
1012
+ TypedData_Get_Struct(self, CAStack, &castack_data_type, ca);
1013
+ parents = ALLOCV_N(CArray *, holder, n);
1014
+ for ( i = 0; i < n; i++ ) {
1015
+ VALUE p = rb_ary_entry(list, i);
1016
+ rb_check_carray_object(p);
1017
+ TypedData_Get_Struct(p, CArray, &carray_data_type, parents[i]);
1018
+ }
1019
+ ca_stack_setup_with_axis(ca, (int32_t) n, parents, k_axis);
1020
+ rb_ivar_set(self, id_parents, rb_ary_dup(list)); /* GC anchor */
1021
+ rb_ca_set_parent(self, ca_stack_face_parent1(rb_ary_entry(list, 0))); /* CAView base (Face-stripped, matches C ->parent) */
1022
+ ALLOCV_END(holder);
1023
+ return self;
1024
+ }
1025
+
1026
+ /* CAStack.new(list, axis: 0) -- singleton override of Class#new that
1027
+ layers Face-aware semantics on top of the raw allocate + initialize
1028
+ chain.
1029
+
1030
+ For a homogeneous Face list (= all same Face class with compatible
1031
+ state + portable):
1032
+ - the raw CAStack is built via rb_obj_alloc + rb_obj_call_init
1033
+ (= bypasses the Class#new dispatcher to avoid recursion into
1034
+ this method)
1035
+ - then ca_face_lift re-wraps the result as the same Face class,
1036
+ carrying state from list[0]
1037
+ For non-Face / mixed / heterogeneous lists, returns the raw CAStack
1038
+ directly (= same result as the inherited Class#new chain).
1039
+
1040
+ Replaces the Ruby-level CAStack.new override in lib/carray/compose.rb
1041
+ (= used alias_method :__new_raw__, :new + class << self def new).
1042
+ Consolidating in C removes the dispatch trick and the per-call
1043
+ rb_funcall hops to face_state_portable? / face_state_compatible?. */
1044
+ static VALUE
1045
+ rb_ca_stack_s_new (int argc, VALUE *argv, VALUE klass)
1046
+ {
1047
+ VALUE list, kwargs;
1048
+ long n, i;
1049
+ int all_face = 1;
1050
+ VALUE face_class = Qnil;
1051
+ CArray *ref_face = NULL;
1052
+ VALUE obj;
1053
+
1054
+ rb_scan_args(argc, argv, "1:", &list, &kwargs);
1055
+ Check_Type(list, T_ARRAY);
1056
+ n = RARRAY_LEN(list);
1057
+ if ( n <= 0 ) {
1058
+ rb_raise(rb_eArgError, "CAStack.new requires at least one parent");
1059
+ }
1060
+
1061
+ /* Inspect Face homogeneity in one pass. Bail to non-Face path as
1062
+ soon as a non-Face or class-mismatched element is seen. */
1063
+ for ( i = 0; i < n; i++ ) {
1064
+ VALUE p = rb_ary_entry(list, i);
1065
+ CArray *ca;
1066
+ rb_check_carray_object(p);
1067
+ TypedData_Get_Struct(p, CArray, &carray_data_type, ca);
1068
+ if ( !ca_is_face(ca) ) { all_face = 0; break; }
1069
+ if ( i == 0 ) {
1070
+ face_class = rb_obj_class(p);
1071
+ ref_face = ca;
1072
+ } else if ( rb_obj_class(p) != face_class ) {
1073
+ all_face = 0; break;
1074
+ }
1075
+ }
1076
+
1077
+ /* Allocate + initialize raw CAStack. rb_obj_alloc calls the
1078
+ TypedData allocator directly (= bypasses the overridden Class#new,
1079
+ avoiding infinite recursion); rb_obj_call_init_kw dispatches to
1080
+ #initialize forwarding the kwargs hash (= Ruby 3.x strict kwarg
1081
+ separation requires the _kw variant when passing through). */
1082
+ obj = rb_obj_alloc(klass);
1083
+ rb_obj_call_init_kw(obj, argc, argv, RB_PASS_CALLED_KEYWORDS);
1084
+
1085
+ /* Single element or non-Face: no lift, return raw. */
1086
+ if ( !all_face || n < 2 ) return obj;
1087
+
1088
+ /* Homogeneous Face: portable + pairwise state-compatible checks,
1089
+ then ca_face_lift to re-wrap as the same Face class. */
1090
+ if ( !ca_face_state_portable(ref_face->obj_type, face_class) ) {
1091
+ rb_raise(rb_eArgError,
1092
+ "CAStack.new: %s state is not portable across multiple "
1093
+ "parents (= per-parent storage like CAConstString's buffer); "
1094
+ "strip Face with .parent if a storage-level CAStack is intended",
1095
+ rb_class2name(face_class));
1096
+ }
1097
+ for ( i = 1; i < n; i++ ) {
1098
+ VALUE p = rb_ary_entry(list, i);
1099
+ CArray *ca;
1100
+ TypedData_Get_Struct(p, CArray, &carray_data_type, ca);
1101
+ if ( !ca_face_state_compatible(rb_ary_entry(list, 0), ref_face,
1102
+ p, ca) ) {
1103
+ rb_raise(rb_eArgError,
1104
+ "CAStack.new: Face state mismatch across parents "
1105
+ "(= %s instance at index %ld differs in state from index 0)",
1106
+ rb_class2name(face_class), i);
1107
+ }
1108
+ }
1109
+ return ca_face_lift(obj, rb_ary_entry(list, 0));
1110
+ }
1111
+
1112
+ /* Public Ruby accessor: stack.n_parents → K */
1113
+ static VALUE
1114
+ rb_ca_stack_n_parents (VALUE self)
1115
+ {
1116
+ CAStack *ca = (CAStack *) DATA_PTR(self);
1117
+ return INT2NUM(ca->n_parents);
1118
+ }
1119
+
1120
+ /* Public Ruby accessor: stack.parents → Array of parent CArrays
1121
+ ([[project_view_hierarchy_introspectability]]). */
1122
+ static VALUE
1123
+ rb_ca_stack_parents (VALUE self)
1124
+ {
1125
+ return rb_ivar_get(self, id_parents);
1126
+ }
1127
+
1128
+ /* Public Ruby accessor: stack.k_axis → K axis insertion position [0, parent_ndim]. */
1129
+ static VALUE
1130
+ rb_ca_stack_k_axis (VALUE self)
1131
+ {
1132
+ CAStack *ca = (CAStack *) DATA_PTR(self);
1133
+ return INT2NUM((int) ca->k_axis);
1134
+ }
1135
+
1136
+ void
1137
+ Init_ca_obj_stack (void)
1138
+ {
1139
+ /* CAStack must match the CAMultiParent layout convention so generic code
1140
+ can fold over parents[] via the CA_FLAG_MULTI_PARENTS path. */
1141
+ if ( offsetof(CAStack, n_parents) != offsetof(CAMultiParent, n_parents) ||
1142
+ offsetof(CAStack, parents) != offsetof(CAMultiParent, parents) ) {
1143
+ rb_raise(rb_eRuntimeError,
1144
+ "CAStack/CAMultiParent layout mismatch (build error)");
1145
+ }
1146
+
1147
+ rb_cCAStack = rb_define_class("CAStack", rb_cCAView);
1148
+ rb_cCAStackMask = rb_define_class("CAStackMask", rb_cCAStack);
1149
+
1150
+ CA_OBJ_STACK = ca_install_obj_type(rb_cCAStack,
1151
+ &castack_data_type,
1152
+ rb_cCAStackMask,
1153
+ &castack_mask_data_type, &ca_stack_func, sizeof(ca_stack_func));
1154
+ rb_define_const(rb_cObject, "CA_OBJ_STACK", INT2NUM(CA_OBJ_STACK));
1155
+
1156
+ id_parents = rb_intern("parents");
1157
+
1158
+ /* CArray.stack / CArray#stack / CAStack#append are defined in Ruby
1159
+ (lib/carray/compose.rb). CAStack.new is C-side: the allocator +
1160
+ #initialize handle the raw build, and rb_ca_stack_s_new overrides
1161
+ Class#new to layer Face-aware lift on top. */
1162
+
1163
+ rb_define_alloc_func(rb_cCAStack, rb_ca_stack_s_allocate);
1164
+ rb_define_singleton_method(rb_cCAStack, "new", rb_ca_stack_s_new, -1);
1165
+ rb_define_method(rb_cCAStack, "initialize",
1166
+ rb_ca_stack_initialize, -1);
1167
+ rb_define_method(rb_cCAStack, "initialize_copy",
1168
+ rb_ca_stack_initialize_copy, 1);
1169
+
1170
+ rb_define_method(rb_cCAStack, "n_parents", rb_ca_stack_n_parents, 0);
1171
+ rb_define_method(rb_cCAStack, "parents", rb_ca_stack_parents, 0);
1172
+ rb_define_method(rb_cCAStack, "k_axis", rb_ca_stack_k_axis, 0);
1173
+ }