carray 3.0.0 → 3.0.1

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 (180) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -1
  3. data/CHANGELOG.md +325 -3
  4. data/{NEWS.md → CHANGELOG.v1.md} +1 -1
  5. data/README.md +32 -32
  6. data/carray.gemspec +12 -8
  7. data/ext/ca_axis_group.c +55 -19
  8. data/ext/ca_binop_dispatch.c +5 -6
  9. data/ext/ca_binop_dispatch.h +0 -7
  10. data/ext/ca_categorical_iterator.c +13 -13
  11. data/ext/ca_for_each_element.h +6 -8
  12. data/ext/ca_group_iter.c +1 -1
  13. data/ext/ca_kernel_iterator.c +69 -23
  14. data/ext/ca_kernel_iterator.h +3 -10
  15. data/ext/ca_obj_bincmp.c +57 -27
  16. data/ext/ca_obj_binop.c +86 -30
  17. data/ext/ca_obj_const_string.c +3 -1
  18. data/ext/ca_obj_face.c +80 -0
  19. data/ext/ca_obj_face.h +33 -1
  20. data/ext/ca_obj_fake.c +11 -0
  21. data/ext/ca_obj_fixlen_string.c +3 -1
  22. data/ext/ca_obj_grid.c +11 -3
  23. data/ext/ca_obj_meld.c +13 -8
  24. data/ext/ca_obj_moncmp.c +33 -14
  25. data/ext/ca_obj_monop.c +19 -7
  26. data/ext/ca_obj_object.c +99 -4
  27. data/ext/ca_obj_record.c +3 -1
  28. data/ext/ca_obj_refer.c +30 -6
  29. data/ext/ca_obj_roll.c +13 -5
  30. data/ext/ca_obj_select_axis.c +16 -6
  31. data/ext/ca_obj_shift.c +3 -3
  32. data/ext/ca_obj_stride.c +106 -23
  33. data/ext/ca_obj_string.c +3 -1
  34. data/ext/ca_obj_tile.c +13 -5
  35. data/ext/ca_obj_time.c +3 -1
  36. data/ext/ca_obj_timedelta.c +3 -1
  37. data/ext/ca_obj_transpose.c +1 -1
  38. data/ext/ca_obj_triop.c +62 -23
  39. data/ext/ca_obj_window.c +86 -17
  40. data/ext/ca_op_cmplx64.h +123 -0
  41. data/ext/ca_op_ipower.c +0 -3
  42. data/ext/ca_sort_kernels.h +5 -5
  43. data/ext/ca_sweep_engine.c +78 -35
  44. data/ext/ca_sweep_engine.h +13 -4
  45. data/ext/ca_transform_common.c +7 -0
  46. data/ext/carray.h +127 -130
  47. data/ext/carray_access.c +56 -34
  48. data/ext/carray_bincount.c +8 -8
  49. data/ext/carray_broadcast.c +99 -6
  50. data/ext/carray_build_flags.h +3 -0
  51. data/ext/carray_call_cfunc.c +1491 -0
  52. data/ext/carray_call_cfunc.h +153 -0
  53. data/ext/carray_cast.c +69 -31
  54. data/ext/carray_conversion.c +22 -22
  55. data/ext/carray_copy.c +12 -2
  56. data/ext/carray_core.c +57 -10
  57. data/ext/carray_count.c +1 -1
  58. data/ext/carray_element.c +7 -7
  59. data/ext/carray_factorize.c +28 -28
  60. data/ext/carray_hold.c +1 -1
  61. data/ext/carray_index_classifier.c +7 -21
  62. data/ext/carray_internal.h +19 -3
  63. data/ext/carray_kernels_bincmp.c +1 -0
  64. data/ext/carray_kernels_binop.c +320 -298
  65. data/ext/carray_kernels_init.c +1095 -0
  66. data/ext/carray_kernels_map.c +1 -0
  67. data/ext/carray_kernels_moncmp.c +1 -0
  68. data/ext/carray_kernels_monop.c +412 -411
  69. data/ext/carray_kernels_reduce_aggregate.c +88 -87
  70. data/ext/carray_kernels_reduce_boolean.c +1 -0
  71. data/ext/carray_kernels_reduce_cumulative.c +54 -53
  72. data/ext/carray_kernels_reduce_extreme.c +51 -50
  73. data/ext/carray_kernels_reduce_variance.c +1 -0
  74. data/ext/carray_kernels_scan.c +1 -0
  75. data/ext/carray_kernels_search.c +93 -92
  76. data/ext/carray_kernels_sort.c +1 -0
  77. data/ext/carray_kernels_triop.c +1 -0
  78. data/ext/carray_lazy.c +175 -5
  79. data/ext/carray_mask.c +6 -5
  80. data/ext/carray_median_percentile.c +1 -1
  81. data/ext/carray_memory_view.c +46 -21
  82. data/ext/carray_operator.c +46 -49
  83. data/ext/carray_partition.c +4 -4
  84. data/ext/carray_random.c +7 -7
  85. data/ext/carray_scatter.c +1 -1
  86. data/ext/carray_sort.c +3 -3
  87. data/ext/carray_sort_kernel.c +10 -10
  88. data/ext/carray_test.c +1 -1
  89. data/ext/extconf.rb +18 -0
  90. data/ext/mk_call_cfunc.rb +243 -52
  91. data/ext/mkkernel.rb +354 -100
  92. data/ext/ruby_carray.c +18 -16
  93. data/ext/version.h +4 -4
  94. data/lib/carray/autoload_carray.rb +19 -0
  95. data/lib/carray/autoload_method_extension.rb +2 -1
  96. data/lib/carray/axis_group.rb +7 -7
  97. data/lib/carray/basics.rb +59 -59
  98. data/lib/carray/bincount_nd.rb +12 -12
  99. data/lib/carray/block_iterator.rb +24 -14
  100. data/lib/carray/categorical.rb +4 -4
  101. data/lib/carray/categorical_iterator.rb +98 -76
  102. data/lib/carray/conditional.rb +14 -14
  103. data/lib/carray/construct.rb +21 -0
  104. data/lib/carray/core_extensions.rb +62 -5
  105. data/lib/carray/data_type_extension.rb +18 -1
  106. data/lib/carray/frame/concat.rb +3 -3
  107. data/lib/carray/frame/convert.rb +1 -1
  108. data/lib/carray/frame/frame.rb +4 -11
  109. data/lib/carray/frame/io.rb +159 -2
  110. data/lib/carray/frame/sort.rb +1 -1
  111. data/lib/carray/frame/verbs.rb +18 -1
  112. data/lib/carray/fuse_source.rb +123 -0
  113. data/lib/carray/fusion.rb +218 -0
  114. data/lib/carray/histogram.rb +16 -16
  115. data/lib/carray/inspect.rb +1 -7
  116. data/lib/carray/iterator.rb +4 -3
  117. data/lib/carray/lazy.rb +125 -73
  118. data/lib/carray/meld_reduce.rb +2 -2
  119. data/lib/carray/methods/align_addr.rb +1 -1
  120. data/lib/carray/methods/composition.rb +1 -1
  121. data/lib/carray/methods/is_in.rb +12 -12
  122. data/lib/carray/methods/locate_addr.rb +6 -1
  123. data/lib/carray/methods/mask_duplicates.rb +1 -1
  124. data/lib/carray/methods/meshgrid.rb +4 -5
  125. data/lib/carray/methods/mode.rb +2 -2
  126. data/lib/carray/methods/nunique.rb +1 -1
  127. data/lib/carray/methods/snap.rb +7 -2
  128. data/lib/carray/methods/unique.rb +3 -3
  129. data/lib/carray/methods/value_counts.rb +2 -2
  130. data/lib/carray/runtime.rb +0 -19
  131. data/lib/carray/slab_iterator.rb +20 -7
  132. data/lib/carray/string_operation_extension.rb +5 -5
  133. data/lib/carray/time.rb +890 -491
  134. data/lib/carray/window_iterator.rb +285 -13
  135. data/lib/carray.rb +5 -5
  136. data/yard-stubs/ca_obj_array.rb +385 -0
  137. data/yard-stubs/ca_obj_bitarray.rb +38 -0
  138. data/yard-stubs/ca_obj_bitfield.rb +43 -0
  139. data/yard-stubs/ca_obj_block.rb +73 -0
  140. data/yard-stubs/ca_obj_byte_swap.rb +56 -0
  141. data/yard-stubs/ca_obj_fake.rb +31 -0
  142. data/yard-stubs/ca_obj_farray.rb +32 -0
  143. data/yard-stubs/ca_obj_field.rb +45 -0
  144. data/yard-stubs/ca_obj_grid.rb +35 -0
  145. data/yard-stubs/ca_obj_refer.rb +72 -0
  146. data/yard-stubs/ca_obj_roll.rb +45 -0
  147. data/yard-stubs/ca_obj_shift.rb +43 -0
  148. data/yard-stubs/ca_obj_stride.rb +181 -0
  149. data/yard-stubs/ca_obj_tile.rb +29 -0
  150. data/yard-stubs/ca_obj_transpose.rb +40 -0
  151. data/yard-stubs/ca_obj_window.rb +49 -0
  152. data/yard-stubs/carray_access.rb +131 -0
  153. data/yard-stubs/carray_attribute.rb +246 -0
  154. data/yard-stubs/carray_broadcast.rb +37 -0
  155. data/yard-stubs/carray_cast.rb +489 -0
  156. data/yard-stubs/carray_class.rb +65 -0
  157. data/yard-stubs/carray_conversion.rb +76 -0
  158. data/yard-stubs/carray_copy.rb +79 -0
  159. data/yard-stubs/carray_core.rb +114 -0
  160. data/yard-stubs/carray_count.rb +79 -0
  161. data/yard-stubs/carray_element.rb +108 -0
  162. data/yard-stubs/carray_generate.rb +66 -0
  163. data/yard-stubs/carray_lazy.rb +23 -0
  164. data/yard-stubs/carray_loop.rb +140 -0
  165. data/yard-stubs/carray_mask.rb +259 -0
  166. data/yard-stubs/carray_math.rb +132 -0
  167. data/yard-stubs/carray_mathfunc.rb +45 -0
  168. data/yard-stubs/carray_median_percentile.rb +89 -0
  169. data/yard-stubs/carray_memory_view.rb +163 -0
  170. data/yard-stubs/carray_order.rb +312 -0
  171. data/yard-stubs/carray_random.rb +89 -0
  172. data/yard-stubs/carray_scatter.rb +106 -0
  173. data/yard-stubs/carray_slab.rb +57 -0
  174. data/yard-stubs/carray_sort.rb +163 -0
  175. data/yard-stubs/carray_test.rb +85 -0
  176. data/yard-stubs/carray_undef.rb +64 -0
  177. data/yard-stubs/carray_utils.rb +97 -0
  178. data/yard-stubs/ruby_carray.rb +193 -0
  179. metadata +59 -10
  180. data/ext/ca_obj_unbound_repeat.c +0 -496
