carray 3.0.3 → 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 (212) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -0
  3. data/README.md +3 -2
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_descriptor.h +27 -24
  6. data/ext/ca_axis_dispatch.c +1 -6
  7. data/ext/ca_axis_group.c +27 -27
  8. data/ext/ca_binop_dispatch.c +1 -29
  9. data/ext/ca_binop_dispatch.h +0 -1
  10. data/ext/ca_categorical_iterator.c +205 -134
  11. data/ext/ca_compare.c +7 -18
  12. data/ext/ca_group_iter.c +16 -17
  13. data/ext/ca_kernel_iterator.c +1234 -1994
  14. data/ext/ca_kernel_iterator.h +391 -437
  15. data/ext/ca_monop_dispatch.c +82 -59
  16. data/ext/ca_monop_dispatch.h +1 -0
  17. data/ext/ca_obj_array.c +96 -39
  18. data/ext/ca_obj_bincmp.c +24 -21
  19. data/ext/ca_obj_binop.c +15 -26
  20. data/ext/ca_obj_bitarray.c +3 -2
  21. data/ext/ca_obj_bitfield.c +5 -6
  22. data/ext/ca_obj_block.c +18 -9
  23. data/ext/ca_obj_byte_swap.c +5 -6
  24. data/ext/ca_obj_const_string.c +173 -19
  25. data/ext/ca_obj_face.c +238 -57
  26. data/ext/ca_obj_face.h +19 -0
  27. data/ext/ca_obj_fake.c +15 -29
  28. data/ext/ca_obj_farray.c +6 -3
  29. data/ext/ca_obj_field.c +17 -5
  30. data/ext/ca_obj_fixlen_string.c +2 -3
  31. data/ext/ca_obj_grid.c +406 -132
  32. data/ext/ca_obj_meld.c +172 -34
  33. data/ext/ca_obj_moncmp.c +23 -8
  34. data/ext/ca_obj_monop.c +67 -57
  35. data/ext/ca_obj_object.c +157 -51
  36. data/ext/ca_obj_record.c +12 -7
  37. data/ext/ca_obj_reduce.c +13 -8
  38. data/ext/ca_obj_refer.c +25 -27
  39. data/ext/ca_obj_remap.c +30 -9
  40. data/ext/ca_obj_repeat.c +9 -10
  41. data/ext/ca_obj_roll.c +6 -5
  42. data/ext/ca_obj_select.c +14 -9
  43. data/ext/ca_obj_select_axis.c +15 -47
  44. data/ext/ca_obj_shift.c +11 -1
  45. data/ext/ca_obj_stack.c +179 -34
  46. data/ext/ca_obj_stride.c +162 -115
  47. data/ext/ca_obj_string.c +71 -4
  48. data/ext/ca_obj_tile.c +6 -5
  49. data/ext/ca_obj_time.c +22 -6
  50. data/ext/ca_obj_timedelta.c +22 -6
  51. data/ext/ca_obj_transpose.c +5 -3
  52. data/ext/ca_obj_triop.c +23 -32
  53. data/ext/ca_obj_window.c +190 -26
  54. data/ext/ca_sweep_engine.c +2 -2
  55. data/ext/caframe_csv_reader.c +80 -1
  56. data/ext/carray.h +181 -75
  57. data/ext/carray_access.c +184 -96
  58. data/ext/carray_address_basis.c +343 -87
  59. data/ext/carray_attribute.c +89 -6
  60. data/ext/carray_bincount.c +10 -8
  61. data/ext/carray_broadcast.c +13 -8
  62. data/ext/carray_build_flags.h +3 -0
  63. data/ext/carray_call_cfunc.c +108 -0
  64. data/ext/carray_cast.c +142 -24
  65. data/ext/carray_cast_func.rb +46 -6
  66. data/ext/carray_class.c +4 -3
  67. data/ext/carray_config.h +28 -0
  68. data/ext/carray_conversion.c +16 -7
  69. data/ext/carray_copy.c +51 -14
  70. data/ext/carray_core.c +147 -55
  71. data/ext/carray_count.c +50 -20
  72. data/ext/carray_element.c +76 -33
  73. data/ext/carray_factorize.c +118 -64
  74. data/ext/carray_generate.c +191 -50
  75. data/ext/carray_histogram.c +62 -17
  76. data/ext/carray_hold.c +12 -15
  77. data/ext/carray_index_classifier.c +388 -176
  78. data/ext/carray_internal.h +336 -29
  79. data/ext/carray_kernels_bincmp.c +4453 -0
  80. data/ext/carray_kernels_binop.c +11368 -0
  81. data/ext/carray_kernels_init.c +1734 -0
  82. data/ext/carray_kernels_moncmp.c +2104 -0
  83. data/ext/carray_kernels_monop.c +18369 -0
  84. data/ext/carray_kernels_reduce_aggregate.c +23818 -0
  85. data/ext/carray_kernels_reduce_boolean.c +406 -0
  86. data/ext/carray_kernels_reduce_cumulative.c +10086 -0
  87. data/ext/carray_kernels_reduce_extreme.c +17634 -0
  88. data/ext/carray_kernels_reduce_variance.c +3849 -0
  89. data/ext/carray_kernels_scan.c +4302 -0
  90. data/ext/carray_kernels_search.c +33829 -0
  91. data/ext/carray_kernels_sort.c +10906 -0
  92. data/ext/carray_kernels_triop.c +1864 -0
  93. data/ext/carray_key_windows.c +216 -0
  94. data/ext/carray_lazy.c +170 -111
  95. data/ext/carray_mask.c +292 -72
  96. data/ext/carray_median_percentile.c +82 -28
  97. data/ext/carray_memory_view.c +669 -561
  98. data/ext/carray_operator.c +124 -47
  99. data/ext/carray_order.c +384 -76
  100. data/ext/carray_partition.c +28 -27
  101. data/ext/carray_random.c +67 -13
  102. data/ext/carray_round.c +273 -0
  103. data/ext/carray_scatter.c +63 -38
  104. data/ext/carray_segment.c +3 -3
  105. data/ext/carray_slab.c +91 -28
  106. data/ext/carray_sort.c +35 -35
  107. data/ext/carray_struct.c +19 -18
  108. data/ext/carray_test.c +26 -5
  109. data/ext/carray_undef.c +4 -0
  110. data/ext/carray_utils.c +246 -45
  111. data/ext/mk_call_cfunc.rb +19 -2
  112. data/ext/mkkernel.rb +198 -240
  113. data/ext/ruby_carray.c +10 -4
  114. data/ext/version.h +4 -4
  115. data/lib/carray/attribute.rb +126 -22
  116. data/lib/carray/autoload_carray.rb +8 -31
  117. data/lib/carray/autoload_method_extension.rb +14 -5
  118. data/lib/carray/axis_group.rb +255 -106
  119. data/lib/carray/basics.rb +78 -10
  120. data/lib/carray/bincount_nd.rb +12 -2
  121. data/lib/carray/block_iterator.rb +126 -23
  122. data/lib/carray/boolean_reduce.rb +17 -17
  123. data/lib/carray/categorical.rb +165 -43
  124. data/lib/carray/categorical_iterator.rb +130 -27
  125. data/lib/carray/complex.rb +5 -5
  126. data/lib/carray/conditional.rb +67 -18
  127. data/lib/carray/const_string.rb +14 -5
  128. data/lib/carray/construct.rb +15 -11
  129. data/lib/carray/data_type_extension.rb +1 -0
  130. data/lib/carray/distance_limit.rb +77 -0
  131. data/lib/carray/fixlen_string.rb +7 -1
  132. data/lib/carray/frame/column_verbs.rb +70 -0
  133. data/lib/carray/frame/convert.rb +69 -0
  134. data/lib/carray/frame/csv_parser.rb +111 -17
  135. data/lib/carray/frame/describe.rb +3 -2
  136. data/lib/carray/frame/frame.rb +246 -21
  137. data/lib/carray/frame/group.rb +510 -95
  138. data/lib/carray/frame/io.rb +115 -28
  139. data/lib/carray/frame/join.rb +312 -51
  140. data/lib/carray/frame/records.rb +83 -7
  141. data/lib/carray/frame/reshape.rb +42 -35
  142. data/lib/carray/frame/sort.rb +7 -19
  143. data/lib/carray/frame/verbs.rb +188 -48
  144. data/lib/carray/frame/windows.rb +117 -0
  145. data/lib/carray/frame.rb +2 -0
  146. data/lib/carray/fuse_source.rb +11 -4
  147. data/lib/carray/fusion.rb +40 -16
  148. data/lib/carray/histogram.rb +6 -1
  149. data/lib/carray/inspect.rb +31 -8
  150. data/lib/carray/iterator.rb +85 -7
  151. data/lib/carray/key_windows.rb +620 -0
  152. data/lib/carray/lazy.rb +182 -183
  153. data/lib/carray/mask_gap_fill.rb +63 -57
  154. data/lib/carray/meld_reduce.rb +12 -14
  155. data/lib/carray/methods/bin.rb +26 -18
  156. data/lib/carray/methods/bincount.rb +16 -0
  157. data/lib/carray/methods/bit_string.rb +1 -1
  158. data/lib/carray/methods/choose.rb +16 -3
  159. data/lib/carray/methods/composition.rb +3 -1
  160. data/lib/carray/methods/gather_nd.rb +14 -5
  161. data/lib/carray/methods/insert_block.rb +2 -2
  162. data/lib/carray/methods/is_between.rb +59 -0
  163. data/lib/carray/methods/is_in.rb +53 -36
  164. data/lib/carray/methods/join.rb +1 -6
  165. data/lib/carray/methods/lag.rb +49 -0
  166. data/lib/carray/methods/locate_addr.rb +47 -51
  167. data/lib/carray/methods/mode.rb +1 -1
  168. data/lib/carray/methods/nunique.rb +11 -6
  169. data/lib/carray/methods/resize.rb +3 -3
  170. data/lib/carray/methods/value_counts.rb +4 -11
  171. data/lib/carray/record_order.rb +3 -3
  172. data/lib/carray/rng.rb +3 -0
  173. data/lib/carray/runtime.rb +53 -8
  174. data/lib/carray/segment_iterator.rb +313 -82
  175. data/lib/carray/serialize.rb +24 -10
  176. data/lib/carray/slab_iterator.rb +50 -0
  177. data/lib/carray/string.rb +105 -5
  178. data/lib/carray/string_operation_extension.rb +8 -1
  179. data/lib/carray/struct.rb +9 -21
  180. data/lib/carray/struct_builder.rb +19 -8
  181. data/lib/carray/time.rb +726 -104
  182. data/lib/carray/window_iterator.rb +122 -16
  183. data/lib/carray.rb +7 -0
  184. data/yard-stubs/ca_obj_bitfield.rb +1 -1
  185. data/yard-stubs/ca_obj_field.rb +4 -0
  186. data/yard-stubs/ca_obj_grid.rb +21 -10
  187. data/yard-stubs/ca_obj_refer.rb +2 -1
  188. data/yard-stubs/ca_obj_roll.rb +2 -2
  189. data/yard-stubs/ca_obj_shift.rb +5 -1
  190. data/yard-stubs/ca_obj_tile.rb +1 -1
  191. data/yard-stubs/ca_obj_window.rb +8 -3
  192. data/yard-stubs/carray_access.rb +6 -1
  193. data/yard-stubs/carray_attribute.rb +0 -5
  194. data/yard-stubs/carray_cast.rb +10 -0
  195. data/yard-stubs/carray_conversion.rb +2 -1
  196. data/yard-stubs/carray_copy.rb +4 -2
  197. data/yard-stubs/carray_count.rb +4 -4
  198. data/yard-stubs/carray_element.rb +10 -4
  199. data/yard-stubs/carray_generate.rb +21 -1
  200. data/yard-stubs/carray_mask.rb +26 -2
  201. data/yard-stubs/carray_math.rb +27 -1
  202. data/yard-stubs/carray_median_percentile.rb +5 -2
  203. data/yard-stubs/carray_memory_view.rb +40 -23
  204. data/yard-stubs/carray_order.rb +29 -18
  205. data/yard-stubs/carray_random.rb +13 -4
  206. data/yard-stubs/carray_scatter.rb +14 -9
  207. data/yard-stubs/carray_slab.rb +9 -2
  208. data/yard-stubs/carray_sort.rb +6 -5
  209. data/yard-stubs/carray_test.rb +5 -3
  210. metadata +25 -3
  211. data/lib/carray/arrow_tensor.rb +0 -401
  212. 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
 
