carray 3.0.1 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +824 -0
  3. data/README.md +5 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +258 -4
  6. data/ext/ca_axis_group.c +368 -184
  7. data/ext/ca_binop_dispatch.c +54 -32
  8. data/ext/ca_binop_dispatch.h +1 -0
  9. data/ext/ca_categorical_iterator.c +90 -41
  10. data/ext/ca_compare.c +8 -2
  11. data/ext/ca_for_buffer.h +21 -68
  12. data/ext/ca_group_iter.c +27 -11
  13. data/ext/ca_kernel_iterator.c +960 -245
  14. data/ext/ca_kernel_iterator.h +352 -181
  15. data/ext/ca_monop_dispatch.c +4 -4
  16. data/ext/ca_monop_dispatch.h +4 -0
  17. data/ext/ca_obj_array.c +194 -42
  18. data/ext/ca_obj_bincmp.c +152 -121
  19. data/ext/ca_obj_binop.c +107 -210
  20. data/ext/ca_obj_bitarray.c +37 -24
  21. data/ext/ca_obj_bitfield.c +95 -62
  22. data/ext/ca_obj_block.c +5 -4
  23. data/ext/ca_obj_byte_swap.c +40 -40
  24. data/ext/ca_obj_const_string.c +180 -50
  25. data/ext/ca_obj_face.c +428 -62
  26. data/ext/ca_obj_face.h +107 -47
  27. data/ext/ca_obj_fake.c +21 -50
  28. data/ext/ca_obj_farray.c +1 -0
  29. data/ext/ca_obj_field.c +16 -3
  30. data/ext/ca_obj_fixlen_string.c +39 -26
  31. data/ext/ca_obj_grid.c +181 -124
  32. data/ext/ca_obj_meld.c +235 -55
  33. data/ext/ca_obj_moncmp.c +61 -61
  34. data/ext/ca_obj_monop.c +125 -107
  35. data/ext/ca_obj_object.c +551 -233
  36. data/ext/ca_obj_record.c +51 -16
  37. data/ext/ca_obj_reduce.c +5 -8
  38. data/ext/ca_obj_refer.c +117 -33
  39. data/ext/ca_obj_remap.c +57 -17
  40. data/ext/ca_obj_repeat.c +21 -5
  41. data/ext/ca_obj_roll.c +115 -11
  42. data/ext/ca_obj_select.c +88 -75
  43. data/ext/ca_obj_select_axis.c +58 -60
  44. data/ext/ca_obj_shift.c +22 -102
  45. data/ext/ca_obj_stack.c +223 -123
  46. data/ext/ca_obj_stride.c +267 -35
  47. data/ext/ca_obj_string.c +25 -22
  48. data/ext/ca_obj_tile.c +32 -16
  49. data/ext/ca_obj_time.c +23 -25
  50. data/ext/ca_obj_timedelta.c +21 -23
  51. data/ext/ca_obj_transpose.c +12 -3
  52. data/ext/ca_obj_triop.c +110 -117
  53. data/ext/ca_obj_window.c +157 -29
  54. data/ext/ca_op_ipower.c +25 -13
  55. data/ext/ca_op_powi.h +57 -30
  56. data/ext/ca_pow5_table.h +656 -0
  57. data/ext/ca_rng_normal.h +42 -0
  58. data/ext/ca_rng_xoshiro256pp.h +105 -0
  59. data/ext/ca_sort_kernels.h +70 -0
  60. data/ext/ca_sweep_engine.c +385 -198
  61. data/ext/ca_sweep_engine.h +31 -23
  62. data/ext/ca_triop_dispatch.c +4 -1
  63. data/ext/ca_triop_dispatch.h +5 -2
  64. data/ext/caframe_csv_reader.c +463 -0
  65. data/ext/carray.h +90 -28
  66. data/ext/carray_access.c +487 -231
  67. data/ext/carray_address_basis.c +731 -0
  68. data/ext/carray_attribute.c +45 -3
  69. data/ext/carray_bincount.c +32 -2
  70. data/ext/carray_broadcast.c +49 -17
  71. data/ext/carray_call_cfunc.c +667 -511
  72. data/ext/carray_cast.c +1556 -247
  73. data/ext/carray_cast_func.rb +34 -21
  74. data/ext/carray_conversion.c +197 -73
  75. data/ext/carray_copy.c +84 -38
  76. data/ext/carray_core.c +591 -119
  77. data/ext/carray_count.c +148 -96
  78. data/ext/carray_element.c +18 -27
  79. data/ext/carray_factorize.c +604 -291
  80. data/ext/carray_generate.c +76 -40
  81. data/ext/carray_histogram.c +152 -94
  82. data/ext/carray_hold.c +2 -1
  83. data/ext/carray_index_classifier.c +63 -96
  84. data/ext/carray_internal.h +400 -1
  85. data/ext/carray_lazy.c +834 -79
  86. data/ext/carray_loop.c +88 -66
  87. data/ext/carray_mask.c +705 -324
  88. data/ext/carray_math_kernel.h +1 -0
  89. data/ext/carray_median_percentile.c +263 -63
  90. data/ext/carray_memory_view.c +254 -248
  91. data/ext/carray_operator.c +1093 -363
  92. data/ext/carray_order.c +133 -69
  93. data/ext/carray_partition.c +102 -56
  94. data/ext/carray_random.c +582 -204
  95. data/ext/carray_scatter.c +308 -65
  96. data/ext/carray_segment.c +257 -0
  97. data/ext/carray_slab.c +183 -70
  98. data/ext/carray_sort.c +235 -240
  99. data/ext/carray_sort_kernel.c +8 -1
  100. data/ext/carray_struct.c +99 -21
  101. data/ext/carray_test.c +167 -64
  102. data/ext/carray_time_text.c +1182 -0
  103. data/ext/carray_utils.c +363 -98
  104. data/ext/extconf.rb +27 -2
  105. data/ext/mk_call_cfunc.rb +103 -118
  106. data/ext/mk_pow5_table.rb +35 -0
  107. data/ext/mkkernel.rb +1349 -851
  108. data/ext/ruby_carray.c +17 -2
  109. data/ext/version.h +4 -4
  110. data/ext/version.rb +4 -2
  111. data/lib/carray/attribute.rb +39 -0
  112. data/lib/carray/autoload_carray.rb +24 -3
  113. data/lib/carray/autoload_method_extension.rb +18 -0
  114. data/lib/carray/axis_group.rb +245 -20
  115. data/lib/carray/basics.rb +95 -25
  116. data/lib/carray/bincount_nd.rb +6 -0
  117. data/lib/carray/block_iterator.rb +200 -54
  118. data/lib/carray/boolean_reduce.rb +21 -29
  119. data/lib/carray/categorical.rb +244 -59
  120. data/lib/carray/categorical_iterator.rb +391 -757
  121. data/lib/carray/complex.rb +36 -29
  122. data/lib/carray/conditional.rb +94 -7
  123. data/lib/carray/const_string.rb +177 -27
  124. data/lib/carray/construct.rb +40 -0
  125. data/lib/carray/data_type_extension.rb +3 -0
  126. data/lib/carray/data_type_limits.rb +91 -0
  127. data/lib/carray/fixlen_string.rb +1 -1
  128. data/lib/carray/frame/concat.rb +157 -6
  129. data/lib/carray/frame/convert.rb +13 -7
  130. data/lib/carray/frame/csv_parser.rb +590 -76
  131. data/lib/carray/frame/describe.rb +79 -0
  132. data/lib/carray/frame/frame.rb +205 -35
  133. data/lib/carray/frame/group.rb +199 -40
  134. data/lib/carray/frame/io.rb +452 -60
  135. data/lib/carray/frame/join.rb +134 -46
  136. data/lib/carray/frame/records.rb +54 -19
  137. data/lib/carray/frame/reshape.rb +448 -0
  138. data/lib/carray/frame/sort.rb +26 -2
  139. data/lib/carray/frame/verbs.rb +726 -56
  140. data/lib/carray/frame.rb +2 -0
  141. data/lib/carray/fuse_source.rb +111 -2
  142. data/lib/carray/fusion.rb +229 -38
  143. data/lib/carray/inspect.rb +71 -20
  144. data/lib/carray/iterator.rb +157 -2
  145. data/lib/carray/lazy.rb +54 -67
  146. data/lib/carray/mask_gap_fill.rb +66 -50
  147. data/lib/carray/math.rb +32 -19
  148. data/lib/carray/meld_reduce.rb +40 -7
  149. data/lib/carray/methods/align_addr.rb +8 -4
  150. data/lib/carray/methods/bincount.rb +2 -2
  151. data/lib/carray/methods/bit_string.rb +14 -3
  152. data/lib/carray/methods/composition.rb +16 -10
  153. data/lib/carray/methods/discovery_along.rb +74 -0
  154. data/lib/carray/methods/factorize.rb +50 -0
  155. data/lib/carray/methods/gather_nd.rb +13 -2
  156. data/lib/carray/methods/insert_block.rb +9 -14
  157. data/lib/carray/methods/is_in.rb +130 -6
  158. data/lib/carray/methods/join.rb +1 -1
  159. data/lib/carray/methods/locate_addr.rb +235 -21
  160. data/lib/carray/methods/mask_duplicates.rb +35 -1
  161. data/lib/carray/methods/mode.rb +18 -1
  162. data/lib/carray/methods/nunique.rb +22 -1
  163. data/lib/carray/methods/repeat.rb +91 -0
  164. data/lib/carray/methods/resize.rb +21 -23
  165. data/lib/carray/methods/snap.rb +34 -12
  166. data/lib/carray/methods/unique.rb +52 -3
  167. data/lib/carray/record_order.rb +208 -0
  168. data/lib/carray/rng.rb +86 -0
  169. data/lib/carray/runtime.rb +14 -11
  170. data/lib/carray/segment_iterator.rb +990 -0
  171. data/lib/carray/serialize.rb +197 -27
  172. data/lib/carray/slab_iterator.rb +109 -40
  173. data/lib/carray/stack.rb +10 -4
  174. data/lib/carray/string_operation_extension.rb +9 -4
  175. data/lib/carray/struct.rb +142 -51
  176. data/lib/carray/struct_builder.rb +110 -14
  177. data/lib/carray/table.rb +2 -2
  178. data/lib/carray/time.rb +535 -105
  179. data/lib/carray/window_iterator.rb +259 -82
  180. data/lib/carray.rb +4 -2
  181. data/yard-stubs/ca_obj_array.rb +4 -4
  182. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  183. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  184. data/yard-stubs/ca_obj_block.rb +2 -7
  185. data/yard-stubs/ca_obj_fake.rb +1 -1
  186. data/yard-stubs/ca_obj_field.rb +1 -1
  187. data/yard-stubs/ca_obj_refer.rb +3 -3
  188. data/yard-stubs/ca_obj_roll.rb +0 -1
  189. data/yard-stubs/ca_obj_stride.rb +10 -7
  190. data/yard-stubs/ca_obj_transpose.rb +1 -1
  191. data/yard-stubs/ca_obj_window.rb +11 -4
  192. data/yard-stubs/carray_access.rb +6 -5
  193. data/yard-stubs/carray_broadcast.rb +2 -2
  194. data/yard-stubs/carray_cast.rb +78 -43
  195. data/yard-stubs/carray_conversion.rb +13 -1
  196. data/yard-stubs/carray_copy.rb +8 -3
  197. data/yard-stubs/carray_core.rb +3 -83
  198. data/yard-stubs/carray_count.rb +16 -2
  199. data/yard-stubs/carray_lazy.rb +210 -1
  200. data/yard-stubs/carray_loop.rb +4 -1
  201. data/yard-stubs/carray_mask.rb +34 -10
  202. data/yard-stubs/carray_math.rb +1499 -4
  203. data/yard-stubs/carray_median_percentile.rb +26 -8
  204. data/yard-stubs/carray_order.rb +43 -82
  205. data/yard-stubs/carray_scatter.rb +10 -3
  206. data/yard-stubs/carray_segment.rb +72 -0
  207. data/yard-stubs/carray_slab.rb +11 -8
  208. data/yard-stubs/carray_sort.rb +50 -8
  209. data/yard-stubs/carray_test.rb +10 -0
  210. data/yard-stubs/carray_utils.rb +0 -26
  211. metadata +19 -19
  212. data/ext/ca_for_each_element.h +0 -239
  213. data/ext/carray_build_flags.h +0 -3
  214. data/ext/carray_config.h +0 -28
  215. data/ext/carray_kernels_bincmp.c +0 -4446
  216. data/ext/carray_kernels_binop.c +0 -11001
  217. data/ext/carray_kernels_init.c +0 -1131
  218. data/ext/carray_kernels_map.c +0 -3467
  219. data/ext/carray_kernels_moncmp.c +0 -2097
  220. data/ext/carray_kernels_monop.c +0 -18313
  221. data/ext/carray_kernels_reduce_aggregate.c +0 -25837
  222. data/ext/carray_kernels_reduce_boolean.c +0 -330
  223. data/ext/carray_kernels_reduce_cumulative.c +0 -14593
  224. data/ext/carray_kernels_reduce_extreme.c +0 -16948
  225. data/ext/carray_kernels_reduce_variance.c +0 -3910
  226. data/ext/carray_kernels_scan.c +0 -3693
  227. data/ext/carray_kernels_search.c +0 -32138
  228. data/ext/carray_kernels_sort.c +0 -10626
  229. data/ext/carray_kernels_triop.c +0 -1392