@@ -34,6 +34,7 @@ static inline VALUE BOOL2VAL (boolean8_t x) { return x ? Qtrue : Qfalse; }
34
34
  #endif
35
35
 
36
36
  #include "ca_op_powi.h"
37
+ #include "ca_op_cmplx64.h"
37
38
 
38
39
  /* ===== sort_index_ki ============================================ */
39
40
 
@@ -34,6 +34,7 @@ static inline VALUE BOOL2VAL (boolean8_t x) { return x ? Qtrue : Qfalse; }
34
34
  #endif
35
35
 
36
36
  #include "ca_op_powi.h"
37
+ #include "ca_op_cmplx64.h"
37
38
 
38
39
  /* ===== triop fma ============================================ */
39
40
 
data/ext/carray_lazy.c CHANGED
@@ -93,6 +93,10 @@ ca_lazy_marker_setup (CALazyMarker *ca, CArray *parent)
93
93
  the parent and violate the shadow semantics. */
94
94
  ca_set_flag(ca, CA_FLAG_READ_ONLY);
95
95
 
96
+ /* Storage-identical wrapper: the kernel_iterator entry strip and the
97
+ view-creation lift both ask for this. */
98
+ ca_set_flag(ca, CA_FLAG_IS_LAZY_MARKER);
99
+
96
100
  return 0;
