carray 3.0.1 → 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 (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +824 -0
  3. data/README.md +5 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +258 -4
  6. data/ext/ca_axis_group.c +368 -184
  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 +90 -41
  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 +960 -245
  14. data/ext/ca_kernel_iterator.h +352 -181
  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 +194 -42
  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 +5 -4
  23. data/ext/ca_obj_byte_swap.c +40 -40
  24. data/ext/ca_obj_const_string.c +180 -50
  25. data/ext/ca_obj_face.c +428 -62
  26. data/ext/ca_obj_face.h +107 -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 +39 -26
  31. data/ext/ca_obj_grid.c +181 -124
  32. data/ext/ca_obj_meld.c +235 -55
  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 +551 -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 +88 -75
  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 -123
  46. data/ext/ca_obj_stride.c +267 -35
  47. data/ext/ca_obj_string.c +25 -22
  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 +157 -29
  54. data/ext/ca_op_ipower.c +25 -13
  55. data/ext/ca_op_powi.h +57 -30
  56. data/ext/ca_pow5_table.h +656 -0
  57. data/ext/ca_rng_normal.h +42 -0
  58. data/ext/ca_rng_xoshiro256pp.h +105 -0
  59. data/ext/ca_sort_kernels.h +70 -0
  60. data/ext/ca_sweep_engine.c +385 -198
  61. data/ext/ca_sweep_engine.h +31 -23
  62. data/ext/ca_triop_dispatch.c +4 -1
  63. data/ext/ca_triop_dispatch.h +5 -2
  64. data/ext/caframe_csv_reader.c +463 -0
  65. data/ext/carray.h +90 -28
  66. data/ext/carray_access.c +487 -231
  67. data/ext/carray_address_basis.c +731 -0
  68. data/ext/carray_attribute.c +45 -3
  69. data/ext/carray_bincount.c +32 -2
  70. data/ext/carray_broadcast.c +49 -17
  71. data/ext/carray_call_cfunc.c +667 -511
  72. data/ext/carray_cast.c +1556 -247
  73. data/ext/carray_cast_func.rb +34 -21
  74. data/ext/carray_conversion.c +197 -73
  75. data/ext/carray_copy.c +84 -38
  76. data/ext/carray_core.c +591 -119
  77. data/ext/carray_count.c +148 -96
  78. data/ext/carray_element.c +18 -27
  79. data/ext/carray_factorize.c +604 -291
  80. data/ext/carray_generate.c +76 -40
  81. data/ext/carray_histogram.c +152 -94
  82. data/ext/carray_hold.c +2 -1
  83. data/ext/carray_index_classifier.c +63 -96
  84. data/ext/carray_internal.h +400 -1
  85. data/ext/carray_lazy.c +834 -79
  86. data/ext/carray_loop.c +88 -66
  87. data/ext/carray_mask.c +705 -324
  88. data/ext/carray_math_kernel.h +1 -0
  89. data/ext/carray_median_percentile.c +263 -63
  90. data/ext/carray_memory_view.c +254 -248
  91. data/ext/carray_operator.c +1093 -363
  92. data/ext/carray_order.c +133 -69
  93. data/ext/carray_partition.c +102 -56
  94. data/ext/carray_random.c +582 -204
  95. data/ext/carray_scatter.c +308 -65
  96. data/ext/carray_segment.c +257 -0
  97. data/ext/carray_slab.c +183 -70
  98. data/ext/carray_sort.c +235 -240
  99. data/ext/carray_sort_kernel.c +8 -1
  100. data/ext/carray_struct.c +99 -21
  101. data/ext/carray_test.c +167 -64
  102. data/ext/carray_time_text.c +1182 -0
  103. data/ext/carray_utils.c +363 -98
  104. data/ext/extconf.rb +27 -2
  105. data/ext/mk_call_cfunc.rb +103 -118
  106. data/ext/mk_pow5_table.rb +35 -0
  107. data/ext/mkkernel.rb +1349 -851
  108. data/ext/ruby_carray.c +17 -2
  109. data/ext/version.h +4 -4
  110. data/ext/version.rb +4 -2
  111. data/lib/carray/attribute.rb +39 -0
  112. data/lib/carray/autoload_carray.rb +24 -3
  113. data/lib/carray/autoload_method_extension.rb +18 -0
  114. data/lib/carray/axis_group.rb +245 -20
  115. data/lib/carray/basics.rb +95 -25
  116. data/lib/carray/bincount_nd.rb +6 -0
  117. data/lib/carray/block_iterator.rb +200 -54
  118. data/lib/carray/boolean_reduce.rb +21 -29
  119. data/lib/carray/categorical.rb +244 -59
  120. data/lib/carray/categorical_iterator.rb +391 -757
  121. data/lib/carray/complex.rb +36 -29
  122. data/lib/carray/conditional.rb +94 -7
  123. data/lib/carray/const_string.rb +177 -27
  124. data/lib/carray/construct.rb +40 -0
  125. data/lib/carray/data_type_extension.rb +3 -0
  126. data/lib/carray/data_type_limits.rb +91 -0
  127. data/lib/carray/fixlen_string.rb +1 -1
  128. data/lib/carray/frame/concat.rb +157 -6
  129. data/lib/carray/frame/convert.rb +13 -7
  130. data/lib/carray/frame/csv_parser.rb +590 -76
  131. data/lib/carray/frame/describe.rb +79 -0
  132. data/lib/carray/frame/frame.rb +205 -35
  133. data/lib/carray/frame/group.rb +199 -40
  134. data/lib/carray/frame/io.rb +452 -60
  135. data/lib/carray/frame/join.rb +134 -46
  136. data/lib/carray/frame/records.rb +54 -19
  137. data/lib/carray/frame/reshape.rb +448 -0
  138. data/lib/carray/frame/sort.rb +26 -2
  139. data/lib/carray/frame/verbs.rb +726 -56
  140. data/lib/carray/frame.rb +2 -0
  141. data/lib/carray/fuse_source.rb +111 -2
  142. data/lib/carray/fusion.rb +229 -38
  143. data/lib/carray/inspect.rb +71 -20
  144. data/lib/carray/iterator.rb +157 -2
  145. data/lib/carray/lazy.rb +54 -67
  146. data/lib/carray/mask_gap_fill.rb +66 -50
  147. data/lib/carray/math.rb +32 -19
  148. data/lib/carray/meld_reduce.rb +40 -7
  149. data/lib/carray/methods/align_addr.rb +8 -4
  150. data/lib/carray/methods/bincount.rb +2 -2
  151. data/lib/carray/methods/bit_string.rb +14 -3
  152. data/lib/carray/methods/composition.rb +16 -10
  153. data/lib/carray/methods/discovery_along.rb +74 -0
  154. data/lib/carray/methods/factorize.rb +50 -0
  155. data/lib/carray/methods/gather_nd.rb +13 -2
  156. data/lib/carray/methods/insert_block.rb +9 -14
  157. data/lib/carray/methods/is_in.rb +130 -6
  158. data/lib/carray/methods/join.rb +1 -1
  159. data/lib/carray/methods/locate_addr.rb +235 -21
  160. data/lib/carray/methods/mask_duplicates.rb +35 -1
  161. data/lib/carray/methods/mode.rb +18 -1
  162. data/lib/carray/methods/nunique.rb +22 -1
  163. data/lib/carray/methods/repeat.rb +91 -0
  164. data/lib/carray/methods/resize.rb +21 -23
  165. data/lib/carray/methods/snap.rb +34 -12
  166. data/lib/carray/methods/unique.rb +52 -3
  167. data/lib/carray/record_order.rb +208 -0
  168. data/lib/carray/rng.rb +86 -0
  169. data/lib/carray/runtime.rb +14 -11
  170. data/lib/carray/segment_iterator.rb +990 -0
  171. data/lib/carray/serialize.rb +197 -27
  172. data/lib/carray/slab_iterator.rb +109 -40
  173. data/lib/carray/stack.rb +10 -4
  174. data/lib/carray/string_operation_extension.rb +9 -4
  175. data/lib/carray/struct.rb +142 -51
  176. data/lib/carray/struct_builder.rb +110 -14
  177. data/lib/carray/table.rb +2 -2
  178. data/lib/carray/time.rb +535 -105
  179. data/lib/carray/window_iterator.rb +259 -82
  180. data/lib/carray.rb +4 -2
  181. data/yard-stubs/ca_obj_array.rb +4 -4
  182. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  183. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  184. data/yard-stubs/ca_obj_block.rb +2 -7
  185. data/yard-stubs/ca_obj_fake.rb +1 -1
  186. data/yard-stubs/ca_obj_field.rb +1 -1
  187. data/yard-stubs/ca_obj_refer.rb +3 -3
  188. data/yard-stubs/ca_obj_roll.rb +0 -1
  189. data/yard-stubs/ca_obj_stride.rb +10 -7
  190. data/yard-stubs/ca_obj_transpose.rb +1 -1
  191. data/yard-stubs/ca_obj_window.rb +11 -4
  192. data/yard-stubs/carray_access.rb +6 -5
  193. data/yard-stubs/carray_broadcast.rb +2 -2
  194. data/yard-stubs/carray_cast.rb +78 -43
  195. data/yard-stubs/carray_conversion.rb +13 -1
  196. data/yard-stubs/carray_copy.rb +8 -3
  197. data/yard-stubs/carray_core.rb +3 -83
  198. data/yard-stubs/carray_count.rb +16 -2
  199. data/yard-stubs/carray_lazy.rb +210 -1
  200. data/yard-stubs/carray_loop.rb +4 -1
  201. data/yard-stubs/carray_mask.rb +34 -10
  202. data/yard-stubs/carray_math.rb +1499 -4
  203. data/yard-stubs/carray_median_percentile.rb +26 -8
  204. data/yard-stubs/carray_order.rb +43 -82
  205. data/yard-stubs/carray_scatter.rb +10 -3
  206. data/yard-stubs/carray_segment.rb +72 -0
  207. data/yard-stubs/carray_slab.rb +11 -8
  208. data/yard-stubs/carray_sort.rb +50 -8
  209. data/yard-stubs/carray_test.rb +10 -0
  210. data/yard-stubs/carray_utils.rb +0 -26
  211. metadata +19 -19
  212. data/ext/ca_for_each_element.h +0 -239
  213. data/ext/carray_build_flags.h +0 -3
  214. data/ext/carray_config.h +0 -28
  215. data/ext/carray_kernels_bincmp.c +0 -4446
  216. data/ext/carray_kernels_binop.c +0 -11001
  217. data/ext/carray_kernels_init.c +0 -1131
  218. data/ext/carray_kernels_map.c +0 -3467
  219. data/ext/carray_kernels_moncmp.c +0 -2097
  220. data/ext/carray_kernels_monop.c +0 -18313
  221. data/ext/carray_kernels_reduce_aggregate.c +0 -25837
  222. data/ext/carray_kernels_reduce_boolean.c +0 -330
  223. data/ext/carray_kernels_reduce_cumulative.c +0 -14593
  224. data/ext/carray_kernels_reduce_extreme.c +0 -16948
  225. data/ext/carray_kernels_reduce_variance.c +0 -3910
  226. data/ext/carray_kernels_scan.c +0 -3693
  227. data/ext/carray_kernels_search.c +0 -32138
  228. data/ext/carray_kernels_sort.c +0 -10626
  229. data/ext/carray_kernels_triop.c +0 -1392
data/ext/ca_obj_stack.c CHANGED
@@ -12,12 +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 / ca_sync_all */
21
22
  #include "ca_composite_dispatch.h"
22
23
  #include "ca_obj_face.h"
23
24
 
@@ -86,9 +87,9 @@ ca_stack_check_uniform (int32_t n_parents, CArray **parents)
86
87
  CArray *p = parents[i];
87
88
  if ( p->data_type != ref->data_type ) {
88
89
  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);
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]);
92
93
  }
