carray 3.0.2 → 3.0.3

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 (218) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +304 -0
  3. data/README.md +3 -4
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +225 -0
  6. data/ext/ca_axis_group.c +177 -99
  7. data/ext/ca_binop_dispatch.c +54 -32
  8. data/ext/ca_binop_dispatch.h +1 -0
  9. data/ext/ca_categorical_iterator.c +66 -71
  10. data/ext/ca_compare.c +8 -2
  11. data/ext/ca_for_buffer.h +21 -68
  12. data/ext/ca_group_iter.c +27 -11
  13. data/ext/ca_kernel_iterator.c +663 -214
  14. data/ext/ca_kernel_iterator.h +231 -167
  15. data/ext/ca_monop_dispatch.c +4 -4
  16. data/ext/ca_monop_dispatch.h +4 -0
  17. data/ext/ca_obj_array.c +132 -22
  18. data/ext/ca_obj_bincmp.c +152 -121
  19. data/ext/ca_obj_binop.c +107 -210
  20. data/ext/ca_obj_bitarray.c +37 -24
  21. data/ext/ca_obj_bitfield.c +95 -62
  22. data/ext/ca_obj_block.c +1 -0
  23. data/ext/ca_obj_byte_swap.c +40 -40
  24. data/ext/ca_obj_const_string.c +96 -25
  25. data/ext/ca_obj_face.c +406 -64
  26. data/ext/ca_obj_face.h +92 -47
  27. data/ext/ca_obj_fake.c +21 -50
  28. data/ext/ca_obj_farray.c +1 -0
  29. data/ext/ca_obj_field.c +16 -3
  30. data/ext/ca_obj_fixlen_string.c +21 -21
  31. data/ext/ca_obj_grid.c +181 -124
  32. data/ext/ca_obj_meld.c +189 -107
  33. data/ext/ca_obj_moncmp.c +61 -61
  34. data/ext/ca_obj_monop.c +125 -107
  35. data/ext/ca_obj_object.c +543 -233
  36. data/ext/ca_obj_record.c +51 -16
  37. data/ext/ca_obj_reduce.c +5 -8
  38. data/ext/ca_obj_refer.c +117 -33
  39. data/ext/ca_obj_remap.c +57 -17
  40. data/ext/ca_obj_repeat.c +21 -5
  41. data/ext/ca_obj_roll.c +115 -11
  42. data/ext/ca_obj_select.c +47 -49
  43. data/ext/ca_obj_select_axis.c +58 -60
  44. data/ext/ca_obj_shift.c +22 -102
  45. data/ext/ca_obj_stack.c +223 -118
  46. data/ext/ca_obj_stride.c +195 -34
  47. data/ext/ca_obj_string.c +17 -18
  48. data/ext/ca_obj_tile.c +32 -16
  49. data/ext/ca_obj_time.c +23 -25
  50. data/ext/ca_obj_timedelta.c +21 -23
  51. data/ext/ca_obj_transpose.c +12 -3
  52. data/ext/ca_obj_triop.c +110 -117
  53. data/ext/ca_obj_window.c +149 -27
  54. data/ext/ca_op_ipower.c +24 -11
  55. data/ext/ca_op_powi.h +57 -30
  56. data/ext/ca_pow5_table.h +656 -0
  57. data/ext/ca_sort_kernels.h +70 -0
  58. data/ext/ca_sweep_engine.c +102 -79
  59. data/ext/ca_sweep_engine.h +7 -20
  60. data/ext/ca_triop_dispatch.c +4 -1
  61. data/ext/ca_triop_dispatch.h +5 -2
  62. data/ext/caframe_csv_reader.c +463 -0
  63. data/ext/carray.h +70 -27
  64. data/ext/carray_access.c +455 -211
  65. data/ext/carray_address_basis.c +177 -36
  66. data/ext/carray_attribute.c +45 -3
  67. data/ext/carray_bincount.c +32 -2
  68. data/ext/carray_broadcast.c +46 -14
  69. data/ext/carray_call_cfunc.c +0 -28
  70. data/ext/carray_cast.c +1444 -209
  71. data/ext/carray_cast_func.rb +34 -21
  72. data/ext/carray_conversion.c +197 -73
  73. data/ext/carray_copy.c +32 -11
  74. data/ext/carray_core.c +514 -122
  75. data/ext/carray_count.c +140 -87
  76. data/ext/carray_element.c +18 -27
  77. data/ext/carray_factorize.c +569 -277
  78. data/ext/carray_generate.c +76 -40
  79. data/ext/carray_histogram.c +152 -94
  80. data/ext/carray_hold.c +2 -1
  81. data/ext/carray_index_classifier.c +63 -96
  82. data/ext/carray_internal.h +385 -3
  83. data/ext/carray_lazy.c +834 -79
  84. data/ext/carray_loop.c +81 -65
  85. data/ext/carray_mask.c +682 -316
  86. data/ext/carray_math_kernel.h +1 -0
  87. data/ext/carray_median_percentile.c +221 -76
  88. data/ext/carray_memory_view.c +254 -248
  89. data/ext/carray_operator.c +1089 -359
  90. data/ext/carray_order.c +133 -69
  91. data/ext/carray_partition.c +102 -56
  92. data/ext/carray_random.c +273 -239
  93. data/ext/carray_scatter.c +308 -65
  94. data/ext/carray_segment.c +257 -0
  95. data/ext/carray_slab.c +170 -70
  96. data/ext/carray_sort.c +215 -218
  97. data/ext/carray_sort_kernel.c +8 -1
  98. data/ext/carray_struct.c +99 -21
  99. data/ext/carray_test.c +167 -64
  100. data/ext/carray_time_text.c +1182 -0
  101. data/ext/carray_utils.c +363 -98
  102. data/ext/extconf.rb +27 -2
  103. data/ext/mk_call_cfunc.rb +0 -2
  104. data/ext/mk_pow5_table.rb +35 -0
  105. data/ext/mkkernel.rb +1076 -846
  106. data/ext/ruby_carray.c +7 -1
  107. data/ext/version.h +4 -4
  108. data/ext/version.rb +4 -2
  109. data/lib/carray/attribute.rb +39 -0
  110. data/lib/carray/autoload_carray.rb +19 -0
  111. data/lib/carray/autoload_method_extension.rb +6 -0
  112. data/lib/carray/axis_group.rb +174 -26
  113. data/lib/carray/basics.rb +91 -25
  114. data/lib/carray/bincount_nd.rb +6 -0
  115. data/lib/carray/block_iterator.rb +118 -48
  116. data/lib/carray/boolean_reduce.rb +21 -29
  117. data/lib/carray/categorical.rb +94 -26
  118. data/lib/carray/categorical_iterator.rb +333 -826
  119. data/lib/carray/complex.rb +36 -29
  120. data/lib/carray/conditional.rb +94 -7
  121. data/lib/carray/const_string.rb +46 -0
  122. data/lib/carray/frame/concat.rb +157 -6
  123. data/lib/carray/frame/convert.rb +13 -7
  124. data/lib/carray/frame/csv_parser.rb +589 -82
  125. data/lib/carray/frame/describe.rb +79 -0
  126. data/lib/carray/frame/frame.rb +124 -25
  127. data/lib/carray/frame/group.rb +189 -63
  128. data/lib/carray/frame/io.rb +412 -72
  129. data/lib/carray/frame/join.rb +134 -46
  130. data/lib/carray/frame/records.rb +37 -16
  131. data/lib/carray/frame/reshape.rb +448 -0
  132. data/lib/carray/frame/sort.rb +26 -2
  133. data/lib/carray/frame/verbs.rb +715 -48
  134. data/lib/carray/frame.rb +2 -0
  135. data/lib/carray/fuse_source.rb +111 -2
  136. data/lib/carray/fusion.rb +229 -38
  137. data/lib/carray/inspect.rb +29 -11
  138. data/lib/carray/iterator.rb +14 -2
  139. data/lib/carray/lazy.rb +54 -30
  140. data/lib/carray/mask_gap_fill.rb +66 -52
  141. data/lib/carray/math.rb +32 -19
  142. data/lib/carray/meld_reduce.rb +40 -7
  143. data/lib/carray/methods/align_addr.rb +8 -4
  144. data/lib/carray/methods/bincount.rb +2 -2
  145. data/lib/carray/methods/bit_string.rb +14 -3
  146. data/lib/carray/methods/composition.rb +16 -10
  147. data/lib/carray/methods/factorize.rb +2 -2
  148. data/lib/carray/methods/gather_nd.rb +13 -2
  149. data/lib/carray/methods/insert_block.rb +9 -14
  150. data/lib/carray/methods/is_in.rb +117 -4
  151. data/lib/carray/methods/join.rb +1 -1
  152. data/lib/carray/methods/locate_addr.rb +177 -36
  153. data/lib/carray/methods/mode.rb +18 -1
  154. data/lib/carray/methods/repeat.rb +1 -20
  155. data/lib/carray/methods/resize.rb +21 -23
  156. data/lib/carray/methods/snap.rb +34 -12
  157. data/lib/carray/methods/unique.rb +11 -2
  158. data/lib/carray/record_order.rb +208 -0
  159. data/lib/carray/runtime.rb +14 -11
  160. data/lib/carray/segment_iterator.rb +990 -0
  161. data/lib/carray/serialize.rb +197 -27
  162. data/lib/carray/slab_iterator.rb +51 -27
  163. data/lib/carray/stack.rb +10 -4
  164. data/lib/carray/string_operation_extension.rb +4 -3
  165. data/lib/carray/struct.rb +142 -51
  166. data/lib/carray/struct_builder.rb +110 -14
  167. data/lib/carray/table.rb +2 -2
  168. data/lib/carray/time.rb +517 -103
  169. data/lib/carray/window_iterator.rb +127 -72
  170. data/lib/carray.rb +2 -2
  171. data/yard-stubs/ca_obj_array.rb +4 -4
  172. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  173. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  174. data/yard-stubs/ca_obj_fake.rb +1 -1
  175. data/yard-stubs/ca_obj_field.rb +1 -1
  176. data/yard-stubs/ca_obj_refer.rb +3 -3
  177. data/yard-stubs/ca_obj_roll.rb +0 -1
  178. data/yard-stubs/ca_obj_stride.rb +10 -7
  179. data/yard-stubs/ca_obj_transpose.rb +1 -1
  180. data/yard-stubs/ca_obj_window.rb +1 -2
  181. data/yard-stubs/carray_access.rb +5 -4
  182. data/yard-stubs/carray_broadcast.rb +1 -1
  183. data/yard-stubs/carray_cast.rb +78 -43
  184. data/yard-stubs/carray_conversion.rb +13 -1
  185. data/yard-stubs/carray_copy.rb +8 -3
  186. data/yard-stubs/carray_core.rb +3 -3
  187. data/yard-stubs/carray_count.rb +9 -0
  188. data/yard-stubs/carray_lazy.rb +5 -1
  189. data/yard-stubs/carray_loop.rb +4 -1
  190. data/yard-stubs/carray_mask.rb +34 -10
  191. data/yard-stubs/carray_math.rb +25 -13
  192. data/yard-stubs/carray_median_percentile.rb +10 -6
  193. data/yard-stubs/carray_order.rb +34 -13
  194. data/yard-stubs/carray_scatter.rb +10 -3
  195. data/yard-stubs/carray_segment.rb +72 -0
  196. data/yard-stubs/carray_slab.rb +2 -1
  197. data/yard-stubs/carray_sort.rb +43 -3
  198. data/yard-stubs/carray_test.rb +10 -0
  199. data/yard-stubs/carray_utils.rb +0 -26
  200. metadata +11 -19
  201. data/ext/ca_for_each_element.h +0 -239
  202. data/ext/carray_build_flags.h +0 -3
  203. data/ext/carray_config.h +0 -28
  204. data/ext/carray_kernels_bincmp.c +0 -4446
  205. data/ext/carray_kernels_binop.c +0 -11001
  206. data/ext/carray_kernels_init.c +0 -1131
  207. data/ext/carray_kernels_map.c +0 -3467
  208. data/ext/carray_kernels_moncmp.c +0 -2097
  209. data/ext/carray_kernels_monop.c +0 -18313
  210. data/ext/carray_kernels_reduce_aggregate.c +0 -26005
  211. data/ext/carray_kernels_reduce_boolean.c +0 -330
  212. data/ext/carray_kernels_reduce_cumulative.c +0 -14862
  213. data/ext/carray_kernels_reduce_extreme.c +0 -17494
  214. data/ext/carray_kernels_reduce_variance.c +0 -3910
  215. data/ext/carray_kernels_scan.c +0 -3693
  216. data/ext/carray_kernels_search.c +0 -32218
  217. data/ext/carray_kernels_sort.c +0 -10626
  218. data/ext/carray_kernels_triop.c +0 -1392
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)
@@ -171,6 +160,8 @@ ca_stack_setup_with_axis (CAStack *ca, int32_t n_parents, CArray **parents,
171
160
  ca->attach = 0;
172
161
  ca->nosync = 0;
173
162
 
163
+ ca->mask_standins = NULL;
164
+ ca->mask_owner = NULL;
174
165
  ca->n_parents = n_parents;
175
166
  ca->parents = ALLOC_N(CArray *, n_parents);
176
167
  for ( i = 0; i < n_parents; i++ ) {
@@ -186,7 +177,7 @@ ca_stack_setup_with_axis (CAStack *ca, int32_t n_parents, CArray **parents,
186
177
  ca->dim[k_axis] = n_parents;
187
178
  for ( a = k_axis + 1; a < ca->ndim; a++ ) ca->dim[a] = ref->dim[a - 1];
188
179
 
189
- /* mask: lazy; horizontal propagation triggers on first mask access. */
180
+ /* mask: lazy, built on first mask access. */
190
181
  return 0;
191
182
  }
192
183
 
@@ -216,6 +207,13 @@ free_ca_stack (void *ap)
216
207
  CAStack *ca = (CAStack *) ap;
217
208
  if ( ca != NULL ) {
218
209
  ca_free(ca->mask);
210
+ if ( ca->mask_standins ) {
211
+ int32_t k;
212
+ for ( k = 0; k < ca->n_parents; k++ ) {
213
+ if ( ca->mask_standins[k] ) ca_free(ca->mask_standins[k]);
214
+ }
215
+ xfree(ca->mask_standins);
216
+ }
219
217
  xfree(ca->parents);
220
218
  xfree(ca->dim);
221
219
  xfree(ca);
@@ -235,17 +233,40 @@ ca_stack_func_clone (void *ap)
235
233
 
236
234
  /* view[..., k at k_axis, ...] = parents[k][parent_idx]
237
235
  where parent_idx = idx[] with the k_axis slot removed. */
236
+ /* On the mask of a CAStack: let its stand-ins give way to the parents' own
237
+ masks (see ca_multi_parent_mask_refresh). */
238
+ static void
239
+ ca_stack_mask_refresh (CAStack *ca, int for_write)
240
+ {
241
+ CAStack *o = (CAStack *) ca->mask_owner;
242
+ if ( o != NULL ) {
243
+ ca_multi_parent_mask_refresh((CArray *) ca, o->parents, o->mask_standins,
244
+ o->n_parents, for_write);
245
+ }
246
+ }
247
+
248
+ static void
249
+ ca_stack_mask_settle (CAStack *ca)
250
+ {
251
+ CAStack *o = (CAStack *) ca->mask_owner;
252
+ if ( o != NULL ) {
253
+ ca_multi_parent_mask_settle((CArray *) ca, o->parents, o->mask_standins,
254
+ o->n_parents);
255
+ }
256
+ }
257
+
238
258
  static void
239
259
  ca_stack_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
240
260
  {
241
261
  CAStack *ca = (CAStack *) ap;
262
+ ca_stack_mask_refresh(ca, dir == CA_XFER_PUT);
242
263
  int8_t k_axis = ca->k_axis;
243
264
  ca_size_t k = idx[k_axis];
244
265
  if ( k < 0 || k >= ca->n_parents ) {
245
266
  rb_raise(rb_eIndexError, "CAStack k-axis (axis %d) index %lld out of range [0, %d)",
246
267
  (int) k_axis, (long long) k, (int) ca->n_parents);
247
268
  }
248
- if ( k_axis == 0 ) {
269
+ if ( k_axis == 0 && ca_fast_paths_enabled() ) {
249
270
  /* Fast path: parent_idx = idx[1..ndim-1]; pass idx+1 directly. */
250
271
  ca_xfer_index(ca->parents[k], idx + 1, data, dir);
251
272
  return;
@@ -274,6 +295,7 @@ ca_stack_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
274
295
  void *data, int dir)
275
296
  {
276
297
  CAStack *ca = (CAStack *) ap;
298
+ ca_stack_mask_refresh(ca, dir == CA_XFER_PUT);
277
299
  int8_t k_axis = ca->k_axis;
278
300
  ca_size_t parent_elements = ca->parents[0]->elements;
279
301
  ca_size_t bytes = ca->bytes;
@@ -286,7 +308,7 @@ ca_stack_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
286
308
  paddrs = ALLOCV_N(ca_size_t, holder1, n);
287
309
  pdata = ALLOCV_N(char, holder2, n * bytes);
288
310
 
289
- if ( k_axis == 0 ) {
311
+ if ( k_axis == 0 && ca_fast_paths_enabled() ) {
290
312
  /* Fast path: addr = k * parent_elements + parent_addr (row-major over
291
313
  view shape with K at axis 0). */
292
314
  for ( k = 0; k < ca->n_parents; k++ ) {
@@ -398,7 +420,9 @@ static void
398
420
  ca_stack_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
399
421
  ca_size_t *strides, void *data, int dir)
400
422
  {
423
+ if ( ca_region_is_empty(((CArray *) ap)->ndim, counts) ) return;
401
424
  CAStack *ca = (CAStack *) ap;
425
+ ca_stack_mask_refresh(ca, dir == CA_XFER_PUT);
402
426
  int8_t k_axis = ca->k_axis;
403
427
  int8_t pndim = ca->ndim - 1;
404
428
  ca_size_t native[CA_RANK_MAX], dstride[CA_RANK_MAX];
@@ -419,19 +443,24 @@ ca_stack_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
419
443
  s *= ca->parents[0]->dim[i];
420
444
  }
421
445
 
422
- /* Structural iff strides[k] == native[k] for all k. */
446
+ /* Structural iff strides[k] == native[k] for all k and the request is a
447
+ box over the view's own axes. Equal strides alone are not enough: a
448
+ size-1 axis gives two axes the same native stride, and a flat window
449
+ reshaped back to ndim has native strides yet wraps across an axis. The
450
+ request is over the view's addresses; see
451
+ ca_xfer_stride_request_is_axis_box (carray.h). */
423
452
  for ( i = 0; i < ca->ndim; i++ ) {
424
453
  if ( strides[i] != native[i] ) { structural = 0; break; }
425
454
  }
455
+ if ( structural &&
456
+ ! ca_xfer_stride_request_is_axis_box(ca, starts, counts, strides) ) {
457
+ structural = 0;
458
+ }
459
+ if ( ! ca_fast_paths_enabled() ) structural = 0;
426
460
 
427
461
  if ( structural ) {
428
462
  ca_size_t k_lo = starts[k_axis];
429
463
  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
464
 
436
465
  /* dst row-major byte strides over output counts[]. */
437
466
  s = ca->bytes;
@@ -518,7 +547,7 @@ ca_stack_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
518
547
  with native[k_axis] removed, in order. */
519
548
  {
520
549
  int8_t p = -1;
521
- int reloc = 1;
550
+ int reloc = ca_fast_paths_enabled();
522
551
  for ( i = 0; i < ca->ndim; i++ ) {
523
552
  if ( strides[i] == native[k_axis] ) { p = i; break; }
524
553
  }
@@ -538,16 +567,27 @@ ca_stack_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
538
567
  if ( strides[i] != native[v] ) { reloc = 0; break; }
539
568
  }
540
569
  }
570
+ /* Each output axis must stay inside the view axis it was matched to;
571
+ otherwise the request wraps (or the match was one of two axes sharing
572
+ a native stride because the other has size 1), and only the per-cell
573
+ walk reads it correctly. */
574
+ if ( reloc ) {
575
+ for ( i = 0; i < ca->ndim; i++ ) {
576
+ int8_t j, v;
577
+ if ( counts[i] <= 1 ) continue;
578
+ if ( i == p ) {
579
+ v = k_axis;
580
+ }
581
+ else {
582
+ j = (i < p) ? i : (i - 1);
583
+ v = (j < k_axis) ? j : (j + 1);
584
+ }
585
+ if ( starts[v] + counts[i] > ca->dim[v] ) { reloc = 0; break; }
586
+ }
587
+ }
541
588
  if ( reloc ) {
542
- ca_size_t kk_lo = starts[k_axis];
543
- ca_size_t kk_hi = starts[k_axis] + counts[p];
544
589
  ca_size_t odo[CA_RANK_MAX];
545
590
  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
591
  if ( p == ca->ndim - 1 ) {
552
592
  /* K innermost: per-cell via the parent's xfer_index directly, escaping
553
593
  the CAStack-level addr2index dispatch hop in the per-cell fallback. */
@@ -624,20 +664,34 @@ ca_stack_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
624
664
  - k_axis != 0: per-parent contiguous read into a temp buffer, then
625
665
  scattered-write to dst at view row-major positions. This is the
626
666
  correctness path (per-parent contig-read / scattered-write), not a
627
- perf-optimised one. */
667
+ perf-optimised one.
668
+
669
+ into_buffers (PUT only) writes each parent's cells into the buffer its
670
+ attach handed out, rather than through the parent to what is behind it:
671
+ the sync of an attached stack, which then syncs each parent. Writing
672
+ past the buffer would leave it stale, and the parent's own sync would
673
+ push the stale buffer back over the cells just written. */
628
674
  static void
629
- ca_stack_func_xfer_all (void *ap, void *data, int dir)
675
+ ca_stack_xfer_parents (CAStack *ca, char *d, int dir, int into_buffers)
630
676
  {
631
- CAStack *ca = (CAStack *) ap;
632
677
  int8_t k_axis = ca->k_axis;
633
678
  ca_size_t bytes = ca->bytes;
634
- char *d = (char *) data;
635
679
  int32_t k;
636
680
 
637
- if ( k_axis == 0 ) {
681
+ /* The walk below visits a parent's first cell before it tests the
682
+ counts. */
683
+ if ( ca->elements == 0 ) return;
684
+
685
+ if ( k_axis == 0 && ca_fast_paths_enabled() ) {
638
686
  ca_size_t parent_bytes_total = ca->parents[0]->elements * bytes;
639
687
  for ( k = 0; k < ca->n_parents; k++ ) {
640
- ca_xfer_all(ca->parents[k], d + k * parent_bytes_total, dir);
688
+ if ( into_buffers ) {
689
+ memcpy(ca->parents[k]->ptr, d + k * parent_bytes_total,
690
+ (size_t) parent_bytes_total);
691
+ }
692
+ else {
693
+ ca_xfer_all(ca->parents[k], d + k * parent_bytes_total, dir);
694
+ }
641
695
  }
642
696
  return;
643
697
  }
@@ -725,7 +779,12 @@ ca_stack_func_xfer_all (void *ap, void *data, int dir)
725
779
  }
726
780
 
727
781
  if ( dir == CA_XFER_PUT ) {
728
- ca_xfer_all(ca->parents[k], buf, CA_XFER_PUT);
782
+ if ( into_buffers ) {
783
+ memcpy(ca->parents[k]->ptr, buf, (size_t) (parent_elements * bytes));
784
+ }
785
+ else {
786
+ ca_xfer_all(ca->parents[k], buf, CA_XFER_PUT);
787
+ }
729
788
  }
730
789
  }
731
790
  }
@@ -733,6 +792,13 @@ ca_stack_func_xfer_all (void *ap, void *data, int dir)
733
792
  }
734
793
  }
735
794
 
795
+ static void
796
+ ca_stack_func_xfer_all (void *ap, void *data, int dir)
797
+ {
798
+ ca_stack_mask_refresh((CAStack *) ap, dir == CA_XFER_PUT);
799
+ ca_stack_xfer_parents((CAStack *) ap, (char *) data, dir, 0);
800
+ }
801
+
736
802
  /* ------------------------------------------------------------------- */
737
803
  /* attach / sync / detach (materialise via xfer_all) */
738
804
  /* ------------------------------------------------------------------- */
@@ -741,28 +807,42 @@ static void
741
807
  ca_stack_func_allocate (void *ap)
742
808
  {
743
809
  CAStack *ca = (CAStack *) ap;
810
+ ca_stack_mask_refresh(ca, 0);
744
811
  ca_attach_all(ca->parents, ca->n_parents);
745
812
  ca->ptr = xmalloc(ca_length(ca));
746
813
  }
747
814
 
815
+ static VALUE
816
+ ca_stack_attach_owned (VALUE arg)
817
+ {
818
+ ca_axis_view_attach_owned((void *) arg);
819
+ return Qnil;
820
+ }
821
+
748
822
  static void
749
823
  ca_stack_func_attach (void *ap)
750
824
  {
751
825
  CAStack *ca = (CAStack *) ap;
826
+ ca_stack_mask_refresh(ca, 0);
827
+ int32_t k;
828
+ int tag = 0;
752
829
  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);
830
+ rb_protect(ca_stack_attach_owned, (VALUE) ca, &tag);
831
+ if ( tag ) {
832
+ for ( k = 0; k < ca->n_parents; k++ ) {
833
+ ca_detach(ca->parents[k]);
834
+ }
835
+ rb_jump_tag(tag);
836
+ }
755
837
  }
756
838
 
757
839
  static void
758
840
  ca_stack_func_sync (void *ap)
759
841
  {
760
842
  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
- }
843
+ ca_stack_xfer_parents(ca, ca->ptr, CA_XFER_PUT, 1);
844
+ ca_sync_all(ca->parents, ca->n_parents);
845
+ ca_stack_mask_settle(ca);
766
846
  }
767
847
 
768
848
  static void
@@ -785,6 +865,7 @@ static void
785
865
  ca_stack_func_fill_data (void *ap, void *ptr)
786
866
  {
787
867
  CAStack *ca = (CAStack *) ap;
868
+ ca_stack_mask_refresh(ca, 1);
788
869
  int32_t k;
789
870
  for ( k = 0; k < ca->n_parents; k++ ) {
790
871
  ca_fill(ca->parents[k], ptr);
@@ -792,12 +873,13 @@ ca_stack_func_fill_data (void *ap, void *ptr)
792
873
  }
793
874
 
794
875
  /* ------------------------------------------------------------------- */
795
- /* create_mask: horizontal propagation */
876
+ /* create_mask */
796
877
  /* ------------------------------------------------------------------- */
797
878
 
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). */
879
+ /* The mask is a CAStack over the K parents' masks, a stand-in standing
880
+ for a parent that has none (ca_multi_parent_mask), so building it gives
881
+ no parent a mask. Self-similar (= CAStack re-used as its own mask
882
+ class). */
801
883
  static void
802
884
  ca_stack_func_create_mask (void *ap)
803
885
  {
@@ -807,12 +889,13 @@ ca_stack_func_create_mask (void *ap)
807
889
  int32_t k;
808
890
 
809
891
  mask_parents = ALLOCV_N(CArray *, holder, ca->n_parents);
892
+ if ( ! ca->mask_standins ) {
893
+ ca->mask_standins = ALLOC_N(CArray *, ca->n_parents);
894
+ MEMZERO(ca->mask_standins, CArray *, ca->n_parents);
895
+ }
810
896
  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;
897
+ mask_parents[k] = ca_multi_parent_mask(ca->parents[k],
898
+ &ca->mask_standins[k]);
816
899
  }
817
900
  /* Build the mask stack with the parent's k_axis from the start so its
818
901
  dim[] is laid out for that axis. (Patching ->k_axis after a default
@@ -821,6 +904,7 @@ ca_stack_func_create_mask (void *ap)
821
904
  xfer_stride k-range check when k_axis != 0.) */
822
905
  ca->mask = (CArray *) ca_stack_new_with_axis(ca->n_parents, mask_parents,
823
906
  ca->k_axis);
907
+ ((CAStack *) ca->mask)->mask_owner = (CArray *) ca;
824
908
  ALLOCV_END(holder);
825
909
  }
826
910
 
@@ -846,6 +930,13 @@ ca_stack_func_fold_stride (void *ap, ca_fold_t *f, void **next_parent)
846
930
  ca_size_t k;
847
931
  int8_t i;
848
932
 
933
+ /* The mask of a stack while it still reads stand-ins: a write must
934
+ reach the stack's transfer, which replaces them first. */
935
+ if ( ca->mask_owner != NULL
936
+ && ((CAStack *) ca->mask_owner)->mask_standins != NULL ) {
937
+ return 0;
938
+ }
939
+
849
940
  /* k_axis != 0: parent byte blocks are interleaved (not contig in view
850
941
  buffer), so the byte-box containment check and the size-1 K-axis
851
942
  degeneration below would require detecting the K-axis contribution
@@ -854,6 +945,7 @@ ca_stack_func_fold_stride (void *ap, ca_fold_t *f, void **next_parent)
854
945
  through to CAStack.xfer_stride (k_axis-aware), which still delivers
855
946
  correct (if non-folded) per-parent dispatch. */
856
947
  if ( ca->k_axis != 0 ) return 0;
948
+ if ( ! ca_fast_paths_enabled() ) return 0;
857
949
 
858
950
  for ( i = 0; i < f->ndim; i++ ) {
859
951
  ca_size_t span = (f->counts[i] - 1) * f->strides[i];
@@ -876,6 +968,56 @@ ca_stack_func_fold_stride (void *ap, ca_fold_t *f, void **next_parent)
876
968
  /* operation table */
877
969
  /* ------------------------------------------------------------------- */
878
970
 
971
+ /* The table's write slots: after a write, refuse a true that reached the
972
+ stand-in of a value array or a mask array (ca_multi_parent_mask_check).
973
+ Only the mask of a CAStack has stand-ins to check. */
974
+ static void
975
+ ca_stack_mask_check (CAStack *ca)
976
+ {
977
+ CAStack *o = (CAStack *) ca->mask_owner;
978
+ if ( o != NULL ) {
979
+ ca_multi_parent_mask_check((CArray *) ca, o->parents, o->mask_standins,
980
+ o->n_parents);
981
+ }
982
+ }
983
+
984
+ static void
985
+ ca_stack_put_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
986
+ {
987
+ ca_stack_func_xfer_index(ap, idx, data, dir);
988
+ if ( dir == CA_XFER_PUT ) ca_stack_mask_check((CAStack *) ap);
989
+ }
990
+
991
+ static void
992
+ ca_stack_put_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs, void *data,
993
+ int dir)
994
+ {
995
+ ca_stack_func_xfer_addrs(ap, n, addrs, data, dir);
996
+ if ( dir == CA_XFER_PUT ) ca_stack_mask_check((CAStack *) ap);
997
+ }
998
+
999
+ static void
1000
+ ca_stack_put_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
1001
+ ca_size_t *strides, void *data, int dir)
1002
+ {
1003
+ ca_stack_func_xfer_stride(ap, starts, counts, strides, data, dir);
1004
+ if ( dir == CA_XFER_PUT ) ca_stack_mask_check((CAStack *) ap);
1005
+ }
1006
+
1007
+ static void
1008
+ ca_stack_put_xfer_all (void *ap, void *data, int dir)
1009
+ {
1010
+ ca_stack_func_xfer_all(ap, data, dir);
1011
+ if ( dir == CA_XFER_PUT ) ca_stack_mask_check((CAStack *) ap);
1012
+ }
1013
+
1014
+ static void
1015
+ ca_stack_put_fill_data (void *ap, void *ptr)
1016
+ {
1017
+ ca_stack_func_fill_data(ap, ptr);
1018
+ ca_stack_mask_check((CAStack *) ap);
1019
+ }
1020
+
879
1021
  ca_operation_function_t ca_stack_func = {
880
1022
  -1, /* CA_OBJ_STACK */
881
1023
  CA_VIEW_ARRAY,
@@ -885,13 +1027,13 @@ ca_operation_function_t ca_stack_func = {
885
1027
  ca_stack_func_attach,
886
1028
  ca_stack_func_sync,
887
1029
  ca_stack_func_detach,
888
- ca_stack_func_fill_data,
1030
+ ca_stack_put_fill_data,
889
1031
  ca_stack_func_create_mask,
890
- ca_stack_func_xfer_index,
891
- ca_stack_func_xfer_addrs,
1032
+ ca_stack_put_xfer_index,
1033
+ ca_stack_put_xfer_addrs,
892
1034
  ca_stack_func_fold_stride,
893
- ca_stack_func_xfer_stride,
894
- ca_stack_func_xfer_all,
1035
+ ca_stack_put_xfer_stride,
1036
+ ca_stack_put_xfer_all,
895
1037
  };
896
1038
 
897
1039
  /* ------------------------------------------------------------------- */
@@ -935,7 +1077,7 @@ rb_ca_stack_new_with_axis (VALUE parents_ary, int8_t k_axis)
935
1077
  ca = ca_stack_new_with_axis((int32_t) n, parents, k_axis);
936
1078
  obj = ca_wrap_struct(ca);
937
1079
  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) */
1080
+ rb_ca_set_parent(obj, ca_face_strip_one(rb_ary_entry(parents_ary, 0))); /* CAView base (Face-stripped, matches C ->parent) */
939
1081
  ALLOCV_END(holder);
940
1082
  return obj;
941
1083
  }
@@ -963,8 +1105,14 @@ rb_ca_stack_initialize_copy (VALUE self, VALUE other)
963
1105
  {
964
1106
  CAStack *ca, *cs;
965
1107
  TypedData_Get_Struct(self, CAStack, &castack_data_type, ca);
1108
+ ca_check_uninitialized(ca);
966
1109
  TypedData_Get_Struct(other, CAStack, &castack_data_type, cs);
967
1110
  ca_stack_setup_with_axis(ca, cs->n_parents, cs->parents, cs->k_axis);
1111
+ /* A copy of a stack's mask reads and writes the same stand-ins, so it
1112
+ answers to the same owner: its writes reach the parents' masks. The
1113
+ copy holds the owner alive through the masked_array ivar that dup
1114
+ carries over (see rb_ca_mask_array). */
1115
+ ca->mask_owner = cs->mask_owner;
968
1116
  return self;
969
1117
  }
970
1118
 
@@ -1005,6 +1153,7 @@ rb_ca_stack_initialize (int argc, VALUE *argv, VALUE self)
1005
1153
  NUM2LONG(axis_val), (int) ref->ndim + 1, "CAStack.new");
1006
1154
  }
