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_lazy.c CHANGED
@@ -1,6 +1,6 @@
1
1
  /* ---------------------------------------------------------------------------
2
2
 
3
- CALazyMarker view + the shared lazy arena.
3
+ CALazyMarker view + the lazy arena.
4
4
 
5
5
  CALazyMarker is a zero-cost marker that holds a CArray entity and
6
6
  causes `.sqrt` / `.sin` / etc. on it to build a lazy CAMonOp tree
@@ -11,7 +11,7 @@
11
11
  is a pure pass-through: every CArray operation delegates to the
12
12
  parent. Detection happens via ca_is_lazy_view().
13
13
 
14
- ca_lazy_arena is the shared slot-pool arena that CAMonOp / CABinOp
14
+ ca_lazy_arena is the per-Ractor slot-pool arena that CAMonOp / CABinOp
15
15
  / CAMonCmp / CABinCmp use for scratch during to_ca; slot buffers
16
16
  stay alive across calls to amortise mmap into steady-state reuse.
17
17
 
@@ -19,6 +19,8 @@
19
19
 
20
20
  #include "carray.h"
21
21
  #include "carray_internal.h" /* ca_lazy_arena_*, ca_is_lazy_view */
22
+ #include "ca_kernel_iterator.h" /* ca_iter_register_source_kind */
23
+ #include <ruby/ractor.h>
22
24
 
23
25
  int8_t CA_OBJ_LAZY_MARKER;
24
26
  VALUE rb_cCALazyMarker;
@@ -78,10 +80,6 @@ ca_lazy_marker_setup (CALazyMarker *ca, CArray *parent)
78
80
 
79
81
  memcpy(ca->dim, parent->dim, parent->ndim * sizeof(ca_size_t));
80
82
 
81
- if ( ca_has_mask(parent) ) {
82
- ca_create_mask(ca);
83
- }
84
-
85
83
  if ( ca_is_scalar(parent) ) {
86
84
  ca_set_flag(ca, CA_FLAG_SCALAR);
87
85
  }
@@ -103,8 +101,10 @@ ca_lazy_marker_setup (CALazyMarker *ca, CArray *parent)
103
101
  CALazyMarker *
104
102
  ca_lazy_marker_new (CArray *parent)
105
103
  {
104
+ /* check: the setup checks nothing */
106
105
  CALazyMarker *ca = ALLOC(CALazyMarker);
107
106
  ca_lazy_marker_setup(ca, parent);
107
+ ca_fill_or_free((CArray *) ca, ca_view_inherit_mask, (VALUE) ca);
108
108
  return ca;
109
109
  }
110
110
 
@@ -153,6 +153,7 @@ static void
153
153
  ca_lazy_marker_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
154
154
  ca_size_t *strides, void *data, int dir)
155
155
  {
156
+ if ( ca_region_is_empty(((CArray *) ap)->ndim, counts) ) return;
156
157
  CALazyMarker *ca = (CALazyMarker *) ap;
157
158
  ca_xfer_stride(ca->parent, starts, counts, strides, data, dir);
158
159
  }
@@ -206,12 +207,8 @@ static void
206
207
  ca_lazy_marker_func_create_mask (void *ap)
207
208
  {
208
209
  CALazyMarker *ca = (CALazyMarker *) ap;
209
- if ( ! ca->parent->mask ) {
210
- ca_create_mask(ca->parent);
211
- }
212
- ca->mask =
213
- (CArray *) ca_refer_new(ca->parent->mask,
214
- CA_BOOLEAN, ca->ndim, ca->dim, 0, 0);
210
+ ca_view_func_create_mask(ca);
211
+ ca_set_flag(ca->mask, CA_FLAG_READ_ONLY);
215
212
  }
216
213
 