97
101
  }
98
102
 
@@ -253,6 +257,105 @@ rb_ca_lazy (VALUE self)
253
257
  return rb_ca_lazy_marker_new(self);
254
258
  }
255
259
 
260
+ /* ------------------------------------------------------------------- */
261
+ /* CA_OBJECT GC guard */
262
+ /* ------------------------------------------------------------------- */
263
+
264
+ /* A CA_OBJECT cell is a VALUE. While a view materialises into a raw
265
+ buffer -- the destination `copy` will hand out, a view's own attach
266
+ buffer, or an arena scratch -- those VALUEs live somewhere the GC
267
+ cannot see: an xmalloc'd block owned by no Ruby object. For every
268
+ other data_type that is harmless, but an object-lane kernel calls
269
+ rb_funcall per cell, and any of those can collect. The results
270
+ written so far are then freed under the buffer, and the array comes
271
+ back holding recycled slots: wrong values, and a crash once a slot is
272
+ reused as something else.
273
+
274
+ So such buffers are registered here for the duration and marked from
275
+ a hidden guard object that lives as long as the process. Two kinds
276
+ are registered:
277
+
278
+ holds a contiguous run of cells, pushed and popped around a
279
+ whole-view transfer or the handoff to a Ruby owner
280
+ slots an arena scratch, tagged at acquire and held until release
281
+
282
+ Only contiguous runs are held. A strided window would cover the same
283
+ cells but its extent has to be taken on trust from the caller's
284
+ strides, and a window wider than the buffer behind it would be filled
285
+ and marked out of bounds. The whole-view entry knows the extent
286
+ exactly, and every partial transfer writes inside it. */
287
+
288
+ #define CA_GC_HOLD_MAX 64
289
+
290
+ typedef struct ca_gc_hold {
291
+ VALUE *ptr;
292
+ ca_size_t elements;
293
+ } ca_gc_hold_t;
294
+
295
+ static ca_gc_hold_t ca_gc_holds[CA_GC_HOLD_MAX];
296
+ static int ca_gc_hold_depth = 0;
297
+
298
+ static void ca_lazy_arena_mark_object_slots (void);
299
+
300
+ /* Returns the depth to hand back to ca_gc_hold_pop_to, so nested holds
301
+ unwind in order. Pushing is best-effort at the ceiling: a chain
302
+ deeper than CA_GC_HOLD_MAX loses the protection for its innermost
303
+ buffers rather than raising in the middle of a materialise. The cells
304
+ must already be valid VALUEs -- the caller fills a fresh buffer with
305
+ Qnil first. */
306
+ int
307
+ ca_gc_hold_push (void *ptr, ca_size_t n_elements)
308
+ {
309
+ int depth = ca_gc_hold_depth;
310
+
311
+ if ( depth >= CA_GC_HOLD_MAX || n_elements <= 0 || ptr == NULL ) {
312
+ return -1;
313
+ }
314
+ ca_gc_holds[depth].ptr = (VALUE *) ptr;
315
+ ca_gc_holds[depth].elements = n_elements;
316
+ ca_gc_hold_depth = depth + 1;
317
+ return depth;
318
+ }
319
+
320
+ void
321
+ ca_gc_hold_pop_to (int depth)
322
+ {
323
+ if ( depth >= 0 && depth < ca_gc_hold_depth ) {
324
+ ca_gc_hold_depth = depth;
325
+ }
326
+ }
327
+
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)
336
+ {
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++);
343
+ }
344
+ ca_lazy_arena_mark_object_slots();
345
+ }
346
+
347
+ static void
348
+ ca_gc_guard_free (void *ptr)
349
+ {
350
+ (void) ptr;
351
+ }
352
+
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
+
256
359
  /* ------------------------------------------------------------------- */