93
94
  if ( p->ndim != ref->ndim ) {
94
95
  rb_raise(rb_eArgError,
@@ -116,17 +117,6 @@ ca_stack_check_uniform (int32_t n_parents, CArray **parents)
116
117
  }
117
118
  }
118
119
 
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
120
  int
131
121
  ca_stack_setup_with_axis (CAStack *ca, int32_t n_parents, CArray **parents,
132
122
  int8_t k_axis)
@@ -170,6 +160,8 @@ ca_stack_setup_with_axis (CAStack *ca, int32_t n_parents, CArray **parents,
170
160
  ca->attach = 0;
171
161
  ca->nosync = 0;
172
162
 
163
+ ca->mask_standins = NULL;
164
+ ca->mask_owner = NULL;
173
165
  ca->n_parents = n_parents;
174
166
  ca->parents = ALLOC_N(CArray *, n_parents);
175
167
  for ( i = 0; i < n_parents; i++ ) {
@@ -185,7 +177,7 @@ ca_stack_setup_with_axis (CAStack *ca, int32_t n_parents, CArray **parents,
185
177
  ca->dim[k_axis] = n_parents;
186
178
  for ( a = k_axis + 1; a < ca->ndim; a++ ) ca->dim[a] = ref->dim[a - 1];
187
179
 
188
- /* mask: lazy; horizontal propagation triggers on first mask access. */
180
+ /* mask: lazy, built on first mask access. */
189
181
  return 0;
190
182
  }
191
183
 
@@ -215,6 +207,13 @@ free_ca_stack (void *ap)
215
207
  CAStack *ca = (CAStack *) ap;
216
208
  if ( ca != NULL ) {
217
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
+ }
218
217
  xfree(ca->parents);
219
218
  xfree(ca->dim);
220
219
  xfree(ca);
@@ -234,17 +233,40 @@ ca_stack_func_clone (void *ap)
234
233
 
235
234
  /* view[..., k at k_axis, ...] = parents[k][parent_idx]
236
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
+
237
258
  static void
238
259
  ca_stack_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
239
260
  {
240
261
  CAStack *ca = (CAStack *) ap;
262
+ ca_stack_mask_refresh(ca, dir == CA_XFER_PUT);
241
263
  int8_t k_axis = ca->k_axis;
242
264
  ca_size_t k = idx[k_axis];
243
265
  if ( k < 0 || k >= ca->n_parents ) {
244
266
  rb_raise(rb_eIndexError, "CAStack k-axis (axis %d) index %lld out of range [0, %d)",
245
267
  (int) k_axis, (long long) k, (int) ca->n_parents);
246
268
  }
247
- if ( k_axis == 0 ) {
269
+ if ( k_axis == 0 && ca_fast_paths_enabled() ) {
248
270
  /* Fast path: parent_idx = idx[1..ndim-1]; pass idx+1 directly. */
249
271
  ca_xfer_index(ca->parents[k], idx + 1, data, dir);
250
272
  return;
@@ -273,6 +295,7 @@ ca_stack_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
273
295
  void *data, int dir)
274
296
  {
275
297
  CAStack *ca = (CAStack *) ap;
298
+ ca_stack_mask_refresh(ca, dir == CA_XFER_PUT);
276
299
  int8_t k_axis = ca->k_axis;
277
300
  ca_size_t parent_elements = ca->parents[0]->elements;
278
301
  ca_size_t bytes = ca->bytes;
@@ -285,7 +308,7 @@ ca_stack_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
285
308
  paddrs = ALLOCV_N(ca_size_t, holder1, n);
286
309
  pdata = ALLOCV_N(char, holder2, n * bytes);
287
310
 
288
- if ( k_axis == 0 ) {
311
+ if ( k_axis == 0 && ca_fast_paths_enabled() ) {
289
312
  /* Fast path: addr = k * parent_elements + parent_addr (row-major over
290
313
  view shape with K at axis 0). */
291
314
  for ( k = 0; k < ca->n_parents; k++ ) {
@@ -397,7 +420,9 @@ static void
397
420
  ca_stack_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
398
421
  ca_size_t *strides, void *data, int dir)
399
422
  {
423
+ if ( ca_region_is_empty(((CArray *) ap)->ndim, counts) ) return;
400
424
  CAStack *ca = (CAStack *) ap;
425
+ ca_stack_mask_refresh(ca, dir == CA_XFER_PUT);
401
426
  int8_t k_axis = ca->k_axis;
402
427
  int8_t pndim = ca->ndim - 1;
403
428
  ca_size_t native[CA_RANK_MAX], dstride[CA_RANK_MAX];
@@ -418,19 +443,24 @@ ca_stack_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
418
443
  s *= ca->parents[0]->dim[i];
419
444
  }
420
445
 
421
- /* 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). */
422
452
  for ( i = 0; i < ca->ndim; i++ ) {
423
453
  if ( strides[i] != native[i] ) { structural = 0; break; }
424
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;
425
460
 
426
461
  if ( structural ) {
427
462
  ca_size_t k_lo = starts[k_axis];
428
463
  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
464
 
435
465
  /* dst row-major byte strides over output counts[]. */
436
466
  s = ca->bytes;
@@ -517,7 +547,7 @@ ca_stack_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
517
547
  with native[k_axis] removed, in order. */
518
548
  {
519
549
  int8_t p = -1;
520
- int reloc = 1;
550
+ int reloc = ca_fast_paths_enabled();
521
551
  for ( i = 0; i < ca->ndim; i++ ) {
522
552
  if ( strides[i] == native[k_axis] ) { p = i; break; }
523
553
  }
@@ -537,16 +567,27 @@ ca_stack_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
537
567
  if ( strides[i] != native[v] ) { reloc = 0; break; }
538
568
  }
539
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
+ }
540
588
  if ( reloc ) {
541
- ca_size_t kk_lo = starts[k_axis];
542
- ca_size_t kk_hi = starts[k_axis] + counts[p];
543
589
  ca_size_t odo[CA_RANK_MAX];
544
590
  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
591
  if ( p == ca->ndim - 1 ) {
551
592
  /* K innermost: per-cell via the parent's xfer_index directly, escaping
552
593
  the CAStack-level addr2index dispatch hop in the per-cell fallback. */
@@ -623,20 +664,34 @@ ca_stack_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
623
664
  - k_axis != 0: per-parent contiguous read into a temp buffer, then
624
665
  scattered-write to dst at view row-major positions. This is the
625
666
  correctness path (per-parent contig-read / scattered-write), not a
626
- 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. */
627
674
  static void
628
- 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)
629
676
  {
630
- CAStack *ca = (CAStack *) ap;
631
677
  int8_t k_axis = ca->k_axis;
632
678
  ca_size_t bytes = ca->bytes;
633
- char *d = (char *) data;
634
679
  int32_t k;
635
680
 
636
- 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() ) {
637
686
  ca_size_t parent_bytes_total = ca->parents[0]->elements * bytes;
638
687
  for ( k = 0; k < ca->n_parents; k++ ) {
639
- 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
+ }
640
695
  }
641
696
  return;
642
697
  }
@@ -724,7 +779,12 @@ ca_stack_func_xfer_all (void *ap, void *data, int dir)
724
779
  }
725
780
 
726
781
  if ( dir == CA_XFER_PUT ) {
727
- 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
+ }
728
788
  }
729
789
  }
730
790
  }
@@ -732,6 +792,13 @@ ca_stack_func_xfer_all (void *ap, void *data, int dir)
732
792
  }