data/ext/carray_lazy.c CHANGED
@@ -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;
@@ -153,6 +155,7 @@ static void
153
155
  ca_lazy_marker_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
154
156
  ca_size_t *strides, void *data, int dir)
155
157
  {
158
+ if ( ca_region_is_empty(((CArray *) ap)->ndim, counts) ) return;
156
159
  CALazyMarker *ca = (CALazyMarker *) ap;
157
160
  ca_xfer_stride(ca->parent, starts, counts, strides, data, dir);
158
161
  }
@@ -206,12 +209,8 @@ static void
206
209
  ca_lazy_marker_func_create_mask (void *ap)
207
210
  {
208
211
  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);
212
+ ca_view_func_create_mask(ca);
213
+ ca_set_flag(ca->mask, CA_FLAG_READ_ONLY);
215
214
  }
216
215
 
217
216
  ca_operation_function_t ca_lazy_marker_func = {
@@ -250,10 +249,19 @@ rb_ca_lazy_marker_new (VALUE cary)
250
249
 
251
250
  /* CArray#lazy — return a zero-cost CALazyMarker wrapping self. Subsequent
252
251
  * element-wise ops (.sqrt / .sin / .+ / ...) build a lazy CAMonOp / CABinOp
253
- * tree instead of evaluating eagerly; `.to_ca` materialises. */
252
+ * tree instead of evaluating eagerly; `.to_ca` materialises.
253
+ *
254
+ * A Face returns itself: a lazy tree computes on storage and would lose
255
+ * the Face's meaning (units, labels, string surface), so its operations
256
+ * stay eager. */
254
257
  VALUE
255
258
  rb_ca_lazy (VALUE self)
256
259
  {
260
+ CArray *ca;
261
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
262
+ if ( ca_is_face(ca) ) {
263
+ return self;
264
+ }
257
265
  return rb_ca_lazy_marker_new(self);
258
266
  }
259
267
 
@@ -292,8 +300,65 @@ typedef struct ca_gc_hold {
292
300
  ca_size_t elements;
293
301
  } ca_gc_hold_t;
294
302
 
295
- static ca_gc_hold_t ca_gc_holds[CA_GC_HOLD_MAX];
296
- static int ca_gc_hold_depth = 0;
303
+ /* Each Ractor keeps its own holds: walks in different Ractors push and
304
+ release at the same time. The GC marks every Ractor's set through
305
+ the storage type's mark function. */
306
+ typedef struct ca_gc_local {
307
+ ca_gc_hold_t holds[CA_GC_HOLD_MAX];
308
+ int hold_depth;
309
+ ca_gc_hold_t *buffers; /* held until released, in any order */
310
+ int buffer_n;
311
+ int buffer_cap;
312
+ } ca_gc_local_t;
313
+
314
+ static rb_ractor_local_key_t ca_gc_local_key;
315
+
316
+ static void
317
+ ca_gc_local_mark (void *ptr)
318
+ {
319
+ ca_gc_local_t *g = (ca_gc_local_t *) ptr;
320
+ int i;
321
+ if ( g == NULL ) return;
322
+ for ( i = 0; i < g->hold_depth; i++ ) {
323
+ VALUE *p = g->holds[i].ptr;
324
+ ca_size_t n = g->holds[i].elements;
325
+ while ( n-- ) rb_gc_mark(*p++);
326
+ }
327
+ for ( i = 0; i < g->buffer_n; i++ ) {
328
+ VALUE *p = g->buffers[i].ptr;
329
+ ca_size_t n = g->buffers[i].elements;
330
+ while ( n-- ) rb_gc_mark(*p++);
331
+ }
332
+ }
333
+
334
+ static void
335
+ ca_gc_local_free (void *ptr)
336
+ {
337
+ ca_gc_local_t *g = (ca_gc_local_t *) ptr;
338
+ if ( g == NULL ) return;
339
+ free(g->buffers);
340
+ free(g);
341
+ }
342
+
343
+ static const struct rb_ractor_local_storage_type ca_gc_local_type = {
344
+ ca_gc_local_mark, ca_gc_local_free
345
+ };
346
+
347
+ /* The state is allocated with the C allocator, like the table it grows:
348
+ a collection must not run while either is half set up. */
349
+ static ca_gc_local_t *
350
+ ca_gc_local (void)
351
+ {
352
+ ca_gc_local_t *g = (ca_gc_local_t *) rb_ractor_local_storage_ptr(ca_gc_local_key);
353
+ if ( g == NULL ) {
354
+ g = (ca_gc_local_t *) calloc(1, sizeof(ca_gc_local_t));
355
+ if ( g == NULL ) {
356
+ rb_memerror();
357
+ }
358
+ rb_ractor_local_storage_ptr_set(ca_gc_local_key, g);
359
+ }
360
+ return g;
361
+ }
297
362
 
298
363
  static void ca_lazy_arena_mark_object_slots (void);
299
364
 
@@ -306,22 +371,69 @@ static void ca_lazy_arena_mark_object_slots (void);
306
371
  int
307
372
  ca_gc_hold_push (void *ptr, ca_size_t n_elements)
308
373
  {
309
- int depth = ca_gc_hold_depth;
374
+ ca_gc_local_t *g;
375
+ int depth;
310
376
 
311
- if ( depth >= CA_GC_HOLD_MAX || n_elements <= 0 || ptr == NULL ) {
377
+ if ( n_elements <= 0 || ptr == NULL ) {
378
+ return -1;
379
+ }
380
+ g = ca_gc_local();
381
+ depth = g->hold_depth;
382
+ if ( depth >= CA_GC_HOLD_MAX ) {
312
383
  return -1;
313
384
  }
314
- ca_gc_holds[depth].ptr = (VALUE *) ptr;
315
- ca_gc_holds[depth].elements = n_elements;
316
- ca_gc_hold_depth = depth + 1;
385
+ g->holds[depth].ptr = (VALUE *) ptr;
386
+ g->holds[depth].elements = n_elements;
387
+ g->hold_depth = depth + 1;
317
388
  return depth;
318
389
  }
319
390
 
320
391
  void
321
392
  ca_gc_hold_pop_to (int depth)
322
393
  {
323
- if ( depth >= 0 && depth < ca_gc_hold_depth ) {
324
- ca_gc_hold_depth = depth;
394
+ ca_gc_local_t *g;
395
+ if ( depth < 0 ) return;
396
+ g = ca_gc_local();
397
+ if ( depth < g->hold_depth ) {
398
+ g->hold_depth = depth;
399
+ }
400
+ }
401
+
402
+ void
403
+ ca_gc_hold_buffer (void *ptr, ca_size_t n_elements)
404
+ {
405
+ ca_gc_local_t *g;
406
+ if ( ptr == NULL || n_elements <= 0 ) {
407
+ return;
408
+ }
409
+ g = ca_gc_local();
410
+ if ( g->buffer_n == g->buffer_cap ) {
411
+ int cap = g->buffer_cap ? g->buffer_cap * 2 : 16;
412
+ ca_gc_hold_t *grown = (ca_gc_hold_t *) realloc(g->buffers,
413
+ cap * sizeof(ca_gc_hold_t));
414
+ if ( grown == NULL ) {
415
+ rb_memerror();
416
+ }
417
+ g->buffers = grown;
418
+ g->buffer_cap = cap;
419
+ }
420
+ g->buffers[g->buffer_n].ptr = (VALUE *) ptr;
421
+ g->buffers[g->buffer_n].elements = n_elements;
422
+ g->buffer_n++;
423
+ }
424
+
425
+ void
426
+ ca_gc_release_buffer (void *ptr)
427
+ {
428
+ ca_gc_local_t *g = (ca_gc_local_t *) rb_ractor_local_storage_ptr(ca_gc_local_key);
429
+ int i;
430
+ if ( g == NULL ) return;
431
+ for ( i = g->buffer_n - 1; i >= 0; i-- ) {
432
+ if ( g->buffers[i].ptr == (VALUE *) ptr ) {
433
+ g->buffers[i] = g->buffers[g->buffer_n - 1];
434
+ g->buffer_n--;
435
+ return;
436
+ }
325
437
  }
326
438
  }
327
439
 
@@ -331,16 +443,11 @@ static VALUE ca_gc_guard = Qnil;
331
443
  function of a TypedData whose data pointer is NULL. */
332
444
  static int ca_gc_guard_body = 0;
333
445
 
446
+ /* The arena's object slots. The holds are marked per Ractor above. */
334
447
  static void
335
448
  ca_gc_guard_mark (void *ptr)
336
449
  {
337
- int i;
338
450
  (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++);
343
- }
344
451
  ca_lazy_arena_mark_object_slots();
345
452
  }
346
453
 
@@ -374,11 +481,12 @@ static const rb_data_type_t ca_gc_guard_data_type = {
374
481
  best-fit allocation keeps small requests (mask scratch) out of
375
482
  large data slots
376
483
 
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.
484
+ Footprint: one slot per scratch held at the same time, each as large
485
+ as the largest request it has served (e.g. N=1M f64, depth-8 chain =
486
+ 8 × 8MB = 64MB kept resident). A request no free slot can hold grows
487
+ a free slot; a new slot opens only while every warm one is in use.
488
+ When all 32 are in use the acquire raises: this signals a programming
489
+ error or a pathological chain rather than a graceful fallback.
382
490
 
383
491
  CAREFUL: single-thread only -- thread-safety across concurrent access
384
492
  is not a goal (see guides/devel/04_attach_lifecycle.md). The arena is
@@ -437,6 +545,57 @@ ca_lazy_arena_exit (void)
437
545
  from being collected in the first place. */
438
546
  }
439
547
 
548
+ /* Runs body(arg) inside an enter / exit bracket that closes however body
549
+ leaves. cleanup(arg), when given, runs first -- the place to release
550
+ the scratches and detach the arrays body acquired -- and the exit runs
551
+ after it.
552
+
553
+ A raise that skipped the exit would leave the depth above 0 for the
554
+ rest of the process: the depth-0 reset that recovers abandoned slots
555
+ would never fire again, and once 32 slots were abandoned every lazy
556
+ expression would fail. */
557
+
558
+ typedef struct {
559
+ VALUE (*cleanup)(VALUE);
560
+ VALUE arg;
561
+ } ca_lazy_arena_ensure_t;
562
+
563
+ static VALUE
564
+ ca_lazy_arena_ensure (VALUE varg)
565
+ {
566
+ ca_lazy_arena_ensure_t *e = (ca_lazy_arena_ensure_t *) varg;
567
+ if ( e->cleanup ) {
568
+ e->cleanup(e->arg);
569
+ }
570
+ ca_lazy_arena_exit();
571
+ return Qnil;
572
+ }
573
+
574
+ VALUE
575
+ ca_lazy_arena_protect (VALUE (*body)(VALUE), VALUE (*cleanup)(VALUE),
576
+ VALUE arg)
577
+ {
578
+ ca_lazy_arena_ensure_t e;
579
+ e.cleanup = cleanup;
580
+ e.arg = arg;
581
+ ca_lazy_arena_enter();
582
+ return rb_ensure(body, arg, ca_lazy_arena_ensure, (VALUE) &e);
583
+ }
584
+
585
+ /* A cleanup for ca_lazy_arena_protect when body holds one scratch: arg
586
+ points at a struct whose first member is that scratch (NULL until it
587
+ is acquired). */
588
+ VALUE
589
+ ca_lazy_arena_release_held (VALUE arg)
590
+ {
591
+ void **held = (void **) arg;
592
+ if ( *held ) {
593
+ ca_lazy_arena_release(*held);
594
+ *held = NULL;
595
+ }
596
+ return Qnil;
597
+ }
598
+
440
599
  void *
441
600
  ca_lazy_arena_acquire (ca_size_t bytes)
442
601
  {
@@ -471,7 +630,40 @@ ca_lazy_arena_acquire (ca_size_t bytes)
471
630
  }
472
631
 
473
632
 
474
- /* Pass 2: empty slot — xmalloc a fresh buffer. */
633
+ /* Pass 2: every free warm slot is too small. Grow the largest of
634
+ them rather than open another slot: a slot that is free now was not
635
+ needed at the same time as this request, so the arena holds one
636
+ buffer per simultaneous acquire rather than one per size ever asked
637
+ for. Taking the largest leaves the small slots to the small
638
+ requests (mask scratch next to data scratch).
639
+
640
+ The slot is emptied before the xmalloc so that a NoMemoryError
641
+ leaves it unallocated instead of pointing at freed memory. */
642
+ best = -1;
643
+ best_bytes = -1;
644
+ for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
645
+ ca_lazy_arena_slot_t *s = &ca_lazy_arena.slots[i];
646
+ if ( s->in_use || s->ptr == NULL ) continue;
647
+ if ( s->bytes > best_bytes ) {
648
+ best = i;
649
+ best_bytes = s->bytes;
650
+ }
651
+ }
652
+ if ( best >= 0 ) {
653
+ ca_lazy_arena_slot_t *s = &ca_lazy_arena.slots[best];
654
+ void *old = s->ptr;
655
+ s->ptr = NULL;
656
+ s->bytes = 0;
657
+ xfree(old);
658
+ s->ptr = xmalloc(bytes);
659
+ s->bytes = bytes;
660
+ s->in_use = 1;
661
+ s->object_elements = 0;
662
+ ca_lazy_arena.debug_xmalloc_count++;
663
+ return s->ptr;
664
+ }
665
+
666
+ /* Pass 3: every warm slot is in use. Open an empty one. */
475
667
  empty = -1;
476
668
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
477
669
  if ( ca_lazy_arena.slots[i].ptr == NULL ) {
@@ -488,26 +680,11 @@ ca_lazy_arena_acquire (ca_size_t bytes)
488
680
  return ca_lazy_arena.slots[empty].ptr;
489
681
  }
490
682
 
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). */
494
- 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;
503
- }
504
- }
505
-
506
683
  /* Pass 4: all CA_LAZY_ARENA_SLOTS in use simultaneously. This is
507
684
  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)",
685
+ rb_raise(rb_eArgError,
686
+ "lazy expression too deep: it needs more than %d intermediate "
687
+ "arrays at once",
511
688
  CA_LAZY_ARENA_SLOTS);
512
689
  return NULL; /* unreachable */
