carray 3.0.2 → 3.0.4

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 (247) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +568 -0
  3. data/README.md +6 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_descriptor.h +27 -24
  6. data/ext/ca_axis_dispatch.c +220 -0
  7. data/ext/ca_axis_group.c +201 -123
  8. data/ext/ca_binop_dispatch.c +46 -52
  9. data/ext/ca_binop_dispatch.h +1 -1
  10. data/ext/ca_categorical_iterator.c +249 -183
  11. data/ext/ca_compare.c +7 -12
  12. data/ext/ca_for_buffer.h +21 -68
  13. data/ext/ca_group_iter.c +38 -23
  14. data/ext/ca_kernel_iterator.c +1765 -2076
  15. data/ext/ca_kernel_iterator.h +584 -566
  16. data/ext/ca_monop_dispatch.c +86 -63
  17. data/ext/ca_monop_dispatch.h +5 -0
  18. data/ext/ca_obj_array.c +223 -56
  19. data/ext/ca_obj_bincmp.c +172 -138
  20. data/ext/ca_obj_binop.c +116 -230
  21. data/ext/ca_obj_bitarray.c +40 -26
  22. data/ext/ca_obj_bitfield.c +100 -68
  23. data/ext/ca_obj_block.c +19 -9
  24. data/ext/ca_obj_byte_swap.c +45 -46
  25. data/ext/ca_obj_const_string.c +269 -44
  26. data/ext/ca_obj_face.c +612 -89
  27. data/ext/ca_obj_face.h +111 -47
  28. data/ext/ca_obj_fake.c +36 -79
  29. data/ext/ca_obj_farray.c +7 -3
  30. data/ext/ca_obj_field.c +33 -8
  31. data/ext/ca_obj_fixlen_string.c +23 -24
  32. data/ext/ca_obj_grid.c +541 -210
  33. data/ext/ca_obj_meld.c +331 -111
  34. data/ext/ca_obj_moncmp.c +80 -65
  35. data/ext/ca_obj_monop.c +177 -149
  36. data/ext/ca_obj_object.c +664 -248
  37. data/ext/ca_obj_record.c +63 -23
  38. data/ext/ca_obj_reduce.c +18 -16
  39. data/ext/ca_obj_refer.c +140 -58
  40. data/ext/ca_obj_remap.c +85 -24
  41. data/ext/ca_obj_repeat.c +24 -9
  42. data/ext/ca_obj_roll.c +121 -16
  43. data/ext/ca_obj_select.c +61 -58
  44. data/ext/ca_obj_select_axis.c +73 -107
  45. data/ext/ca_obj_shift.c +32 -102
  46. data/ext/ca_obj_stack.c +372 -122
  47. data/ext/ca_obj_stride.c +348 -140
  48. data/ext/ca_obj_string.c +88 -22
  49. data/ext/ca_obj_tile.c +38 -21
  50. data/ext/ca_obj_time.c +45 -31
  51. data/ext/ca_obj_timedelta.c +43 -29
  52. data/ext/ca_obj_transpose.c +17 -6
  53. data/ext/ca_obj_triop.c +125 -141
  54. data/ext/ca_obj_window.c +330 -44
  55. data/ext/ca_op_ipower.c +24 -11
  56. data/ext/ca_op_powi.h +57 -30
  57. data/ext/ca_pow5_table.h +656 -0
  58. data/ext/ca_sort_kernels.h +70 -0
  59. data/ext/ca_sweep_engine.c +104 -81
  60. data/ext/ca_sweep_engine.h +7 -20
  61. data/ext/ca_triop_dispatch.c +4 -1
  62. data/ext/ca_triop_dispatch.h +5 -2
  63. data/ext/caframe_csv_reader.c +542 -0
  64. data/ext/carray.h +241 -92
  65. data/ext/carray_access.c +608 -276
  66. data/ext/carray_address_basis.c +494 -97
  67. data/ext/carray_attribute.c +129 -4
  68. data/ext/carray_bincount.c +39 -7
  69. data/ext/carray_broadcast.c +56 -19
  70. data/ext/carray_build_flags.h +1 -1
  71. data/ext/carray_call_cfunc.c +108 -28
  72. data/ext/carray_cast.c +1576 -223
  73. data/ext/carray_cast_func.rb +77 -24
  74. data/ext/carray_class.c +4 -3
  75. data/ext/carray_conversion.c +208 -75
  76. data/ext/carray_copy.c +82 -24
  77. data/ext/carray_core.c +646 -162
  78. data/ext/carray_count.c +171 -88
  79. data/ext/carray_element.c +94 -60
  80. data/ext/carray_factorize.c +670 -324
  81. data/ext/carray_generate.c +260 -83
  82. data/ext/carray_histogram.c +209 -106
  83. data/ext/carray_hold.c +14 -16
  84. data/ext/carray_index_classifier.c +422 -243
  85. data/ext/carray_internal.h +692 -3
  86. data/ext/carray_kernels_bincmp.c +7 -0
  87. data/ext/carray_kernels_binop.c +487 -120
  88. data/ext/carray_kernels_init.c +653 -50
  89. data/ext/carray_kernels_moncmp.c +15 -8
  90. data/ext/carray_kernels_monop.c +99 -43
  91. data/ext/carray_kernels_reduce_aggregate.c +3925 -6112
  92. data/ext/carray_kernels_reduce_boolean.c +112 -36
  93. data/ext/carray_kernels_reduce_cumulative.c +2127 -6903
  94. data/ext/carray_kernels_reduce_extreme.c +3032 -2892
  95. data/ext/carray_kernels_reduce_variance.c +439 -500
  96. data/ext/carray_kernels_scan.c +1097 -488
  97. data/ext/carray_kernels_search.c +3787 -2176
  98. data/ext/carray_kernels_sort.c +1200 -920
  99. data/ext/carray_kernels_triop.c +479 -7
  100. data/ext/carray_key_windows.c +216 -0
  101. data/ext/carray_lazy.c +967 -153
  102. data/ext/carray_loop.c +81 -65
  103. data/ext/carray_mask.c +910 -324
  104. data/ext/carray_math_kernel.h +1 -0
  105. data/ext/carray_median_percentile.c +299 -100
  106. data/ext/carray_memory_view.c +872 -758
  107. data/ext/carray_operator.c +1171 -364
  108. data/ext/carray_order.c +505 -133
  109. data/ext/carray_partition.c +112 -65
  110. data/ext/carray_random.c +327 -239
  111. data/ext/carray_round.c +273 -0
  112. data/ext/carray_scatter.c +346 -78
  113. data/ext/carray_segment.c +257 -0
  114. data/ext/carray_slab.c +249 -86
  115. data/ext/carray_sort.c +224 -227
  116. data/ext/carray_sort_kernel.c +8 -1
  117. data/ext/carray_struct.c +118 -39
  118. data/ext/carray_test.c +193 -69
  119. data/ext/carray_time_text.c +1182 -0
  120. data/ext/carray_undef.c +4 -0
  121. data/ext/carray_utils.c +579 -113
  122. data/ext/extconf.rb +27 -2
  123. data/ext/mk_call_cfunc.rb +19 -4
  124. data/ext/mk_pow5_table.rb +35 -0
  125. data/ext/mkkernel.rb +1202 -1014
  126. data/ext/ruby_carray.c +17 -5
  127. data/ext/version.h +4 -4
  128. data/ext/version.rb +4 -2
  129. data/lib/carray/attribute.rb +160 -17
  130. data/lib/carray/autoload_carray.rb +23 -27
  131. data/lib/carray/autoload_method_extension.rb +20 -5
  132. data/lib/carray/axis_group.rb +360 -63
  133. data/lib/carray/basics.rb +162 -28
  134. data/lib/carray/bincount_nd.rb +18 -2
  135. data/lib/carray/block_iterator.rb +231 -58
  136. data/lib/carray/boolean_reduce.rb +34 -42
  137. data/lib/carray/categorical.rb +242 -52
  138. data/lib/carray/categorical_iterator.rb +437 -827
  139. data/lib/carray/complex.rb +41 -34
  140. data/lib/carray/conditional.rb +150 -14
  141. data/lib/carray/const_string.rb +59 -4
  142. data/lib/carray/construct.rb +15 -11
  143. data/lib/carray/data_type_extension.rb +1 -0
  144. data/lib/carray/distance_limit.rb +77 -0
  145. data/lib/carray/fixlen_string.rb +7 -1
  146. data/lib/carray/frame/column_verbs.rb +70 -0
  147. data/lib/carray/frame/concat.rb +157 -6
  148. data/lib/carray/frame/convert.rb +82 -7
  149. data/lib/carray/frame/csv_parser.rb +688 -87
  150. data/lib/carray/frame/describe.rb +80 -0
  151. data/lib/carray/frame/frame.rb +369 -45
  152. data/lib/carray/frame/group.rb +631 -90
  153. data/lib/carray/frame/io.rb +506 -79
  154. data/lib/carray/frame/join.rb +411 -62
  155. data/lib/carray/frame/records.rb +116 -19
  156. data/lib/carray/frame/reshape.rb +455 -0
  157. data/lib/carray/frame/sort.rb +30 -18
  158. data/lib/carray/frame/verbs.rb +868 -61
  159. data/lib/carray/frame/windows.rb +117 -0
  160. data/lib/carray/frame.rb +4 -0
  161. data/lib/carray/fuse_source.rb +118 -2
  162. data/lib/carray/fusion.rb +257 -42
  163. data/lib/carray/histogram.rb +6 -1
  164. data/lib/carray/inspect.rb +54 -13
  165. data/lib/carray/iterator.rb +99 -9
  166. data/lib/carray/key_windows.rb +620 -0
  167. data/lib/carray/lazy.rb +216 -193
  168. data/lib/carray/mask_gap_fill.rb +112 -92
  169. data/lib/carray/math.rb +32 -19
  170. data/lib/carray/meld_reduce.rb +52 -21
  171. data/lib/carray/methods/align_addr.rb +8 -4
  172. data/lib/carray/methods/bin.rb +26 -18
  173. data/lib/carray/methods/bincount.rb +18 -2
  174. data/lib/carray/methods/bit_string.rb +15 -4
  175. data/lib/carray/methods/choose.rb +16 -3
  176. data/lib/carray/methods/composition.rb +18 -10
  177. data/lib/carray/methods/factorize.rb +2 -2
  178. data/lib/carray/methods/gather_nd.rb +25 -5
  179. data/lib/carray/methods/insert_block.rb +9 -14
  180. data/lib/carray/methods/is_between.rb +59 -0
  181. data/lib/carray/methods/is_in.rb +140 -10
  182. data/lib/carray/methods/join.rb +2 -7
  183. data/lib/carray/methods/lag.rb +49 -0
  184. data/lib/carray/methods/locate_addr.rb +182 -45
  185. data/lib/carray/methods/mode.rb +18 -1
  186. data/lib/carray/methods/nunique.rb +11 -6
  187. data/lib/carray/methods/repeat.rb +1 -20
  188. data/lib/carray/methods/resize.rb +21 -23
  189. data/lib/carray/methods/snap.rb +34 -12
  190. data/lib/carray/methods/unique.rb +11 -2
  191. data/lib/carray/methods/value_counts.rb +4 -11
  192. data/lib/carray/record_order.rb +208 -0
  193. data/lib/carray/rng.rb +3 -0
  194. data/lib/carray/runtime.rb +65 -17
  195. data/lib/carray/segment_iterator.rb +1221 -0
  196. data/lib/carray/serialize.rb +214 -30
  197. data/lib/carray/slab_iterator.rb +101 -27
  198. data/lib/carray/stack.rb +10 -4
  199. data/lib/carray/string.rb +105 -5
  200. data/lib/carray/string_operation_extension.rb +12 -4
  201. data/lib/carray/struct.rb +137 -58
  202. data/lib/carray/struct_builder.rb +124 -17
  203. data/lib/carray/time.rb +1222 -186
  204. data/lib/carray/window_iterator.rb +242 -81
  205. data/lib/carray.rb +9 -2
  206. data/yard-stubs/ca_obj_array.rb +4 -4
  207. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  208. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  209. data/yard-stubs/ca_obj_fake.rb +1 -1
  210. data/yard-stubs/ca_obj_field.rb +5 -1
  211. data/yard-stubs/ca_obj_grid.rb +21 -10
  212. data/yard-stubs/ca_obj_refer.rb +5 -4
  213. data/yard-stubs/ca_obj_roll.rb +2 -3
  214. data/yard-stubs/ca_obj_shift.rb +5 -1
  215. data/yard-stubs/ca_obj_stride.rb +10 -7
  216. data/yard-stubs/ca_obj_tile.rb +1 -1
  217. data/yard-stubs/ca_obj_transpose.rb +1 -1
  218. data/yard-stubs/ca_obj_window.rb +9 -5
  219. data/yard-stubs/carray_access.rb +11 -5
  220. data/yard-stubs/carray_attribute.rb +0 -5
  221. data/yard-stubs/carray_broadcast.rb +1 -1
  222. data/yard-stubs/carray_cast.rb +88 -43
  223. data/yard-stubs/carray_conversion.rb +15 -2
  224. data/yard-stubs/carray_copy.rb +12 -5
  225. data/yard-stubs/carray_core.rb +3 -3
  226. data/yard-stubs/carray_count.rb +9 -0
  227. data/yard-stubs/carray_element.rb +10 -4
  228. data/yard-stubs/carray_generate.rb +21 -1
  229. data/yard-stubs/carray_lazy.rb +5 -1
  230. data/yard-stubs/carray_loop.rb +4 -1
  231. data/yard-stubs/carray_mask.rb +60 -12
  232. data/yard-stubs/carray_math.rb +52 -14
  233. data/yard-stubs/carray_median_percentile.rb +15 -8
  234. data/yard-stubs/carray_memory_view.rb +40 -23
  235. data/yard-stubs/carray_order.rb +61 -29
  236. data/yard-stubs/carray_random.rb +13 -4
  237. data/yard-stubs/carray_scatter.rb +21 -9
  238. data/yard-stubs/carray_segment.rb +72 -0
  239. data/yard-stubs/carray_slab.rb +10 -2
  240. data/yard-stubs/carray_sort.rb +49 -8
  241. data/yard-stubs/carray_test.rb +15 -3
  242. data/yard-stubs/carray_utils.rb +0 -26
  243. metadata +19 -5
  244. data/ext/ca_for_each_element.h +0 -239
  245. data/ext/carray_kernels_map.c +0 -3467
  246. data/lib/carray/arrow_tensor.rb +0 -401
  247. data/lib/carray/table.rb +0 -108