@@ -80,10 +80,6 @@ ca_lazy_marker_setup (CALazyMarker *ca, CArray *parent)
80
80
 
81
81
  memcpy(ca->dim, parent->dim, parent->ndim * sizeof(ca_size_t));
82
82
 
83
- if ( ca_has_mask(parent) ) {
84
- ca_create_mask(ca);
85
- }
86
-
87
83
  if ( ca_is_scalar(parent) ) {
88
84
  ca_set_flag(ca, CA_FLAG_SCALAR);
89
85
  }
@@ -105,8 +101,10 @@ ca_lazy_marker_setup (CALazyMarker *ca, CArray *parent)
105
101
  CALazyMarker *
106
102
  ca_lazy_marker_new (CArray *parent)
107
103
  {
104
+ /* check: the setup checks nothing */
108
105
  CALazyMarker *ca = ALLOC(CALazyMarker);
109
106
  ca_lazy_marker_setup(ca, parent);
107
+ ca_fill_or_free((CArray *) ca, ca_view_inherit_mask, (VALUE) ca);
110
108
  return ca;
111
109
  }
112
110
 
@@ -360,7 +358,6 @@ ca_gc_local (void)
360
358
  return g;
361
359
  }
362
360
 
363
- static void ca_lazy_arena_mark_object_slots (void);
364
361
 