217
214
  ca_operation_function_t ca_lazy_marker_func = {
@@ -250,10 +247,19 @@ rb_ca_lazy_marker_new (VALUE cary)
250
247
 
251
248
  /* CArray#lazy — return a zero-cost CALazyMarker wrapping self. Subsequent
252
249
  * element-wise ops (.sqrt / .sin / .+ / ...) build a lazy CAMonOp / CABinOp
253
- * tree instead of evaluating eagerly; `.to_ca` materialises. */
250
+ * tree instead of evaluating eagerly; `.to_ca` materialises.
251
+ *
252
+ * A Face returns itself: a lazy tree computes on storage and would lose
253
+ * the Face's meaning (units, labels, string surface), so its operations
254
+ * stay eager. */
254
255
  VALUE
255
256
  rb_ca_lazy (VALUE self)
256
257
  {
258
+ CArray *ca;
259
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
260
+ if ( ca_is_face(ca) ) {
261
+ return self;
262
+ }
257
263
  return rb_ca_lazy_marker_new(self);
258
264
  }
259
265
 
@@ -292,10 +298,66 @@ typedef struct ca_gc_hold {
292
298
  ca_size_t elements;
293
299
  } ca_gc_hold_t;
294
300
 
295
- static ca_gc_hold_t ca_gc_holds[CA_GC_HOLD_MAX];
296
- static int ca_gc_hold_depth = 0;
301
+ /* Each Ractor keeps its own holds: walks in different Ractors push and
302
+ release at the same time. The GC marks every Ractor's set through
303
+ the storage type's mark function. */
304
+ typedef struct ca_gc_local {
305
+ ca_gc_hold_t holds[CA_GC_HOLD_MAX];
306
+ int hold_depth;
307
+ ca_gc_hold_t *buffers; /* held until released, in any order */
308
+ int buffer_n;
309
+ int buffer_cap;
310
+ } ca_gc_local_t;
311
+
312
+ static rb_ractor_local_key_t ca_gc_local_key;
313
+
314
+ static void
315
+ ca_gc_local_mark (void *ptr)
316
+ {
317
+ ca_gc_local_t *g = (ca_gc_local_t *) ptr;
318
+ int i;
319
+ if ( g == NULL ) return;
320
+ for ( i = 0; i < g->hold_depth; i++ ) {
321
+ VALUE *p = g->holds[i].ptr;
322
+ ca_size_t n = g->holds[i].elements;
323
+ while ( n-- ) rb_gc_mark(*p++);
324
+ }
325
+ for ( i = 0; i < g->buffer_n; i++ ) {
326
+ VALUE *p = g->buffers[i].ptr;
327
+ ca_size_t n = g->buffers[i].elements;
328
+ while ( n-- ) rb_gc_mark(*p++);
329
+ }
330
+ }
331
+
332
+ static void
333
+ ca_gc_local_free (void *ptr)
334
+ {
335
+ ca_gc_local_t *g = (ca_gc_local_t *) ptr;
336
+ if ( g == NULL ) return;
337
+ free(g->buffers);
338
+ free(g);
339
+ }
340
+
341
+ static const struct rb_ractor_local_storage_type ca_gc_local_type = {
342
+ ca_gc_local_mark, ca_gc_local_free
343
+ };
344
+
345
+ /* The state is allocated with the C allocator, like the table it grows:
346
+ a collection must not run while either is half set up. */
347
+ static ca_gc_local_t *
348
+ ca_gc_local (void)
349
+ {
350
+ ca_gc_local_t *g = (ca_gc_local_t *) rb_ractor_local_storage_ptr(ca_gc_local_key);
351
+ if ( g == NULL ) {
352
+ g = (ca_gc_local_t *) calloc(1, sizeof(ca_gc_local_t));
353
+ if ( g == NULL ) {
354
+ rb_memerror();
355
+ }
356
+ rb_ractor_local_storage_ptr_set(ca_gc_local_key, g);
357
+ }
358
+ return g;
359
+ }
297
360
 
298
- static void ca_lazy_arena_mark_object_slots (void);
299
361
 
300
362
  /* Returns the depth to hand back to ca_gc_hold_pop_to, so nested holds
301
363
  unwind in order. Pushing is best-effort at the ceiling: a chain
@@ -306,56 +368,72 @@ static void ca_lazy_arena_mark_object_slots (void);
306
368
  int
307
369
  ca_gc_hold_push (void *ptr, ca_size_t n_elements)
308
370
  {
309
- int depth = ca_gc_hold_depth;
371
+ ca_gc_local_t *g;
372
+ int depth;
310
373
 
311
- if ( depth >= CA_GC_HOLD_MAX || n_elements <= 0 || ptr == NULL ) {
374
+ if ( n_elements <= 0 || ptr == NULL ) {
312
375
  return -1;
313
376
  }
314
- ca_gc_holds[depth].ptr = (VALUE *) ptr;
315
- ca_gc_holds[depth].elements = n_elements;
316
- ca_gc_hold_depth = depth + 1;
377
+ g = ca_gc_local();
378
+ depth = g->hold_depth;
379
+ if ( depth >= CA_GC_HOLD_MAX ) {
380
+ return -1;
381
+ }
382
+ g->holds[depth].ptr = (VALUE *) ptr;
383
+ g->holds[depth].elements = n_elements;
384
+ g->hold_depth = depth + 1;
317
385
  return depth;
318
386
  }
319
387
 
320
388
  void
321
389
  ca_gc_hold_pop_to (int depth)
322
390
  {
323
- if ( depth >= 0 && depth < ca_gc_hold_depth ) {
324
- ca_gc_hold_depth = depth;
391
+ ca_gc_local_t *g;
392
+ if ( depth < 0 ) return;
393
+ g = ca_gc_local();
394
+ if ( depth < g->hold_depth ) {
395
+ g->hold_depth = depth;
325
396
  }
326
397
  }
327
398
 
328
- static VALUE ca_gc_guard = Qnil;
329
-
330
- /* The wrapped pointer must be non-NULL: Ruby's GC skips the mark
331
- function of a TypedData whose data pointer is NULL. */
332
- static int ca_gc_guard_body = 0;
333
-
334
- static void
335
- ca_gc_guard_mark (void *ptr)
399
+ void
400
+ ca_gc_hold_buffer (void *ptr, ca_size_t n_elements)
336
401
  {
337
- int i;
338
- (void) ptr;
339
- for ( i = 0; i < ca_gc_hold_depth; i++ ) {
340
- VALUE *p = ca_gc_holds[i].ptr;
341
- ca_size_t n = ca_gc_holds[i].elements;
342
- while ( n-- ) rb_gc_mark(*p++);
402
+ ca_gc_local_t *g;
403
+ if ( ptr == NULL || n_elements <= 0 ) {
404
+ return;
343
405
  }
344
- ca_lazy_arena_mark_object_slots();
406
+ g = ca_gc_local();
407
+ if ( g->buffer_n == g->buffer_cap ) {
408
+ int cap = g->buffer_cap ? g->buffer_cap * 2 : 16;
409
+ ca_gc_hold_t *grown = (ca_gc_hold_t *) realloc(g->buffers,
410
+ cap * sizeof(ca_gc_hold_t));
411
+ if ( grown == NULL ) {
412
+ rb_memerror();
413
+ }
414
+ g->buffers = grown;
415
+ g->buffer_cap = cap;
416
+ }
417
+ g->buffers[g->buffer_n].ptr = (VALUE *) ptr;
418
+ g->buffers[g->buffer_n].elements = n_elements;
419
+ g->buffer_n++;
345
420
  }
346
421
 
347
- static void
348
- ca_gc_guard_free (void *ptr)
422
+ void
423
+ ca_gc_release_buffer (void *ptr)
349
424
  {
350
- (void) ptr;
425
+ ca_gc_local_t *g = (ca_gc_local_t *) rb_ractor_local_storage_ptr(ca_gc_local_key);
426
+ int i;
427
+ if ( g == NULL ) return;
428
+ for ( i = g->buffer_n - 1; i >= 0; i-- ) {
429
+ if ( g->buffers[i].ptr == (VALUE *) ptr ) {
430
+ g->buffers[i] = g->buffers[g->buffer_n - 1];
431
+ g->buffer_n--;
432
+ return;
433
+ }
434
+ }
351
435
  }
352
436
 
353
- static const rb_data_type_t ca_gc_guard_data_type = {
354
- "carray_object_gc_guard",
355
- { ca_gc_guard_mark, ca_gc_guard_free, NULL, },
356
- 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
357
- };
358
-
359
437
  /* ------------------------------------------------------------------- */
360
438
  /* ca_lazy_arena */
361
439
  /* ------------------------------------------------------------------- */
@@ -374,15 +452,17 @@ static const rb_data_type_t ca_gc_guard_data_type = {
374
452
  best-fit allocation keeps small requests (mask scratch) out of
375
453
  large data slots
376
454
 
377
- Footprint: 32 slots × max observed size (up to 32 × max_slab_bytes;
378
- e.g. N=1M f64, depth-8 chain = 8 × 8MB = 64MB kept resident). When
379
- the slot pool is exhausted the acquire raises: this signals a
380
- programming error or a pathological chain rather than a graceful
381
- fallback.
455
+ Footprint: one slot per scratch held at the same time, each as large
456
+ as the largest request it has served (e.g. N=1M f64, depth-8 chain =
457
+ 8 × 8MB = 64MB kept resident). A request no free slot can hold grows
458
+ a free slot; a new slot opens only while every warm one is in use.
459
+ Past 256 MB the idle slots are given back at the next top-level entry
460
+ (ca_lazy_arena_trim). When all 32 are in use the acquire raises: this signals a programming
461
+ error or a pathological chain rather than a graceful fallback.
382
462
 
383
- CAREFUL: single-thread only -- thread-safety across concurrent access
384
- is not a goal (see guides/devel/04_attach_lifecycle.md). The arena is
385
- process-global static state. */
463
+ One arena per Ractor (see ca_lazy_arena_get). Within a Ractor it is
464
+ not guarded: thread-safety across concurrent access is not a goal (see
465
+ guides/devel/04_attach_lifecycle.md). */
386
466
 
387
467
  #define CA_LAZY_ARENA_SLOTS 32
388
468
 
@@ -401,29 +481,109 @@ typedef struct ca_lazy_arena {
401
481
  ca_size_t debug_reuse_count; /* test: how many acquire = reuse */
402
482
  } ca_lazy_arena_t;
403
483
 
404
- static ca_lazy_arena_t ca_lazy_arena = {0};
484
+ /* One arena per Ractor: the slots are handed out by pointer and toggled
485
+ without a lock, so two Ractors sharing them would hand the same buffer
486
+ to both. The storage marks the object slots in use and frees the
487
+ buffers when the Ractor ends. */
488
+ static rb_ractor_local_key_t ca_lazy_arena_key;
489
+
490
+ static void ca_lazy_arena_mark_object_slots (ca_lazy_arena_t *arena);
491
+
492
+ static void
493
+ ca_lazy_arena_mark (void *ptr)
494
+ {
495
+ if ( ptr ) ca_lazy_arena_mark_object_slots((ca_lazy_arena_t *) ptr);
496
+ }
497
+
498
+ static void
499
+ ca_lazy_arena_free (void *ptr)
500
+ {
501
+ ca_lazy_arena_t *arena = (ca_lazy_arena_t *) ptr;
502
+ int i;
503
+ if ( arena == NULL ) return;
504
+ for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
505
+ if ( arena->slots[i].ptr ) ruby_xfree(arena->slots[i].ptr);
506
+ }
507
+ ruby_xfree(arena);
508
+ }
509
+
510
+ static const struct rb_ractor_local_storage_type ca_lazy_arena_type = {
511
+ ca_lazy_arena_mark, ca_lazy_arena_free
512
+ };
513
+
514
+ static ca_lazy_arena_t *
515
+ ca_lazy_arena_get (void)
516
+ {
517
+ ca_lazy_arena_t *arena =
518
+ (ca_lazy_arena_t *) rb_ractor_local_storage_ptr(ca_lazy_arena_key);
519
+ if ( arena == NULL ) {
520
+ arena = ZALLOC(ca_lazy_arena_t);
521
+ rb_ractor_local_storage_ptr_set(ca_lazy_arena_key, arena);
522
+ }
523
+ return arena;
524
+ }
525
+
526
+ /* Above this the arena returns memory: the idle slots are freed, the
527
+ smallest first, until what it holds is under the limit. The largest
528
+ slot is always kept, so a workload that repeats one request keeps its
529
+ warm buffer however large it is. */
530
+ #define CA_LAZY_ARENA_KEEP_BYTES ((ca_size_t) 256 << 20)
531
+
532
+ static void
533
+ ca_lazy_arena_trim (ca_lazy_arena_t *arena)
534
+ {
535
+ ca_size_t total = 0;
536
+ int i, largest = -1;
537
+ for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
538
+ if ( arena->slots[i].ptr == NULL ) continue;
539
+ total += arena->slots[i].bytes;
540
+ if ( largest < 0 || arena->slots[i].bytes > arena->slots[largest].bytes ) {
541
+ largest = i;
542
+ }
543
+ }
544
+ while ( total > CA_LAZY_ARENA_KEEP_BYTES ) {
545
+ int smallest = -1;
546
+ for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
547
+ ca_lazy_arena_slot_t *s = &arena->slots[i];
548
+ if ( s->ptr == NULL || s->in_use || i == largest ) continue;
549
+ if ( smallest < 0 || s->bytes < arena->slots[smallest].bytes ) {
550
+ smallest = i;
551
+ }
552
+ }
553
+ if ( smallest < 0 ) break;
554
+ total -= arena->slots[smallest].bytes;
555
+ xfree(arena->slots[smallest].ptr);
556
+ arena->slots[smallest].ptr = NULL;
557
+ arena->slots[smallest].bytes = 0;
558
+ }
559
+ }
405
560
 