733
793
  }
734
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
+
735
802
  /* ------------------------------------------------------------------- */
736
803
  /* attach / sync / detach (materialise via xfer_all) */
737
804
  /* ------------------------------------------------------------------- */
@@ -740,34 +807,42 @@ static void
740
807
  ca_stack_func_allocate (void *ap)
741
808
  {
742
809
  CAStack *ca = (CAStack *) ap;
743
- int32_t k;
744
- for ( k = 0; k < ca->n_parents; k++ ) {
745
- ca_attach(ca->parents[k]);
746
- }
810
+ ca_stack_mask_refresh(ca, 0);
811
+ ca_attach_all(ca->parents, ca->n_parents);
747
812
  ca->ptr = xmalloc(ca_length(ca));
748
813
  }
749
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
+
750
822
  static void
751
823
  ca_stack_func_attach (void *ap)
752
824
  {
753
825
  CAStack *ca = (CAStack *) ap;
826
+ ca_stack_mask_refresh(ca, 0);
754
827
  int32_t k;
755
- for ( k = 0; k < ca->n_parents; k++ ) {
756
- ca_attach(ca->parents[k]);
828
+ int tag = 0;
829
+ ca_attach_all(ca->parents, ca->n_parents);
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);
757
836
  }
758
- ca->ptr = xmalloc(ca_length(ca));
759
- ca_stack_func_xfer_all(ca, ca->ptr, CA_XFER_GET);
760
837
  }