365
362
  /* Returns the depth to hand back to ca_gc_hold_pop_to, so nested holds
366
363
  unwind in order. Pushing is best-effort at the ceiling: a chain
@@ -437,32 +434,6 @@ ca_gc_release_buffer (void *ptr)
437
434
  }
438
435
  }
439
436
 
440
- static VALUE ca_gc_guard = Qnil;
441
-
442
- /* The wrapped pointer must be non-NULL: Ruby's GC skips the mark
443
- function of a TypedData whose data pointer is NULL. */
444
- static int ca_gc_guard_body = 0;
445
-
446
- /* The arena's object slots. The holds are marked per Ractor above. */
447
- static void
448
- ca_gc_guard_mark (void *ptr)
449
- {
450
- (void) ptr;
451
- ca_lazy_arena_mark_object_slots();
452
- }
453
-
454
- static void
455
- ca_gc_guard_free (void *ptr)
456
- {
457
- (void) ptr;
458
- }
459
-
460
- static const rb_data_type_t ca_gc_guard_data_type = {
461
- "carray_object_gc_guard",
462
- { ca_gc_guard_mark, ca_gc_guard_free, NULL, },
463
- 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
464
- };
465
-
466
437
  /* ------------------------------------------------------------------- */
467
438
  /* ca_lazy_arena */