406
561
  void
407
562
  ca_lazy_arena_enter (void)
408
563
  {
409
- if ( ca_lazy_arena.depth == 0 ) {
564
+ ca_lazy_arena_t *arena = ca_lazy_arena_get();
565
+ if ( arena->depth == 0 ) {
566
+ /* Only a slot no one holds is freed: a lazy view's attach takes
567
+ scratch outside any enter / exit bracket. */
568
+ ca_lazy_arena_trim(arena);
410
569
  /* Top-level reset to recover from a prior exception that left
411
570
  slots in_use without a matching release. Safe because
412
571
  depth==0 means no nested caller is mid-acquire. */
413
572
  int i;
414
573
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
415
- ca_lazy_arena.slots[i].in_use = 0;
416
- ca_lazy_arena.slots[i].object_elements = 0;
574
+ arena->slots[i].in_use = 0;
575
+ arena->slots[i].object_elements = 0;
417
576
  }
418
577
  }
419
- ca_lazy_arena.depth++;
578
+ arena->depth++;
420
579
  }
421
580
 
422
581
  void
423
582
  ca_lazy_arena_exit (void)
424
583
  {
425
- if ( ca_lazy_arena.depth > 0 ) {
426
- ca_lazy_arena.depth--;
584
+ ca_lazy_arena_t *arena = ca_lazy_arena_get();
585
+ if ( arena->depth > 0 ) {
586
+ arena->depth--;
427
587
  }
428
588
  /* Keep all slot buffers, and leave in_use and the object tags alone.
429
589
  A scratch can be acquired outside any enter/exit bracket -- a lazy
@@ -437,15 +597,67 @@ ca_lazy_arena_exit (void)
437
597
  from being collected in the first place. */
438
598
  }
439
599
 
600
+ /* Runs body(arg) inside an enter / exit bracket that closes however body
601
+ leaves. cleanup(arg), when given, runs first -- the place to release
602
+ the scratches and detach the arrays body acquired -- and the exit runs
603
+ after it.
604
+
605
+ A raise that skipped the exit would leave the depth above 0 for the
606
+ rest of the process: the depth-0 reset that recovers abandoned slots
607
+ would never fire again, and once 32 slots were abandoned every lazy
608
+ expression would fail. */
609
+
610
+ typedef struct {
611
+ VALUE (*cleanup)(VALUE);
612
+ VALUE arg;
613
+ } ca_lazy_arena_ensure_t;
614
+
615
+ static VALUE
616
+ ca_lazy_arena_ensure (VALUE varg)
617
+ {
618
+ ca_lazy_arena_ensure_t *e = (ca_lazy_arena_ensure_t *) varg;
619
+ if ( e->cleanup ) {
620
+ e->cleanup(e->arg);
621
+ }
622
+ ca_lazy_arena_exit();
623
+ return Qnil;
624
+ }
625
+
626
+ VALUE
627
+ ca_lazy_arena_protect (VALUE (*body)(VALUE), VALUE (*cleanup)(VALUE),
628
+ VALUE arg)
629
+ {
630
+ ca_lazy_arena_ensure_t e;
631
+ e.cleanup = cleanup;
632
+ e.arg = arg;
633
+ ca_lazy_arena_enter();
634
+ return rb_ensure(body, arg, ca_lazy_arena_ensure, (VALUE) &e);
635
+ }
636
+
637
+ /* A cleanup for ca_lazy_arena_protect when body holds one scratch: arg
638
+ points at a struct whose first member is that scratch (NULL until it
639
+ is acquired). */
640
+ VALUE
641
+ ca_lazy_arena_release_held (VALUE arg)
642
+ {
643
+ void **held = (void **) arg;
644
+ if ( *held ) {
645
+ ca_lazy_arena_release(*held);
646
+ *held = NULL;
647
+ }
648
+ return Qnil;
649
+ }
650
+
440
651
  void *
441
652
  ca_lazy_arena_acquire (ca_size_t bytes)
442
653
  {
654
+ ca_lazy_arena_t *arena = ca_lazy_arena_get();
443
655
  int i, best, empty;
444
656
  ca_size_t best_bytes;
445
657
 
446
658
  if ( bytes <= 0 ) bytes = 1;
447
659
 
448
- ca_lazy_arena.debug_acquire_count++;
660
+ arena->debug_acquire_count++;
449
661
 
450
662
  /* Pass 1: best-fit among unused warm slots (smallest >= bytes).
451
663
  CAREFUL: ca_size_t is signed int64_t, so the "no match yet"
@@ -454,7 +666,7 @@ ca_lazy_arena_acquire (ca_size_t bytes)
454
666
  best = -1;
455
667
  best_bytes = CA_LENGTH_MAX;
456
668
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
457
- ca_lazy_arena_slot_t *s = &ca_lazy_arena.slots[i];
669
+ ca_lazy_arena_slot_t *s = &arena->slots[i];
458
670
  if ( s->in_use ) continue;
459
671
  if ( s->ptr == NULL ) continue;
460
672
  if ( s->bytes < bytes ) continue;
@@ -464,50 +676,68 @@ ca_lazy_arena_acquire (ca_size_t bytes)
464
676
  }
465
677
  }
466
678
  if ( best >= 0 ) {
467
- ca_lazy_arena.slots[best].in_use = 1;
468
- ca_lazy_arena.slots[best].object_elements = 0;
469
- ca_lazy_arena.debug_reuse_count++;
470
- return ca_lazy_arena.slots[best].ptr;
679
+ arena->slots[best].in_use = 1;
680
+ arena->slots[best].object_elements = 0;
681
+ arena->debug_reuse_count++;
682
+ return arena->slots[best].ptr;
471
683
  }
472
684
 
473
685
 
474
- /* Pass 2: empty slot — xmalloc a fresh buffer. */
475
- empty = -1;
686
+ /* Pass 2: every free warm slot is too small. Grow the largest of
687
+ them rather than open another slot: a slot that is free now was not
688
+ needed at the same time as this request, so the arena holds one
689
+ buffer per simultaneous acquire rather than one per size ever asked
690
+ for. Taking the largest leaves the small slots to the small
691
+ requests (mask scratch next to data scratch).
692
+
693
+ The slot is emptied before the xmalloc so that a NoMemoryError
694
+ leaves it unallocated instead of pointing at freed memory. */
695
+ best = -1;
696
+ best_bytes = -1;
476
697
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
477
- if ( ca_lazy_arena.slots[i].ptr == NULL ) {
478
- empty = i;
479
- break;
698
+ ca_lazy_arena_slot_t *s = &arena->slots[i];
699
+ if ( s->in_use || s->ptr == NULL ) continue;
700
+ if ( s->bytes > best_bytes ) {
701
+ best = i;
702
+ best_bytes = s->bytes;
480
703
  }
481
704
  }
482
- if ( empty >= 0 ) {
483
- ca_lazy_arena.slots[empty].ptr = xmalloc(bytes);
484
- ca_lazy_arena.slots[empty].bytes = bytes;
485
- ca_lazy_arena.slots[empty].in_use = 1;
486
- ca_lazy_arena.slots[empty].object_elements = 0;
487
- ca_lazy_arena.debug_xmalloc_count++;
488
- return ca_lazy_arena.slots[empty].ptr;
705
+ if ( best >= 0 ) {
706
+ ca_lazy_arena_slot_t *s = &arena->slots[best];
707
+ void *old = s->ptr;
708
+ s->ptr = NULL;
709
+ s->bytes = 0;
710
+ xfree(old);
711
+ s->ptr = xmalloc(bytes); /* cells: ca_lazy_arena_acquire_object clears object slots */
712
+ s->bytes = bytes;
713
+ s->in_use = 1;
714
+ s->object_elements = 0;
715
+ arena->debug_xmalloc_count++;
716
+ return s->ptr;
489
717
  }
490
718
 
491
- /* Pass 3: all slots have ptrs. Find a non-in-use slot and grow it
492
- (= xfree + xmalloc). This pays a mmap roundtrip but only on
493
- slot pool exhaustion (= unusual size request). */
719
+ /* Pass 3: every warm slot is in use. Open an empty one. */
720
+ empty = -1;
494
721
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
495
- if ( ! ca_lazy_arena.slots[i].in_use ) {
496
- xfree(ca_lazy_arena.slots[i].ptr);
497
- ca_lazy_arena.slots[i].ptr = xmalloc(bytes);
498
- ca_lazy_arena.slots[i].bytes = bytes;
499
- ca_lazy_arena.slots[i].in_use = 1;
500
- ca_lazy_arena.slots[i].object_elements = 0;
501
- ca_lazy_arena.debug_xmalloc_count++;
502
- return ca_lazy_arena.slots[i].ptr;
722
+ if ( arena->slots[i].ptr == NULL ) {
723
+ empty = i;
724
+ break;
503
725
  }
504
726
  }
727
+ if ( empty >= 0 ) {
728
+ arena->slots[empty].ptr = xmalloc(bytes); /* cells: ca_lazy_arena_acquire_object clears object slots */
729
+ arena->slots[empty].bytes = bytes;
730
+ arena->slots[empty].in_use = 1;
731
+ arena->slots[empty].object_elements = 0;
732
+ arena->debug_xmalloc_count++;
733
+ return arena->slots[empty].ptr;
734
+ }
505
735
 
506
736
  /* Pass 4: all CA_LAZY_ARENA_SLOTS in use simultaneously. This is
507
737
  a programming error or a pathological chain. Raise. */
508
- rb_raise(rb_eRuntimeError,
509
- "ca_lazy_arena_acquire: all %d slots in use simultaneously "
510
- "(chain depth exceeds arena capacity)",
738
+ rb_raise(rb_eArgError,
739
+ "lazy expression too deep: it needs more than %d intermediate "
740
+ "arrays at once",
511
741
  CA_LAZY_ARENA_SLOTS);
512
742
  return NULL; /* unreachable */
513
743
  }
@@ -515,12 +745,13 @@ ca_lazy_arena_acquire (ca_size_t bytes)
515
745
  void
516
746
  ca_lazy_arena_release (void *ptr)
517
747
  {
748
+ ca_lazy_arena_t *arena = ca_lazy_arena_get();
518
749
  int i;
519
750
  if ( ptr == NULL ) return;
520
751
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
521
- if ( ca_lazy_arena.slots[i].ptr == ptr ) {
522
- ca_lazy_arena.slots[i].in_use = 0;
523
- ca_lazy_arena.slots[i].object_elements = 0;
752
+ if ( arena->slots[i].ptr == ptr ) {
753
+ arena->slots[i].in_use = 0;
754
+ arena->slots[i].object_elements = 0;
524
755
  return;
525
756
  }
526
757
  }
@@ -539,21 +770,18 @@ ca_lazy_arena_release (void *ptr)
539
770
  void *
540
771
  ca_lazy_arena_acquire_object (ca_size_t n_elements)
541
772
  {
773
+ ca_lazy_arena_t *arena = ca_lazy_arena_get();
542
774
  void *ptr;
543
775
  int i;
544
776
 
545
777
  if ( n_elements < 0 ) n_elements = 0;
546
778
  ptr = ca_lazy_arena_acquire(n_elements * (ca_size_t) sizeof(VALUE));
547
779
 
548
- {
549
- VALUE *p = (VALUE *) ptr;
550
- ca_size_t n = n_elements;
551
- while ( n-- ) *p++ = Qnil;
552
- }
780
+ ca_object_cells_clear(ptr, n_elements);
553
781
 
554
782
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
555
- if ( ca_lazy_arena.slots[i].ptr == ptr ) {
556
- ca_lazy_arena.slots[i].object_elements = n_elements;
783
+ if ( arena->slots[i].ptr == ptr ) {
784
+ arena->slots[i].object_elements = n_elements;
557
785
  break;
558
786
  }
559
787
  }
@@ -561,11 +789,11 @@ ca_lazy_arena_acquire_object (ca_size_t n_elements)
561
789
  }
562
790
 
563
791
  static void
564
- ca_lazy_arena_mark_object_slots (void)
792
+ ca_lazy_arena_mark_object_slots (ca_lazy_arena_t *arena)
565
793
  {
566
794
  int i;
567
795
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
568
- ca_lazy_arena_slot_t *s = &ca_lazy_arena.slots[i];
796
+ ca_lazy_arena_slot_t *s = &arena->slots[i];
569
797
  ca_size_t n;
570
798
  VALUE *p;
571
799
  if ( ! s->in_use || s->object_elements <= 0 ) continue;
@@ -577,45 +805,52 @@ ca_lazy_arena_mark_object_slots (void)
577
805
 
578
806
  /* Test instrumentation — exposed to Ruby for the arena smoke and
579
807
  regression assertions. Not user-facing API. */
808
+ #ifdef CARRAY_DEV_BUILD
580
809
  static VALUE
581
810
  rb_ca_lazy_arena_s_reset_counters (VALUE klass)
582
811
  {
583
- ca_lazy_arena.debug_acquire_count = 0;
584
- ca_lazy_arena.debug_xmalloc_count = 0;
585
- ca_lazy_arena.debug_reuse_count = 0;
812
+ ca_lazy_arena_t *arena = ca_lazy_arena_get();
813
+ arena->debug_acquire_count = 0;
814
+ arena->debug_xmalloc_count = 0;
815
+ arena->debug_reuse_count = 0;
586
816
  return Qnil;
587
817
  }
588
818
 
589
819
  static VALUE
590
820
  rb_ca_lazy_arena_s_acquire_count (VALUE klass)
591
821
  {
592
- return SIZE2NUM(ca_lazy_arena.debug_acquire_count);
822
+ ca_lazy_arena_t *arena = ca_lazy_arena_get();
823
+ return SIZE2NUM(arena->debug_acquire_count);
593
824
  }
594
825
 
595
826
  static VALUE
596
827
  rb_ca_lazy_arena_s_xmalloc_count (VALUE klass)
597
828
  {
598
- return SIZE2NUM(ca_lazy_arena.debug_xmalloc_count);
829
+ ca_lazy_arena_t *arena = ca_lazy_arena_get();
830
+ return SIZE2NUM(arena->debug_xmalloc_count);
599
831
  }
600
832
 
601
833
  static VALUE
602
834
  rb_ca_lazy_arena_s_reuse_count (VALUE klass)
603
835
  {
604
- return SIZE2NUM(ca_lazy_arena.debug_reuse_count);
836
+ ca_lazy_arena_t *arena = ca_lazy_arena_get();
837
+ return SIZE2NUM(arena->debug_reuse_count);
605
838
  }
606
839
 
607
840
  static VALUE
608
841
  rb_ca_lazy_arena_s_depth (VALUE klass)
609
842
  {
610
- return INT2NUM(ca_lazy_arena.depth);
843
+ ca_lazy_arena_t *arena = ca_lazy_arena_get();
844
+ return INT2NUM(arena->depth);
611
845
  }
612
846
 
613
847
  static VALUE
614
848
  rb_ca_lazy_arena_s_slot_in_use_count (VALUE klass)
615
849
  {
850
+ ca_lazy_arena_t *arena = ca_lazy_arena_get();
616
851
  int i, n = 0;
617
852
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
618
- if ( ca_lazy_arena.slots[i].in_use ) n++;
853
+ if ( arena->slots[i].in_use ) n++;
619
854
  }
620
855
  return INT2NUM(n);
621
856
  }
@@ -629,51 +864,21 @@ rb_ca_lazy_arena_s_slot_in_use_count (VALUE klass)
629
864
  static VALUE
630
865
  rb_ca_lazy_arena_s_slot_capacities (VALUE klass)
631
866
  {
867
+ ca_lazy_arena_t *arena = ca_lazy_arena_get();
632
868
  int i;
633
869
  VALUE arr = rb_ary_new();
634
870
  (void) klass;
635
871
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
636
- if ( ca_lazy_arena.slots[i].ptr != NULL ) {
637
- rb_ary_push(arr, SIZE2NUM(ca_lazy_arena.slots[i].bytes));
872
+ if ( arena->slots[i].ptr != NULL ) {
873
+ rb_ary_push(arr, SIZE2NUM(arena->slots[i].bytes));
638
874
  }
639
875
  }
640
876
  return arr;
641
877
  }
878
+ #endif /* CARRAY_DEV_BUILD */
642
879
 
643
880
  /* ------------------------------------------------------------------- */
644
881
 
645
- /* ca_lazy_wrap_scalar(other, self_ca) — wrap a non-CArray Ruby value
646
- * as a 1-element CScalar whose data_type follows the array's family,
647
- * mirroring the array+scalar branch of rb_ca_cast_self_or_other. The
648
- * scalar takes on self's data_type so binops preserve self's
649
- * precision (`f32_array + 2.5` stays f32).
650
- *
651
- * CA_OBJECT self -> CA_OBJECT
652
- * CA_BOOLEAN self -> derive from other's Ruby type via
653
- * ca_value_to_data_type (bool arithmetic is a
654
- * corner case; eager widens the array, so we
655
- * just let the scalar choose)
656
- * numeric self -> self_ca->data_type
657
- *
658
- * Callers broadcast against the array via stride 0 in xfer_stride. */
659
- VALUE
660
- ca_lazy_wrap_scalar (VALUE other, CArray *self_ca)
661
- {
662
- int8_t dt;
663
-
664
- if ( ca_is_object_type(self_ca) ) {
665
- dt = CA_OBJECT;
666
- }
667
- else if ( self_ca->data_type == CA_BOOLEAN ) {
668
- dt = ca_value_to_data_type(other);
669
- }
670
- else {
671
- dt = self_ca->data_type;
672
- }
673
-
674
- return rb_cscalar_new_with_value(dt, 0, other);
675
- }
676
-
677
882
  static VALUE
678
883
  rb_ca_lazy_marker_s_allocate (VALUE klass)
679
884
  {
@@ -687,11 +892,15 @@ rb_ca_lazy_marker_initialize_copy (VALUE self, VALUE other)
687
892
  {
688
893
  CALazyMarker *ca, *cs;
689
894
  TypedData_Get_Struct(self, CALazyMarker, &calazy_marker_data_type, ca);
895
+ ca_check_uninitialized(ca);
690
896
  TypedData_Get_Struct(other, CALazyMarker, &calazy_marker_data_type, cs);
691
897
  ca_lazy_marker_setup(ca, cs->parent);
898
+ ca_view_inherit_mask((VALUE) ca);
692
899
  return self;
693
900
  }
694
901
 
902
+ static void Init_ca_mask_of_operands (void);
903
+
695
904
  void
696
905
  Init_carray_lazy (void)
697
906
  {
@@ -707,16 +916,15 @@ Init_carray_lazy (void)
707
916
 
708
917
  rb_define_method(rb_cCArray, "lazy", rb_ca_lazy, 0);
709
918
 
710
- /* Hidden, never collected: its mark function is what keeps the
711
- in-flight CA_OBJECT buffers reachable. */
712
- ca_gc_guard = TypedData_Wrap_Struct(rb_cObject, &ca_gc_guard_data_type,
713
- &ca_gc_guard_body);
714
- rb_gc_register_mark_object(ca_gc_guard);
919
+ ca_gc_local_key = rb_ractor_local_storage_ptr_newkey(&ca_gc_local_type);
920
+
921
+ ca_lazy_arena_key = rb_ractor_local_storage_ptr_newkey(&ca_lazy_arena_type);
715
922
 
716
923
  rb_define_alloc_func(rb_cCALazyMarker, rb_ca_lazy_marker_s_allocate);
717
924
  rb_define_method(rb_cCALazyMarker, "initialize_copy",
718
925
  rb_ca_lazy_marker_initialize_copy, 1);
719
926
 
927
+ #ifdef CARRAY_DEV_BUILD
720
928
  /* Arena test instrumentation (not user-facing). Bound on CArray
721
929
  for symmetry with CAMonOp / CABinOp counters, but the arena is
722
930
  a singleton process-wide state. */
@@ -734,4 +942,610 @@ Init_carray_lazy (void)
734
942
  rb_ca_lazy_arena_s_slot_in_use_count, 0);
735
943
  rb_define_singleton_method(rb_cCArray, "__lazy_arena_slot_capacities__",
736
944
  rb_ca_lazy_arena_s_slot_capacities, 0);
945
+ #endif
946
+
947
+ Init_ca_mask_of_operands();
948
+ }
949
+
950
+ /* ---------------------------------------------------------------------------
951
+ Requests to the element-wise lazy views (ca_lazy_req_t, carray_internal.h).
952
+
953
+ An operand is pulled over the cells the request names. In the region
954
+ form the strides address the view's cells, so they are restated at the
955
+ operand's cell width: strides[k] / bytes is the index step the two share.
956
+ (A packed stride over counts is not a substitute -- it walks cells 0, 1,
957
+ 2 where the request asked for 0, 2, 4.)
958
+ --------------------------------------------------------------------------- */
959
+
960
+ void
961
+ ca_lazy_req_region (ca_lazy_req_t *req, void *view, ca_size_t *starts,
962
+ ca_size_t *counts, ca_size_t *strides)
963
+ {
964
+ CArray *ca = (CArray *) view;
965
+ int8_t k;
966
+ req->n = 1;
967
+ for ( k = 0; k < ca->ndim; k++ ) {
968
+ req->n *= counts[k];
969
+ }
970
+ req->addrs = NULL;
971
+ req->starts = starts;
972
+ req->counts = counts;
973
+ req->strides = strides;
974
+ req->bytes = ca->bytes;
975
+ req->ndim = ca->ndim;
976
+ }
977
+
978
+ void
979
+ ca_lazy_req_addrs (ca_lazy_req_t *req, ca_size_t n, ca_size_t *addrs)
980
+ {
981
+ req->n = n;
982
+ req->addrs = addrs;
983
+ req->starts = NULL;
984
+ req->counts = NULL;
985
+ req->strides = NULL;
986
+ req->bytes = 0;
987
+ req->ndim = 0;
988
+ }
989
+
990
+ /* Whether the request names one contiguous run of the view's cells, in
991
+ the view's own row-major order -- the case in which an operand's buffer
992
+ already holds the cells in the order the kernel reads them. The strides
993
+ must be the native row-major ones, and the box must be whole on every
994
+ axis inside the outermost one it does not span with a count of 1. */
995
+ int
996
+ ca_lazy_req_is_packed (const ca_lazy_req_t *req, void *view)
997
+ {
998
+ CArray *ca = (CArray *) view;
999
+ ca_size_t native = ca->bytes;
1000
+ int partial = 0;
1001
+ int8_t k;
1002
+ if ( req->addrs ) {
1003
+ return 0;
1004
+ }
1005
+ for ( k = ca->ndim - 1; k >= 0; k-- ) {
1006
+ if ( req->strides[k] != native ) {
1007
+ return 0;
1008
+ }
1009
+ if ( partial && req->counts[k] != 1 ) {
1010
+ return 0;
1011
+ }
1012
+ if ( req->counts[k] != ca->dim[k] ) {
1013
+ partial = 1;
1014
+ }
1015
+ native *= ca->dim[k];
1016
+ }
1017
+ return 1;
1018
+ }
1019
+
1020
+ /* The cells of a request that any of `ops` masks, as a packed boolean
1021
+ slab in arena scratch, or NULL when none of them is masked. An operand
1022
+ of one cell masks every cell or none. Kernels that can raise on a cell,
1023
+ or that call Ruby for it, take this to skip the masked cells, as the
1024
+ eager operators do. Release with ca_lazy_arena_release. */
1025
+ boolean8_t *
1026
+ ca_lazy_req_mask (const ca_lazy_req_t *req, int n, CArray **ops)
1027
+ {
1028
+ boolean8_t *m = NULL;
1029
+ ca_size_t j;
1030
+ int i;
1031
+ int8_t k;
1032
+
1033
+ for ( i = 0; i < n; i++ ) {
1034
+ CArray *op = ops[i];
1035
+ if ( ! op || ! ca_has_mask(op) ) {
1036
+ continue;
1037
+ }
1038
+ if ( ! m ) {
1039
+ m = (boolean8_t *) ca_lazy_arena_acquire(req->n);
1040
+ memset(m, 0, req->n);
1041
+ }
1042
+ if ( op->elements == 1 ) {
1043
+ ca_size_t starts[CA_RANK_MAX], counts[CA_RANK_MAX];
1044
+ ca_size_t strides[CA_RANK_MAX];
1045
+ boolean8_t bit = 0;
1046
+ for ( k = 0; k < op->ndim; k++ ) {
1047
+ starts[k] = 0; counts[k] = 1; strides[k] = 1;
1048
+ }
1049
+ ca_xfer_stride(op->mask, starts, counts, strides, &bit, CA_XFER_GET);
1050
+ if ( bit ) {
1051
+ memset(m, 1, req->n);
1052
+ }
1053
+ }
1054
+ else {
1055
+ volatile VALUE holder;
1056
+ boolean8_t *om = ALLOCV_N(boolean8_t, holder, req->n);
1057
+ ca_lazy_req_pull(op->mask, req, om, CA_XFER_GET);
1058
+ for ( j = 0; j < req->n; j++ ) {
1059
+ m[j] |= om[j];
1060
+ }
1061
+ ALLOCV_END(holder);
1062
+ }
1063
+ }
1064
+ return m;
1065
+ }
1066
+
1067
+ /* A scalar operand (a Ruby value or a CScalar) that the eager promotion
1068
+ has cast to the array's data_type comes back as a cast view over a
1069
+ CScalar. Fix it as the CScalar the cast gives, so the expression holds
1070
+ a plain value leaf. */
1071
+ VALUE
1072
+ ca_lazy_settle_scalar (VALUE operand, int was_scalar)
1073
+ {
1074
+ CArray *ca;
1075
+ if ( ! was_scalar ) {
1076
+ return operand;
1077
+ }
1078
+ /* rb_obj_is_cscalar is true for any view carrying the scalar flag, the
1079
+ cast view included; the obj_type says whether it is a CScalar. */
1080
+ TypedData_Get_Struct(operand, CArray, &carray_data_type, ca);
1081
+ if ( ca->obj_type != CA_OBJ_SCALAR ) {
1082
+ return rb_ca_copy(operand);
1083
+ }
1084
+ return operand;
1085
+ }
1086
+
1087
+ void
1088
+ ca_lazy_req_pull (void *operand, const ca_lazy_req_t *req, void *buf, int dir)
1089
+ {
1090
+ CArray *op = (CArray *) operand;
1091
+ ca_size_t strides[CA_RANK_MAX];
1092
+ int8_t k;
1093
+
1094
+ if ( req->addrs ) {
1095
+ ca_xfer_addrs(op, req->n, req->addrs, buf, dir);
1096
+ return;
1097
+ }
1098
+ for ( k = 0; k < req->ndim; k++ ) {
1099
+ strides[k] = req->strides[k] / req->bytes * op->bytes;
1100
+ }
1101
+ ca_xfer_stride(op, req->starts, req->counts, strides, buf, dir);
1102
+ }
1103
+
1104
+ /* An expression the caller is about to compute whole -- a reduction along
1105
+ an axis, a sort, a median -- computed by the registered expression
1106
+ evaluator instead, when there is one and it takes the expression.
1107
+ Returns the array it made, or Qnil where nothing is registered, self is
1108
+ not a lazy expression, or the evaluator declined (CArray::Fusion.evaluate
1109
+ decides, and asks only above its size threshold). The caller goes on
1110
+ with that array in place of self, or with self as before.
1111
+
1112
+ Only where the expression would be made whole anyway: a reduction that
1113
+ streams the expression in chunks keeps doing so, since asking here would
1114
+ make it whole and take that much more memory. */
1115
+ VALUE
1116
+ ca_lazy_evaluated (VALUE self)
1117
+ {
1118
+ /* rb_intern caches each literal by itself; a shared guard over several
1119
+ IDs would let a second Ractor read one not yet set. */
1120
+ ID id_evaluator = rb_intern("@expression_evaluator");
1121
+ ID id_fusion = rb_intern("Fusion");
1122
+ ID id_evaluate = rb_intern("evaluate");
1123
+ CArray *ca;
1124
+ GetCArray(self, ca);
1125
+ if ( ! ca_is_lazy_view(ca) ) {
1126
+ return Qnil;
1127
+ }
1128
+ /* registered in the main Ractor, and asked only there */
1129
+ if ( ! ca_in_main_ractor() || ! RTEST(rb_attr_get(rb_cCArray, id_evaluator)) ) {
1130
+ return Qnil;
1131
+ }
1132
+ return rb_funcall(rb_const_get(rb_cCArray, id_fusion), id_evaluate, 1, self);
1133
+ }
1134
+
1135
+ /* ---------------------------------------------------------------------------
1136
+ CAMaskOfOperands: the mask of an element-wise lazy operation over several
1137
+ operands (CABinOp, CABinCmp, CATriOp).
1138
+
1139
+ The operation computes its values each time it is read, so its mask is
1140
+ computed the same way: a request pulls each operand's mask over the cells
1141
+ it names and ORs them. An operand with no mask reads as all false, so an
1142
+ operand that is given a mask after the expression was first read is seen
1143
+ on the next read -- and reading the mask never gives an operand one.
1144
+
1145
+ For the boolean `|` and `&` (three-valued) a masked cell is not masked in
1146
+ the result when an unmasked operand already decides it: true for `|`,
1147
+ false for `&`. The operands' values are pulled for that.
1148
+
1149
+ The view is read-only and has no mask of its own (CA_FLAG_VALUE_ARRAY),
1150
+ so nothing above it asks for one. Its parent is the operation, which
1151
+ owns it as its mask; the operands are read through the operation's
1152
+ parents[].
1153
+ --------------------------------------------------------------------------- */
1154
+
1155
+ int8_t CA_OBJ_MASK_OF_OPERANDS;
1156
+ VALUE rb_cCAMaskOfOperands;
1157
+
1158
+ typedef struct CAMaskOfOperands {
1159
+ int16_t obj_type;
1160
+ int8_t data_type;
1161
+ int8_t ndim;
1162
+ int32_t flags;
1163
+ ca_size_t bytes;
1164
+ ca_size_t elements;
1165
+ ca_size_t *dim;
1166
+ char *ptr;
1167
+ CArray *mask;
1168
+ char *_pool;
1169
+ CArray *parent;
1170
+ uint32_t attach;
1171
+ uint8_t nosync;
1172
+ int8_t mode; /* CA_LAZY_MASK_OR / _KLEENE_OR / _KLEENE_AND / _SELECT */
1173
+ } CAMaskOfOperands;
1174
+
1175
+ static size_t
1176
+ ca_mask_of_operands_dsize (const void *ap)
1177
+ {
1178
+ const CAMaskOfOperands *ca = (const CAMaskOfOperands *) ap;
1179
+ return sizeof(CAMaskOfOperands) + ca->ndim * sizeof(ca_size_t);
1180
+ }
1181
+
1182
+ const rb_data_type_t camask_of_operands_data_type = {
1183
+ .parent = &caview_data_type,
1184
+ .wrap_struct_name = "CAMaskOfOperands",
1185
+ .function = {
1186
+ .dmark = ca_mark,
1187
+ .dfree = ca_free,
1188
+ .dsize = ca_mask_of_operands_dsize,
1189
+ .dcompact = NULL
1190
+ },
1191
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
1192
+ };
1193
+
1194
+ CArray *
1195
+ ca_mask_of_operands_new (CArray *operation, int mode)
1196
+ {
1197
+ CAMaskOfOperands *ca = ALLOC(CAMaskOfOperands);
1198
+ ca->_pool = NULL;
1199
+ ca->obj_type = CA_OBJ_MASK_OF_OPERANDS;
1200
+ ca->data_type = CA_BOOLEAN;
1201
+ ca->flags = CA_FLAG_READ_ONLY | CA_FLAG_VALUE_ARRAY;
1202
+ ca->ndim = operation->ndim;
1203
+ ca->bytes = 1;
1204
+ ca->elements = operation->elements;
1205
+ ca->dim = ALLOC_N(ca_size_t, operation->ndim);
1206
+ ca->ptr = NULL;
1207
+ ca->mask = NULL;
1208
+ ca->parent = operation;
1209
+ ca->attach = 0;
1210
+ ca->nosync = 0;
1211
+ ca->mode = (int8_t) mode;
1212
+ memcpy(ca->dim, operation->dim, operation->ndim * sizeof(ca_size_t));
1213
+ if ( ca_is_scalar(operation) ) {
1214
+ ca_set_flag(ca, CA_FLAG_SCALAR);
1215
+ }
1216
+ return (CArray *) ca;
1217
+ }
1218
+
1219
+ static void
1220
+ free_ca_mask_of_operands (void *ap)
1221
+ {
1222
+ CAMaskOfOperands *ca = (CAMaskOfOperands *) ap;
1223
+ if ( ca != NULL ) {
1224
+ xfree(ca->ptr);
1225
+ xfree(ca->dim);
1226
+ xfree(ca);
1227
+ }
1228
+ }
1229
+
1230
+ static void *
1231
+ ca_mask_of_operands_func_clone (void *ap)
1232
+ {
1233
+ CAMaskOfOperands *ca = (CAMaskOfOperands *) ap;
1234
+ return ca_mask_of_operands_new(ca->parent, ca->mode);
1235
+ }
1236
+
1237
+ /* An operand of one cell beside a larger operation is broadcast: its one
1238
+ cell stands for every cell of the request. */
1239
+ static int
1240
+ ca_mask_of_operands_operand_is_scalar (CAMaskOfOperands *lm, CArray *op)
1241
+ {
1242
+ return ( op->elements == 1 && lm->elements > 1 );
1243
+ }
1244
+
1245
+ static void
1246
+ ca_mask_of_operands_pull (CAMaskOfOperands *lm, CArray *src, const ca_lazy_req_t *req,
1247
+ boolean8_t *buf)
1248
+ {
1249
+ if ( ca_mask_of_operands_operand_is_scalar(lm, src) ) {
1250
+ boolean8_t one;
1251
+ ca_xfer_all(src, &one, CA_XFER_GET);
1252
+ memset(buf, one ? 1 : 0, req->n);
1253
+ }
1254
+ else {
1255
+ ca_lazy_req_pull(src, req, buf, CA_XFER_GET);
1256
+ }
1257
+ }
1258
+
1259
+ /* select: masked where the condition is, else where the branch it
1260
+ chooses is. The condition arrives converted to the branches' type
1261
+ from boolean, so a cell is true exactly when one of its bytes is not
1262
+ zero. */
1263
+ static void
1264
+ ca_mask_of_operands_eval_select (CAMaskOfOperands *lm, const ca_lazy_req_t *req,
1265
+ boolean8_t *dst)
1266
+ {
1267
+ CAMultiParent *mp = (CAMultiParent *) lm->parent;
1268
+ CArray *cond = mp->parents[0];
1269
+ ca_size_t bytes = cond->bytes;
1270
+ ca_size_t n = req->n, i, b;
1271
+ volatile VALUE h_value, h_m1, h_m2;
1272
+ boolean8_t *m1, *m2;
1273
+ char *v;
1274
+ int32_t k;
1275
+
1276
+ memset(dst, 0, n);
1277
+ if ( ca_has_mask(cond) ) {
1278
+ ca_mask_of_operands_pull(lm, cond->mask, req, dst);
1279
+ }
1280
+ m1 = ALLOCV_N(boolean8_t, h_m1, n);
1281
+ m2 = ALLOCV_N(boolean8_t, h_m2, n);
1282
+ for ( k = 1; k <= 2; k++ ) {
1283
+ CArray *op = mp->parents[k];
1284
+ boolean8_t *m = ( k == 1 ) ? m1 : m2;
1285
+ if ( ca_has_mask(op) ) {
1286
+ ca_mask_of_operands_pull(lm, op->mask, req, m);
1287
+ }
1288
+ else {
1289
+ memset(m, 0, n);
1290
+ }
1291
+ }
1292
+ if ( ca_mask_of_operands_operand_is_scalar(lm, cond) ) {
1293
+ v = ALLOCV_N(char, h_value, bytes);
1294
+ ca_xfer_all(cond, v, CA_XFER_GET);
1295
+ for ( b = 0; b < bytes && ! v[b]; b++ ) ;
1296
+ for ( i = 0; i < n; i++ ) dst[i] |= ( b < bytes ) ? m1[i] : m2[i];
1297
+ }
1298
+ else {
1299
+ v = ALLOCV_N(char, h_value, n * bytes);
1300
+ ca_lazy_req_pull(cond, req, v, CA_XFER_GET);
1301
+ for ( i = 0; i < n; i++ ) {
1302
+ const char *c = v + i * bytes;
1303
+ for ( b = 0; b < bytes && ! c[b]; b++ ) ;
1304
+ dst[i] |= ( b < bytes ) ? m1[i] : m2[i];
1305
+ }
1306
+ }
1307
+ ALLOCV_END(h_value);
1308
+ ALLOCV_END(h_m2);
1309
+ ALLOCV_END(h_m1);
1310
+ }
1311
+
1312
+ static void
1313
+ ca_mask_of_operands_eval (CAMaskOfOperands *lm, const ca_lazy_req_t *req, boolean8_t *dst)
1314
+ {
1315
+ CAMultiParent *mp = (CAMultiParent *) lm->parent;
1316
+ int kleene = ( lm->mode == CA_LAZY_MASK_KLEENE_OR ||
1317
+ lm->mode == CA_LAZY_MASK_KLEENE_AND );
1318
+ boolean8_t absorbing = ( lm->mode == CA_LAZY_MASK_KLEENE_OR ) ? 1 : 0;
1319
+ volatile VALUE h_mask, h_value, h_known;
1320
+ boolean8_t *m, *v = NULL, *known = NULL;
1321
+ ca_size_t n = req->n, i;
1322
+ int32_t k;
1323
+
1324
+ if ( lm->mode == CA_LAZY_MASK_SELECT ) {
1325
+ ca_mask_of_operands_eval_select(lm, req, dst);
1326
+ return;
1327
+ }
1328
+
1329
+ memset(dst, 0, n);
1330
+ m = ALLOCV_N(boolean8_t, h_mask, n);
1331
+ if ( kleene ) {
1332
+ v = ALLOCV_N(boolean8_t, h_value, n);
1333
+ known = ALLOCV_N(boolean8_t, h_known, n);
1334
+ memset(known, 0, n);
1335
+ }
1336
+
1337
+ for ( k = 0; k < mp->n_parents; k++ ) {
1338
+ CArray *op = mp->parents[k];
1339
+ if ( ca_has_mask(op) ) {
1340
+ ca_mask_of_operands_pull(lm, op->mask, req, m);
1341
+ for ( i = 0; i < n; i++ ) dst[i] |= m[i];
1342
+ }
1343
+ else {
1344
+ memset(m, 0, n);
1345
+ }
1346
+ if ( kleene ) {
1347
+ ca_mask_of_operands_pull(lm, op, req, v);
1348
+ for ( i = 0; i < n; i++ ) {
1349
+ if ( ! m[i] && ( v[i] ? 1 : 0 ) == absorbing ) known[i] = 1;
1350
+ }
1351
+ }
1352
+ }
1353
+
1354
+ if ( kleene ) {
1355
+ for ( i = 0; i < n; i++ ) {
1356
+ if ( known[i] ) dst[i] = 0;
1357
+ }
1358
+ ALLOCV_END(h_known);
1359
+ ALLOCV_END(h_value);
1360
+ }
1361
+ ALLOCV_END(h_mask);
1362
+ }
1363
+
1364
+ NORETURN(static void ca_mask_of_operands_read_only (void));
1365
+ static void
1366
+ ca_mask_of_operands_read_only (void)
1367
+ {
1368
+ /* read-only-check: says it is the mask, which no lazy node owns */
1369
+ rb_raise(rb_eRuntimeError, "the mask of a lazy operation is read-only");
1370
+ }
1371
+
1372
+ static void
1373
+ ca_mask_of_operands_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
1374
+ ca_size_t *strides, void *data, int dir)
1375
+ {
1376
+ ca_lazy_req_t req;
1377
+ if ( ca_region_is_empty(((CArray *) ap)->ndim, counts) ) return;
1378
+ if ( dir != CA_XFER_GET ) ca_mask_of_operands_read_only();
1379
+ ca_lazy_req_region(&req, ap, starts, counts, strides);
1380
+ ca_mask_of_operands_eval((CAMaskOfOperands *) ap, &req, (boolean8_t *) data);
1381
+ }
1382
+
1383
+ static void
1384
+ ca_mask_of_operands_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
1385
+ void *data, int dir)
1386
+ {
1387
+ ca_lazy_req_t req;
1388
+ if ( dir != CA_XFER_GET ) ca_mask_of_operands_read_only();
1389
+ ca_lazy_req_addrs(&req, n, addrs);
1390
+ ca_mask_of_operands_eval((CAMaskOfOperands *) ap, &req, (boolean8_t *) data);
1391
+ }
1392
+
1393
+ static void
1394
+ ca_mask_of_operands_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
1395
+ {
1396
+ CArray *ca = (CArray *) ap;
1397
+ ca_size_t addr = 0;
1398
+ int8_t k;
1399
+ if ( dir != CA_XFER_GET ) ca_mask_of_operands_read_only();
1400
+ for ( k = 0; k < ca->ndim; k++ ) {
1401
+ addr = addr * ca->dim[k] + idx[k];
1402
+ }
1403
+ ca_mask_of_operands_func_xfer_addrs(ap, 1, &addr, data, dir);
1404
+ }
1405
+
1406
+ static void
1407
+ ca_mask_of_operands_func_xfer_all (void *ap, void *data, int dir)
1408
+ {
1409
+ CArray *ca = (CArray *) ap;
1410
+ ca_size_t starts[CA_RANK_MAX];
1411
+ ca_size_t native[CA_RANK_MAX];
1412
+ ca_size_t s = 1;
1413
+ int8_t k;
1414
+ for ( k = ca->ndim - 1; k >= 0; k-- ) { native[k] = s; s *= ca->dim[k]; }
1415
+ for ( k = 0; k < ca->ndim; k++ ) starts[k] = 0;
1416
+ ca_mask_of_operands_func_xfer_stride(ap, starts, ca->dim, native, data, dir);
1417
+ }
1418
+
1419
+ static VALUE
1420
+ ca_mask_of_operands_fill_buffer (VALUE arg)
1421
+ {
1422
+ void **a = (void **) arg;
1423
+ ca_mask_of_operands_func_xfer_all(a[0], a[1], CA_XFER_GET);
1424
+ return Qnil;
1425
+ }
1426
+
1427
+ /* The buffer is published only once it is filled: a read that raises
1428
+ frees it and leaves the view unattached. */
1429
+ static void
1430
+ ca_mask_of_operands_func_attach (void *ap)
1431
+ {
1432
+ CAMaskOfOperands *ca = (CAMaskOfOperands *) ap;
1433
+ char *buf = ca_alloc_cells((CArray *) ca);
1434
+ void *a[2];
1435
+ a[0] = ca;
1436
+ a[1] = buf;
1437
+ ca_fill_or_xfree(buf, ca_mask_of_operands_fill_buffer, (VALUE) a);
1438
+ ca->ptr = buf;
1439
+ }
1440
+
1441
+ static void
1442
+ ca_mask_of_operands_func_allocate (void *ap)
1443
+ {
1444
+ CAMaskOfOperands *ca = (CAMaskOfOperands *) ap;
1445
+ ca->ptr = ca_alloc_cells((CArray *) ca);
1446
+ memset(ca->ptr, 0, ca->elements);
1447
+ }
1448
+
1449
+ static void
1450
+ ca_mask_of_operands_func_sync (void *ap)
1451
+ {
1452
+ /* read-only */
1453
+ }
1454
+
1455
+ static void
1456
+ ca_mask_of_operands_func_detach (void *ap)
1457
+ {
1458
+ CAMaskOfOperands *ca = (CAMaskOfOperands *) ap;
1459
+ xfree(ca->ptr);
1460
+ ca->ptr = NULL;
1461
+ }
1462
+
1463
+ NORETURN(static void ca_mask_of_operands_func_fill_data (void *ap, void *ptr));
1464
+ static void
1465
+ ca_mask_of_operands_func_fill_data (void *ap, void *ptr)
1466
+ {
1467
+ ca_mask_of_operands_read_only();
1468
+ }
1469
+
1470
+ NORETURN(static void ca_mask_of_operands_func_create_mask (void *ap));
1471
+ static void
1472
+ ca_mask_of_operands_func_create_mask (void *ap)
1473
+ {
1474
+ rb_raise(rb_eTypeError, "can not create mask array for the mask array");
1475
+ }
1476
+
1477
+ ca_operation_function_t ca_mask_of_operands_func = {
1478
+ -1, /* CA_OBJ_MASK_OF_OPERANDS, set at install time */
1479
+ CA_VIEW_ARRAY,
1480
+ free_ca_mask_of_operands,
1481
+ ca_mask_of_operands_func_clone,
1482
+ ca_mask_of_operands_func_allocate,
1483
+ ca_mask_of_operands_func_attach,
1484
+ ca_mask_of_operands_func_sync,
1485
+ ca_mask_of_operands_func_detach,
1486
+ ca_mask_of_operands_func_fill_data,
1487
+ ca_mask_of_operands_func_create_mask,
1488
+ ca_mask_of_operands_func_xfer_index,
1489
+ ca_mask_of_operands_func_xfer_addrs,
1490
+ NULL, /* fold_stride: computed, never folds */
1491
+ ca_mask_of_operands_func_xfer_stride,
1492
+ ca_mask_of_operands_func_xfer_all,
1493
+ };
1494
+
1495
+ static void
1496
+ Init_ca_mask_of_operands (void)
1497
+ {
1498
+ rb_cCAMaskOfOperands = rb_define_class("CAMaskOfOperands", rb_cCAView);
1499
+ CA_OBJ_MASK_OF_OPERANDS = ca_install_obj_type(rb_cCAMaskOfOperands,
1500
+ &camask_of_operands_data_type,
1501
+ rb_cCArrayMask,
1502
+ &carray_mask_data_type,
1503
+ &ca_mask_of_operands_func,
1504
+ sizeof(ca_mask_of_operands_func));
1505
+ rb_undef_alloc_func(rb_cCAMaskOfOperands);
1506
+ ca_iter_register_source_kind(CA_OBJ_MASK_OF_OPERANDS, CA_ITER_SRC_ATTACH);
1507
+ }
1508
+
1509
+ /* The mask of a lazy operation over several operands: shared with the one
1510
+ masked operand when no other operand can ever be given a mask, computed
1511
+ from all of them on each read otherwise (CAMaskOfOperands above).
1512
+
1513
+ An operand can be given a mask later unless it is broadcast from one
1514
+ cell (a scalar the expression made from a literal), a value array, or a
1515
+ read-only entity. The shared mask is read-only, as the operation is.
1516
+
1517
+ With no masked operand the mask is still made when asked for (a view
1518
+ over the operation that masks cells of its own, such as a shift with
1519
+ fill UNDEF, needs one to lie over): it reads all false until an
1520
+ operand is given a mask. */
1521
+ CArray *
1522
+ ca_lazy_operation_mask (CArray *operation, int mode)
1523
+ {
1524
+ CAMultiParent *mp = (CAMultiParent *) operation;
1525
+ CArray *masked = NULL;
1526
+ int32_t k, n_masked = 0, n_open = 0;
1527
+
1528
+ for ( k = 0; k < mp->n_parents; k++ ) {
1529
+ CArray *op = mp->parents[k];
1530
+ if ( ca_has_mask(op) ) {
1531
+ masked = op;
1532
+ n_masked++;
1533
+ }
1534
+ else if ( ! ( op->elements == 1 && operation->elements > 1 )
1535
+ && ! ca_is_value_array(op)
1536
+ && ! ( ca_is_entity(op) && ca_is_readonly(op) ) ) {
1537
+ n_open++;
1538
+ }
1539
+ }
1540
+
1541
+ if ( mode == CA_LAZY_MASK_OR && n_masked == 1 && n_open == 0
1542
+ && masked->elements == operation->elements ) {
1543
+ CArray *shared = (CArray *) ca_refer_new(masked->mask, CA_BOOLEAN,
1544
+ operation->ndim, operation->dim,
1545
+ 0, 0);
1546
+ ca_set_flag(shared, CA_FLAG_READ_ONLY);
1547
+ return shared;
1548
+ }
1549
+
1550
+ return ca_mask_of_operands_new(operation, mode);
737
1551
  }