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/carray_slab.c CHANGED
@@ -28,7 +28,20 @@
28
28
  --------------------------------------------------------------------------- */
29
29
 
30
30
  #include "carray.h"
31
+ #include "carray_internal.h" /* ca_attach_window, ca_calling_method_name, ca_normalize_axis_arg */
31
32
  #include "carray_slab.h"
33
+ #include "ca_obj_face.h"
34
+
35
+ /* Promise 8 of the kernel iterator: a slab may be lent to Ruby as a view
36
+ onto the source's own memory only when the source is an entity and the
37
+ walk reads that memory in place (alias_ptr is src->ptr, not a scratch or
38
+ a root it gathered from). Any other slab is a copy, and a view built on
39
+ it would read memory the walk frees or reuses. */
40
+ static int
41
+ ca_slab_walk_reads_src_in_place (const ca_iter_state *it, CArray *src)
42
+ {
43
+ return ca_is_entity(src) && it->alias_ptr == src->ptr;
44
+ }
32
45
 
33
46
  /* Forward declarations: per-form loop bodies are defined later in this
34
47
  file and take a state pointer directly. */
@@ -65,24 +78,18 @@ ca_slab_parse_axes (ca_slab_iter_state_t *st, VALUE axis_arg, int8_t src_ndim)
65
78
  }
66
79
 
67
80
  n = (int) RARRAY_LEN(arr);
68
- if ( n < 1 || n > CA_RANK_MAX ) {
81
+ if ( n < 1 || n > CA_RANK_MAX ) { /* rank-check: a list of axes, no longer than the rank */
69
82
  rb_raise(rb_eArgError,
70
- "CArray::SlabIterator: axis count %d out of range [1..%d]",
71
- n, CA_RANK_MAX);
83
+ "%s: axis count %d out of range [1..%d]",
84
+ ca_calling_method_name(), n, CA_RANK_MAX);
72
85
  }
73
86
 
74
87
  for ( i = 0; i < n; i++ ) {
75
- int k = NUM2INT(rb_ary_entry(arr, i));
76
- if ( k < 0 ) k += src_ndim;
77
- if ( k < 0 || k >= src_ndim ) {
78
- rb_raise(rb_eArgError,
79
- "CArray::SlabIterator: axis %d out of range for ndim %d",
80
- NUM2INT(rb_ary_entry(arr, i)), src_ndim);
81
- }
88
+ int k = ca_normalize_axis_arg(rb_ary_entry(arr, i), src_ndim, NULL);
82
89
  for ( j = 0; j < i; j++ ) {
83
90
  if ( st->slab_axes[j] == (int8_t) k ) {
84
91
  rb_raise(rb_eArgError,
85
- "CArray::SlabIterator: duplicate axis %d", k);
92
+ "%s: duplicate axis %d", ca_calling_method_name(), k);
86
93
  }
87
94
  }
88
95
  st->slab_axes[i] = (int8_t) k;