data/ext/ca_obj_stack.c CHANGED
@@ -12,13 +12,13 @@
12
12
  partial cost)
13
13
  - conditional fold_stride: counts[0] == 1 → fold to that parent;
14
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
15
+ - create_mask: a mask CAStack over the parents' masks, with an all-false
16
+ stand-in for a parent that has none (ca_multi_parent_mask)
17
17
 
18
18
  ---------------------------------------------------------------------------- */
19
19
 
20
20
  #include "carray.h"
21
- #include "carray_internal.h" /* ca_attach_all */
21
+ #include "carray_internal.h" /* ca_attach_all / ca_sync_all */
22
22
  #include "ca_composite_dispatch.h"
23
23
  #include "ca_obj_face.h"
24
24
 
@@ -87,9 +87,9 @@ ca_stack_check_uniform (int32_t n_parents, CArray **parents)
87
87
  CArray *p = parents[i];
88
88
  if ( p->data_type != ref->data_type ) {
89
89
  rb_raise(rb_eArgError,
90
- "CAStack parents must have uniform data_type "
91
- "(parent[0]=%d, parent[%d]=%d)",
92
- ref->data_type, i, p->data_type);
90
+ "stack: the arrays must have the same data type "
91
+ "(array 0 is %s, array %d is %s)",
92
+ ca_type_name[ref->data_type], i, ca_type_name[p->data_type]);
93
93
  }