1007
1155
  TypedData_Get_Struct(self, CAStack, &castack_data_type, ca);
1156
+ ca_check_uninitialized(ca);
1008
1157
  parents = ALLOCV_N(CArray *, holder, n);
1009
1158
  for ( i = 0; i < n; i++ ) {
1010
1159
  VALUE p = rb_ary_entry(list, i);
@@ -1013,7 +1162,7 @@ rb_ca_stack_initialize (int argc, VALUE *argv, VALUE self)
1013
1162
  }
1014
1163
  ca_stack_setup_with_axis(ca, (int32_t) n, parents, k_axis);
1015
1164
  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) */
1165
+ rb_ca_set_parent(self, ca_face_strip_one(rb_ary_entry(list, 0))); /* CAView base (Face-stripped, matches C ->parent) */
1017
1166
  ALLOCV_END(holder);
1018
1167
  return self;
1019
1168
  }
@@ -1029,8 +1178,9 @@ rb_ca_stack_initialize (int argc, VALUE *argv, VALUE self)
1029
1178
  this method)
1030
1179
  - then ca_face_lift re-wraps the result as the same Face class,
1031
1180
  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).
1181
+ A single Face parent is lifted too. A list mixing Face and non-Face
1182
+ parents, or two Face classes, is refused; a non-Face list returns the
1183
+ raw CAStack (= same result as the inherited Class#new chain).
1034
1184
 
1035
1185
  Replaces the Ruby-level CAStack.new override in lib/carray/compose.rb
1036
1186
  (= used alias_method :__new_raw__, :new + class << self def new).
@@ -1039,68 +1189,23 @@ rb_ca_stack_initialize (int argc, VALUE *argv, VALUE self)
1039
1189
  static VALUE
1040
1190
  rb_ca_stack_s_new (int argc, VALUE *argv, VALUE klass)
1041
1191
  {
1042
- VALUE list, kwargs;
1043
- long n, i;
1044
- int all_face = 1;
1045
- VALUE face_class = Qnil;
1046
- CArray *ref_face = NULL;
1192
+ VALUE list, kwargs, face_class;
1047
1193
  VALUE obj;
1048
1194
 
1049
1195
  rb_scan_args(argc, argv, "1:", &list, &kwargs);
1050
1196
  Check_Type(list, T_ARRAY);
1051
- n = RARRAY_LEN(list);
1052
- if ( n <= 0 ) {
1197
+ if ( RARRAY_LEN(list) <= 0 ) {
1053
1198
  rb_raise(rb_eArgError, "CAStack.new requires at least one parent");
1054
1199
  }
1200
+ face_class = ca_face_list_class(list, "CAStack.new");
1055
1201
 
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). */
1202
+ /* rb_obj_alloc calls the TypedData allocator directly (= bypasses the
1203
+ overridden Class#new, avoiding infinite recursion); rb_obj_call_init_kw
1204
+ forwards the kwargs hash. */
1077
1205
  obj = rb_obj_alloc(klass);
1078
1206
  rb_obj_call_init_kw(obj, argc, argv, RB_PASS_CALLED_KEYWORDS);
1079
1207
 
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
- }
1208
+ if ( NIL_P(face_class) ) return obj;
1104
1209
  return ca_face_lift(obj, rb_ary_entry(list, 0));
1105
1210
  }
1106
1211