468
439
  /* ------------------------------------------------------------------- */
@@ -485,12 +456,13 @@ static const rb_data_type_t ca_gc_guard_data_type = {
485
456
  as the largest request it has served (e.g. N=1M f64, depth-8 chain =
486
457
  8 × 8MB = 64MB kept resident). A request no free slot can hold grows
487
458
  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
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
489
461
  error or a pathological chain rather than a graceful fallback.
490
462
 
491
- CAREFUL: single-thread only -- thread-safety across concurrent access
492
- is not a goal (see guides/devel/04_attach_lifecycle.md). The arena is
493
- 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). */
494
466
 
495
467
  #define CA_LAZY_ARENA_SLOTS 32
496
468
 
@@ -509,29 +481,109 @@ typedef struct ca_lazy_arena {
509
481
  ca_size_t debug_reuse_count; /* test: how many acquire = reuse */
510
482
  } ca_lazy_arena_t;
511
483
 
512
- 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
+ }
513
560
 
514
561
  void
515
562
  ca_lazy_arena_enter (void)
516
563
  {
517
- 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);
518
569
  /* Top-level reset to recover from a prior exception that left
519
570
  slots in_use without a matching release. Safe because
520
571
  depth==0 means no nested caller is mid-acquire. */
521
572
  int i;