257
360
  /* ca_lazy_arena */
258
361
  /* ------------------------------------------------------------------- */
@@ -277,8 +380,9 @@ rb_ca_lazy (VALUE self)
277
380
  programming error or a pathological chain rather than a graceful
278
381
  fallback.
279
382
 
280
- CAREFUL: single-thread only (see CLAUDE.md "thread-safety is not a
281
- goal"). The arena is process-global static state. */
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. */
282
386
 
283
387
  #define CA_LAZY_ARENA_SLOTS 32
284
388
 
@@ -286,6 +390,7 @@ typedef struct ca_lazy_arena_slot {
286
390
  void *ptr; /* xmalloc'd buffer, NULL = unused-and-unallocated */
287
391
  ca_size_t bytes; /* allocated size of ptr (= capacity of this slot) */
288
392
  int in_use; /* 1 = currently acquired, 0 = available */
393
+ ca_size_t object_elements; /* >0 = holds VALUEs; marked while in_use */
289
394
  } ca_lazy_arena_slot_t;
290
395
 
291
396
  typedef struct ca_lazy_arena {
@@ -308,6 +413,7 @@ ca_lazy_arena_enter (void)
308
413
  int i;
309
414
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
310
415
  ca_lazy_arena.slots[i].in_use = 0;
416
+ ca_lazy_arena.slots[i].object_elements = 0;
311
417
  }
312
418
  }
313
419
  ca_lazy_arena.depth++;