761
838
 
762
839
  static void
763
840
  ca_stack_func_sync (void *ap)
764
841
  {
765
842
  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
- }
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);
771
846
  }
772
847
 
773
848
  static void
@@ -790,6 +865,7 @@ static void
790
865
  ca_stack_func_fill_data (void *ap, void *ptr)
791
866
  {
792
867
  CAStack *ca = (CAStack *) ap;
868
+ ca_stack_mask_refresh(ca, 1);
793
869
  int32_t k;
794
870
  for ( k = 0; k < ca->n_parents; k++ ) {
795
871
  ca_fill(ca->parents[k], ptr);
@@ -797,12 +873,13 @@ ca_stack_func_fill_data (void *ap, void *ptr)
797
873
  }
798
874
 
799
875
  /* ------------------------------------------------------------------- */
800
- /* create_mask: horizontal propagation */
876
+ /* create_mask */
801
877
  /* ------------------------------------------------------------------- */
802
878
 
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). */
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). */
806
883
  static void
807
884
  ca_stack_func_create_mask (void *ap)
808
885
  {
@@ -812,12 +889,13 @@ ca_stack_func_create_mask (void *ap)
812
889
  int32_t k;
813
890
 
814
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
+ }
815
896
  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;
897
+ mask_parents[k] = ca_multi_parent_mask(ca->parents[k],
898
+ &ca->mask_standins[k]);
821
899
  }