94
94
  if ( p->ndim != ref->ndim ) {
95
95
  rb_raise(rb_eArgError,
@@ -117,17 +117,6 @@ ca_stack_check_uniform (int32_t n_parents, CArray **parents)
117
117
  }
118
118
  }
119
119
 
120
- /* One-level strip of a Face VALUE to its storage-side parent (non-Face as-is).
121
- Used to keep the @parent GC/`.parent` ivar in step with the pre-stripped C
122
- parents so the Ruby-visible chain of a stacked Face is single-Face too. */
123
- static VALUE
124
- ca_stack_face_parent1 (VALUE v)
125
- {
126
- CArray *c;
127
- TypedData_Get_Struct(v, CArray, &carray_data_type, c);
128
- return ca_is_face(c) ? rb_ca_parent(v) : v;
129
- }
130
-
131
120
  int
132
121
  ca_stack_setup_with_axis (CAStack *ca, int32_t n_parents, CArray **parents,
133
122
  int8_t k_axis)
@@ -136,6 +125,26 @@ ca_stack_setup_with_axis (CAStack *ca, int32_t n_parents, CArray **parents,
136
125
  int32_t i;
137
126
  int8_t a;
138
127
 
128
+ /* ca == NULL asks for the checks only, so that a constructor can run
129
+ them before it allocates. They see the parents as the setup will,
130
+ one Face level down, without changing the caller's list. */
131
+ if ( ca == NULL ) {
132
+ volatile VALUE holder = 0;
133
+ CArray **seen = ALLOCV_N(CArray *, holder, n_parents > 0 ? n_parents : 1);
134
+ for ( i = 0; i < n_parents; i++ ) {
135
+ seen[i] = ca_is_face(parents[i]) ? CAVIEW(parents[i])->parent : parents[i];
136
+ }
137
+ ca_stack_check_uniform(n_parents, seen);
138
+ ca_check_rank_fits(seen[0]->ndim + 1, NULL);
139
+ if ( k_axis < 0 || k_axis > seen[0]->ndim ) {
140
+ rb_raise(rb_eArgError,
141
+ "CAStack k_axis %d out of range [0, %d]",
142
+ (int) k_axis, (int) seen[0]->ndim);
143
+ }
144
+ ALLOCV_END(holder);
145
+ return 0;
146
+ }
147
+
139
148
  /* §8.3 / MEMO_FACE_DOUBLE_LIFT §15.3: pre-strip Face parents one level to
140
149
  storage so a stacked Face lifts to a single-Face chain
141
150
  (CATime[CAStack[entity, ...]]) instead of a Face on top of Face parents.
@@ -152,6 +161,7 @@ ca_stack_setup_with_axis (CAStack *ca, int32_t n_parents, CArray **parents,
152
161
  ca_stack_check_uniform(n_parents, parents);
153
162
  ref = parents[0];
154
163
 
164
+ ca_check_rank_fits(ref->ndim + 1, NULL);
155
165
  if ( k_axis < 0 || k_axis > ref->ndim ) {
156
166
  rb_raise(rb_eArgError,
157
167
  "CAStack k_axis %d out of range [0, %d]",
@@ -171,6 +181,8 @@ ca_stack_setup_with_axis (CAStack *ca, int32_t n_parents, CArray **parents,
171
181
  ca->attach = 0;
172
182
  ca->nosync = 0;
173
183
 
184
+ ca->mask_standins = NULL;
185
+ ca->mask_owner = NULL;
174
186
  ca->n_parents = n_parents;
175
187
  ca->parents = ALLOC_N(CArray *, n_parents);
176
188
  for ( i = 0; i < n_parents; i++ ) {
@@ -186,7 +198,7 @@ ca_stack_setup_with_axis (CAStack *ca, int32_t n_parents, CArray **parents,
186
198
  ca->dim[k_axis] = n_parents;
187
199
  for ( a = k_axis + 1; a < ca->ndim; a++ ) ca->dim[a] = ref->dim[a - 1];
188
200
 
189
- /* mask: lazy; horizontal propagation triggers on first mask access. */
201
+ /* mask: lazy, built on first mask access. */
190
202
  return 0;
191
203
  }
192
204
 
@@ -199,7 +211,9 @@ ca_stack_setup (CAStack *ca, int32_t n_parents, CArray **parents)
199
211
  CAStack *
200
212
  ca_stack_new_with_axis (int32_t n_parents, CArray **parents, int8_t k_axis)
201
213
  {
202
- CAStack *ca = ALLOC(CAStack);
214
+ CAStack *ca;
215
+ ca_stack_setup_with_axis(NULL, n_parents, parents, k_axis);
216
+ ca = ALLOC(CAStack);
203
217
  ca_stack_setup_with_axis(ca, n_parents, parents, k_axis);
204
218
  return ca;
205
219
  }
@@ -216,6 +230,13 @@ free_ca_stack (void *ap)
216
230
  CAStack *ca = (CAStack *) ap;