522
573
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
523
- ca_lazy_arena.slots[i].in_use = 0;
524
- ca_lazy_arena.slots[i].object_elements = 0;
574
+ arena->slots[i].in_use = 0;
575
+ arena->slots[i].object_elements = 0;
525
576
  }
526
577
  }
527
- ca_lazy_arena.depth++;
578
+ arena->depth++;
528
579
  }
529
580
 
530
581
  void
531
582
  ca_lazy_arena_exit (void)
532
583
  {
533
- if ( ca_lazy_arena.depth > 0 ) {
534
- ca_lazy_arena.depth--;
584
+ ca_lazy_arena_t *arena = ca_lazy_arena_get();
585
+ if ( arena->depth > 0 ) {
586
+ arena->depth--;
535
587
  }
536
588
  /* Keep all slot buffers, and leave in_use and the object tags alone.
537
589
  A scratch can be acquired outside any enter/exit bracket -- a lazy
@@ -599,12 +651,13 @@ ca_lazy_arena_release_held (VALUE arg)
599
651
  void *
600
652
  ca_lazy_arena_acquire (ca_size_t bytes)
601
653
  {
654
+ ca_lazy_arena_t *arena = ca_lazy_arena_get();
602
655
  int i, best, empty;
603
656
  ca_size_t best_bytes;
604
657
 
605
658
  if ( bytes <= 0 ) bytes = 1;
606
659
 
607
- ca_lazy_arena.debug_acquire_count++;
660
+ arena->debug_acquire_count++;
608
661
 
609
662
  /* Pass 1: best-fit among unused warm slots (smallest >= bytes).
610
663
  CAREFUL: ca_size_t is signed int64_t, so the "no match yet"
@@ -613,7 +666,7 @@ ca_lazy_arena_acquire (ca_size_t bytes)
613
666
  best = -1;
614
667
  best_bytes = CA_LENGTH_MAX;
615
668
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
616
- ca_lazy_arena_slot_t *s = &ca_lazy_arena.slots[i];
669
+ ca_lazy_arena_slot_t *s = &arena->slots[i];
617
670
  if ( s->in_use ) continue;
618
671
  if ( s->ptr == NULL ) continue;
619
672
  if ( s->bytes < bytes ) continue;
@@ -623,10 +676,10 @@ ca_lazy_arena_acquire (ca_size_t bytes)
623
676
  }
624
677
  }
625
678
  if ( best >= 0 ) {
626
- ca_lazy_arena.slots[best].in_use = 1;
627
- ca_lazy_arena.slots[best].object_elements = 0;
628
- ca_lazy_arena.debug_reuse_count++;
629
- 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;
630
683
  }
631
684
 
632
685
 
@@ -642,7 +695,7 @@ ca_lazy_arena_acquire (ca_size_t bytes)
642
695
  best = -1;
643
696
  best_bytes = -1;
644
697
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
645
- ca_lazy_arena_slot_t *s = &ca_lazy_arena.slots[i];
698
+ ca_lazy_arena_slot_t *s = &arena->slots[i];
646
699
  if ( s->in_use || s->ptr == NULL ) continue;
647
700
  if ( s->bytes > best_bytes ) {
648
701
  best = i;
@@ -650,34 +703,34 @@ ca_lazy_arena_acquire (ca_size_t bytes)
650
703
  }
651
704
  }
652
705
  if ( best >= 0 ) {
653
- ca_lazy_arena_slot_t *s = &ca_lazy_arena.slots[best];
706
+ ca_lazy_arena_slot_t *s = &arena->slots[best];
654
707
  void *old = s->ptr;
655
708
  s->ptr = NULL;
656
709
  s->bytes = 0;
657
710
  xfree(old);
658
- s->ptr = xmalloc(bytes);
711
+ s->ptr = xmalloc(bytes); /* cells: ca_lazy_arena_acquire_object clears object slots */
659
712
  s->bytes = bytes;
660
713
  s->in_use = 1;
661
714
  s->object_elements = 0;
662
- ca_lazy_arena.debug_xmalloc_count++;
715
+ arena->debug_xmalloc_count++;
663
716
  return s->ptr;
664
717
  }
665
718
 
666
719
  /* Pass 3: every warm slot is in use. Open an empty one. */
667
720
  empty = -1;
668
721
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
669
- if ( ca_lazy_arena.slots[i].ptr == NULL ) {
722
+ if ( arena->slots[i].ptr == NULL ) {
670
723
  empty = i;
671
724
  break;
672
725
  }
673
726
  }
674
727
  if ( empty >= 0 ) {
675
- ca_lazy_arena.slots[empty].ptr = xmalloc(bytes);
676
- ca_lazy_arena.slots[empty].bytes = bytes;
677
- ca_lazy_arena.slots[empty].in_use = 1;
678
- ca_lazy_arena.slots[empty].object_elements = 0;
679
- ca_lazy_arena.debug_xmalloc_count++;
680
- return ca_lazy_arena.slots[empty].ptr;
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;
681
734
  }
682
735
 
683
736
  /* Pass 4: all CA_LAZY_ARENA_SLOTS in use simultaneously. This is
@@ -692,12 +745,13 @@ ca_lazy_arena_acquire (ca_size_t bytes)
692
745
  void
693
746
  ca_lazy_arena_release (void *ptr)
694
747
  {
748
+ ca_lazy_arena_t *arena = ca_lazy_arena_get();
695
749
  int i;
696
750
  if ( ptr == NULL ) return;
697
751
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
698
- if ( ca_lazy_arena.slots[i].ptr == ptr ) {
699
- ca_lazy_arena.slots[i].in_use = 0;
700
- 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;
701
755
  return;
702
756
  }
703
757
  }
@@ -716,21 +770,18 @@ ca_lazy_arena_release (void *ptr)
716
770
  void *
717
771
  ca_lazy_arena_acquire_object (ca_size_t n_elements)
718
772
  {
773
+ ca_lazy_arena_t *arena = ca_lazy_arena_get();
719
774
  void *ptr;
720
775
  int i;
721
776
 
722
777
  if ( n_elements < 0 ) n_elements = 0;
723
778
  ptr = ca_lazy_arena_acquire(n_elements * (ca_size_t) sizeof(VALUE));
724
779
 
725
- {
726
- VALUE *p = (VALUE *) ptr;
727
- ca_size_t n = n_elements;
728
- while ( n-- ) *p++ = Qnil;
729
- }
780
+ ca_object_cells_clear(ptr, n_elements);
730
781
 
731
782
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
732
- if ( ca_lazy_arena.slots[i].ptr == ptr ) {
733
- ca_lazy_arena.slots[i].object_elements = n_elements;
783
+ if ( arena->slots[i].ptr == ptr ) {
784
+ arena->slots[i].object_elements = n_elements;
734
785
  break;
735
786
  }
736
787
  }
@@ -738,11 +789,11 @@ ca_lazy_arena_acquire_object (ca_size_t n_elements)
738
789
  }
739
790
 
740
791
  static void
741
- ca_lazy_arena_mark_object_slots (void)
792
+ ca_lazy_arena_mark_object_slots (ca_lazy_arena_t *arena)
742
793
  {
743
794
  int i;
744
795
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
745
- ca_lazy_arena_slot_t *s = &ca_lazy_arena.slots[i];
796
+ ca_lazy_arena_slot_t *s = &arena->slots[i];
746
797
  ca_size_t n;
747
798
  VALUE *p;
748
799
  if ( ! s->in_use || s->object_elements <= 0 ) continue;
@@ -754,45 +805,52 @@ ca_lazy_arena_mark_object_slots (void)
754
805
 
755
806
  /* Test instrumentation — exposed to Ruby for the arena smoke and
756
807
  regression assertions. Not user-facing API. */
808
+ #ifdef CARRAY_DEV_BUILD
757
809
  static VALUE
758
810
  rb_ca_lazy_arena_s_reset_counters (VALUE klass)
759
811
  {
760
- ca_lazy_arena.debug_acquire_count = 0;
761
- ca_lazy_arena.debug_xmalloc_count = 0;
762
- 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;
763
816
  return Qnil;
764
817
  }
765
818
 
766
819
  static VALUE
767
820
  rb_ca_lazy_arena_s_acquire_count (VALUE klass)
768
821
  {
769
- 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);
770
824
  }