822
900
  /* Build the mask stack with the parent's k_axis from the start so its
823
901
  dim[] is laid out for that axis. (Patching ->k_axis after a default
@@ -826,6 +904,7 @@ ca_stack_func_create_mask (void *ap)
826
904
  xfer_stride k-range check when k_axis != 0.) */
827
905
  ca->mask = (CArray *) ca_stack_new_with_axis(ca->n_parents, mask_parents,
828
906
  ca->k_axis);
907
+ ((CAStack *) ca->mask)->mask_owner = (CArray *) ca;
829
908
  ALLOCV_END(holder);
830
909
  }
831
910
 
@@ -851,6 +930,13 @@ ca_stack_func_fold_stride (void *ap, ca_fold_t *f, void **next_parent)
851
930
  ca_size_t k;
852
931
  int8_t i;
853
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
+
854
940
  /* k_axis != 0: parent byte blocks are interleaved (not contig in view
855
941
  buffer), so the byte-box containment check and the size-1 K-axis
856
942
  degeneration below would require detecting the K-axis contribution
@@ -859,6 +945,7 @@ ca_stack_func_fold_stride (void *ap, ca_fold_t *f, void **next_parent)
859
945
  through to CAStack.xfer_stride (k_axis-aware), which still delivers
860
946
  correct (if non-folded) per-parent dispatch. */