@@ -319,7 +425,16 @@ ca_lazy_arena_exit (void)
319
425
  if ( ca_lazy_arena.depth > 0 ) {
320
426
  ca_lazy_arena.depth--;
321
427
  }
322
- /* Keep all slot buffers; they amortise into subsequent calls. */
428
+ /* Keep all slot buffers, and leave in_use and the object tags alone.
429
+ A scratch can be acquired outside any enter/exit bracket -- a lazy
430
+ view's attach path does exactly that -- so an inner materialise
431
+ dropping to depth 0 is not evidence that nothing is held. Recovery
432
+ from an abandoned slot happens on the next depth-0 entry instead.
433
+
434
+ An abandoned object slot stays marked until then, which is a leak
435
+ but not a hazard: acquire only ever hands out a slot that is not in
436
+ use, so nothing overwrites it, and marking is what keeps its cells
437
+ from being collected in the first place. */
323
438
  }
324
439
 
325
440
  void *
@@ -350,6 +465,7 @@ ca_lazy_arena_acquire (ca_size_t bytes)
350
465
  }
351
466
  if ( best >= 0 ) {
352
467
  ca_lazy_arena.slots[best].in_use = 1;
468
+ ca_lazy_arena.slots[best].object_elements = 0;
353
469
  ca_lazy_arena.debug_reuse_count++;
354
470
  return ca_lazy_arena.slots[best].ptr;
355
471
  }
@@ -367,6 +483,7 @@ ca_lazy_arena_acquire (ca_size_t bytes)
367
483
  ca_lazy_arena.slots[empty].ptr = xmalloc(bytes);
368
484
  ca_lazy_arena.slots[empty].bytes = bytes;
369
485
  ca_lazy_arena.slots[empty].in_use = 1;
486
+ ca_lazy_arena.slots[empty].object_elements = 0;
370
487
  ca_lazy_arena.debug_xmalloc_count++;
371
488
  return ca_lazy_arena.slots[empty].ptr;
372
489
  }
@@ -380,6 +497,7 @@ ca_lazy_arena_acquire (ca_size_t bytes)
380
497
  ca_lazy_arena.slots[i].ptr = xmalloc(bytes);
381
498
  ca_lazy_arena.slots[i].bytes = bytes;
382
499
  ca_lazy_arena.slots[i].in_use = 1;
500
+ ca_lazy_arena.slots[i].object_elements = 0;
383
501
  ca_lazy_arena.debug_xmalloc_count++;
384
502
  return ca_lazy_arena.slots[i].ptr;
385
503
  }
@@ -402,6 +520,7 @@ ca_lazy_arena_release (void *ptr)
402
520
  for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
403
521
  if ( ca_lazy_arena.slots[i].ptr == ptr ) {
404
522
  ca_lazy_arena.slots[i].in_use = 0;
523
+ ca_lazy_arena.slots[i].object_elements = 0;
405
524
  return;
406
525
  }
407
526
  }
@@ -410,6 +529,52 @@ ca_lazy_arena_release (void *ptr)
410
529
  genuine bugs at unrelated callsites. */
411
530
  }
412
531
 
532
+ /* Acquire a scratch that will hold `n_elements` VALUEs.
533
+
534
+ Unlike the byte-sized acquire this one Qnil-fills the buffer and tags
535
+ the slot, so the cells stay markable for as long as the caller holds
536
+ it. Object-lane kernels run rb_funcall over their operands, so a
537
+ scratch pulled from a lazy operand holds VALUEs that exist nowhere
538
+ else until the kernel has consumed them. */
539
+ void *
540
+ ca_lazy_arena_acquire_object (ca_size_t n_elements)
541
+ {
542
+ void *ptr;
543
+ int i;
544
+
545
+ if ( n_elements < 0 ) n_elements = 0;
546
+ ptr = ca_lazy_arena_acquire(n_elements * (ca_size_t) sizeof(VALUE));
547
+
548
+ {
549
+ VALUE *p = (VALUE *) ptr;
550
+ ca_size_t n = n_elements;
551
+ while ( n-- ) *p++ = Qnil;
552
+ }
553
+
554
+ 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;
557
+ break;
558
+ }
559
+ }
560
+ return ptr;
561
+ }
562
+
563
+ static void
564
+ ca_lazy_arena_mark_object_slots (void)
565
+ {
566
+ int i;
567
+ for ( i = 0; i < CA_LAZY_ARENA_SLOTS; i++ ) {
568
+ ca_lazy_arena_slot_t *s = &ca_lazy_arena.slots[i];
569
+ ca_size_t n;
570
+ VALUE *p;
571
+ if ( ! s->in_use || s->object_elements <= 0 ) continue;
572
+ p = (VALUE *) s->ptr;
573
+ n = s->object_elements;
574
+ while ( n-- ) rb_gc_mark(*p++);
575
+ }
576
+ }
577
+
413
578
  /* Test instrumentation — exposed to Ruby for the arena smoke and
414
579
  regression assertions. Not user-facing API. */