771
825
 
772
826
  static VALUE
773
827
  rb_ca_lazy_arena_s_xmalloc_count (VALUE klass)
774
828
  {
775
- 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);
776
831
  }
777
832
 
778
833
  static VALUE
779
834
  rb_ca_lazy_arena_s_reuse_count (VALUE klass)
780
835
  {
781
- 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);
782
838
  }
783
839
 
784
840
  static VALUE
785
841
  rb_ca_lazy_arena_s_depth (VALUE klass)
786
842
  {
787
- return INT2NUM(ca_lazy_arena.depth);
843
+ ca_lazy_arena_t *arena = ca_lazy_arena_get();
844
+ return INT2NUM(arena->depth);
788
845
  }
789
846
 
790
847
  static VALUE
791
848
  rb_ca_lazy_arena_s_slot_in_use_count (VALUE klass)
792
849
  {
850
+ ca_lazy_arena_t *arena = ca_lazy_arena_get();
793
851
  int i, n = 0;
794
852
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
795
- if ( ca_lazy_arena.slots[i].in_use ) n++;
853
+ if ( arena->slots[i].in_use ) n++;
796
854
  }
797
855
  return INT2NUM(n);
798
856
  }
@@ -806,16 +864,18 @@ rb_ca_lazy_arena_s_slot_in_use_count (VALUE klass)
806
864
  static VALUE