861
947
  if ( ca->k_axis != 0 ) return 0;
948
+ if ( ! ca_fast_paths_enabled() ) return 0;
862
949
 
863
950
  for ( i = 0; i < f->ndim; i++ ) {
864
951
  ca_size_t span = (f->counts[i] - 1) * f->strides[i];
@@ -881,6 +968,56 @@ ca_stack_func_fold_stride (void *ap, ca_fold_t *f, void **next_parent)
881
968
  /* operation table */
882
969
  /* ------------------------------------------------------------------- */
883
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
+
884
1021
  ca_operation_function_t ca_stack_func = {
885
1022
  -1, /* CA_OBJ_STACK */
886
1023
  CA_VIEW_ARRAY,
@@ -890,13 +1027,13 @@ ca_operation_function_t ca_stack_func = {
890
1027
  ca_stack_func_attach,
891
1028
  ca_stack_func_sync,
892
1029
  ca_stack_func_detach,
893
- ca_stack_func_fill_data,
1030
+ ca_stack_put_fill_data,
894
1031
  ca_stack_func_create_mask,
895
- ca_stack_func_xfer_index,
896
- ca_stack_func_xfer_addrs,
1032
+ ca_stack_put_xfer_index,
1033
+ ca_stack_put_xfer_addrs,
897
1034
  ca_stack_func_fold_stride,
898
- ca_stack_func_xfer_stride,
899
- ca_stack_func_xfer_all,
1035
+ ca_stack_put_xfer_stride,
1036
+ ca_stack_put_xfer_all,
900
1037
  };
901
1038
 
902
1039
  /* ------------------------------------------------------------------- */
@@ -940,7 +1077,7 @@ rb_ca_stack_new_with_axis (VALUE parents_ary, int8_t k_axis)
940
1077
  ca = ca_stack_new_with_axis((int32_t) n, parents, k_axis);
941
1078
  obj = ca_wrap_struct(ca);
942
1079
  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) */
1080
+ rb_ca_set_parent(obj, ca_face_strip_one(rb_ary_entry(parents_ary, 0))); /* CAView base (Face-stripped, matches C ->parent) */
944
1081
  ALLOCV_END(holder);
945
1082
  return obj;
946
1083
  }