415
580
  static VALUE
@@ -490,8 +655,7 @@ rb_ca_lazy_arena_s_slot_capacities (VALUE klass)
490
655
  * just let the scalar choose)
491
656
  * numeric self -> self_ca->data_type
492
657
  *
493
- * Callers broadcast against the array via stride 0 in xfer_stride
494
- * (or wrap the scalar in CAUnboundRepeat when the chain needs it). */
658
+ * Callers broadcast against the array via stride 0 in xfer_stride. */
495
659
  VALUE
496
660
  ca_lazy_wrap_scalar (VALUE other, CArray *self_ca)
497
661
  {
@@ -543,6 +707,12 @@ Init_carray_lazy (void)
543
707
 
544
708
  rb_define_method(rb_cCArray, "lazy", rb_ca_lazy, 0);
545
709
 
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);
715
+
546
716
  rb_define_alloc_func(rb_cCALazyMarker, rb_ca_lazy_marker_s_allocate);
547
717
  rb_define_method(rb_cCALazyMarker, "initialize_copy",
548
718
  rb_ca_lazy_marker_initialize_copy, 1);
data/ext/carray_mask.c CHANGED
@@ -607,7 +607,7 @@ ca_count_not_masked (void *ap)
607
607
  /* Scalar-fill every masked cell with fill_value, then clear its mask bit.
608
608
  This depends only on the cell BYTE WIDTH, not the data type (the work is
609
609
  a width-sized copy), so a single width switch covers every numeric /
610
- complex / object dtype; the default arm handles fixlen and any other
610
+ complex / object data type; the default arm handles fixlen and any other
611
611
  width via memcpy. The typed arms let the compiler emit a plain store. */
612
612
  #define proc_fill_bang_width(T) \