@@ -368,23 +375,17 @@ ca_slab_setup_input_slab_view (ca_slab_iter_state_t *st, CArray *src,
368
375
  p_in via slab_strides into the scratch, present slab_view as
369
376
  a CAStride over the carrier with contig strides.
370
377
 
371
- Both paths require ALIAS T1 mode (= p_in is in src memory).
372
- Non-ALIAS multi-axis T1 modes (= PER_SLAB scratch where T1 picks
373
- its own gather for descriptor-framework sources) remain
374
- NotImpError until a follow-on substrate phase. */
375
- uint8_t am = st->t1->alias_mode;
376
- int contig;
377
- if ( am != CA_ITER_ALIAS_CONTIG &&
378
- am != CA_ITER_ALIAS_STRIDED &&
379
- am != CA_ITER_ALIAS_ATTACH ) {
380
- rb_raise(rb_eNotImpError,
381
- "multi-axis slab with non-ALIAS T1 mode not implemented yet "
382
- "(alias_mode = %d)", (int) am);
383
- }
384
- contig = ca_slab_strides_are_row_major_contig(slab_ndim,
385
- st->t1->slab_dims,
386
- st->t1->slab_strides,
387
- src->bytes);
378
+ Path (a) needs p_in to lie in src's own buffer: src is an entity
379
+ and the iterator walks that buffer (alias_ptr is src->ptr, not a
380
+ scratch it gathered into). Any other source -- a view, whose cells
381
+ the iterator hands out from the root, a parent or its own scratch --
382
+ takes path (b): the iterator describes every slab by p_in and
383
+ slab_strides, whatever its mode. */
384
+ int contig = ca_slab_walk_reads_src_in_place(st->t1, src)
385
+ && ca_slab_strides_are_row_major_contig(slab_ndim,
386
+ st->t1->slab_dims,
387
+ st->t1->slab_strides,
388
+ src->bytes);
388
389
  if ( contig ) {
389
390
  /* (a) contig multi-axis ALIAS: parent=src, K-D strides match src. */
390
391
  st->slab_view = ca_slab_build_slab_view(st->self, src->data_type,
@@ -440,7 +441,11 @@ ca_slab_setup_input_slab_view (ca_slab_iter_state_t *st, CArray *src,
440
441
  ca_size_t strides[1];
441
442
  dim[0] = fiber_len;
442
443
  strides[0] = src->bytes;
443
- in_alias = (in_stride == src->bytes);
444
+ /* A window onto src only when the fiber lies in src's own buffer:
445
+ an entity the iterator walks in place, whose fiber is contiguous.
446
+ A view's fiber comes from the root or the iterator's scratch, so it
447
+ goes through a carrier. */
448
+ in_alias = ca_slab_walk_reads_src_in_place(st->t1, src) && (in_stride == src->bytes);
444
449
 
445
450
  if ( in_alias ) {
446
451
  /* ALIAS: parent = src. ca_stride_setup auto-creates slab_view->mask
@@ -496,8 +501,18 @@ ca_slab_advance_slab_view (ca_slab_iter_state_t *st,
496
501
  slab_cs->base_offset = (ca_size_t) (p_in - src->ptr);
497
502
  if ( slab_cs->mask != NULL ) {
498
503
  CAStride *mask_cs = (CAStride *) slab_cs->mask;
499
- mask_cs->ptr = (char *) m_in;
500
- mask_cs->base_offset = (ca_size_t) ((char *) m_in - src->mask->ptr);
504
+ if ( m_in != NULL ) {
505
+ mask_cs->ptr = (char *) m_in;
506
+ mask_cs->base_offset = (ca_size_t) ((char *) m_in - src->mask->ptr);
507
+ }
508
+ else {
509
+ /* The walk was opened on a source without a mask and the block
510
+ has given it one since: there is no mask cursor, so the slab's
511
+ mask reads the source's mask at the cell the data starts on. */
512
+ mask_cs->ptr = NULL;
513
+ mask_cs->base_offset = (ca_size_t) ((p_in - src->ptr) / src->bytes)
514
+ * src->mask->bytes;
515
+ }
501
516
  }
502
517
  } else if ( st->own_data_scratch != NULL ) {
503
518
  /* Non-contig multi-axis: K-D gather from src memory (via
@@ -517,7 +532,7 @@ ca_slab_advance_slab_view (ca_slab_iter_state_t *st,
517
532
  both slab_view and carrier at the iter's delivered addr. */
518
533
  slab_cs->ptr = p_in;
519
534
  carrier_cw->ptr = p_in;
520
- if ( slab_cs->mask != NULL ) {
535
+ if ( slab_cs->mask != NULL && m_in != NULL ) {
521
536
  CAStride *mask_cs = (CAStride *) slab_cs->mask;
522
537
  mask_cs->ptr = (char *) m_in;
523
538
  carrier_cw->mask->ptr = (char *) m_in;
@@ -525,6 +540,44 @@ ca_slab_advance_slab_view (ca_slab_iter_state_t *st,
525
540
  }
526
541
  }
527
542
 
543
+ /* map_slab: mark in the output the cells the block's result has masked.
544
+ The output is an entity walked in place, so the slab starts at
545
+ last_data_off in its buffer and steps by slab_strides; the output gets
546
+ a mask only when some result has a masked cell. */
547
+ static void
548
+ ca_map_slab_carry_mask (ca_slab_iter_state_t *st, CArray *out, CArray *res,
549
+ ca_size_t slab_elements)
550
+ {
551
+ ca_iter_state *t = st->t1_out;
552
+ volatile VALUE h = 0;
553
+ boolean8_t *m;
554
+ ca_size_t idx[CA_RANK_MAX];
555
+ ca_size_t i;
556
+ int8_t k;
557
+
558
+ if ( ! ca_has_mask(res) ) return;
559
+ ca_update_mask(res);
560
+ m = (boolean8_t *) ALLOCV(h, slab_elements);
561
+ ca_xfer_all(res->mask, m, CA_XFER_GET);
562
+ for ( i = 0; i < slab_elements && ! m[i]; i++ ) ;
563
+ if ( i < slab_elements ) {
564
+ ca_create_mask(out);
565
+ for ( k = 0; k < t->slab_ndim; k++ ) idx[k] = 0;
566
+ for ( i = 0; i < slab_elements; i++ ) {
567
+ if ( m[i] ) {
568
+ ca_size_t off = t->last_data_off;
569
+ for ( k = 0; k < t->slab_ndim; k++ ) off += idx[k] * t->slab_strides[k];
570
+ ((boolean8_t *) out->mask->ptr)[off / out->bytes] = 1;
571
+ }
572
+ for ( k = t->slab_ndim - 1; k >= 0; k-- ) {
573
+ if ( ++idx[k] < t->slab_dims[k] ) break;
574
+ idx[k] = 0;
575
+ }
576
+ }
577
+ }
578
+ ALLOCV_END(h);
579
+ }
580
+
528
581
  /* Convert a Ruby Numeric scalar into bytes of slab_view's data_type and
529
582
  fill the entire slab buffer at out_ptr. Used when the user block
530
583
  returns a scalar instead of a CArray (= broadcast over the slab).
@@ -533,9 +586,6 @@ static void
533
586
  ca_slab_fill_scalar (VALUE source, char *out_ptr, ca_size_t slab_elements,
534
587
  int8_t out_data_type, ca_size_t out_bytes, VALUE val)
535
588
  {
536
- char buf[64];
537
- char *scratch = (out_bytes <= (ca_size_t) sizeof(buf)) ? buf
538
- : xmalloc(out_bytes);
539
589
  ca_size_t i;
540
590
  /* Use source's data_type-aware conversion (rb_ca_obj2ptr looks at the
541
591
  receiver's data_type). Build a stand-in scalar buffer via the
@@ -546,24 +596,73 @@ ca_slab_fill_scalar (VALUE source, char *out_ptr, ca_size_t slab_elements,
546
596
  conversion through the actual output entity (= we already have
547
597
  `source`-keyed obj2ptr in carray_cast.c). Here we construct an
548
598
  ephemeral CScalar of the output data_type to anchor the cast. */
549
- VALUE cs_anchor =
599
+ volatile VALUE cs_anchor =
550
600
  rb_cscalar_new_with_value((int) out_data_type, (int) out_bytes, val);
551
601
  CScalar *anchor_ca;
552
602
  (void) source;
553
603
  TypedData_Get_Struct(cs_anchor, CScalar, &cscalar_data_type, anchor_ca);
554
- ca_attach(anchor_ca);
555
- memcpy(scratch, anchor_ca->ptr, out_bytes);
556
- ca_detach(anchor_ca);
557
604
  for ( i = 0; i < slab_elements; i++ ) {
558
- memcpy(out_ptr + i * out_bytes, scratch, out_bytes);
605
+ memcpy(out_ptr + i * out_bytes, anchor_ca->ptr, out_bytes);
606
+ }
607
+ RB_GC_GUARD(cs_anchor);
608
+ }
609
+
610
+ /* map_slab's cast-on-scatter: each cell of the block's result converted
611
+ into the output data type, with the result attached for the fetches. */
612
+
613
+ typedef struct {
614
+ VALUE result;
615
+ CArray *res;
616
+ VALUE output_view;
617
+ char *write_target;
618
+ ca_size_t out_bytes;
619
+ ca_size_t slab_elements;
620
+ } ca_map_slab_cast_ctx_t;
621
+
622
+ static VALUE
623
+ ca_map_slab_cast_body (VALUE arg)
624
+ {
625
+ ca_map_slab_cast_ctx_t *c = (ca_map_slab_cast_ctx_t *) arg;
626
+ volatile VALUE holder;
627
+ char *scratch = ALLOCV_N(char, holder, c->out_bytes);
628
+ ca_size_t i;
629
+ for ( i = 0; i < c->slab_elements; i++ ) {
630
+ VALUE elem;
631
+ ca_size_t idx[CA_RANK_MAX] = { 0 };
632
+ ca_size_t flat = i;
633
+ int8_t k;
634
+ for ( k = c->res->ndim - 1; k >= 0; k-- ) {
635
+ idx[k] = flat % c->res->dim[k];
636
+ flat /= c->res->dim[k];
637
+ }
638
+ elem = rb_ca_fetch_index(c->result, idx);
639
+ rb_ca_obj2ptr(c->output_view, elem, scratch);
640
+ memcpy(c->write_target + i * c->out_bytes, scratch, c->out_bytes);
641
+ }
642
+ ALLOCV_END(holder);
643
+ return Qnil;
644
+ }
645
+
646
+ /* What the block sees of the slab: the slab view itself, or, over a Face,
647
+ the slab view under that Face, as `a[...]` would give it. The slab
648
+ view is moved per slab in place, and the Face reads through it, so one
649
+ is built per walk. */
650
+ static VALUE
651
+ ca_slab_piece (ca_slab_iter_state_t *st)
652
+ {
653
+ CArray *src;
654
+ TypedData_Get_Struct(st->self, CArray, &carray_data_type, src);
655
+ if ( ! ca_is_face(src) ) {
656
+ return st->slab_view;
559
657
  }
560
- if ( scratch != buf ) xfree(scratch);
658
+ return ca_face_lift(st->slab_view, st->self);
561
659
  }
562
660
 
563
661
  static VALUE
564
662
  ca_slab_run_map (ca_slab_iter_state_t *st)
565
663
  {
566
664
  CArray *src;
665
+ volatile VALUE piece;
567
666
  CArray *out;
568
667
  uint32_t flags_in;
569
668
  uint32_t flags_out;
@@ -573,16 +672,13 @@ ca_slab_run_map (ca_slab_iter_state_t *st)
573
672
 
574
673
  TypedData_Get_Struct(st->self, CArray, &carray_data_type, src);
575
674
 
576
- /* Mask transparent carry: input mask is exposed via slab_view
577
- (= user's block can see slab.mask / slab.has_mask?). Output mask
578
- is intentionally NOT scattered — block return CArray mask info is
579
- dropped (= simplest contract). */
675
+ /* The input slab carries the source's mask; a CArray the block
676
+ returns carries its mask into the output (ca_map_slab_carry_mask). */
580
677
 
581
678
  /* Step 1: allocate output entity (same shape as src, data_type from opts). */
582
679
  st->output = rb_carray_new(st->out_data_type, src->ndim, src->dim,
583
680
  st->out_bytes, NULL);
584
681
  TypedData_Get_Struct(st->output, CArray, &carray_data_type, out);
585
- ca_allocate(out); /* entity ptr usable as both read + write target */
586
682
 
587
683
  /* Step 2: T1 init on both sides. FIBER_CONTIG is naxes==1 only;
588
684
  multi-axis omits it. Output side gets CA_KERNEL_WRITE. */
@@ -652,6 +748,7 @@ ca_slab_run_map (ca_slab_iter_state_t *st)
652
748
  TypedData_Get_Struct(st->output_slab_view, CAWrap,
653
749
  &cawrap_data_type, out_slab_cw);
654
750
 
751
+ piece = ca_slab_piece(st);
655
752
  while ( ca_iter_state_next_slab_axes(st->t1, &p_in, &m_in) &&
656
753
  ca_iter_state_next_slab_axes(st->t1_out, &p_out, &m_out) ) {
657
754
  VALUE result;
@@ -669,7 +766,7 @@ ca_slab_run_map (ca_slab_iter_state_t *st)
669
766
  ? st->own_out_scratch : p_out;
670
767
  out_slab_cw->ptr = write_target;
671
768
 
672
- result = rb_yield(st->slab_view);
769
+ result = rb_yield(piece);
673
770
 
674
771
  /* Strict shape check. */
675
772
  if ( rb_obj_is_kind_of(result, rb_cCArray) ) {
@@ -698,34 +795,22 @@ ca_slab_run_map (ca_slab_iter_state_t *st)
698
795
  (long long) slab_elements, (int) slab_cs->ndim);
699
796
  }
700
797
  if ( res->data_type == st->out_data_type && res->bytes == st->out_bytes ) {
701
- ca_attach(res);
702
798
  ca_xfer_all(res, write_target, CA_XFER_GET);
703
- ca_detach(res);
704
799
  }
705
800
  else {
706
801
  /* Cast-on-scatter via per-cell obj2ptr. Writes to write_target
707
- (= either p_out direct or own_out_scratch). */
708
- char buf[64];
709
- char *scratch = (st->out_bytes <= (ca_size_t) sizeof(buf))
710
- ? buf : xmalloc(st->out_bytes);
711
- ca_size_t i;
712
- ca_attach(res);
713
- for ( i = 0; i < slab_elements; i++ ) {
714
- VALUE elem;
715
- ca_size_t idx[CA_RANK_MAX] = { 0 };
716
- ca_size_t flat = i;
717
- int8_t k;
718
- for ( k = res->ndim - 1; k >= 0; k-- ) {
719
- idx[k] = flat % res->dim[k];
720
- flat /= res->dim[k];
721
- }
722
- elem = rb_ca_fetch_index(result, idx);
723
- rb_ca_obj2ptr(st->output_slab_view, elem, scratch);
724
- memcpy(write_target + i * st->out_bytes, scratch, st->out_bytes);
725
- }
726
- ca_detach(res);
727
- if ( scratch != buf ) xfree(scratch);
802
+ (= either p_out direct or own_out_scratch). The conversion
803
+ can raise, so the result is read in a window. */
804
+ ca_map_slab_cast_ctx_t c;
805
+ c.result = result;
806
+ c.res = res;
807
+ c.output_view = st->output_slab_view;
808
+ c.write_target = write_target;
809
+ c.out_bytes = st->out_bytes;
810
+ c.slab_elements = slab_elements;
811
+ ca_attach_window(1, &res, "r", ca_map_slab_cast_body, (VALUE) &c);
728
812
  }
813
+ ca_map_slab_carry_mask(st, out, res, slab_elements);
729
814
  }
730
815
  else if ( rb_obj_is_kind_of(result, rb_cNumeric) ||
731
816
  result == Qtrue || result == Qfalse ||
@@ -820,7 +905,6 @@ ca_slab_reduce_setup (ca_slab_iter_state_t *st, CArray **out_src,
820
905
  st->output = rb_carray_new(st->out_data_type, out_ndim, out_dim,
821
906
  st->out_bytes, NULL);
822
907
  TypedData_Get_Struct(st->output, CArray, &carray_data_type, out);
823
- ca_allocate(out);
824
908
 
825
909
  /* FIBER_CONTIG is naxes==1 only (T1 substrate contract). Multi-axis
826
910
  omits the flag and uses bare CA_SLAB_AXES; T1 picks an in-src
@@ -848,10 +932,36 @@ ca_slab_reduce_setup (ca_slab_iter_state_t *st, CArray **out_src,
848
932
  /* Per-slab block form: block receives the slab CArray, returns a
849
933
  scalar; each scalar is cast into the corresponding output cell.
850
934
  (`init:` absent ⇒ this path) */
935
+ /* Write one reduced value into the reduction output. UNDEF from the
936
+ block masks the cell, as storing UNDEF into an array does; obj2ptr
937
+ would read it as 0. */
938
+ static void
939
+ ca_slab_store_reduced (ca_slab_iter_state_t *st, CArray *out,
940
+ ca_size_t out_idx, VALUE value, char *scratch)
941
+ {
942
+ char *dst = out->ptr + out_idx * st->out_bytes;
943
+ if ( value == CA_UNDEF ) {
944
+ if ( out->mask == NULL ) {
945
+ ca_create_mask(out);
946
+ }
947
+ ((boolean8_t *) out->mask->ptr)[out_idx] = 1;
948
+ if ( out->data_type == CA_OBJECT ) {
949
+ *(VALUE *) dst = INT2FIX(0);
950
+ }
951
+ else {
952
+ memset(dst, 0, st->out_bytes);
953
+ }
954
+ return;
955
+ }
956
+ rb_ca_obj2ptr(st->output, value, scratch);
957
+ memcpy(dst, scratch, st->out_bytes);
958
+ }
959
+
851
960
  static VALUE
852
961
  ca_slab_run_reduce_slab (ca_slab_iter_state_t *st)
853
962
  {
854
963
  CArray *src;
964
+ volatile VALUE piece;
855
965
  CArray *out;
856
966
  ca_size_t fiber_len;
857
967
  char *p_in;
@@ -862,6 +972,7 @@ ca_slab_run_reduce_slab (ca_slab_iter_state_t *st)
862
972
  ca_size_t out_idx = 0;
863
973
  char buf[64];
864
974
  char *scratch;
975
+ volatile VALUE scratch_holder = 0; /* 0, not Qnil: ALLOCV_END reads a non-zero holder */
865
976
 
866
977
  ca_slab_reduce_setup(st, &src, &fiber_len);
867
978
  TypedData_Get_Struct(st->output, CArray, &carray_data_type, out);
@@ -871,16 +982,19 @@ ca_slab_run_reduce_slab (ca_slab_iter_state_t *st)
871
982
  TypedData_Get_Struct(st->carrier, CAWrap, &cawrap_data_type, carrier_cw);
872
983
  }
873
984
 
874
- scratch = (st->out_bytes <= (ca_size_t) sizeof(buf)) ? buf
875
- : xmalloc(st->out_bytes);
985
+ /* The block and the conversion of its value can raise, so a scratch
986
+ too wide for buf is Ruby's temporary. */
987
+ scratch = (st->out_bytes <= (ca_size_t) sizeof(buf))
988
+ ? buf : ALLOCV_N(char, scratch_holder, st->out_bytes);
876
989
 
990
+ piece = ca_slab_piece(st);
877
991
  while ( ca_iter_state_next_slab_axes(st->t1, &p_in, &m_in) ) {
878
992
  VALUE result;
879
993
 
880
994
  ca_slab_advance_slab_view(st, slab_cs, carrier_cw,
881
995
  src, p_in, m_in, in_alias);
882
996
 
883
- result = rb_yield(st->slab_view);
997
+ result = rb_yield(piece);
884
998
 
885
999
  /* Strict scalar contract: block must return a scalar Ruby value
886
1000
  (Numeric / Object / etc.). A CArray is a
@@ -891,7 +1005,6 @@ ca_slab_run_reduce_slab (ca_slab_iter_state_t *st)
891
1005
  if ( rb_obj_is_kind_of(result, rb_cCArray) ) {
892
1006
  CArray *res;
893
1007
  TypedData_Get_Struct(result, CArray, &carray_data_type, res);
894
- if ( scratch != buf ) xfree(scratch);
895
1008
  rb_raise(rb_eArgError,
896
1009
  "CArray#reduce_slab: per-slab block must return a scalar "
897
1010
  "(got CArray with %lld element%s; use `slab[0]` or "
@@ -900,12 +1013,11 @@ ca_slab_run_reduce_slab (ca_slab_iter_state_t *st)
900
1013
  res->elements == 1 ? "" : "s");
901
1014
  }
902
1015
 
903
- rb_ca_obj2ptr(st->output, result, scratch);
904
- memcpy(out->ptr + out_idx * st->out_bytes, scratch, st->out_bytes);
1016
+ ca_slab_store_reduced(st, out, out_idx, result, scratch);
905
1017
  out_idx++;
906
1018
  }
907
1019
 
908
- if ( scratch != buf ) xfree(scratch);
1020
+ ALLOCV_END(scratch_holder);
909
1021
  return st->output;
910
1022
  }
911
1023
 
@@ -916,6 +1028,7 @@ static VALUE
916
1028
  ca_slab_run_reduce_fiber (ca_slab_iter_state_t *st)
917
1029
  {
918
1030
  CArray *src;
1031
+ volatile VALUE piece;
919
1032
  CArray *out;
920
1033
  ca_size_t fiber_len;
921
1034
  char *p_in;
@@ -926,6 +1039,7 @@ ca_slab_run_reduce_fiber (ca_slab_iter_state_t *st)
926
1039
  ca_size_t out_idx = 0;
927
1040
  char buf[64];
928
1041
  char *scratch;
1042
+ volatile VALUE scratch_holder = 0; /* 0, not Qnil: ALLOCV_END reads a non-zero holder */
929
1043
 
930
1044
  ca_slab_reduce_setup(st, &src, &fiber_len);
931
1045
  TypedData_Get_Struct(st->output, CArray, &carray_data_type, out);
@@ -935,9 +1049,12 @@ ca_slab_run_reduce_fiber (ca_slab_iter_state_t *st)
935
1049
  TypedData_Get_Struct(st->carrier, CAWrap, &cawrap_data_type, carrier_cw);
936
1050
  }
937
1051
 
938
- scratch = (st->out_bytes <= (ca_size_t) sizeof(buf)) ? buf
939
- : xmalloc(st->out_bytes);
1052
+ /* The block and the conversion of its value can raise, so a scratch
1053
+ too wide for buf is Ruby's temporary. */
1054
+ scratch = (st->out_bytes <= (ca_size_t) sizeof(buf))
1055
+ ? buf : ALLOCV_N(char, scratch_holder, st->out_bytes);
940
1056
 
1057
+ piece = ca_slab_piece(st);
941
1058
  while ( ca_iter_state_next_slab_axes(st->t1, &p_in, &m_in) ) {
942
1059
  VALUE acc;
943
1060
  ca_size_t i;
@@ -955,7 +1072,7 @@ ca_slab_run_reduce_fiber (ca_slab_iter_state_t *st)
955
1072
  ca_size_t total = st->t1->slab_elements;
956
1073
  for ( i = 0; i < total; i++ ) {
957
1074
  VALUE x;
958
- x = rb_ca_fetch_index(st->slab_view, kidx);
1075
+ x = rb_ca_fetch_index(piece, kidx);
959
1076
  acc = rb_yield_values(2, acc, x);
960
1077
  /* Increment kidx[] row-major. */
961
1078
  for ( k = slab_cs->ndim - 1; k >= 0; k-- ) {
@@ -966,12 +1083,11 @@ ca_slab_run_reduce_fiber (ca_slab_iter_state_t *st)
966
1083
  (void) fiber_len;
967
1084
  }
968
1085
 
969
- rb_ca_obj2ptr(st->output, acc, scratch);
970
- memcpy(out->ptr + out_idx * st->out_bytes, scratch, st->out_bytes);
1086
+ ca_slab_store_reduced(st, out, out_idx, acc, scratch);
971
1087
  out_idx++;
972
1088
  }
973
1089
 
974
- if ( scratch != buf ) xfree(scratch);
1090
+ ALLOCV_END(scratch_holder);
975
1091
  return st->output;
976
1092
  }
977
1093
 
@@ -994,6 +1110,7 @@ static VALUE
994
1110
  ca_slab_run_each (ca_slab_iter_state_t *st)
995
1111
  {
996
1112
  CArray *src;
1113
+ volatile VALUE piece;
997
1114
  char *p_in;
998
1115
  boolean8_t *m_in;
999
1116
  CAStride *slab_cs;
@@ -1027,10 +1144,11 @@ ca_slab_run_each (ca_slab_iter_state_t *st)
1027
1144
  TypedData_Get_Struct(st->carrier, CAWrap, &cawrap_data_type, carrier_cw);
1028
1145
  }
1029
1146
 
1147
+ piece = ca_slab_piece(st);
1030
1148
  while ( ca_iter_state_next_slab_axes(st->t1, &p_in, &m_in) ) {
1031
1149
  ca_slab_advance_slab_view(st, slab_cs, carrier_cw,
1032
1150
  src, p_in, m_in, in_alias);
1033
- rb_yield(st->slab_view); /* return value discarded */
1151
+ rb_yield(piece); /* return value discarded */
1034
1152
  }
1035
1153
 
1036
1154
  return st->self;
@@ -1055,11 +1173,56 @@ slab_state_run_body (VALUE arg)
1055
1173
  return Qnil;
1056
1174
  }
1057
1175
 
1176
+ /* The slab views outlive the walk: the block may keep the slab or a view
1177
+ of it (`row.dup`), and the GC marks an object array's cells for as long
1178
+ as it has a ptr. Before the scratch they point into is freed, the
1179
+ input slab and its carrier are moved onto a copy of the last slab,
1180
+ held by the carrier, and the internal output view lets go of its
1181
+ buffer. A slab that is a window onto the source itself points into
1182
+ the source and needs nothing. */
1183
+ static void
1184
+ slab_state_release_views (ca_slab_iter_state_t *st)
1185
+ {
1186
+ if ( st->output_slab_view != Qnil ) {
1187
+ CArray *ov;
1188
+ TypedData_Get_Struct(st->output_slab_view, CArray, &carray_data_type, ov);
1189
+ ov->ptr = NULL;
1190
+ }
1191
+ if ( st->carrier != Qnil && st->slab_view != Qnil ) {
1192
+ CArray *cw, *cs, *backing;
1193
+ volatile VALUE bv;
1194
+ TypedData_Get_Struct(st->carrier, CArray, &carray_data_type, cw);
1195
+ TypedData_Get_Struct(st->slab_view, CArray, &carray_data_type, cs);
1196
+ if ( cw->ptr == NULL ) {
1197
+ return;
1198
+ }
1199
+ backing = ca_copy(cw);
1200
+ bv = ca_wrap_struct(backing);
1201
+ if ( cw->mask && ! backing->mask ) {
1202
+ ca_create_mask(backing);
1203
+ }
1204
+ rb_ivar_set(st->carrier, rb_intern("__slab_backing__"), bv);
1205
+ if ( cs->ptr ) {
1206
+ cs->ptr = backing->ptr;
1207
+ }
1208
+ if ( cs->mask && cs->mask->ptr ) {
1209
+ cs->mask->ptr = backing->mask->ptr;
1210
+ }
1211
+ cw->ptr = backing->ptr;
1212
+ if ( cw->mask ) {
1213
+ cw->mask->ptr = backing->mask->ptr;
1214
+ }
1215
+ RB_GC_GUARD(bv);
1216
+ }
1217
+ }
1218
+
1058
1219
  static VALUE
1059
1220
  slab_state_finish (VALUE arg)
1060
1221
  {
1061
1222
  ca_slab_iter_state_t *st = (ca_slab_iter_state_t *)(uintptr_t) arg;
1062
1223
 
1224
+ slab_state_release_views(st);
1225
+
1063
1226
  /* T1 substrate teardown.
1064
1227
  CAREFUL: ca_detach(slab_view) is NEVER called — T1 owns the buffer;
1065
1228
  dropping the VALUE handles lets GC reclaim the CAStride wrappers
@@ -1124,7 +1287,7 @@ static VALUE
1124
1287
  slab_extract_kwargs (int argc, VALUE *argv, const char *method_name)
1125
1288
  {
1126
1289
  VALUE kw = Qnil;
1127
- if ( argc == 1 && RB_TYPE_P(argv[0], T_HASH) ) {
1290
+ if ( argc == 1 && rb_keyword_given_p() && RB_TYPE_P(argv[0], T_HASH) ) {
1128
1291
  kw = argv[0];
1129
1292
  }
1130
1293
  else if ( argc != 0 ) {