@@ -968,8 +1105,14 @@ rb_ca_stack_initialize_copy (VALUE self, VALUE other)
968
1105
  {
969
1106
  CAStack *ca, *cs;
970
1107
  TypedData_Get_Struct(self, CAStack, &castack_data_type, ca);
1108
+ ca_check_uninitialized(ca);
971
1109
  TypedData_Get_Struct(other, CAStack, &castack_data_type, cs);
972
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;
973
1116
  return self;
974
1117
  }
975
1118
 
@@ -1010,6 +1153,7 @@ rb_ca_stack_initialize (int argc, VALUE *argv, VALUE self)
1010
1153
  NUM2LONG(axis_val), (int) ref->ndim + 1, "CAStack.new");
1011
1154
  }
1012
1155
  TypedData_Get_Struct(self, CAStack, &castack_data_type, ca);
1156
+ ca_check_uninitialized(ca);
1013
1157
  parents = ALLOCV_N(CArray *, holder, n);
1014
1158
  for ( i = 0; i < n; i++ ) {
1015
1159
  VALUE p = rb_ary_entry(list, i);
@@ -1018,7 +1162,7 @@ rb_ca_stack_initialize (int argc, VALUE *argv, VALUE self)
1018
1162
  }
1019
1163
  ca_stack_setup_with_axis(ca, (int32_t) n, parents, k_axis);
1020
1164
  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) */
1165
+ rb_ca_set_parent(self, ca_face_strip_one(rb_ary_entry(list, 0))); /* CAView base (Face-stripped, matches C ->parent) */
1022
1166
  ALLOCV_END(holder);
1023
1167
  return self;
1024
1168
  }
@@ -1034,8 +1178,9 @@ rb_ca_stack_initialize (int argc, VALUE *argv, VALUE self)
1034
1178
  this method)
1035
1179
  - then ca_face_lift re-wraps the result as the same Face class,
1036
1180
  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).
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).
1039
1184
 
1040
1185
  Replaces the Ruby-level CAStack.new override in lib/carray/compose.rb
1041
1186
  (= used alias_method :__new_raw__, :new + class << self def new).
@@ -1044,68 +1189,23 @@ rb_ca_stack_initialize (int argc, VALUE *argv, VALUE self)
1044
1189
  static VALUE
1045
1190
  rb_ca_stack_s_new (int argc, VALUE *argv, VALUE klass)
1046
1191
  {
1047
- VALUE list, kwargs;
1048
- long n, i;
1049
- int all_face = 1;
1050
- VALUE face_class = Qnil;
1051
- CArray *ref_face = NULL;
1192
+ VALUE list, kwargs, face_class;
1052
1193
  VALUE obj;
1053
1194
 
1054
1195
  rb_scan_args(argc, argv, "1:", &list, &kwargs);
1055
1196
  Check_Type(list, T_ARRAY);
1056
- n = RARRAY_LEN(list);
1057
- if ( n <= 0 ) {
1197
+ if ( RARRAY_LEN(list) <= 0 ) {
1058
1198
  rb_raise(rb_eArgError, "CAStack.new requires at least one parent");
1059
1199
  }
1200
+ face_class = ca_face_list_class(list, "CAStack.new");
1060
1201
 
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). */
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. */
1082
1205
  obj = rb_obj_alloc(klass);
1083
1206
  rb_obj_call_init_kw(obj, argc, argv, RB_PASS_CALLED_KEYWORDS);
1084
1207
 
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
- }
1208
+ if ( NIL_P(face_class) ) return obj;
1109
1209
  return ca_face_lift(obj, rb_ary_entry(list, 0));
1110
1210
  }
1111
1211