613
613
  { \
@@ -894,6 +894,7 @@ rb_ca_value_array (VALUE self)
894
894
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
895
895
 
896
896
  obj = rb_ca_refer_new(self, ca->data_type, ca->ndim, ca->dim, ca->bytes, 0);
897
+
897
898
  TypedData_Get_Struct(obj, CArray, &carray_data_type, co);
898
899
 
899
900
  /* Value arrays ignore the mask by definition. ca_stride_setup
@@ -1112,7 +1113,7 @@ The returned array doesn't have the mask array.
1112
1113
  static VALUE
1113
1114
  rb_ca_unmask_method (int argc, VALUE *argv, VALUE self)
1114
1115
  {
1115
- volatile VALUE rfval = CA_NIL, rcs;
1116
+ volatile VALUE rfval = CA_UNSPECIFIED, rcs;
1116
1117
  CArray *ca;
1117
1118
  CScalar *cv;
1118
1119
  char *fval = NULL;
@@ -1125,7 +1126,7 @@ rb_ca_unmask_method (int argc, VALUE *argv, VALUE self)
1125
1126
 
1126
1127
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
1127
1128
 
1128
- if ( rfval != CA_NIL ) {
1129
+ if ( rfval != CA_UNSPECIFIED ) {
1129
1130
  /* Face has surface != storage; the fill value is cast in the storage data_type. */
1130
1131
  int8_t conv_type = ca->data_type;
1131
1132
  if ( ca_is_face(ca) ) {
@@ -1171,7 +1172,7 @@ The returned array doesn't have the mask array.
1171
1172
  static VALUE
1172
1173
  rb_ca_unmask_copy_method (int argc, VALUE *argv, VALUE self)
1173
1174
  {
1174
- volatile VALUE obj, rfval = CA_NIL, rcs;
1175
+ volatile VALUE obj, rfval = CA_UNSPECIFIED, rcs;
1175
1176
  CArray *ca, *co;
1176
1177
  CScalar *cv;
1177
1178
  char *fval = NULL;
@@ -1182,7 +1183,7 @@ rb_ca_unmask_copy_method (int argc, VALUE *argv, VALUE self)
1182
1183
 
1183
1184
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
1184
1185
 
1185
- if ( rfval != CA_NIL ) {
1186
+ if ( rfval != CA_UNSPECIFIED ) {
1186
1187
  /* Face has surface != storage; the fill value is cast in the storage data_type. */
1187
1188
  int8_t conv_type = ca->data_type;
1188
1189
  if ( ca_is_face(ca) ) {
@@ -152,7 +152,7 @@ typedef enum { PCT_LOWER_ONLY, PCT_UPPER_ONLY, PCT_BOTH } pct_need_t;
152
152
 
153
153
  /* [shared] which of lower (sorted[k]) / upper (sorted[k+1]) the method
154
154
  needs. Lets the caller skip the fetch it won't use. Used by both
155
- lanes (the need classification is dtype-independent). */
155
+ lanes (the need classification is data type-independent). */
156
156
  static pct_need_t
157
157
  pct_need (VALUE method, long k, double r, long n)
158
158
  {
@@ -99,46 +99,70 @@ static ca_mv_runtime_type_t ca_mv_runtime_types[CA_MV_NUM_RUNTIME_OBJ_TYPES] = {
99
99
 
100
100
  /* ---------------- format / data_type mapping ----------------
101
101
  Bidirectional table. Outbound table (ca_mv_format_for) picks the
102
- canonical PEP 3118 specifier for each CArray data_type. Inbound
103
- table (ca_mv_data_type_from_format) is Postel: it accepts a wider
104
- set of synonyms that other producers emit (they describe int32
105
- differently). */
102
+ specifier each CArray data_type publishes at the top level of a
103
+ MemoryView. Inbound table (ca_mv_data_type_from_format) is Postel:
104
+ it accepts a wider set of synonyms that other producers emit (they
105
+ describe int32 differently).
106
+
107
+ The outbound spellings follow ruby/memory_view.h, which documents
108
+ `format` as a sequence of pack-derived specifiers and expects
109
+ item_size to equal rb_memory_view_item_size_from_format(format).
110
+ PEP 3118 reuses several of those letters for other types, so the two
111
+ vocabularies agree on `i` / `I` / `q` / `Q` / `f` / `d` and disagree
112
+ below 32 bits: what PEP calls `b` / `B` / `h` / `H`, Ruby calls
113
+ `c` / `C` / `s` / `S`. Emitting Ruby's spelling is what lets a
114
+ generic Ruby consumer -- Fiddle::MemoryView, say -- read elements out
115
+ of a CArray at all; PEP-speaking consumers are reached across the
116
+ language boundary, where pycall-memoryview translates.
117
+
118
+ Three types have no Ruby spelling at all: boolean and the two
119
+ complex widths. They keep their PEP form, which Ruby's parser
120
+ rejects, because the alternative ("C" for a boolean, "dd" for a
121
+ complex) would misdescribe the data rather than merely fail to
122
+ describe it. */
106
123
 
107
124
  static const char *
108
125
  ca_mv_format_for (int8_t data_type)
109
126
  {
110
127
  switch (data_type) {
111
- case CA_BOOLEAN: return "?";
112
- case CA_INT8: return "b";
113
- case CA_UINT8: return "B";
114
- case CA_INT16: return "h";
115
- case CA_UINT16: return "H";
128
+ case CA_BOOLEAN: return "?"; /* no Ruby spelling */
129
+ case CA_INT8: return "c";
130
+ case CA_UINT8: return "C";
131
+ case CA_INT16: return "s";
132
+ case CA_UINT16: return "S";
116
133
  case CA_INT32: return "i";
117
134
  case CA_UINT32: return "I";
118
135
  case CA_INT64: return "q";
119
136
  case CA_UINT64: return "Q";
120
137
  case CA_FLOAT32: return "f";
121
138
  case CA_FLOAT64: return "d";
122
- case CA_CMPLX64: return "Zf";
123
- case CA_CMPLX128: return "Zd";
139
+ case CA_CMPLX64: return "Zf"; /* no Ruby spelling */
140
+ case CA_CMPLX128: return "Zd"; /* no Ruby spelling */
124
141
  default:
125
142
  return NULL;
126
143
  }
127
144
  }
128
145
 
129
146
  /* PEP 3118 single-character specifier for a primitive data_type, for
130
- use inside a `T{...}` struct format body. Identical to the top-level
131
- producer specifier: since the 2026-06-29 PEP 3118 strict flip both the
132
- top-level and struct-body contexts emit the same chars, so this
133
- delegates to ca_mv_format_for. Kept as a distinct entry point so the
134
- struct-body context can diverge again without touching call sites.
147
+ use inside a `T{...}` struct format body. This is the context the
148
+ distinct entry point was kept for: `T{...}` is a PEP 3118 construct
149
+ with no Ruby counterpart, so a record body stays in PEP's vocabulary
150
+ even though the top level now publishes Ruby's. A consumer reading
151
+ the body reads it as PEP, and a bridge that forwards the record
152
+ whole -- as pycall-memoryview does -- keeps it intact.
135
153
 
136
154
  Returns NULL for non-primitive (CA_FIXLEN / CA_OBJECT) or for the
137
155
  retired float128 / cmplx256 enum slots. */
138
156
  static const char *
139
157
  ca_mv_pep3118_char (int8_t data_type)
140
158
  {
141
- return ca_mv_format_for(data_type);
159
+ switch (data_type) {
160
+ case CA_INT8: return "b";
161
+ case CA_UINT8: return "B";
162
+ case CA_INT16: return "h";
163
+ case CA_UINT16: return "H";
164
+ default: return ca_mv_format_for(data_type);
165
+ }
142
166
  }
143
167
 
144
168
  /* Build (or fetch from cache) the PEP 3118 struct format string for
@@ -362,7 +386,7 @@ ca_mv_data_type_from_format (const char *format, ssize_t item_size)
362
386
  }
363
387
 
364
388
  /* Derive the data_type a MemoryView producer would import as, by parsing its
365
- format — without importing the data. This is the canonical "what dtype is
389
+ format — without importing the data. This is the canonical "what data type is
366
390
  this MV" question, decoupled from the import strategy (copy via
367
391
  from_memory_view vs zero-copy via wrap_memory_view). Used by
368
392
  ca_arg_to_data_type so CArray.result_type / promote_list can treat an MV
@@ -414,7 +438,6 @@ ca_mv_strategy_for (int16_t obj_type)
414
438
  if (obj_type == CA_OBJ_SELECT) return CA_MV_ATTACH;
415
439
  if (obj_type == CA_OBJ_REPEAT) return CA_MV_REPEAT;
416
440
  if (obj_type == CA_OBJ_OBJECT) return CA_MV_REJECT;
417
- if (obj_type == CA_OBJ_UNBOUND_REPEAT) return CA_MV_REJECT;
418
441
  /* Plain CAStride (created via #as_strided, or wrap_memory_view from
419
442
  a strided producer). Reuse the CA_MV_TRANS handler since it
420
443
  already reads strides[] / base_offset directly from the CAStride
@@ -510,7 +533,6 @@ ca_mv_reject_reason_for (CArray *ca)
510
533
  /* Compile-time obj_types we know about */
511
534
  if (ca->obj_type == CA_OBJ_SELECT) return "boolean-mask selection (positions not expressible as strides)";
512
535
  if (ca->obj_type == CA_OBJ_OBJECT) return "stores Ruby VALUEs, not raw bytes";
513
- if (ca->obj_type == CA_OBJ_UNBOUND_REPEAT) return "shape is not bound";
514
536
  /* Runtime-assigned obj_types: look up by name in the strategy table */
515
537
  for (int i = 0; i < CA_MV_NUM_RUNTIME_OBJ_TYPES; i++) {
516
538
  if (ca_mv_runtime_types[i].id == ca->obj_type) {
@@ -1110,7 +1132,10 @@ ca_mv_acquire_mask_view (VALUE mask_src, int8_t data_ndim,
1110
1132
  if (c == '<' || c == '>' || c == '=' || c == '!' || c == '@') {
1111
1133
  c = mv->format[1];
1112
1134
  }
1113
- if (c != '?' && c != 'B' && c != 'b') {
1135
+ /* One byte per element, under either vocabulary: PEP 3118 spells the
1136
+ integer widths 'b' / 'B', Ruby's MemoryView spells them 'c' / 'C'.
1137
+ '?' is PEP's boolean, which Ruby has no spelling for. */
1138
+ if (c != '?' && c != 'B' && c != 'b' && c != 'C' && c != 'c') {
1114
1139
  char fmt_buf[64];
1115
1140
  snprintf(fmt_buf, sizeof(fmt_buf), "%s", mv->format);
1116
1141
  MV_MASK_CLEANUP_AND_RAISE("mask: format must be bool/uint8/int8 (got %s)",