807
865
  rb_ca_lazy_arena_s_slot_capacities (VALUE klass)
808
866
  {
867
+ ca_lazy_arena_t *arena = ca_lazy_arena_get();
809
868
  int i;
810
869
  VALUE arr = rb_ary_new();
811
870
  (void) klass;
812
871
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
813
- if ( ca_lazy_arena.slots[i].ptr != NULL ) {
814
- 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));
815
874
  }
816
875
  }
817
876
  return arr;
818
877
  }
878
+ #endif /* CARRAY_DEV_BUILD */
819
879
 
820
880
  /* ------------------------------------------------------------------- */
821
881
 
@@ -835,6 +895,7 @@ rb_ca_lazy_marker_initialize_copy (VALUE self, VALUE other)
835
895
  ca_check_uninitialized(ca);
836
896
  TypedData_Get_Struct(other, CALazyMarker, &calazy_marker_data_type, cs);
837
897
  ca_lazy_marker_setup(ca, cs->parent);
898
+ ca_view_inherit_mask((VALUE) ca);
838
899
  return self;
839
900
  }
840
901
 
@@ -857,16 +918,13 @@ Init_carray_lazy (void)
857
918
 
858
919
  ca_gc_local_key = rb_ractor_local_storage_ptr_newkey(&ca_gc_local_type);
859
920
 
860
- /* Hidden, never collected: its mark function is what keeps the
861
- arena's CA_OBJECT slots reachable. */
862
- ca_gc_guard = TypedData_Wrap_Struct(rb_cObject, &ca_gc_guard_data_type,
863
- &ca_gc_guard_body);
864
- rb_gc_register_mark_object(ca_gc_guard);
921
+ ca_lazy_arena_key = rb_ractor_local_storage_ptr_newkey(&ca_lazy_arena_type);
865
922
 
866
923
  rb_define_alloc_func(rb_cCALazyMarker, rb_ca_lazy_marker_s_allocate);
867
924
  rb_define_method(rb_cCALazyMarker, "initialize_copy",
868
925
  rb_ca_lazy_marker_initialize_copy, 1);
869
926
 
927
+ #ifdef CARRAY_DEV_BUILD
870
928
  /* Arena test instrumentation (not user-facing). Bound on CArray
871
929
  for symmetry with CAMonOp / CABinOp counters, but the arena is
872
930
  a singleton process-wide state. */
@@ -884,6 +942,7 @@ Init_carray_lazy (void)
884
942
  rb_ca_lazy_arena_s_slot_in_use_count, 0);