217
231
  if ( ca != NULL ) {
218
232
  ca_free(ca->mask);
233
+ if ( ca->mask_standins ) {
234
+ int32_t k;
235
+ for ( k = 0; k < ca->n_parents; k++ ) {
236
+ if ( ca->mask_standins[k] ) ca_free(ca->mask_standins[k]);
237
+ }
238
+ xfree(ca->mask_standins);
239
+ }
219
240
  xfree(ca->parents);
220
241
  xfree(ca->dim);
221
242
  xfree(ca);
@@ -235,17 +256,40 @@ ca_stack_func_clone (void *ap)
235
256
 
236
257
  /* view[..., k at k_axis, ...] = parents[k][parent_idx]
237
258
  where parent_idx = idx[] with the k_axis slot removed. */
259
+ /* On the mask of a CAStack: let its stand-ins give way to the parents' own
260
+ masks (see ca_multi_parent_mask_refresh). */
261
+ static void
262
+ ca_stack_mask_refresh (CAStack *ca)
263
+ {
264
+ CAStack *o = (CAStack *) ca->mask_owner;
265
+ if ( o != NULL ) {
266
+ ca_multi_parent_mask_refresh((CArray *) ca, o->parents, o->mask_standins,
267
+ o->n_parents);
268
+ }
269
+ }
270
+
271
+ static void
272
+ ca_stack_mask_settle (CAStack *ca)
273
+ {
274
+ CAStack *o = (CAStack *) ca->mask_owner;
275
+ if ( o != NULL ) {
276
+ ca_multi_parent_mask_settle((CArray *) ca, o->parents, o->mask_standins,
277
+ o->n_parents);
278
+ }
279
+ }
280
+
238
281
  static void
239
282
  ca_stack_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
240
283
  {
241
284
  CAStack *ca = (CAStack *) ap;
285
+ ca_stack_mask_refresh(ca);
242
286
  int8_t k_axis = ca->k_axis;
243
287
  ca_size_t k = idx[k_axis];
244
288
  if ( k < 0 || k >= ca->n_parents ) {
245
289
  rb_raise(rb_eIndexError, "CAStack k-axis (axis %d) index %lld out of range [0, %d)",
246
290
  (int) k_axis, (long long) k, (int) ca->n_parents);
247
291
  }
248
- if ( k_axis == 0 ) {
292
+ if ( k_axis == 0 && ca_fast_paths_enabled() ) {
249
293
  /* Fast path: parent_idx = idx[1..ndim-1]; pass idx+1 directly. */
250
294
  ca_xfer_index(ca->parents[k], idx + 1, data, dir);
251
295
  return;
@@ -274,6 +318,7 @@ ca_stack_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
274
318
  void *data, int dir)
275
319
  {
276
320
  CAStack *ca = (CAStack *) ap;
321
+ ca_stack_mask_refresh(ca);
277
322
  int8_t k_axis = ca->k_axis;
278
323
  ca_size_t parent_elements = ca->parents[0]->elements;
279
324
  ca_size_t bytes = ca->bytes;
@@ -286,7 +331,7 @@ ca_stack_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
286
331
  paddrs = ALLOCV_N(ca_size_t, holder1, n);
287
332
  pdata = ALLOCV_N(char, holder2, n * bytes);
288
333
 
289
- if ( k_axis == 0 ) {
334
+ if ( k_axis == 0 && ca_fast_paths_enabled() ) {
290
335
  /* Fast path: addr = k * parent_elements + parent_addr (row-major over
291
336
  view shape with K at axis 0). */
292
337
  for ( k = 0; k < ca->n_parents; k++ ) {
@@ -398,7 +443,9 @@ static void
398
443
  ca_stack_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
399
444
  ca_size_t *strides, void *data, int dir)
400
445
  {
446
+ if ( ca_region_is_empty(((CArray *) ap)->ndim, counts) ) return;
401
447
  CAStack *ca = (CAStack *) ap;
448
+ ca_stack_mask_refresh(ca);
402
449
  int8_t k_axis = ca->k_axis;
403
450
  int8_t pndim = ca->ndim - 1;
404
451
  ca_size_t native[CA_RANK_MAX], dstride[CA_RANK_MAX];
@@ -419,19 +466,24 @@ ca_stack_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
419
466
  s *= ca->parents[0]->dim[i];
420
467
  }
421
468
 
422
- /* Structural iff strides[k] == native[k] for all k. */
469
+ /* Structural iff strides[k] == native[k] for all k and the request is a
470
+ box over the view's own axes. Equal strides alone are not enough: a
471
+ size-1 axis gives two axes the same native stride, and a flat window
472
+ reshaped back to ndim has native strides yet wraps across an axis. The
473
+ request is over the view's addresses; see
474
+ ca_xfer_stride_request_is_axis_box (carray.h). */
423
475
  for ( i = 0; i < ca->ndim; i++ ) {
424
476
  if ( strides[i] != native[i] ) { structural = 0; break; }
425
477
  }
478
+ if ( structural &&
479
+ ! ca_xfer_stride_request_is_axis_box(ca, starts, counts, strides) ) {
480
+ structural = 0;
481
+ }
482
+ if ( ! ca_fast_paths_enabled() ) structural = 0;
426
483
 
427
484
  if ( structural ) {
428
485
  ca_size_t k_lo = starts[k_axis];
429
486
  ca_size_t k_hi = starts[k_axis] + counts[k_axis];
430
- if ( k_lo < 0 || k_hi > ca->n_parents ) {
431
- rb_raise(rb_eIndexError,
432
- "CAStack xfer_stride k-axis (axis %d) [%lld, %lld) out of range [0, %d)",
433
- (int) k_axis, (long long) k_lo, (long long) k_hi, (int) ca->n_parents);
434
- }
435
487
 
436
488
  /* dst row-major byte strides over output counts[]. */
437
489
  s = ca->bytes;
@@ -486,6 +538,7 @@ ca_stack_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
486
538
  ca_size_t pidx[CA_RANK_MAX];
487
539
  ca_size_t paddr = 0;
488
540
  for ( a = 0; a < pndim; a++ ) pidx[a] = 0;
541
+ /* empty: the slot returns on an empty region at its entry */
489
542
  while ( 1 ) {
490
543
  ca_size_t voff = k_off;
491
544
  for ( a = 0; a < k_axis; a++ ) voff += pidx[a] * dstride[a];
@@ -518,7 +571,7 @@ ca_stack_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
518
571
  with native[k_axis] removed, in order. */
519
572
  {
520
573
  int8_t p = -1;
521
- int reloc = 1;
574
+ int reloc = ca_fast_paths_enabled();
522
575
  for ( i = 0; i < ca->ndim; i++ ) {
523
576
  if ( strides[i] == native[k_axis] ) { p = i; break; }
524
577
  }
@@ -538,21 +591,33 @@ ca_stack_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
538
591
  if ( strides[i] != native[v] ) { reloc = 0; break; }
539
592
  }
540
593
  }
594
+ /* Each output axis must stay inside the view axis it was matched to;
595
+ otherwise the request wraps (or the match was one of two axes sharing
596
+ a native stride because the other has size 1), and only the per-cell
597
+ walk reads it correctly. */
598
+ if ( reloc ) {
599
+ for ( i = 0; i < ca->ndim; i++ ) {
600
+ int8_t j, v;
601
+ if ( counts[i] <= 1 ) continue;
602
+ if ( i == p ) {
603
+ v = k_axis;
604
+ }
605
+ else {
606
+ j = (i < p) ? i : (i - 1);
607
+ v = (j < k_axis) ? j : (j + 1);
608
+ }
609
+ if ( starts[v] + counts[i] > ca->dim[v] ) { reloc = 0; break; }
610
+ }
611
+ }
541
612
  if ( reloc ) {
542
- ca_size_t kk_lo = starts[k_axis];
543
- ca_size_t kk_hi = starts[k_axis] + counts[p];
544
613
  ca_size_t odo[CA_RANK_MAX];
545
614
  int8_t a;
546
- if ( kk_lo < 0 || kk_hi > ca->n_parents ) {
547
- rb_raise(rb_eIndexError,
548
- "CAStack xfer_stride k-axis [%lld, %lld) out of range [0, %d)",
549
- (long long) kk_lo, (long long) kk_hi, (int) ca->n_parents);
550
- }
551
615
  if ( p == ca->ndim - 1 ) {
552
616
  /* K innermost: per-cell via the parent's xfer_index directly, escaping
553
617
  the CAStack-level addr2index dispatch hop in the per-cell fallback. */
554
618
  ca_size_t pidx[CA_RANK_MAX], doff = 0;
555
619
  for ( i = 0; i < ca->ndim; i++ ) odo[i] = 0;
620
+ /* empty: the slot returns on an empty region at its entry */
556
621
  while ( 1 ) {
557
622
  ca_size_t k2 = starts[k_axis] + odo[p];
558
623
  for ( a = 0; a < pndim; a++ ) {
@@ -571,6 +636,7 @@ ca_stack_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
571
636
  s = ca->bytes;
572
637
  for ( i = ca->ndim - 1; i >= 0; i-- ) { dstride[i] = s; s *= counts[i]; }
573
638
  for ( i = 0; i <= p; i++ ) odo[i] = 0;
639
+ /* empty: the slot returns on an empty region at its entry */
574
640
  while ( 1 ) {
575
641
  ca_size_t k2 = starts[k_axis] + odo[p];
576
642
  ca_size_t doff = 0;
@@ -600,6 +666,7 @@ ca_stack_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
600
666
  ca_size_t idx[CA_RANK_MAX], doff = 0, base = 0;
601
667
  for ( i = 0; i < ca->ndim; i++ ) base += starts[i] * native[i];
602
668
  for ( i = 0; i < ca->ndim; i++ ) idx[i] = 0;
669
+ /* empty: the slot returns on an empty region at its entry */
603
670
  while ( 1 ) {
604
671
  ca_size_t toff = base, vidx[CA_RANK_MAX];
605
672
  for ( i = 0; i < ca->ndim; i++ ) toff += idx[i] * strides[i];
@@ -624,20 +691,34 @@ ca_stack_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
624
691
  - k_axis != 0: per-parent contiguous read into a temp buffer, then
625
692
  scattered-write to dst at view row-major positions. This is the
626
693
  correctness path (per-parent contig-read / scattered-write), not a
627
- perf-optimised one. */
694
+ perf-optimised one.
695
+
696
+ into_buffers (PUT only) writes each parent's cells into the buffer its
697
+ attach handed out, rather than through the parent to what is behind it:
698
+ the sync of an attached stack, which then syncs each parent. Writing
699
+ past the buffer would leave it stale, and the parent's own sync would
700
+ push the stale buffer back over the cells just written. */
628
701
  static void
629
- ca_stack_func_xfer_all (void *ap, void *data, int dir)
702
+ ca_stack_xfer_parents (CAStack *ca, char *d, int dir, int into_buffers)
630
703
  {
631
- CAStack *ca = (CAStack *) ap;
632
704
  int8_t k_axis = ca->k_axis;
633
705
  ca_size_t bytes = ca->bytes;
634
- char *d = (char *) data;
635
706
  int32_t k;
636
707
 
637
- if ( k_axis == 0 ) {
708
+ /* The walk below visits a parent's first cell before it tests the
709
+ counts. */
710
+ if ( ca->elements == 0 ) return;
711
+
712
+ if ( k_axis == 0 && ca_fast_paths_enabled() ) {
638
713
  ca_size_t parent_bytes_total = ca->parents[0]->elements * bytes;
639
714
  for ( k = 0; k < ca->n_parents; k++ ) {
640
- ca_xfer_all(ca->parents[k], d + k * parent_bytes_total, dir);
715
+ if ( into_buffers ) {
716
+ memcpy(ca->parents[k]->ptr, d + k * parent_bytes_total,
717
+ (size_t) parent_bytes_total);
718
+ }
719
+ else {
720
+ ca_xfer_all(ca->parents[k], d + k * parent_bytes_total, dir);
721
+ }
641
722
  }
642
723
  return;
643
724
  }
@@ -695,6 +776,7 @@ ca_stack_func_xfer_all (void *ap, void *data, int dir)
695
776
  }
696
777
  else if ( dir == CA_XFER_GET ) {
697
778
  /* dir-hoisted GET inner loop, incremental voff. */
779
+ /* empty: the function returns on an empty view at its entry */
698
780
  while ( 1 ) {
699
781
  memcpy(d + voff, buf + paddr_bytes, bytes);
700
782
  paddr_bytes += bytes;
@@ -710,6 +792,7 @@ ca_stack_func_xfer_all (void *ap, void *data, int dir)
710
792
  }
711
793
  else {
712
794
  /* dir-hoisted PUT inner loop, incremental voff. */
795
+ /* empty: the function returns on an empty view at its entry */
713
796
  while ( 1 ) {
714
797
  memcpy(buf + paddr_bytes, d + voff, bytes);
715
798
  paddr_bytes += bytes;
@@ -725,7 +808,12 @@ ca_stack_func_xfer_all (void *ap, void *data, int dir)
725
808
  }
726
809
 
727
810
  if ( dir == CA_XFER_PUT ) {
728
- ca_xfer_all(ca->parents[k], buf, CA_XFER_PUT);
811
+ if ( into_buffers ) {
812
+ memcpy(ca->parents[k]->ptr, buf, (size_t) (parent_elements * bytes));
813
+ }
814
+ else {
815
+ ca_xfer_all(ca->parents[k], buf, CA_XFER_PUT);
816
+ }
729
817
  }
730
818
  }
731
819
  }
@@ -733,6 +821,13 @@ ca_stack_func_xfer_all (void *ap, void *data, int dir)
733
821
  }
734
822
  }
735
823
 
824
+ static void
825
+ ca_stack_func_xfer_all (void *ap, void *data, int dir)
826
+ {
827
+ ca_stack_mask_refresh((CAStack *) ap);
828
+ ca_stack_xfer_parents((CAStack *) ap, (char *) data, dir, 0);
829
+ }
830
+
736
831
  /* ------------------------------------------------------------------- */
737
832
  /* attach / sync / detach (materialise via xfer_all) */
738
833
  /* ------------------------------------------------------------------- */
@@ -741,28 +836,37 @@ static void
741
836
  ca_stack_func_allocate (void *ap)
742
837
  {
743
838
  CAStack *ca = (CAStack *) ap;
839
+ ca_stack_mask_refresh(ca);
744
840
  ca_attach_all(ca->parents, ca->n_parents);
745
- ca->ptr = xmalloc(ca_length(ca));
841
+ ca->ptr = ca_alloc_cells((CArray *) ca);
842
+ }
843
+
844
+ static VALUE
845
+ ca_stack_attach_owned (VALUE arg)
846
+ {
847
+ ca_axis_view_attach_owned((void *) arg);
848
+ return Qnil;
746
849
  }
747
850
 
748
851
  static void
749
852
  ca_stack_func_attach (void *ap)
750
853
  {
751
854
  CAStack *ca = (CAStack *) ap;
855
+ ca_stack_mask_refresh(ca);
752
856
  ca_attach_all(ca->parents, ca->n_parents);
753
- ca->ptr = xmalloc(ca_length(ca));
754
- ca_stack_func_xfer_all(ca, ca->ptr, CA_XFER_GET);
857
+ ca_fill_or_detach(ca->n_parents, ca->parents, ca_stack_attach_owned, (VALUE) ca);
755
858
  }
756
859
 
860
+ static void ca_stack_mask_refuse_sync (CAStack *ca);
861
+
757
862
  static void
758
863
  ca_stack_func_sync (void *ap)
759
864
  {
760
865
  CAStack *ca = (CAStack *) ap;
761
- int32_t k;
762
- ca_stack_func_xfer_all(ca, ca->ptr, CA_XFER_PUT);
763
- for ( k = 0; k < ca->n_parents; k++ ) {
764
- ca_sync(ca->parents[k]);
765
- }
866
+ ca_stack_mask_refuse_sync(ca);
867
+ ca_stack_xfer_parents(ca, ca->ptr, CA_XFER_PUT, 1);
868
+ ca_sync_all(ca->parents, ca->n_parents);
869
+ ca_stack_mask_settle(ca);
766
870
  }
767
871
 
768
872
  static void
@@ -785,6 +889,7 @@ static void
785
889
  ca_stack_func_fill_data (void *ap, void *ptr)
786
890
  {
787
891
  CAStack *ca = (CAStack *) ap;
892
+ ca_stack_mask_refresh(ca);
788
893
  int32_t k;
789
894
  for ( k = 0; k < ca->n_parents; k++ ) {
790
895
  ca_fill(ca->parents[k], ptr);
@@ -792,12 +897,13 @@ ca_stack_func_fill_data (void *ap, void *ptr)
792
897
  }
793
898
 
794
899
  /* ------------------------------------------------------------------- */
795
- /* create_mask: horizontal propagation */
900
+ /* create_mask */
796
901
  /* ------------------------------------------------------------------- */
797
902
 
798
- /* All parents' roots gain all-zero mask (if not already), then build
799
- mask CAStack from the K parent mask CArrays. Self-similar (= CAStack
800
- re-used as its own mask class). */
903
+ /* The mask is a CAStack over the K parents' masks, a stand-in standing
904
+ for a parent that has none (ca_multi_parent_mask), so building it gives
905
+ no parent a mask. Self-similar (= CAStack re-used as its own mask
906
+ class). */
801
907
  static void
802
908
  ca_stack_func_create_mask (void *ap)
803
909
  {
@@ -807,12 +913,13 @@ ca_stack_func_create_mask (void *ap)
807
913
  int32_t k;
808
914
 
809
915
  mask_parents = ALLOCV_N(CArray *, holder, ca->n_parents);
916
+ if ( ! ca->mask_standins ) {
917
+ ca->mask_standins = ALLOC_N(CArray *, ca->n_parents);
918
+ MEMZERO(ca->mask_standins, CArray *, ca->n_parents);
919
+ }
810
920
  for ( k = 0; k < ca->n_parents; k++ ) {
811
- ca_update_mask(ca->parents[k]);
812
- if ( ! ca->parents[k]->mask ) {
813
- ca_create_mask(ca->parents[k]);
814
- }
815
- mask_parents[k] = ca->parents[k]->mask;
921
+ mask_parents[k] = ca_multi_parent_mask(ca->parents[k],
922
+ &ca->mask_standins[k]);
816
923
  }
817
924
  /* Build the mask stack with the parent's k_axis from the start so its
818
925
  dim[] is laid out for that axis. (Patching ->k_axis after a default
@@ -821,6 +928,7 @@ ca_stack_func_create_mask (void *ap)
821
928
  xfer_stride k-range check when k_axis != 0.) */
822
929
  ca->mask = (CArray *) ca_stack_new_with_axis(ca->n_parents, mask_parents,
823
930
  ca->k_axis);
931
+ ((CAStack *) ca->mask)->mask_owner = (CArray *) ca;
824
932
  ALLOCV_END(holder);
825
933
  }
826
934
 
@@ -846,6 +954,13 @@ ca_stack_func_fold_stride (void *ap, ca_fold_t *f, void **next_parent)
846
954
  ca_size_t k;
847
955
  int8_t i;
848
956
 
957
+ /* The mask of a stack while it still reads stand-ins: a write must
958
+ reach the stack's transfer, which replaces them first. */
959
+ if ( ca->mask_owner != NULL
960
+ && ((CAStack *) ca->mask_owner)->mask_standins != NULL ) {
961
+ return 0;
962
+ }
963
+
849
964
  /* k_axis != 0: parent byte blocks are interleaved (not contig in view
850
965
  buffer), so the byte-box containment check and the size-1 K-axis
851
966
  degeneration below would require detecting the K-axis contribution
@@ -854,6 +969,7 @@ ca_stack_func_fold_stride (void *ap, ca_fold_t *f, void **next_parent)
854
969
  through to CAStack.xfer_stride (k_axis-aware), which still delivers
855
970
  correct (if non-folded) per-parent dispatch. */
856
971
  if ( ca->k_axis != 0 ) return 0;
972
+ if ( ! ca_fast_paths_enabled() ) return 0;
857
973
 
858
974
  for ( i = 0; i < f->ndim; i++ ) {
859
975
  ca_size_t span = (f->counts[i] - 1) * f->strides[i];
@@ -876,6 +992,174 @@ ca_stack_func_fold_stride (void *ap, ca_fold_t *f, void **next_parent)
876
992
  /* operation table */
877
993
  /* ------------------------------------------------------------------- */
878
994
 
995
+ /* The table's write slots: after a write that may have put a true into a
996
+ stand-in, the true goes on to the parent's mask, or is refused for a
997
+ value array or a mask array (ca_mask_standin_settle). Only the mask of
998
+ a CAStack has stand-ins. */
999
+ /* A write to the mask of a CAStack, run once against blank masks first
1000
+ (ca_multi_parent_mask_refuse_first), so that a write refused for a value
1001
+ array or mask array part changes no other part. */
1002
+ enum { CA_STACK_PUT_INDEX, CA_STACK_PUT_ADDRS, CA_STACK_PUT_STRIDE, CA_STACK_PUT_ALL,
1003
+ CA_STACK_PUT_FILL, CA_STACK_PUT_SYNC, CA_STACK_PUT_FILL_ADDRS,
1004
+ CA_STACK_PUT_FILL_STRIDE };
1005
+
1006
+ typedef struct {
1007
+ int slot;
1008
+ void *ap;
1009
+ ca_size_t *idx;
1010
+ ca_size_t n;
1011
+ ca_size_t *addrs;
1012
+ ca_size_t *starts;
1013
+ ca_size_t *counts;
1014
+ ca_size_t *strides;
1015
+ void *data;
1016
+ ca_size_t base;
1017
+ int8_t ndim;
1018
+ ca_size_t *steps;
1019
+ } ca_stack_put_t;
1020
+
1021
+ static VALUE
1022
+ ca_stack_put_body (VALUE arg)
1023
+ {
1024
+ ca_stack_put_t *a = (ca_stack_put_t *) arg;
1025
+ switch ( a->slot ) {
1026
+ case CA_STACK_PUT_INDEX:
1027
+ ca_stack_func_xfer_index(a->ap, a->idx, a->data, CA_XFER_PUT); break;
1028
+ case CA_STACK_PUT_ADDRS:
1029
+ ca_stack_func_xfer_addrs(a->ap, a->n, a->addrs, a->data, CA_XFER_PUT); break;
1030
+ case CA_STACK_PUT_STRIDE:
1031
+ ca_stack_func_xfer_stride(a->ap, a->starts, a->counts, a->strides, a->data,
1032
+ CA_XFER_PUT); break;
1033
+ case CA_STACK_PUT_ALL:
1034
+ ca_stack_func_xfer_all(a->ap, a->data, CA_XFER_PUT); break;
1035
+ case CA_STACK_PUT_FILL:
1036
+ ca_stack_func_fill_data(a->ap, a->data); break;
1037
+ case CA_STACK_PUT_FILL_ADDRS:
1038
+ ca_fill_addrs_default(a->ap, a->n, a->addrs, a->data); break;
1039
+ case CA_STACK_PUT_FILL_STRIDE:
1040
+ ca_fill_stride_default(a->ap, a->base, a->ndim, a->counts, a->steps,
1041
+ a->data); break;
1042
+ case CA_STACK_PUT_SYNC:
1043
+ ca_stack_xfer_parents((CAStack *) a->ap, ((CArray *) a->ap)->ptr, CA_XFER_PUT, 1);
1044
+ break;
1045
+ }
1046
+ return Qnil;
1047
+ }
1048
+
1049
+ static void
1050
+ ca_stack_mask_refuse_first (ca_stack_put_t *a)
1051
+ {
1052
+ CAStack *o = (CAStack *) ((CAStack *) a->ap)->mask_owner;
1053
+ if ( o != NULL ) {
1054
+ ca_multi_parent_mask_refuse_first((CArray *) a->ap, o->parents,
1055
+ o->mask_standins, o->n_parents,
1056
+ ca_stack_put_body, (VALUE) a);
1057
+ }
1058
+ }
1059
+
1060
+ static void
1061
+ ca_stack_mask_refuse_sync (CAStack *ca)
1062
+ {
1063
+ ca_stack_put_t a = { CA_STACK_PUT_SYNC, ca, NULL, 0, NULL, NULL, NULL, NULL, NULL };
1064
+ ca_stack_mask_refuse_first(&a);
1065
+ }
1066
+
1067
+ /* After a write of n cells of `data` to the mask of a CAStack: a true
1068
+ that went into a stand-in goes on to its parent's mask. */
1069
+ static void
1070
+ ca_stack_mask_settle_after (CAStack *ca, const void *data, ca_size_t n)
1071
+ {
1072
+ if ( ca->mask_owner != NULL && ca_mask_any_true(data, n) ) {
1073
+ ca_stack_mask_settle(ca);
1074
+ }
1075
+ }
1076
+
1077
+ static void
1078
+ ca_stack_put_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
1079
+ {
1080
+ if ( dir == CA_XFER_PUT ) {
1081
+ ca_stack_put_t a = { CA_STACK_PUT_INDEX, ap, idx, 0, NULL, NULL, NULL, NULL, data };
1082
+ ca_stack_mask_refuse_first(&a);
1083
+ }
1084
+ ca_stack_func_xfer_index(ap, idx, data, dir);
1085
+ if ( dir == CA_XFER_PUT ) ca_stack_mask_settle_after((CAStack *) ap, data, 1);
1086
+ }
1087
+
1088
+ static void
1089
+ ca_stack_put_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs, void *data,
1090
+ int dir)
1091
+ {
1092
+ if ( dir == CA_XFER_PUT ) {
1093
+ ca_stack_put_t a = { CA_STACK_PUT_ADDRS, ap, NULL, n, addrs, NULL, NULL, NULL, data };
1094
+ ca_stack_mask_refuse_first(&a);
1095
+ }
1096
+ ca_stack_func_xfer_addrs(ap, n, addrs, data, dir);
1097
+ if ( dir == CA_XFER_PUT ) ca_stack_mask_settle_after((CAStack *) ap, data, n);
1098
+ }
1099
+
1100
+ static void
1101
+ ca_stack_put_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
1102
+ ca_size_t *strides, void *data, int dir)
1103
+ {
1104
+ if ( dir == CA_XFER_PUT ) {
1105
+ ca_stack_put_t a = { CA_STACK_PUT_STRIDE, ap, NULL, 0, NULL, starts, counts, strides, data };
1106
+ ca_stack_mask_refuse_first(&a);
1107
+ }
1108
+ ca_stack_func_xfer_stride(ap, starts, counts, strides, data, dir);
1109
+ if ( dir == CA_XFER_PUT ) {
1110
+ ca_stack_mask_settle_after((CAStack *) ap, data,
1111
+ ca_shape_elements(((CArray *) ap)->ndim, counts, 1));
1112
+ }
1113
+ }
1114
+
1115
+ static void
1116
+ ca_stack_put_xfer_all (void *ap, void *data, int dir)
1117
+ {
1118
+ if ( dir == CA_XFER_PUT ) {
1119
+ ca_stack_put_t a = { CA_STACK_PUT_ALL, ap, NULL, 0, NULL, NULL, NULL, NULL, data };
1120
+ ca_stack_mask_refuse_first(&a);
1121
+ }
1122
+ ca_stack_func_xfer_all(ap, data, dir);
1123
+ if ( dir == CA_XFER_PUT ) {
1124
+ ca_stack_mask_settle_after((CAStack *) ap, data, ((CArray *) ap)->elements);
1125
+ }
1126
+ }
1127
+
1128
+ static void
1129
+ ca_stack_put_fill_data (void *ap, void *ptr)
1130
+ {
1131
+ {
1132
+ ca_stack_put_t a = { CA_STACK_PUT_FILL, ap, NULL, 0, NULL, NULL, NULL, NULL, ptr };
1133
+ ca_stack_mask_refuse_first(&a);
1134
+ }
1135
+ ca_stack_func_fill_data(ap, ptr);
1136
+ ca_stack_mask_settle_after((CAStack *) ap, ptr, 1);
1137
+ }
1138
+
1139
+ /* A fill of part of the mask goes cell by cell through xfer_index; run
1140
+ the whole of it against blank masks first, so that a cell refused late
1141
+ does not leave the cells before it written. */
1142
+ static void
1143
+ ca_stack_put_fill_addrs (void *ap, ca_size_t n, ca_size_t *addrs, void *ptr)
1144
+ {
1145
+ ca_stack_put_t a = { CA_STACK_PUT_FILL_ADDRS, ap, NULL, n, addrs, NULL, NULL,
1146
+ NULL, ptr };
1147
+ ca_stack_mask_refuse_first(&a);
1148
+ ca_fill_addrs_default(ap, n, addrs, ptr);
1149
+ ca_stack_mask_settle_after((CAStack *) ap, ptr, 1);
1150
+ }
1151
+
1152
+ static void
1153
+ ca_stack_put_fill_stride (void *ap, ca_size_t base, int8_t ndim,
1154
+ ca_size_t *counts, ca_size_t *steps, void *ptr)
1155
+ {
1156
+ ca_stack_put_t a = { CA_STACK_PUT_FILL_STRIDE, ap, NULL, 0, NULL, NULL, counts,
1157
+ NULL, ptr, base, ndim, steps };
1158
+ ca_stack_mask_refuse_first(&a);
1159
+ ca_fill_stride_default(ap, base, ndim, counts, steps, ptr);
1160
+ ca_stack_mask_settle_after((CAStack *) ap, ptr, 1);
1161
+ }
1162
+
879
1163
  ca_operation_function_t ca_stack_func = {
880
1164
  -1, /* CA_OBJ_STACK */
881
1165
  CA_VIEW_ARRAY,
@@ -885,13 +1169,13 @@ ca_operation_function_t ca_stack_func = {
885
1169
  ca_stack_func_attach,
886
1170
  ca_stack_func_sync,
887
1171
  ca_stack_func_detach,
888
- ca_stack_func_fill_data,
1172
+ ca_stack_put_fill_data,
889
1173
  ca_stack_func_create_mask,
890
- ca_stack_func_xfer_index,
891
- ca_stack_func_xfer_addrs,
1174
+ ca_stack_put_xfer_index,
1175
+ ca_stack_put_xfer_addrs,
892
1176
  ca_stack_func_fold_stride,
893
- ca_stack_func_xfer_stride,
894
- ca_stack_func_xfer_all,
1177
+ ca_stack_put_xfer_stride,
1178
+ ca_stack_put_xfer_all,
895
1179
  };
896
1180
 
897
1181
  /* ------------------------------------------------------------------- */
@@ -935,7 +1219,7 @@ rb_ca_stack_new_with_axis (VALUE parents_ary, int8_t k_axis)
935
1219
  ca = ca_stack_new_with_axis((int32_t) n, parents, k_axis);
936
1220
  obj = ca_wrap_struct(ca);
937
1221
  rb_ivar_set(obj, id_parents, rb_ary_dup(parents_ary)); /* GC anchor */
938
- rb_ca_set_parent(obj, ca_stack_face_parent1(rb_ary_entry(parents_ary, 0))); /* CAView base (Face-stripped, matches C ->parent) */
1222
+ rb_ca_set_parent(obj, ca_face_strip_one(rb_ary_entry(parents_ary, 0))); /* CAView base (Face-stripped, matches C ->parent) */
939
1223
  ALLOCV_END(holder);
940
1224
  return obj;
941
1225
  }
@@ -963,8 +1247,14 @@ rb_ca_stack_initialize_copy (VALUE self, VALUE other)
963
1247
  {
964
1248
  CAStack *ca, *cs;
965
1249
  TypedData_Get_Struct(self, CAStack, &castack_data_type, ca);
1250
+ ca_check_uninitialized(ca);
966
1251
  TypedData_Get_Struct(other, CAStack, &castack_data_type, cs);
967
1252
  ca_stack_setup_with_axis(ca, cs->n_parents, cs->parents, cs->k_axis);
1253
+ /* A copy of a stack's mask reads and writes the same stand-ins, so it
1254
+ answers to the same owner: its writes reach the parents' masks. The
1255
+ copy holds the owner alive through the masked_array ivar that dup
1256
+ carries over (see rb_ca_mask_array). */
1257
+ ca->mask_owner = cs->mask_owner;
968
1258
  return self;
969
1259
  }
970
1260
 
@@ -1001,10 +1291,11 @@ rb_ca_stack_initialize (int argc, VALUE *argv, VALUE self)
1001
1291
  VALUE first = rb_ary_entry(list, 0);
1002
1292
  rb_check_carray_object(first);
1003
1293
  TypedData_Get_Struct(first, CArray, &carray_data_type, ref);
1004
- k_axis = (int8_t) rb_ca_normalize_axis_for_ndim(
1005
- NUM2LONG(axis_val), (int) ref->ndim + 1, "CAStack.new");
1294
+ k_axis = (int8_t) ca_normalize_axis_arg(
1295
+ axis_val, (int) ref->ndim + 1, "CAStack.new");
1006
1296
  }
1007
1297
  TypedData_Get_Struct(self, CAStack, &castack_data_type, ca);
1298
+ ca_check_uninitialized(ca);
1008
1299
  parents = ALLOCV_N(CArray *, holder, n);
1009
1300
  for ( i = 0; i < n; i++ ) {
1010
1301
  VALUE p = rb_ary_entry(list, i);
@@ -1013,7 +1304,7 @@ rb_ca_stack_initialize (int argc, VALUE *argv, VALUE self)
1013
1304
  }
1014
1305
  ca_stack_setup_with_axis(ca, (int32_t) n, parents, k_axis);
1015
1306
  rb_ivar_set(self, id_parents, rb_ary_dup(list)); /* GC anchor */
1016
- rb_ca_set_parent(self, ca_stack_face_parent1(rb_ary_entry(list, 0))); /* CAView base (Face-stripped, matches C ->parent) */
1307
+ rb_ca_set_parent(self, ca_face_strip_one(rb_ary_entry(list, 0))); /* CAView base (Face-stripped, matches C ->parent) */
1017
1308
  ALLOCV_END(holder);
1018
1309
  return self;
1019
1310
  }
@@ -1029,8 +1320,9 @@ rb_ca_stack_initialize (int argc, VALUE *argv, VALUE self)
1029
1320
  this method)
1030
1321
  - then ca_face_lift re-wraps the result as the same Face class,
1031
1322
  carrying state from list[0]
1032
- For non-Face / mixed / heterogeneous lists, returns the raw CAStack
1033
- directly (= same result as the inherited Class#new chain).
1323
+ A single Face parent is lifted too. A list mixing Face and non-Face
1324
+ parents, or two Face classes, is refused; a non-Face list returns the
1325
+ raw CAStack (= same result as the inherited Class#new chain).
1034
1326
 
1035
1327
  Replaces the Ruby-level CAStack.new override in lib/carray/compose.rb
1036
1328
  (= used alias_method :__new_raw__, :new + class << self def new).
@@ -1039,68 +1331,23 @@ rb_ca_stack_initialize (int argc, VALUE *argv, VALUE self)
1039
1331
  static VALUE
1040
1332
  rb_ca_stack_s_new (int argc, VALUE *argv, VALUE klass)
1041
1333
  {
1042
- VALUE list, kwargs;
1043
- long n, i;
1044
- int all_face = 1;
1045
- VALUE face_class = Qnil;
1046
- CArray *ref_face = NULL;
1334
+ VALUE list, kwargs, face_class;
1047
1335
  VALUE obj;
1048
1336
 
1049
1337
  rb_scan_args(argc, argv, "1:", &list, &kwargs);
1050
1338
  Check_Type(list, T_ARRAY);
1051
- n = RARRAY_LEN(list);
1052
- if ( n <= 0 ) {
1339
+ if ( RARRAY_LEN(list) <= 0 ) {
1053
1340
  rb_raise(rb_eArgError, "CAStack.new requires at least one parent");
1054
1341
  }
1342
+ face_class = ca_face_list_class(list, "CAStack.new");
1055
1343
 
1056
- /* Inspect Face homogeneity in one pass. Bail to non-Face path as
1057
- soon as a non-Face or class-mismatched element is seen. */
1058
- for ( i = 0; i < n; i++ ) {
1059
- VALUE p = rb_ary_entry(list, i);
1060
- CArray *ca;
1061
- rb_check_carray_object(p);
1062
- TypedData_Get_Struct(p, CArray, &carray_data_type, ca);
1063
- if ( !ca_is_face(ca) ) { all_face = 0; break; }
1064
- if ( i == 0 ) {
1065
- face_class = rb_obj_class(p);
1066
- ref_face = ca;
1067
- } else if ( rb_obj_class(p) != face_class ) {
1068
- all_face = 0; break;
1069
- }
1070
- }
1071
-
1072
- /* Allocate + initialize raw CAStack. rb_obj_alloc calls the
1073
- TypedData allocator directly (= bypasses the overridden Class#new,
1074
- avoiding infinite recursion); rb_obj_call_init_kw dispatches to
1075
- #initialize forwarding the kwargs hash (= Ruby 3.x strict kwarg
1076
- separation requires the _kw variant when passing through). */
1344
+ /* rb_obj_alloc calls the TypedData allocator directly (= bypasses the
1345
+ overridden Class#new, avoiding infinite recursion); rb_obj_call_init_kw
1346
+ forwards the kwargs hash. */
1077
1347
  obj = rb_obj_alloc(klass);
1078
1348
  rb_obj_call_init_kw(obj, argc, argv, RB_PASS_CALLED_KEYWORDS);
1079
1349
 
1080
- /* Single element or non-Face: no lift, return raw. */
1081
- if ( !all_face || n < 2 ) return obj;
1082
-
1083
- /* Homogeneous Face: portable + pairwise state-compatible checks,
1084
- then ca_face_lift to re-wrap as the same Face class. */
1085
- if ( !ca_face_state_portable(ref_face->obj_type, face_class) ) {
1086
- rb_raise(rb_eArgError,
1087
- "CAStack.new: %s state is not portable across multiple "
1088
- "parents (= per-parent storage like CAConstString's buffer); "
1089
- "strip Face with .parent if a storage-level CAStack is intended",
1090
- rb_class2name(face_class));
1091
- }
1092
- for ( i = 1; i < n; i++ ) {
1093
- VALUE p = rb_ary_entry(list, i);
1094
- CArray *ca;
1095
- TypedData_Get_Struct(p, CArray, &carray_data_type, ca);
1096
- if ( !ca_face_state_compatible(rb_ary_entry(list, 0), ref_face,
1097
- p, ca) ) {
1098
- rb_raise(rb_eArgError,
1099
- "CAStack.new: Face state mismatch across parents "
1100
- "(= %s instance at index %ld differs in state from index 0)",
1101
- rb_class2name(face_class), i);
1102
- }
1103
- }
1350
+ if ( NIL_P(face_class) ) return obj;
1104
1351
  return ca_face_lift(obj, rb_ary_entry(list, 0));
1105
1352
  }
1106
1353
 
@@ -1142,6 +1389,9 @@ Init_ca_obj_stack (void)
1142
1389
  rb_cCAStack = rb_define_class("CAStack", rb_cCAView);
1143
1390
  rb_cCAStackMask = rb_define_class("CAStackMask", rb_cCAStack);
1144
1391
 
1392
+ ca_stack_func.fill_addrs = ca_stack_put_fill_addrs;
1393
+ ca_stack_func.fill_stride = ca_stack_put_fill_stride;
1394
+
1145
1395
  CA_OBJ_STACK = ca_install_obj_type(rb_cCAStack,
1146
1396
  &castack_data_type,
1147
1397
  rb_cCAStackMask,