513
690
  }
@@ -642,38 +819,6 @@ rb_ca_lazy_arena_s_slot_capacities (VALUE klass)
642
819
 
643
820
  /* ------------------------------------------------------------------- */
644
821
 
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
822
  static VALUE
678
823
  rb_ca_lazy_marker_s_allocate (VALUE klass)
679
824
  {
@@ -687,11 +832,14 @@ rb_ca_lazy_marker_initialize_copy (VALUE self, VALUE other)
687
832
  {
688
833
  CALazyMarker *ca, *cs;
689
834
  TypedData_Get_Struct(self, CALazyMarker, &calazy_marker_data_type, ca);
835
+ ca_check_uninitialized(ca);
690
836
  TypedData_Get_Struct(other, CALazyMarker, &calazy_marker_data_type, cs);
691
837
  ca_lazy_marker_setup(ca, cs->parent);
692
838
  return self;
693
839
  }
694
840
 
841
+ static void Init_ca_mask_of_operands (void);
842
+
695
843
  void
696
844
  Init_carray_lazy (void)
697
845
  {
@@ -707,8 +855,10 @@ Init_carray_lazy (void)
707
855
 
708
856
  rb_define_method(rb_cCArray, "lazy", rb_ca_lazy, 0);
709
857
 
858
+ ca_gc_local_key = rb_ractor_local_storage_ptr_newkey(&ca_gc_local_type);
859
+
710
860
  /* Hidden, never collected: its mark function is what keeps the
711
- in-flight CA_OBJECT buffers reachable. */
861
+ arena's CA_OBJECT slots reachable. */
712
862
  ca_gc_guard = TypedData_Wrap_Struct(rb_cObject, &ca_gc_guard_data_type,
713
863
  &ca_gc_guard_body);
714
864
  rb_gc_register_mark_object(ca_gc_guard);
@@ -734,4 +884,609 @@ Init_carray_lazy (void)
734
884
  rb_ca_lazy_arena_s_slot_in_use_count, 0);
735
885
  rb_define_singleton_method(rb_cCArray, "__lazy_arena_slot_capacities__",
736
886
  rb_ca_lazy_arena_s_slot_capacities, 0);
887
+
888
+ Init_ca_mask_of_operands();
889
+ }
890
+
891
+ /* ---------------------------------------------------------------------------
892
+ Requests to the element-wise lazy views (ca_lazy_req_t, carray_internal.h).
893
+
894
+ An operand is pulled over the cells the request names. In the region
895
+ form the strides address the view's cells, so they are restated at the
896
+ operand's cell width: strides[k] / bytes is the index step the two share.
897
+ (A packed stride over counts is not a substitute -- it walks cells 0, 1,
898
+ 2 where the request asked for 0, 2, 4.)
899
+ --------------------------------------------------------------------------- */
900
+
901
+ void
902
+ ca_lazy_req_region (ca_lazy_req_t *req, void *view, ca_size_t *starts,
903
+ ca_size_t *counts, ca_size_t *strides)
904
+ {
905
+ CArray *ca = (CArray *) view;
906
+ int8_t k;
907
+ req->n = 1;
908
+ for ( k = 0; k < ca->ndim; k++ ) {
909
+ req->n *= counts[k];
910
+ }
911
+ req->addrs = NULL;
912
+ req->starts = starts;
913
+ req->counts = counts;
914
+ req->strides = strides;
915
+ req->bytes = ca->bytes;
916
+ req->ndim = ca->ndim;
917
+ }
918
+
919
+ void
920
+ ca_lazy_req_addrs (ca_lazy_req_t *req, ca_size_t n, ca_size_t *addrs)
921
+ {
922
+ req->n = n;
923
+ req->addrs = addrs;
924
+ req->starts = NULL;
925
+ req->counts = NULL;
926
+ req->strides = NULL;
927
+ req->bytes = 0;
928
+ req->ndim = 0;
929
+ }
930
+
931
+ /* Whether the request names one contiguous run of the view's cells, in
932
+ the view's own row-major order -- the case in which an operand's buffer
933
+ already holds the cells in the order the kernel reads them. The strides
934
+ must be the native row-major ones, and the box must be whole on every
935
+ axis inside the outermost one it does not span with a count of 1. */
936
+ int
937
+ ca_lazy_req_is_packed (const ca_lazy_req_t *req, void *view)
938
+ {
939
+ CArray *ca = (CArray *) view;
940
+ ca_size_t native = ca->bytes;
941
+ int partial = 0;
942
+ int8_t k;
943
+ if ( req->addrs ) {
944
+ return 0;
945
+ }
946
+ for ( k = ca->ndim - 1; k >= 0; k-- ) {
947
+ if ( req->strides[k] != native ) {
948
+ return 0;
949
+ }
950
+ if ( partial && req->counts[k] != 1 ) {
951
+ return 0;
952
+ }
953
+ if ( req->counts[k] != ca->dim[k] ) {
954
+ partial = 1;
955
+ }
956
+ native *= ca->dim[k];
957
+ }
958
+ return 1;
959
+ }
960
+
961
+ /* The cells of a request that any of `ops` masks, as a packed boolean
962
+ slab in arena scratch, or NULL when none of them is masked. An operand
963
+ of one cell masks every cell or none. Kernels that can raise on a cell,
964
+ or that call Ruby for it, take this to skip the masked cells, as the
965
+ eager operators do. Release with ca_lazy_arena_release. */
966
+ boolean8_t *
967
+ ca_lazy_req_mask (const ca_lazy_req_t *req, int n, CArray **ops)
968
+ {
969
+ boolean8_t *m = NULL;
970
+ ca_size_t j;
971
+ int i;
972
+ int8_t k;
973
+
974
+ for ( i = 0; i < n; i++ ) {
975
+ CArray *op = ops[i];
976
+ if ( ! op || ! ca_has_mask(op) ) {
977
+ continue;
978
+ }
979
+ if ( ! m ) {
980
+ m = (boolean8_t *) ca_lazy_arena_acquire(req->n);
981
+ memset(m, 0, req->n);
982
+ }
983
+ if ( op->elements == 1 ) {
984
+ ca_size_t starts[CA_RANK_MAX], counts[CA_RANK_MAX];
985
+ ca_size_t strides[CA_RANK_MAX];
986
+ boolean8_t bit = 0;
987
+ for ( k = 0; k < op->ndim; k++ ) {
988
+ starts[k] = 0; counts[k] = 1; strides[k] = 1;
989
+ }
990
+ ca_xfer_stride(op->mask, starts, counts, strides, &bit, CA_XFER_GET);
991
+ if ( bit ) {
992
+ memset(m, 1, req->n);
993
+ }
994
+ }
995
+ else {
996
+ volatile VALUE holder;
997
+ boolean8_t *om = ALLOCV_N(boolean8_t, holder, req->n);
998
+ ca_lazy_req_pull(op->mask, req, om, CA_XFER_GET);
999
+ for ( j = 0; j < req->n; j++ ) {
1000
+ m[j] |= om[j];
1001
+ }
1002
+ ALLOCV_END(holder);
1003
+ }
1004
+ }
1005
+ return m;
1006
+ }
1007
+
1008
+ /* A scalar operand (a Ruby value or a CScalar) that the eager promotion
1009
+ has cast to the array's data_type comes back as a cast view over a
1010
+ CScalar. Fix it as the CScalar the cast gives, so the expression holds
1011
+ a plain value leaf. */
1012
+ VALUE
1013
+ ca_lazy_settle_scalar (VALUE operand, int was_scalar)
1014
+ {
1015
+ CArray *ca;
1016
+ if ( ! was_scalar ) {
1017
+ return operand;
1018
+ }
1019
+ /* rb_obj_is_cscalar is true for any view carrying the scalar flag, the
1020
+ cast view included; the obj_type says whether it is a CScalar. */
1021
+ TypedData_Get_Struct(operand, CArray, &carray_data_type, ca);
1022
+ if ( ca->obj_type != CA_OBJ_SCALAR ) {
1023
+ return rb_ca_copy(operand);
1024
+ }
1025
+ return operand;
1026
+ }
1027
+
1028
+ void
1029
+ ca_lazy_req_pull (void *operand, const ca_lazy_req_t *req, void *buf, int dir)
1030
+ {
1031
+ CArray *op = (CArray *) operand;
1032
+ ca_size_t strides[CA_RANK_MAX];
1033
+ int8_t k;
1034
+
1035
+ if ( req->addrs ) {
1036
+ ca_xfer_addrs(op, req->n, req->addrs, buf, dir);
1037
+ return;
1038
+ }
1039
+ for ( k = 0; k < req->ndim; k++ ) {
1040
+ strides[k] = req->strides[k] / req->bytes * op->bytes;
1041
+ }
1042
+ ca_xfer_stride(op, req->starts, req->counts, strides, buf, dir);
1043
+ }
1044
+
1045
+ /* An expression the caller is about to compute whole -- a reduction along
1046
+ an axis, a sort, a median -- computed by the registered expression
1047
+ evaluator instead, when there is one and it takes the expression.
1048
+ Returns the array it made, or Qnil where nothing is registered, self is
1049
+ not a lazy expression, or the evaluator declined (CArray::Fusion.evaluate
1050
+ decides, and asks only above its size threshold). The caller goes on
1051
+ with that array in place of self, or with self as before.
1052
+
1053
+ Only where the expression would be made whole anyway: a reduction that
1054
+ streams the expression in chunks keeps doing so, since asking here would
1055
+ make it whole and take that much more memory. */
1056
+ VALUE
1057
+ ca_lazy_evaluated (VALUE self)
1058
+ {
1059
+ static ID id_evaluator = 0, id_fusion, id_evaluate;
1060
+ CArray *ca;
1061
+ if ( ! id_evaluator ) {
1062
+ id_evaluator = rb_intern("@expression_evaluator");
1063
+ id_fusion = rb_intern("Fusion");
1064
+ id_evaluate = rb_intern("evaluate");
1065
+ }
1066
+ GetCArray(self, ca);
1067
+ if ( ! ca_is_lazy_view(ca) ) {
1068
+ return Qnil;
1069
+ }
1070
+ if ( ! RTEST(rb_attr_get(rb_cCArray, id_evaluator)) ) {
1071
+ return Qnil;
1072
+ }
1073
+ return rb_funcall(rb_const_get(rb_cCArray, id_fusion), id_evaluate, 1, self);
1074
+ }
1075
+
1076
+ /* ---------------------------------------------------------------------------
1077
+ CAMaskOfOperands: the mask of an element-wise lazy operation over several
1078
+ operands (CABinOp, CABinCmp, CATriOp).
1079
+
1080
+ The operation computes its values each time it is read, so its mask is
1081
+ computed the same way: a request pulls each operand's mask over the cells
1082
+ it names and ORs them. An operand with no mask reads as all false, so an
1083
+ operand that is given a mask after the expression was first read is seen
1084
+ on the next read -- and reading the mask never gives an operand one.
1085
+
1086
+ For the boolean `|` and `&` (three-valued) a masked cell is not masked in
1087
+ the result when an unmasked operand already decides it: true for `|`,
1088
+ false for `&`. The operands' values are pulled for that.
1089
+
1090
+ The view is read-only and has no mask of its own (CA_FLAG_VALUE_ARRAY),
1091
+ so nothing above it asks for one. Its parent is the operation, which
1092
+ owns it as its mask; the operands are read through the operation's
1093
+ parents[].
1094
+ --------------------------------------------------------------------------- */
1095
+
1096
+ int8_t CA_OBJ_MASK_OF_OPERANDS;
1097
+ VALUE rb_cCAMaskOfOperands;
1098
+
1099
+ typedef struct CAMaskOfOperands {
1100
+ int16_t obj_type;
1101
+ int8_t data_type;
1102
+ int8_t ndim;
1103
+ int32_t flags;
1104
+ ca_size_t bytes;
1105
+ ca_size_t elements;
1106
+ ca_size_t *dim;
1107
+ char *ptr;
1108
+ CArray *mask;
1109
+ char *_pool;
1110
+ CArray *parent;
1111
+ uint32_t attach;
1112
+ uint8_t nosync;
1113
+ int8_t mode; /* CA_LAZY_MASK_OR / _KLEENE_OR / _KLEENE_AND / _SELECT */
1114
+ } CAMaskOfOperands;
1115
+
1116
+ static size_t
1117
+ ca_mask_of_operands_dsize (const void *ap)
1118
+ {
1119
+ const CAMaskOfOperands *ca = (const CAMaskOfOperands *) ap;
1120
+ return sizeof(CAMaskOfOperands) + ca->ndim * sizeof(ca_size_t);
1121
+ }
1122
+
1123
+ const rb_data_type_t camask_of_operands_data_type = {
1124
+ .parent = &caview_data_type,
1125
+ .wrap_struct_name = "CAMaskOfOperands",
1126
+ .function = {
1127
+ .dmark = ca_mark,
1128
+ .dfree = ca_free,
1129
+ .dsize = ca_mask_of_operands_dsize,
1130
+ .dcompact = NULL
1131
+ },
1132
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
1133
+ };
1134
+
1135
+ CArray *
1136
+ ca_mask_of_operands_new (CArray *operation, int mode)
1137
+ {
1138
+ CAMaskOfOperands *ca = ALLOC(CAMaskOfOperands);
1139
+ ca->_pool = NULL;
1140
+ ca->obj_type = CA_OBJ_MASK_OF_OPERANDS;
1141
+ ca->data_type = CA_BOOLEAN;
1142
+ ca->flags = CA_FLAG_READ_ONLY | CA_FLAG_VALUE_ARRAY;
1143
+ ca->ndim = operation->ndim;
1144
+ ca->bytes = 1;
1145
+ ca->elements = operation->elements;
1146
+ ca->dim = ALLOC_N(ca_size_t, operation->ndim);
1147
+ ca->ptr = NULL;
1148
+ ca->mask = NULL;
1149
+ ca->parent = operation;
1150
+ ca->attach = 0;
1151
+ ca->nosync = 0;
1152
+ ca->mode = (int8_t) mode;
1153
+ memcpy(ca->dim, operation->dim, operation->ndim * sizeof(ca_size_t));
1154
+ if ( ca_is_scalar(operation) ) {
1155
+ ca_set_flag(ca, CA_FLAG_SCALAR);
1156
+ }
1157
+ return (CArray *) ca;
1158
+ }
1159
+
1160
+ static void
1161
+ free_ca_mask_of_operands (void *ap)
1162
+ {
1163
+ CAMaskOfOperands *ca = (CAMaskOfOperands *) ap;
1164
+ if ( ca != NULL ) {
1165
+ xfree(ca->ptr);
1166
+ xfree(ca->dim);
1167
+ xfree(ca);
1168
+ }
1169
+ }
1170
+
1171
+ static void *
1172
+ ca_mask_of_operands_func_clone (void *ap)
1173
+ {
1174
+ CAMaskOfOperands *ca = (CAMaskOfOperands *) ap;
1175
+ return ca_mask_of_operands_new(ca->parent, ca->mode);
1176
+ }
1177
+
1178
+ /* An operand of one cell beside a larger operation is broadcast: its one
1179
+ cell stands for every cell of the request. */
1180
+ static int
1181
+ ca_mask_of_operands_operand_is_scalar (CAMaskOfOperands *lm, CArray *op)
1182
+ {
1183
+ return ( op->elements == 1 && lm->elements > 1 );
1184
+ }
1185
+
1186
+ static void
1187
+ ca_mask_of_operands_pull (CAMaskOfOperands *lm, CArray *src, const ca_lazy_req_t *req,
1188
+ boolean8_t *buf)
1189
+ {
1190
+ if ( ca_mask_of_operands_operand_is_scalar(lm, src) ) {
1191
+ boolean8_t one;
1192
+ ca_xfer_all(src, &one, CA_XFER_GET);
1193
+ memset(buf, one ? 1 : 0, req->n);
1194
+ }
1195
+ else {
1196
+ ca_lazy_req_pull(src, req, buf, CA_XFER_GET);
1197
+ }
1198
+ }
1199
+
1200
+ /* select: masked where the condition is, else where the branch it
1201
+ chooses is. The condition arrives converted to the branches' type
1202
+ from boolean, so a cell is true exactly when one of its bytes is not
1203
+ zero. */
1204
+ static void
1205
+ ca_mask_of_operands_eval_select (CAMaskOfOperands *lm, const ca_lazy_req_t *req,
1206
+ boolean8_t *dst)
1207
+ {
1208
+ CAMultiParent *mp = (CAMultiParent *) lm->parent;
1209
+ CArray *cond = mp->parents[0];
1210
+ ca_size_t bytes = cond->bytes;
1211
+ ca_size_t n = req->n, i, b;
1212
+ volatile VALUE h_value, h_m1, h_m2;
1213
+ boolean8_t *m1, *m2;
1214
+ char *v;
1215
+ int32_t k;
1216
+
1217
+ memset(dst, 0, n);
1218
+ if ( ca_has_mask(cond) ) {
1219
+ ca_mask_of_operands_pull(lm, cond->mask, req, dst);
1220
+ }
1221
+ m1 = ALLOCV_N(boolean8_t, h_m1, n);
1222
+ m2 = ALLOCV_N(boolean8_t, h_m2, n);
1223
+ for ( k = 1; k <= 2; k++ ) {
1224
+ CArray *op = mp->parents[k];
1225
+ boolean8_t *m = ( k == 1 ) ? m1 : m2;
1226
+ if ( ca_has_mask(op) ) {
1227
+ ca_mask_of_operands_pull(lm, op->mask, req, m);
1228
+ }
1229
+ else {
1230
+ memset(m, 0, n);
1231
+ }
1232
+ }
1233
+ if ( ca_mask_of_operands_operand_is_scalar(lm, cond) ) {
1234
+ v = ALLOCV_N(char, h_value, bytes);
1235
+ ca_xfer_all(cond, v, CA_XFER_GET);
1236
+ for ( b = 0; b < bytes && ! v[b]; b++ ) ;
1237
+ for ( i = 0; i < n; i++ ) dst[i] |= ( b < bytes ) ? m1[i] : m2[i];
1238
+ }
1239
+ else {
1240
+ v = ALLOCV_N(char, h_value, n * bytes);
1241
+ ca_lazy_req_pull(cond, req, v, CA_XFER_GET);
1242
+ for ( i = 0; i < n; i++ ) {
1243
+ const char *c = v + i * bytes;
1244
+ for ( b = 0; b < bytes && ! c[b]; b++ ) ;
1245
+ dst[i] |= ( b < bytes ) ? m1[i] : m2[i];
1246
+ }
1247
+ }
1248
+ ALLOCV_END(h_value);
1249
+ ALLOCV_END(h_m2);
1250
+ ALLOCV_END(h_m1);
1251
+ }
1252
+
1253
+ static void
1254
+ ca_mask_of_operands_eval (CAMaskOfOperands *lm, const ca_lazy_req_t *req, boolean8_t *dst)
1255
+ {
1256
+ CAMultiParent *mp = (CAMultiParent *) lm->parent;
1257
+ int kleene = ( lm->mode == CA_LAZY_MASK_KLEENE_OR ||
1258
+ lm->mode == CA_LAZY_MASK_KLEENE_AND );
1259
+ boolean8_t absorbing = ( lm->mode == CA_LAZY_MASK_KLEENE_OR ) ? 1 : 0;
1260
+ volatile VALUE h_mask, h_value, h_known;
1261
+ boolean8_t *m, *v = NULL, *known = NULL;
1262
+ ca_size_t n = req->n, i;
1263
+ int32_t k;
1264
+
1265
+ if ( lm->mode == CA_LAZY_MASK_SELECT ) {
1266
+ ca_mask_of_operands_eval_select(lm, req, dst);
1267
+ return;
1268
+ }
1269
+
1270
+ memset(dst, 0, n);
1271
+ m = ALLOCV_N(boolean8_t, h_mask, n);
1272
+ if ( kleene ) {
1273
+ v = ALLOCV_N(boolean8_t, h_value, n);
1274
+ known = ALLOCV_N(boolean8_t, h_known, n);
1275
+ memset(known, 0, n);
1276
+ }
1277
+
1278
+ for ( k = 0; k < mp->n_parents; k++ ) {
1279
+ CArray *op = mp->parents[k];
1280
+ if ( ca_has_mask(op) ) {
1281
+ ca_mask_of_operands_pull(lm, op->mask, req, m);
1282
+ for ( i = 0; i < n; i++ ) dst[i] |= m[i];
1283
+ }
1284
+ else {
1285
+ memset(m, 0, n);
1286
+ }
1287
+ if ( kleene ) {
1288
+ ca_mask_of_operands_pull(lm, op, req, v);
1289
+ for ( i = 0; i < n; i++ ) {
1290
+ if ( ! m[i] && ( v[i] ? 1 : 0 ) == absorbing ) known[i] = 1;
1291
+ }
1292
+ }
1293
+ }
1294
+
1295
+ if ( kleene ) {
1296
+ for ( i = 0; i < n; i++ ) {
1297
+ if ( known[i] ) dst[i] = 0;
1298
+ }
1299
+ ALLOCV_END(h_known);
1300
+ ALLOCV_END(h_value);
1301
+ }
1302
+ ALLOCV_END(h_mask);
1303
+ }
1304
+
1305
+ NORETURN(static void ca_mask_of_operands_read_only (void));
1306
+ static void
1307
+ ca_mask_of_operands_read_only (void)
1308
+ {
1309
+ rb_raise(rb_eRuntimeError, "the mask of a lazy operation is read-only");
1310
+ }
1311
+
1312
+ static void
1313
+ ca_mask_of_operands_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
1314
+ ca_size_t *strides, void *data, int dir)
1315
+ {
1316
+ ca_lazy_req_t req;
1317
+ if ( ca_region_is_empty(((CArray *) ap)->ndim, counts) ) return;
1318
+ if ( dir != CA_XFER_GET ) ca_mask_of_operands_read_only();
1319
+ ca_lazy_req_region(&req, ap, starts, counts, strides);
1320
+ ca_mask_of_operands_eval((CAMaskOfOperands *) ap, &req, (boolean8_t *) data);
1321
+ }
1322
+
1323
+ static void
1324
+ ca_mask_of_operands_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
1325
+ void *data, int dir)
1326
+ {
1327
+ ca_lazy_req_t req;
1328
+ if ( dir != CA_XFER_GET ) ca_mask_of_operands_read_only();
1329
+ ca_lazy_req_addrs(&req, n, addrs);
1330
+ ca_mask_of_operands_eval((CAMaskOfOperands *) ap, &req, (boolean8_t *) data);
1331
+ }
1332
+
1333
+ static void
1334
+ ca_mask_of_operands_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
1335
+ {
1336
+ CArray *ca = (CArray *) ap;
1337
+ ca_size_t addr = 0;
1338
+ int8_t k;
1339
+ if ( dir != CA_XFER_GET ) ca_mask_of_operands_read_only();
1340
+ for ( k = 0; k < ca->ndim; k++ ) {
1341
+ addr = addr * ca->dim[k] + idx[k];
1342
+ }
1343
+ ca_mask_of_operands_func_xfer_addrs(ap, 1, &addr, data, dir);
1344
+ }
1345
+
1346
+ static void
1347
+ ca_mask_of_operands_func_xfer_all (void *ap, void *data, int dir)
1348
+ {
1349
+ CArray *ca = (CArray *) ap;
1350
+ ca_size_t starts[CA_RANK_MAX];
1351
+ ca_size_t native[CA_RANK_MAX];
1352
+ ca_size_t s = 1;
1353
+ int8_t k;
1354
+ for ( k = ca->ndim - 1; k >= 0; k-- ) { native[k] = s; s *= ca->dim[k]; }
1355
+ for ( k = 0; k < ca->ndim; k++ ) starts[k] = 0;
1356
+ ca_mask_of_operands_func_xfer_stride(ap, starts, ca->dim, native, data, dir);
1357
+ }
1358
+
1359
+ static VALUE
1360
+ ca_mask_of_operands_fill_buffer (VALUE arg)
1361
+ {
1362
+ CAMaskOfOperands *ca = (CAMaskOfOperands *) arg;
1363
+ ca_mask_of_operands_func_xfer_all(ca, ca->ptr, CA_XFER_GET);
1364
+ return Qnil;
1365
+ }
1366
+
1367
+ /* The buffer is published only once it is filled: a read that raises
1368
+ frees it and leaves the view unattached. */
1369
+ static void
1370
+ ca_mask_of_operands_func_attach (void *ap)
1371
+ {
1372
+ CAMaskOfOperands *ca = (CAMaskOfOperands *) ap;
1373
+ int tag = 0;
1374
+ ca->ptr = xmalloc(ca->elements);
1375
+ rb_protect(ca_mask_of_operands_fill_buffer, (VALUE) ca, &tag);
1376
+ if ( tag ) {
1377
+ xfree(ca->ptr);
1378
+ ca->ptr = NULL;
1379
+ rb_jump_tag(tag);
1380
+ }
1381
+ }
1382
+
1383
+ static void
1384
+ ca_mask_of_operands_func_allocate (void *ap)
1385
+ {
1386
+ CAMaskOfOperands *ca = (CAMaskOfOperands *) ap;
1387
+ ca->ptr = xmalloc(ca->elements);
1388
+ memset(ca->ptr, 0, ca->elements);
1389
+ }
1390
+
1391
+ static void
1392
+ ca_mask_of_operands_func_sync (void *ap)
1393
+ {
1394
+ /* read-only */
1395
+ }
1396
+
1397
+ static void
1398
+ ca_mask_of_operands_func_detach (void *ap)
1399
+ {
1400
+ CAMaskOfOperands *ca = (CAMaskOfOperands *) ap;
1401
+ xfree(ca->ptr);
1402
+ ca->ptr = NULL;
1403
+ }
1404
+
1405
+ NORETURN(static void ca_mask_of_operands_func_fill_data (void *ap, void *ptr));
1406
+ static void
1407
+ ca_mask_of_operands_func_fill_data (void *ap, void *ptr)
1408
+ {
1409
+ ca_mask_of_operands_read_only();
1410
+ }
1411
+
1412
+ NORETURN(static void ca_mask_of_operands_func_create_mask (void *ap));
1413
+ static void
1414
+ ca_mask_of_operands_func_create_mask (void *ap)
1415
+ {
1416
+ rb_raise(rb_eTypeError, "can not create mask array for the mask array");
1417
+ }
1418
+
1419
+ ca_operation_function_t ca_mask_of_operands_func = {
1420
+ -1, /* CA_OBJ_MASK_OF_OPERANDS, set at install time */
1421
+ CA_VIEW_ARRAY,
1422
+ free_ca_mask_of_operands,
1423
+ ca_mask_of_operands_func_clone,
1424
+ ca_mask_of_operands_func_allocate,
1425
+ ca_mask_of_operands_func_attach,
1426
+ ca_mask_of_operands_func_sync,
1427
+ ca_mask_of_operands_func_detach,
1428
+ ca_mask_of_operands_func_fill_data,
1429
+ ca_mask_of_operands_func_create_mask,
1430
+ ca_mask_of_operands_func_xfer_index,
1431
+ ca_mask_of_operands_func_xfer_addrs,
1432
+ NULL, /* fold_stride: computed, never folds */
1433
+ ca_mask_of_operands_func_xfer_stride,
1434
+ ca_mask_of_operands_func_xfer_all,
1435
+ };
1436
+
1437
+ static void
1438
+ Init_ca_mask_of_operands (void)
1439
+ {
1440
+ rb_cCAMaskOfOperands = rb_define_class("CAMaskOfOperands", rb_cCAView);
1441
+ CA_OBJ_MASK_OF_OPERANDS = ca_install_obj_type(rb_cCAMaskOfOperands,
1442
+ &camask_of_operands_data_type,
1443
+ rb_cCArrayMask,
1444
+ &carray_mask_data_type,
1445
+ &ca_mask_of_operands_func,
1446
+ sizeof(ca_mask_of_operands_func));
1447
+ rb_undef_alloc_func(rb_cCAMaskOfOperands);
1448
+ ca_iter_register_source_kind(CA_OBJ_MASK_OF_OPERANDS, CA_ITER_SRC_ATTACH);
1449
+ }
1450
+
1451
+ /* The mask of a lazy operation over several operands: shared with the one
1452
+ masked operand when no other operand can ever be given a mask, computed
1453
+ from all of them on each read otherwise (CAMaskOfOperands above).
1454
+
1455
+ An operand can be given a mask later unless it is broadcast from one
1456
+ cell (a scalar the expression made from a literal), a value array, or a
1457
+ read-only entity. The shared mask is read-only, as the operation is. */
1458
+ CArray *
1459
+ ca_lazy_operation_mask (CArray *operation, int mode)
1460
+ {
1461
+ CAMultiParent *mp = (CAMultiParent *) operation;
1462
+ CArray *masked = NULL;
1463
+ int32_t k, n_masked = 0, n_open = 0;
1464
+
1465
+ for ( k = 0; k < mp->n_parents; k++ ) {
1466
+ CArray *op = mp->parents[k];
1467
+ if ( ca_has_mask(op) ) {
1468
+ masked = op;
1469
+ n_masked++;
1470
+ }
1471
+ else if ( ! ( op->elements == 1 && operation->elements > 1 )
1472
+ && ! ca_is_value_array(op)
1473
+ && ! ( ca_is_entity(op) && ca_is_readonly(op) ) ) {
1474
+ n_open++;
1475
+ }
1476
+ }
1477
+
1478
+ if ( n_masked == 0 ) {
1479
+ return NULL;
1480
+ }
1481
+
1482
+ if ( mode == CA_LAZY_MASK_OR && n_masked == 1 && n_open == 0
1483
+ && masked->elements == operation->elements ) {
1484
+ CArray *shared = (CArray *) ca_refer_new(masked->mask, CA_BOOLEAN,
1485
+ operation->ndim, operation->dim,
1486
+ 0, 0);
1487
+ ca_set_flag(shared, CA_FLAG_READ_ONLY);
1488
+ return shared;
1489
+ }
1490
+
1491
+ return ca_mask_of_operands_new(operation, mode);
737
1492
  }