885
943
  rb_define_singleton_method(rb_cCArray, "__lazy_arena_slot_capacities__",
886
944
  rb_ca_lazy_arena_s_slot_capacities, 0);
945
+ #endif
887
946
 
888
947
  Init_ca_mask_of_operands();
889
948
  }
@@ -1056,18 +1115,18 @@ ca_lazy_req_pull (void *operand, const ca_lazy_req_t *req, void *buf, int dir)
1056
1115
  VALUE
1057
1116
  ca_lazy_evaluated (VALUE self)
1058
1117
  {
1059
- static ID id_evaluator = 0, id_fusion, id_evaluate;
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");
1060
1123
  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
1124
  GetCArray(self, ca);
1067
1125
  if ( ! ca_is_lazy_view(ca) ) {
1068
1126
  return Qnil;
1069
1127
  }
1070
- if ( ! RTEST(rb_attr_get(rb_cCArray, id_evaluator)) ) {
1128
+ /* registered in the main Ractor, and asked only there */
1129
+ if ( ! ca_in_main_ractor() || ! RTEST(rb_attr_get(rb_cCArray, id_evaluator)) ) {
1071
1130
  return Qnil;
1072
1131
  }
1073
1132
  return rb_funcall(rb_const_get(rb_cCArray, id_fusion), id_evaluate, 1, self);
@@ -1306,6 +1365,7 @@ NORETURN(static void ca_mask_of_operands_read_only (void));
1306
1365
  static void
1307
1366
  ca_mask_of_operands_read_only (void)
1308
1367
  {
1368
+ /* read-only-check: says it is the mask, which no lazy node owns */
1309
1369
  rb_raise(rb_eRuntimeError, "the mask of a lazy operation is read-only");
1310
1370
  }
1311
1371
 
@@ -1359,8 +1419,8 @@ ca_mask_of_operands_func_xfer_all (void *ap, void *data, int dir)
1359
1419
  static VALUE
1360
1420
  ca_mask_of_operands_fill_buffer (VALUE arg)
1361
1421
  {
1362
- CAMaskOfOperands *ca = (CAMaskOfOperands *) arg;
1363
- ca_mask_of_operands_func_xfer_all(ca, ca->ptr, CA_XFER_GET);
1422
+ void **a = (void **) arg;
1423
+ ca_mask_of_operands_func_xfer_all(a[0], a[1], CA_XFER_GET);
1364
1424
  return Qnil;
1365
1425
  }
1366
1426
 
@@ -1370,21 +1430,19 @@ static void
1370
1430
  ca_mask_of_operands_func_attach (void *ap)
1371
1431
  {
1372
1432
  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
- }
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;
1381
1439
  }
1382
1440
 
1383
1441
  static void
1384
1442
  ca_mask_of_operands_func_allocate (void *ap)
1385
1443
  {
1386
1444
  CAMaskOfOperands *ca = (CAMaskOfOperands *) ap;
1387
- ca->ptr = xmalloc(ca->elements);
1445
+ ca->ptr = ca_alloc_cells((CArray *) ca);
1388
1446
  memset(ca->ptr, 0, ca->elements);
1389
1447
  }
1390
1448
 
@@ -1454,7 +1512,12 @@ Init_ca_mask_of_operands (void)
1454
1512
 
1455
1513
  An operand can be given a mask later unless it is broadcast from one
1456
1514
  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. */
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. */
1458
1521
  CArray *
1459
1522
  ca_lazy_operation_mask (CArray *operation, int mode)
1460
1523
  {
@@ -1475,10 +1538,6 @@ ca_lazy_operation_mask (CArray *operation, int mode)
1475
1538
  }
1476
1539
  }
1477
1540
 
1478
- if ( n_masked == 0 ) {
1479
- return NULL;
1480
- }
1481
-
1482
1541
  if ( mode == CA_LAZY_MASK_OR && n_masked == 1 && n_open == 0
1483
1542
  && masked->elements == operation->elements ) {
1484
1543
  CArray *shared = (CArray *) ca_refer_new(masked->mask, CA_BOOLEAN,