carray 3.0.0 → 3.0.2

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 (202) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -1
  3. data/CHANGELOG.md +845 -3
  4. data/{NEWS.md → CHANGELOG.v1.md} +1 -1
  5. data/README.md +33 -33
  6. data/carray.gemspec +12 -8
  7. data/ext/ca_axis_dispatch.c +33 -4
  8. data/ext/ca_axis_group.c +256 -114
  9. data/ext/ca_binop_dispatch.c +5 -6
  10. data/ext/ca_binop_dispatch.h +0 -7
  11. data/ext/ca_categorical_iterator.c +121 -67
  12. data/ext/ca_for_each_element.h +6 -8
  13. data/ext/ca_group_iter.c +1 -1
  14. data/ext/ca_kernel_iterator.c +386 -74
  15. data/ext/ca_kernel_iterator.h +145 -45
  16. data/ext/ca_obj_array.c +62 -20
  17. data/ext/ca_obj_bincmp.c +57 -27
  18. data/ext/ca_obj_binop.c +86 -30
  19. data/ext/ca_obj_block.c +4 -4
  20. data/ext/ca_obj_const_string.c +88 -27
  21. data/ext/ca_obj_face.c +104 -0
  22. data/ext/ca_obj_face.h +48 -1
  23. data/ext/ca_obj_fake.c +11 -0
  24. data/ext/ca_obj_fixlen_string.c +21 -6
  25. data/ext/ca_obj_grid.c +11 -3
  26. data/ext/ca_obj_meld.c +125 -22
  27. data/ext/ca_obj_moncmp.c +33 -14
  28. data/ext/ca_obj_monop.c +19 -7
  29. data/ext/ca_obj_object.c +107 -4
  30. data/ext/ca_obj_record.c +3 -1
  31. data/ext/ca_obj_refer.c +30 -6
  32. data/ext/ca_obj_roll.c +13 -5
  33. data/ext/ca_obj_select.c +49 -34
  34. data/ext/ca_obj_select_axis.c +16 -6
  35. data/ext/ca_obj_shift.c +3 -3
  36. data/ext/ca_obj_stack.c +3 -8
  37. data/ext/ca_obj_stride.c +178 -24
  38. data/ext/ca_obj_string.c +11 -5
  39. data/ext/ca_obj_tile.c +13 -5
  40. data/ext/ca_obj_time.c +3 -1
  41. data/ext/ca_obj_timedelta.c +3 -1
  42. data/ext/ca_obj_transpose.c +1 -1
  43. data/ext/ca_obj_triop.c +62 -23
  44. data/ext/ca_obj_window.c +94 -19
  45. data/ext/ca_op_cmplx64.h +123 -0
  46. data/ext/ca_op_ipower.c +1 -5
  47. data/ext/ca_rng_normal.h +42 -0
  48. data/ext/ca_rng_xoshiro256pp.h +105 -0
  49. data/ext/ca_sort_kernels.h +5 -5
  50. data/ext/ca_sweep_engine.c +362 -155
  51. data/ext/ca_sweep_engine.h +39 -9
  52. data/ext/ca_transform_common.c +7 -0
  53. data/ext/carray.h +148 -132
  54. data/ext/carray_access.c +83 -49
  55. data/ext/carray_address_basis.c +590 -0
  56. data/ext/carray_bincount.c +8 -8
  57. data/ext/carray_broadcast.c +102 -9
  58. data/ext/carray_build_flags.h +3 -0
  59. data/ext/carray_call_cfunc.c +2010 -335
  60. data/ext/carray_call_cfunc.h +153 -0
  61. data/ext/carray_cast.c +184 -72
  62. data/ext/carray_conversion.c +22 -22
  63. data/ext/carray_copy.c +67 -32
  64. data/ext/carray_core.c +140 -13
  65. data/ext/carray_count.c +10 -11
  66. data/ext/carray_element.c +7 -7
  67. data/ext/carray_factorize.c +72 -51
  68. data/ext/carray_hold.c +1 -1
  69. data/ext/carray_index_classifier.c +7 -21
  70. data/ext/carray_internal.h +36 -3
  71. data/ext/carray_kernels_bincmp.c +1 -0
  72. data/ext/carray_kernels_binop.c +320 -298
  73. data/ext/carray_kernels_init.c +1095 -0
  74. data/ext/carray_kernels_map.c +1 -0
  75. data/ext/carray_kernels_moncmp.c +1 -0
  76. data/ext/carray_kernels_monop.c +412 -411
  77. data/ext/carray_kernels_reduce_aggregate.c +256 -87
  78. data/ext/carray_kernels_reduce_boolean.c +1 -0
  79. data/ext/carray_kernels_reduce_cumulative.c +324 -54
  80. data/ext/carray_kernels_reduce_extreme.c +605 -58
  81. data/ext/carray_kernels_reduce_variance.c +1 -0
  82. data/ext/carray_kernels_scan.c +5 -4
  83. data/ext/carray_kernels_search.c +187 -106
  84. data/ext/carray_kernels_sort.c +1 -0
  85. data/ext/carray_kernels_triop.c +1 -0
  86. data/ext/carray_lazy.c +175 -5
  87. data/ext/carray_loop.c +7 -1
  88. data/ext/carray_mask.c +29 -13
  89. data/ext/carray_median_percentile.c +56 -1
  90. data/ext/carray_memory_view.c +46 -21
  91. data/ext/carray_operator.c +50 -53
  92. data/ext/carray_order.c +1 -1
  93. data/ext/carray_partition.c +4 -4
  94. data/ext/carray_random.c +391 -47
  95. data/ext/carray_scatter.c +1 -1
  96. data/ext/carray_slab.c +13 -0
  97. data/ext/carray_sort.c +22 -24
  98. data/ext/carray_sort_kernel.c +10 -10
  99. data/ext/carray_test.c +1 -1
  100. data/ext/extconf.rb +18 -0
  101. data/ext/mk_call_cfunc.rb +323 -145
  102. data/ext/mkkernel.rb +651 -129
  103. data/ext/ruby_carray.c +28 -17
  104. data/ext/version.h +4 -4
  105. data/lib/carray/autoload_carray.rb +24 -3
  106. data/lib/carray/autoload_method_extension.rb +14 -1
  107. data/lib/carray/axis_group.rb +84 -7
  108. data/lib/carray/basics.rb +63 -59
  109. data/lib/carray/bincount_nd.rb +12 -12
  110. data/lib/carray/block_iterator.rb +111 -25
  111. data/lib/carray/categorical.rb +154 -37
  112. data/lib/carray/categorical_iterator.rb +300 -151
  113. data/lib/carray/conditional.rb +14 -14
  114. data/lib/carray/const_string.rb +131 -27
  115. data/lib/carray/construct.rb +61 -0
  116. data/lib/carray/core_extensions.rb +62 -5
  117. data/lib/carray/data_type_extension.rb +21 -1
  118. data/lib/carray/data_type_limits.rb +91 -0
  119. data/lib/carray/fixlen_string.rb +1 -1
  120. data/lib/carray/frame/concat.rb +3 -3
  121. data/lib/carray/frame/convert.rb +1 -1
  122. data/lib/carray/frame/csv_parser.rb +11 -4
  123. data/lib/carray/frame/frame.rb +85 -21
  124. data/lib/carray/frame/group.rb +36 -3
  125. data/lib/carray/frame/io.rb +224 -15
  126. data/lib/carray/frame/records.rb +18 -4
  127. data/lib/carray/frame/sort.rb +1 -1
  128. data/lib/carray/frame/verbs.rb +32 -12
  129. data/lib/carray/fuse_source.rb +123 -0
  130. data/lib/carray/fusion.rb +218 -0
  131. data/lib/carray/histogram.rb +16 -16
  132. data/lib/carray/inspect.rb +43 -16
  133. data/lib/carray/iterator.rb +147 -3
  134. data/lib/carray/lazy.rb +88 -73
  135. data/lib/carray/mask_gap_fill.rb +3 -1
  136. data/lib/carray/meld_reduce.rb +2 -2
  137. data/lib/carray/methods/align_addr.rb +1 -1
  138. data/lib/carray/methods/composition.rb +1 -1
  139. data/lib/carray/methods/discovery_along.rb +74 -0
  140. data/lib/carray/methods/factorize.rb +50 -0
  141. data/lib/carray/methods/is_in.rb +25 -14
  142. data/lib/carray/methods/locate_addr.rb +81 -3
  143. data/lib/carray/methods/mask_duplicates.rb +36 -2
  144. data/lib/carray/methods/meshgrid.rb +4 -5
  145. data/lib/carray/methods/mode.rb +2 -2
  146. data/lib/carray/methods/nunique.rb +23 -2
  147. data/lib/carray/methods/repeat.rb +110 -0
  148. data/lib/carray/methods/snap.rb +7 -2
  149. data/lib/carray/methods/unique.rb +44 -4
  150. data/lib/carray/methods/value_counts.rb +2 -2
  151. data/lib/carray/rng.rb +86 -0
  152. data/lib/carray/runtime.rb +0 -19
  153. data/lib/carray/slab_iterator.rb +77 -19
  154. data/lib/carray/string_operation_extension.rb +10 -6
  155. data/lib/carray/time.rb +906 -491
  156. data/lib/carray/window_iterator.rb +421 -27
  157. data/lib/carray.rb +7 -5
  158. data/yard-stubs/ca_obj_array.rb +385 -0
  159. data/yard-stubs/ca_obj_bitarray.rb +38 -0
  160. data/yard-stubs/ca_obj_bitfield.rb +43 -0
  161. data/yard-stubs/ca_obj_block.rb +68 -0
  162. data/yard-stubs/ca_obj_byte_swap.rb +56 -0
  163. data/yard-stubs/ca_obj_fake.rb +31 -0
  164. data/yard-stubs/ca_obj_farray.rb +32 -0
  165. data/yard-stubs/ca_obj_field.rb +45 -0
  166. data/yard-stubs/ca_obj_grid.rb +35 -0
  167. data/yard-stubs/ca_obj_refer.rb +72 -0
  168. data/yard-stubs/ca_obj_roll.rb +45 -0
  169. data/yard-stubs/ca_obj_shift.rb +43 -0
  170. data/yard-stubs/ca_obj_stride.rb +181 -0
  171. data/yard-stubs/ca_obj_tile.rb +29 -0
  172. data/yard-stubs/ca_obj_transpose.rb +40 -0
  173. data/yard-stubs/ca_obj_window.rb +57 -0
  174. data/yard-stubs/carray_access.rb +131 -0
  175. data/yard-stubs/carray_attribute.rb +246 -0
  176. data/yard-stubs/carray_broadcast.rb +37 -0
  177. data/yard-stubs/carray_cast.rb +489 -0
  178. data/yard-stubs/carray_class.rb +65 -0
  179. data/yard-stubs/carray_conversion.rb +76 -0
  180. data/yard-stubs/carray_copy.rb +79 -0
  181. data/yard-stubs/carray_core.rb +34 -0
  182. data/yard-stubs/carray_count.rb +84 -0
  183. data/yard-stubs/carray_element.rb +108 -0
  184. data/yard-stubs/carray_generate.rb +66 -0
  185. data/yard-stubs/carray_lazy.rb +228 -0
  186. data/yard-stubs/carray_loop.rb +140 -0
  187. data/yard-stubs/carray_mask.rb +259 -0
  188. data/yard-stubs/carray_math.rb +1615 -0
  189. data/yard-stubs/carray_mathfunc.rb +45 -0
  190. data/yard-stubs/carray_median_percentile.rb +103 -0
  191. data/yard-stubs/carray_memory_view.rb +163 -0
  192. data/yard-stubs/carray_order.rb +252 -0
  193. data/yard-stubs/carray_random.rb +89 -0
  194. data/yard-stubs/carray_scatter.rb +106 -0
  195. data/yard-stubs/carray_slab.rb +59 -0
  196. data/yard-stubs/carray_sort.rb +165 -0
  197. data/yard-stubs/carray_test.rb +85 -0
  198. data/yard-stubs/carray_undef.rb +64 -0
  199. data/yard-stubs/carray_utils.rb +97 -0
  200. data/yard-stubs/ruby_carray.rb +193 -0
  201. metadata +67 -10
  202. data/ext/ca_obj_unbound_repeat.c +0 -496
@@ -198,15 +198,9 @@ typedef enum {
198
198
  #define CA_ITER_ERR_READONLY 4 /* WRITE on readonly view (CARepeat etc.) */
199
199
  #define CA_ITER_ERR_MASK 5 /* masked source — step 4-5 only, lifted in step 6 */
200
200
  #define CA_ITER_ERR_MASK_NOT_ALLOWED 6 /* NO_MASK flag set on a masked source (step 7) */
201
- #define CA_ITER_ERR_UNBOUND_SHAPE 7 /* CAUnboundRepeat passed before bind() — reserved for
202
- sub-step 9.3 (= used iff unbound CAUbrep smoke shows
203
- unsafe behavior on the existing SRC_CASTRIDE path) */
204
-
205
201
  /* ---- source kind (step 5+, internal routing) ------------------------- */
206
202
  #define CA_ITER_SRC_NONE 0
207
- #define CA_ITER_SRC_CASTRIDE 1 /* entity / CAStride family (step 1-4).
208
- CAUnboundRepeat is also classified
209
- here via ca_ubrep_func = ca_stride_func. */
203
+ #define CA_ITER_SRC_CASTRIDE 1 /* entity / CAStride family (step 1-4) */
210
204
  #define CA_ITER_SRC_DESCRIPTOR 2 /* CSA / CAGrid / CASelect / CAMapping / CAWindow / CAShift (step 5+) */
211
205
  #define CA_ITER_SRC_ATTACH 3 /* CAFake / CAByteSwap / CABitfield /
212
206
  CABitarray / CAReduce — view's own
@@ -534,7 +528,24 @@ int ca_iter_state_init_l2 (ca_iter_state *st,
534
528
  source carries a mask (= ca_has_mask(src)), or NULL otherwise.
535
529
  The mask layout matches the value layout (= same iteration order
536
530
  and same n). Step 6+: kernels use the CA_FOR_EACH_UNMASKED macro
537
- family (carray.h) to skip masked cells. */
531
+ family (carray.h) to skip masked cells.
532
+
533
+ The mask is an input. What it points at is a copy taken when the
534
+ walk began, so writing through it changes nothing -- not even under
535
+ CA_KERNEL_WRITE, where the value half of the same yield is often a
536
+ live alias. It also cannot express the thing an author would most
537
+ want it for: an unmasked source yields NULL, so there is nowhere to
538
+ record that a cell has become undefined. A kernel authors its
539
+ output mask on its own output array instead:
540
+
541
+ if ( op_mask == NULL ) {
542
+ ca_create_mask(co);
543
+ op_mask = (boolean8_t *) co->mask->ptr;
544
+ }
545
+ op_mask[out_i] = 1;
546
+
547
+ which is what every kernel in carray does (carray_hold.c, and the
548
+ reduction kernels mkkernel emits). */
538
549
  int ca_iter_state_next_slab (ca_iter_state *st,
539
550
  char **out_ptr,
540
551
  boolean8_t **out_mask,
@@ -609,6 +620,13 @@ void ca_iter_state_sync_slab (ca_iter_state *st);
609
620
  exactly once after a successful init (either level). */
610
621
  void ca_iter_state_finish (ca_iter_state *st);
611
622
 
623
+ /* Raise unless rc is CA_ITER_OK. The block macros call this on the value
624
+ ca_iter_state_init_l1 / _l2 returned, so an author who never looks at a
625
+ return code still hears about a request the iterator would not serve.
626
+ Returns rc when there is nothing to report, so it composes inside the
627
+ macros' comma expressions. */
628
+ int ca_iter_check_init (int rc);
629
+
612
630
  /* ---- Phase C C.3: kernel author block macros ------------------------
613
631
  (PROPOSAL_CAPSTONE_PHASE_C.md D3.1 (A) do/while/for + D3.2 (C) 2 kinds)
614
632
 
@@ -623,17 +641,24 @@ void ca_iter_state_finish (ca_iter_state *st);
623
641
  surrounding scope.
624
642
  - `flags` arg propagates to init_l2 (= CA_KERNEL_WRITE supported).
625
643
  `sync_slab` is called automatically after each iteration; it's a
626
- no-op when WRITE flag is absent.
627
- - Init failure (ca_iter_state_init_l2 returns CA_ITER_ERR_*) is
628
- silently discarded: the body runs zero times and finish is still
629
- called. Production kernels that need explicit error messages
630
- (e.g., sum_ki's rc=%d raise) should drop down to the raw API
631
- instead of using this macro.
644
+ no-op when WRITE flag is absent. The mask cursor is an input even
645
+ under WRITE -- see ca_iter_state_next_slab above for why, and for
646
+ where an output mask is written instead.
647
+ - Init failure (ca_iter_state_init_l2 returns CA_ITER_ERR_*) raises:
648
+ the macro passes the code to ca_iter_check_init, which reports what
649
+ the iterator declined to do. The body does not run and finish is
650
+ not reached, which is safe because a failed init allocates nothing.
651
+ Kernels that want to handle a refusal rather than propagate it
652
+ should drop down to the raw API and read the code themselves.
632
653
  - `break;` from inside the body exits the loop AND triggers finish
633
654
  correctly (= outer for's "increment" clause runs once on natural
634
655
  exit; `break` from the inner while breaks both). `return` inside
635
656
  the body LEAKS resources (scratch_ptr, parent attach) — drop to
636
- raw API if early return is needed.
657
+ raw API if early return is needed. A body that raises leaks the
658
+ same way: the engine releases what it holds when the walk itself
659
+ raises (a gather or a write-back through the source's slots), but
660
+ the body runs in the caller's frame, where it has no hold on it.
661
+ An object-lane body calling back into Ruby is the case to watch.
637
662
  - Macros are not statement-equivalent (= they expand to nested for
638
663
  constructs). Don't follow them with `else` etc. */
639
664
 
@@ -766,8 +791,9 @@ void ca_iter_state_finish (ca_iter_state *st);
766
791
  always-constant argument). CA_SLAB_AXES is still FROZEN, because
767
792
  raw-API kernels pass it to ca_iter_state_init_l2 directly. */
768
793
  #define CA_FOR_EACH_SLAB(st, ca, axes, naxes, flags, p, m) \
769
- for ( int __caf_init = (ca_iter_state_init_l2(&(st), (ca), CA_SLAB_AXES, \
770
- (axes), (naxes), (flags)), \
794
+ for ( int __caf_init = (ca_iter_check_init( \
795
+ ca_iter_state_init_l2(&(st), (ca), CA_SLAB_AXES, \
796
+ (axes), (naxes), (flags))), \
771
797
  1); \
772
798
  __caf_init; \
773
799
  __caf_init = 0, ca_iter_state_finish(&(st)) ) \
@@ -783,16 +809,18 @@ void ca_iter_state_finish (ca_iter_state *st);
783
809
  Shape mismatch between ca_in / ca_out is NOT validated by the macro
784
810
  — caller responsibility (= typically output is `rb_ca_template_with_type`
785
811
  of input, guaranteeing same shape). Init failure on either iter
786
- silently skips the body. */
812
+ raises — see CA_FOR_EACH_SLAB above. */
787
813
  /* Policy fixed to CA_SLAB_AXES internally — see CA_FOR_EACH_SLAB above. */
788
814
  #define CA_FOR_EACH_SLAB_INOUT(st_in, st_out, ca_in, ca_out, \
789
815
  axes, naxes, \
790
816
  p_in, p_out, m_in, m_out) \
791
817
  for ( int __cafi_init = ( \
792
- ca_iter_state_init_l2(&(st_in), (ca_in), CA_SLAB_AXES, \
793
- (axes), (naxes), 0), \
794
- ca_iter_state_init_l2(&(st_out), (ca_out), CA_SLAB_AXES, \
795
- (axes), (naxes), CA_KERNEL_WRITE), \
818
+ ca_iter_check_init( \
819
+ ca_iter_state_init_l2(&(st_in), (ca_in), CA_SLAB_AXES, \
820
+ (axes), (naxes), 0)), \
821
+ ca_iter_check_init( \
822
+ ca_iter_state_init_l2(&(st_out), (ca_out), CA_SLAB_AXES, \
823
+ (axes), (naxes), CA_KERNEL_WRITE)), \
796
824
  1); \
797
825
  __cafi_init; \
798
826
  __cafi_init = 0, \
@@ -833,9 +861,10 @@ void ca_iter_state_finish (ca_iter_state *st);
833
861
 
834
862
  #define CA_FOR_EACH_FIBER(st, ca, axis, flags, p, n) \
835
863
  for ( int __cff_init = ( \
836
- ca_iter_state_init_l2(&(st), (ca), CA_SLAB_AXES, \
837
- (int8_t[]){(int8_t)(axis)}, 1, \
838
- (flags) | CA_KERNEL_FIBER_CONTIG), \
864
+ ca_iter_check_init( \
865
+ ca_iter_state_init_l2(&(st), (ca), CA_SLAB_AXES, \
866
+ (int8_t[]){(int8_t)(axis)}, 1, \
867
+ (flags) | CA_KERNEL_FIBER_CONTIG)), \
839
868
  (n) = (st).slab_dims[0], \
840
869
  1); \
841
870
  __cff_init; \
@@ -845,9 +874,10 @@ void ca_iter_state_finish (ca_iter_state *st);
845
874
 
846
875
  #define CA_FOR_EACH_FIBER_MASKED(st, ca, axis, flags, p, n, m) \
847
876
  for ( int __cffm_init = ( \
848
- ca_iter_state_init_l2(&(st), (ca), CA_SLAB_AXES, \
849
- (int8_t[]){(int8_t)(axis)}, 1, \
850
- (flags) | CA_KERNEL_FIBER_CONTIG), \
877
+ ca_iter_check_init( \
878
+ ca_iter_state_init_l2(&(st), (ca), CA_SLAB_AXES, \
879
+ (int8_t[]){(int8_t)(axis)}, 1, \
880
+ (flags) | CA_KERNEL_FIBER_CONTIG)), \
851
881
  (n) = (st).slab_dims[0], \
852
882
  1); \
853
883
  __cffm_init; \
@@ -870,13 +900,15 @@ void ca_iter_state_finish (ca_iter_state *st);
870
900
  #define CA_FOR_EACH_FIBER_INOUT(st_in, st_out, ca_in, ca_out, axis, \
871
901
  flags, p_in, p_out, n) \
872
902
  for ( int __cffi_init = ( \
873
- ca_iter_state_init_l2(&(st_in), (ca_in), CA_SLAB_AXES, \
874
- (int8_t[]){(int8_t)(axis)}, 1, \
875
- (flags) | CA_KERNEL_FIBER_CONTIG), \
876
- ca_iter_state_init_l2(&(st_out), (ca_out), CA_SLAB_AXES, \
877
- (int8_t[]){(int8_t)(axis)}, 1, \
878
- ((flags) | CA_KERNEL_FIBER_CONTIG \
879
- | CA_KERNEL_WRITE)), \
903
+ ca_iter_check_init( \
904
+ ca_iter_state_init_l2(&(st_in), (ca_in), CA_SLAB_AXES, \
905
+ (int8_t[]){(int8_t)(axis)}, 1, \
906
+ (flags) | CA_KERNEL_FIBER_CONTIG)), \
907
+ ca_iter_check_init( \
908
+ ca_iter_state_init_l2(&(st_out), (ca_out), CA_SLAB_AXES, \
909
+ (int8_t[]){(int8_t)(axis)}, 1, \
910
+ ((flags) | CA_KERNEL_FIBER_CONTIG \
911
+ | CA_KERNEL_WRITE))), \
880
912
  (n) = (st_in).slab_dims[0], \
881
913
  1); \
882
914
  __cffi_init; \
@@ -894,13 +926,15 @@ void ca_iter_state_finish (ca_iter_state *st);
894
926
  #define CA_FOR_EACH_FIBER_INOUT_MASKED(st_in, st_out, ca_in, ca_out, axis, \
895
927
  flags, p_in, p_out, n, m) \
896
928
  for ( int __cffim_init = ( \
897
- ca_iter_state_init_l2(&(st_in), (ca_in), CA_SLAB_AXES, \
898
- (int8_t[]){(int8_t)(axis)}, 1, \
899
- (flags) | CA_KERNEL_FIBER_CONTIG), \
900
- ca_iter_state_init_l2(&(st_out), (ca_out), CA_SLAB_AXES, \
901
- (int8_t[]){(int8_t)(axis)}, 1, \
902
- ((flags) | CA_KERNEL_FIBER_CONTIG \
903
- | CA_KERNEL_WRITE)), \
929
+ ca_iter_check_init( \
930
+ ca_iter_state_init_l2(&(st_in), (ca_in), CA_SLAB_AXES, \
931
+ (int8_t[]){(int8_t)(axis)}, 1, \
932
+ (flags) | CA_KERNEL_FIBER_CONTIG)), \
933
+ ca_iter_check_init( \
934
+ ca_iter_state_init_l2(&(st_out), (ca_out), CA_SLAB_AXES, \
935
+ (int8_t[]){(int8_t)(axis)}, 1, \
936
+ ((flags) | CA_KERNEL_FIBER_CONTIG \
937
+ | CA_KERNEL_WRITE))), \
904
938
  (n) = (st_in).slab_dims[0], \
905
939
  1); \
906
940
  __cffim_init; \
@@ -915,6 +949,73 @@ void ca_iter_state_finish (ca_iter_state *st);
915
949
  ca_iter_state_sync_slab(&(st_in)), \
916
950
  ca_iter_state_sync_slab(&(st_out)) )
917
951
 
952
+ /* PAIR forms: two sources read together along the same axis.
953
+
954
+ The INOUT forms cover input + output. These cover input + input, which
955
+ is what a routine taking two vectors of the same length wants -- a
956
+ correlation, a dot product, a distance. Neither state gets
957
+ CA_KERNEL_WRITE, and `flags` must not carry it: use the INOUT forms to
958
+ write.
959
+
960
+ Both fibers are contig-delivered, so the pair may be handed straight to
961
+ a C routine that walks it itself. The MASKED form yields BOTH mask
962
+ cursors: which cells a pair of fibers may be used at is a question about
963
+ both of them, and the INOUT forms answer only about the input.
964
+
965
+ Shape agreement is guarded the way the INOUT forms guard it (ndim,
966
+ elements, fiber length), and the body is skipped on mismatch. The two
967
+ sources may be the same array. */
968
+
969
+ #define CA_FOR_EACH_FIBER_PAIR(st_a, st_b, ca_a, ca_b, axis, \
970
+ flags, p_a, p_b, n) \
971
+ for ( int __cffp_init = ( \
972
+ ca_iter_check_init( \
973
+ ca_iter_state_init_l2(&(st_a), (ca_a), CA_SLAB_AXES, \
974
+ (int8_t[]){(int8_t)(axis)}, 1, \
975
+ (flags) | CA_KERNEL_FIBER_CONTIG)), \
976
+ ca_iter_check_init( \
977
+ ca_iter_state_init_l2(&(st_b), (ca_b), CA_SLAB_AXES, \
978
+ (int8_t[]){(int8_t)(axis)}, 1, \
979
+ (flags) | CA_KERNEL_FIBER_CONTIG)), \
980
+ (n) = (st_a).slab_dims[0], \
981
+ 1); \
982
+ __cffp_init; \
983
+ __cffp_init = 0, \
984
+ ca_iter_state_finish(&(st_a)), \
985
+ ca_iter_state_finish(&(st_b)) ) \
986
+ for ( ; (st_a).src->ndim == (st_b).src->ndim \
987
+ && (st_a).src->elements == (st_b).src->elements \
988
+ && (st_a).slab_dims[0] == (st_b).slab_dims[0] \
989
+ && ca_iter_state_next_slab_axes(&(st_a), &(p_a), NULL) \
990
+ && ca_iter_state_next_slab_axes(&(st_b), &(p_b), NULL); \
991
+ ca_iter_state_sync_slab(&(st_a)), \
992
+ ca_iter_state_sync_slab(&(st_b)) )
993
+
994
+ #define CA_FOR_EACH_FIBER_PAIR_MASKED(st_a, st_b, ca_a, ca_b, axis, \
995
+ flags, p_a, p_b, n, m_a, m_b) \
996
+ for ( int __cffpm_init = ( \
997
+ ca_iter_check_init( \
998
+ ca_iter_state_init_l2(&(st_a), (ca_a), CA_SLAB_AXES, \
999
+ (int8_t[]){(int8_t)(axis)}, 1, \
1000
+ (flags) | CA_KERNEL_FIBER_CONTIG)), \
1001
+ ca_iter_check_init( \
1002
+ ca_iter_state_init_l2(&(st_b), (ca_b), CA_SLAB_AXES, \
1003
+ (int8_t[]){(int8_t)(axis)}, 1, \
1004
+ (flags) | CA_KERNEL_FIBER_CONTIG)), \
1005
+ (n) = (st_a).slab_dims[0], \
1006
+ 1); \
1007
+ __cffpm_init; \
1008
+ __cffpm_init = 0, \
1009
+ ca_iter_state_finish(&(st_a)), \
1010
+ ca_iter_state_finish(&(st_b)) ) \
1011
+ for ( ; (st_a).src->ndim == (st_b).src->ndim \
1012
+ && (st_a).src->elements == (st_b).src->elements \
1013
+ && (st_a).slab_dims[0] == (st_b).slab_dims[0] \
1014
+ && ca_iter_state_next_slab_axes(&(st_a), &(p_a), &(m_a)) \
1015
+ && ca_iter_state_next_slab_axes(&(st_b), &(p_b), &(m_b)); \
1016
+ ca_iter_state_sync_slab(&(st_a)), \
1017
+ ca_iter_state_sync_slab(&(st_b)) )
1018
+
918
1019
  /* ---- Phase D: per-data_type reduction macro suite ----------------------- */
919
1020
 
920
1021
  /* CA_SLAB_REDUCE_T(T, ...): generic per-data_type slab reduction. T is the
@@ -1100,8 +1201,7 @@ void ca_iter_state_finish (ca_iter_state *st);
1100
1201
  *
1101
1202
  * PoC (2026-06-12): N=1M f64 sum 906 us -> 116 us (= 7.8x, 68.8 GB/s).
1102
1203
  * Parity: ε-close (relative error < 2e-16 for f64 sum), bit-exact
1103
- * not guaranteed (= reassoc license, documented in CLAUDE.md
1104
- * ε-close policy section, SL.1.5).
1204
+ * not guaranteed (= the SIMD license; see guides/devel/11_kernel_iterator.md).
1105
1205
  *
1106
1206
  * Other state vars referenced inside REDUCE (induction counters
1107
1207
  * like `cnt`, position counters like `idx`) are auto-vectorised
@@ -1217,7 +1317,7 @@ void ca_iter_state_finish (ca_iter_state *st);
1217
1317
  * EXPR(x) is a function-like macro producing the per-element contribution
1218
1318
  * (Pass 1: (double)(x); Pass 2: ((double)(x)-mean)*((double)(x)-mean)).
1219
1319
  * Reassoc across the 8 lanes is the same ε-close license as _PLUS_EX
1220
- * (bit-exact not guaranteed; CLAUDE.md ε-close policy, SL.1.5).
1320
+ * (bit-exact not guaranteed; see guides/devel/11_kernel_iterator.md).
1221
1321
  *
1222
1322
  * Only the no-mask + contig branch is 8-way; masked / non-contig
1223
1323
  * branches stay single-accumulator (not the hot path). Position
data/ext/ca_obj_array.c CHANGED
@@ -825,26 +825,17 @@ rb_ca_s_allocate (VALUE klass)
825
825
  return TypedData_Make_Struct(klass, CArray, &carray_data_type, ca);
826
826
  }
827
827
 
828
- /* @overload initialize(data_type, dim, bytes=0) { ... }
828
+ /* Reads the (data_type, dim, bytes: nil) argument list that CArray.new
829
+ and CArray.__empty__ share. The two differ only in whether the buffer
830
+ is filled, so the rule for reading their arguments -- the optional
831
+ bytes for a fixlen, the refusal of a Class, the guess of the data type,
832
+ the demand that dim be an Array -- is written once here. */
829
833
 
830
- Constructs a new CArray object of <i>data_type</i>, which has the
831
- ndim and the dimensions specified by an <code>Array</code> of
832
- <code>Integer</code> or an argument list of <code>Integer</code>.
833
- The byte size of each element for the fixed length data type
834
- (<code>data_type == CA_FIXLEN</code>) is specified optional argument
835
- <i>bytes</i>. Otherwise, this optional argument has no
836
- effect. If the block is given, the new CArray
837
- object will be initialized by the value returned from the block.
838
- */
839
-
840
- static VALUE
841
- rb_ca_initialize (int argc, VALUE *argv, VALUE self)
834
+ static void
835
+ ca_scan_construct_args (int argc, VALUE *argv, int8_t *data_type,
836
+ ca_size_t *bytes, int8_t *ndim, ca_size_t *dim)
842
837
  {
843
838
  volatile VALUE rtype, rdim, ropt, rbytes = Qnil;
844
- CArray *ca;
845
- int8_t data_type, ndim;
846
- ca_size_t dim[CA_RANK_MAX];
847
- ca_size_t bytes;
848
839
  int8_t i;
849
840
 
850
841
  rb_scan_args(argc, argv, "21", (VALUE *)&rtype, (VALUE *) &rdim, (VALUE *) &ropt);
@@ -863,13 +854,36 @@ rb_ca_initialize (int argc, VALUE *argv, VALUE self)
863
854
  rtype, rtype, rtype);
864
855
  }
865
856
 
866
- rb_ca_guess_type_and_bytes(rtype, rbytes, &data_type, &bytes);
857
+ rb_ca_guess_type_and_bytes(rtype, rbytes, data_type, bytes);
867
858
 
868
859
  Check_Type(rdim, T_ARRAY);
869
- ndim = RARRAY_LEN(rdim);
870
- for (i=0; i<ndim; i++) {
860
+ *ndim = RARRAY_LEN(rdim);
861
+ for (i=0; i<*ndim; i++) {
871
862
  dim[i] = NUM2SIZE(rb_ary_entry(rdim, i));
872
863
  }
864
+ }
865
+
866
+ /* @overload initialize(data_type, dim, bytes=0) { ... }
867
+
868
+ Constructs a new CArray object of <i>data_type</i>, which has the
869
+ ndim and the dimensions specified by an <code>Array</code> of
870
+ <code>Integer</code> or an argument list of <code>Integer</code>.
871
+ The byte size of each element for the fixed length data type
872
+ (<code>data_type == CA_FIXLEN</code>) is specified optional argument
873
+ <i>bytes</i>. Otherwise, this optional argument has no
874
+ effect. If the block is given, the new CArray
875
+ object will be initialized by the value returned from the block.
876
+ */
877
+
878
+ static VALUE
879
+ rb_ca_initialize (int argc, VALUE *argv, VALUE self)
880
+ {
881
+ CArray *ca;
882
+ int8_t data_type, ndim;
883
+ ca_size_t dim[CA_RANK_MAX];
884
+ ca_size_t bytes;
885
+
886
+ ca_scan_construct_args(argc, argv, &data_type, &bytes, &ndim, dim);
873
887
 
874
888
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
875
889
  if ( ca_func[CA_OBJ_ARRAY].pool_init ) {
@@ -1438,6 +1452,33 @@ rb_ca_s_alloc_uninit (VALUE klass, VALUE rtype, VALUE rshape)
1438
1452
  return rb_carray_new(data_type, ndim, dim, bytes, NULL);
1439
1453
  }
1440
1454
 
1455
+ /* Internal primitive behind CArray.empty(data_type, dim, bytes: nil) in
1456
+ lib/carray/construct.rb, which also routes the compatibility spelling
1457
+ CArray.empty(*shape). It is CArray.new with the fill left out: the
1458
+ arguments are read by the same function, and the buffer comes from
1459
+ rb_carray_new (= no MEMZERO) rather than rb_carray_new_safe. A block
1460
+ is refused -- filling is what CArray.new is for. CA_OBJECT still
1461
+ falls through to the zero-VALUE init inside carray_setup_i (= required
1462
+ for GC), so any data_type is safe to ask for. */
1463
+
1464
+ static VALUE
1465
+ rb_ca_s_empty (int argc, VALUE *argv, VALUE klass)
1466
+ {
1467
+ int8_t data_type, ndim;
1468
+ ca_size_t dim[CA_RANK_MAX];
1469
+ ca_size_t bytes;
1470
+
1471
+ if ( rb_block_given_p() ) {
1472
+ rb_raise(rb_eArgError,
1473
+ "CArray.empty does not take a block "
1474
+ "(its contents are left undefined); use CArray.new to fill.");
1475
+ }
1476
+
1477
+ ca_scan_construct_args(argc, argv, &data_type, &bytes, &ndim, dim);
1478
+
1479
+ return rb_carray_new(data_type, ndim, dim, bytes, NULL);
1480
+ }
1481
+
1441
1482
  void
1442
1483
  Init_ca_obj_array (void)
1443
1484
  {
@@ -1449,6 +1490,7 @@ Init_ca_obj_array (void)
1449
1490
  rb_define_method(rb_cCArray, "initialize", rb_ca_initialize, -1);
1450
1491
  rb_define_singleton_method(rb_cCArray, "__alloc_uninit__",
1451
1492
  rb_ca_s_alloc_uninit, 2);
1493
+ rb_define_singleton_method(rb_cCArray, "__empty__", rb_ca_s_empty, -1);
1452
1494
 
1453
1495
  rb_define_singleton_method(rb_cCArray, "fixlen", rb_ca_s_fixlen, -1);
1454
1496
  rb_define_singleton_method(rb_cCArray, "boolean", rb_ca_s_boolean, -1);
data/ext/ca_obj_bincmp.c CHANGED
@@ -9,7 +9,7 @@
9
9
  ca_bincmp_kernel_lookup from ca_bincmp_dispatch.h.
10
10
 
11
11
  Dispatch by data_type:
12
- numeric (i8..f64, +complex) -> ca_bincmp_<op>_<dtype> kernel
12
+ numeric (i8..f64, +complex) -> ca_bincmp_<op>_<type> kernel
13
13
  boolean / fixlen / object -> not implemented (raise)
14
14
 
15
15
  CAREFUL: the binop in-place trick (= pull left into the output
@@ -60,6 +60,12 @@ typedef struct CABinCmp {
60
60
  CArray *parent; /* = left */
61
61
  uint32_t attach;
62
62
  uint8_t nosync;
63
+ /* ---- CAMultiParent conformance (CA_FLAG_MULTI_PARENTS): n_parents and
64
+ parents[] sit immediately after the CAView header, as carray.h's
65
+ layout convention requires, so ca_has_mask can fold over both
66
+ operands and build the mask on demand instead of at setup. ---- */
67
+ int32_t n_parents; /* always 2 */
68
+ CArray **parents; /* = &operands[0]; no separate allocation */
63
69
  /* CABinCmp-specific tail */
64
70
  CArray *right;
65
71
  uint16_t op_id;
@@ -72,6 +78,7 @@ typedef struct CABinCmp {
72
78
  `eps` name is retained to keep the
73
79
  `__eps__` Ruby accessor stable across
74
80
  the dual purpose. */
81
+ CArray *operands[2]; /* {left, right}; what parents points at */
75
82
  } CABinCmp;
76
83
 
77
84
  static size_t
@@ -119,7 +126,7 @@ ca_bincmp_setup (CABinCmp *ca, CArray *left, CArray *right, uint16_t op_id,
119
126
  {
120
127
  ca->obj_type = CA_OBJ_BINCMP;
121
128
  ca->data_type = CA_BOOLEAN; /* output is always boolean */
122
- ca->flags = CA_FLAG_READ_ONLY;
129
+ ca->flags = CA_FLAG_READ_ONLY | CA_FLAG_MULTI_PARENTS;
123
130
  ca->ndim = left->ndim;
124
131
  ca->bytes = 1; /* boolean8_t */
125
132
  ca->elements = left->elements;
@@ -137,12 +144,16 @@ ca_bincmp_setup (CABinCmp *ca, CArray *left, CArray *right, uint16_t op_id,
137
144
  /* Builder has already cast both operands to common data_type. */
138
145
  ca->common_dt = left->data_type;
139
146
  ca->eps = eps;
147
+ ca->operands[0] = left;
148
+ ca->operands[1] = right;
149
+ ca->parents = ca->operands;
150
+ ca->n_parents = 2;
140
151
 
141
152
  memcpy(ca->dim, left->dim, left->ndim * sizeof(ca_size_t));
142
153
 
143
- if ( ca_has_mask(left) || ca_has_mask(right) ) {
144
- ca_create_mask(ca);
145
- }
154
+ /* The mask is NOT built here. ca_has_mask folds over parents[] for a
155
+ multi-parent view and creates it on demand, so an expression whose mask
156
+ nobody reads never allocates one. */
146
157
 
147
158
  if ( ca_is_scalar(left) && ca_is_scalar(right) ) {
148
159
  ca_set_flag(ca, CA_FLAG_SCALAR);
@@ -294,7 +305,9 @@ ca_bincmp_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
294
305
  int8_t k;
295
306
  void *left_scratch, *right_scratch;
296
307
  ca_size_t operand_bytes;
308
+ ca_size_t operand_strides[CA_RANK_MAX];
297
309
  ca_size_t right_step;
310
+ int is_contig;
298
311
  int left_is_inplace = 0; /* skip release for leaf-opt path */
299
312
  int right_is_inplace = 0;
300
313
 
@@ -308,10 +321,33 @@ ca_bincmp_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
308
321
 
309
322
  operand_bytes = ca_sizeof[bc->common_dt];
310
323
 
324
+ /* An operand cell is operand_bytes wide where this view's is one byte
325
+ (CA_BOOLEAN), so the caller's strides cannot be handed to the operand
326
+ pull the way a binop hands them on -- they have to be restated at the
327
+ operand's cell size. strides[k] / bytes is the index step, which is
328
+ what the two spaces share.
329
+
330
+ is_contig says the caller asked for the row-major slab that xfer_all
331
+ and attach send. Only then does an operand's own buffer hold the
332
+ wanted cells in the packed order the kernel reads them in, so it is
333
+ also the only case the leaf in-place path is good for. */
334
+ {
335
+ ca_size_t native = bc->bytes;
336
+ is_contig = 1;
337
+ for ( k = bc->ndim - 1; k >= 0; k-- ) {
338
+ operand_strides[k] = strides[k] / bc->bytes * operand_bytes;
339
+ if ( strides[k] != native ) {
340
+ is_contig = 0;
341
+ }
342
+ native *= bc->dim[k];
343
+ }
344
+ }
345
+
311
346
  /* === 1. pull LEFT (leaf in-place read or arena scratch) === */
312
347
  {
313
348
  char *left_inplace = NULL;
314
- if ( ca_bincmp_try_leaf_inplace(bc->parent, bc->common_dt,
349
+ if ( is_contig &&
350
+ ca_bincmp_try_leaf_inplace(bc->parent, bc->common_dt,
315
351
  starts, counts, operand_bytes,
316
352
  &left_inplace) ) {
317
353
  /* Leaf-opt path: use parent->ptr + byte_offset directly. */
@@ -320,15 +356,11 @@ ca_bincmp_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
320
356
  ca_bincmp_leaf_inplace_count++;
321
357
  }
322
358
  else {
323
- ca_size_t left_strides[CA_RANK_MAX];
324
- ca_size_t s = operand_bytes;
325
- for ( k = bc->ndim - 1; k >= 0; k-- ) {
326
- left_strides[k] = s;
327
- s *= counts[k];
328
- }
329
- left_scratch = ca_lazy_arena_acquire(slab_n * operand_bytes);
359
+ left_scratch = ( bc->common_dt == CA_OBJECT )
360
+ ? ca_lazy_arena_acquire_object(slab_n)
361
+ : ca_lazy_arena_acquire(slab_n * operand_bytes);
330
362
  ca_bincmp_scratch_acquire_count++;
331
- ca_xfer_stride(bc->parent, starts, counts, left_strides, left_scratch,
363
+ ca_xfer_stride(bc->parent, starts, counts, operand_strides, left_scratch,
332
364
  CA_XFER_GET);
333
365
  }
334
366
  }
@@ -355,7 +387,9 @@ ca_bincmp_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
355
387
  else {
356
388
  ca_size_t one_strides[CA_RANK_MAX];
357
389
  for ( k = 0; k < bc->right->ndim; k++ ) one_strides[k] = operand_bytes;
358
- right_scratch = ca_lazy_arena_acquire(operand_bytes);
390
+ right_scratch = ( bc->common_dt == CA_OBJECT )
391
+ ? ca_lazy_arena_acquire_object(1)
392
+ : ca_lazy_arena_acquire(operand_bytes);
359
393
  ca_bincmp_scratch_acquire_count++;
360
394
  ca_xfer_stride(bc->right, one_starts, one_counts, one_strides,
361
395
  right_scratch, CA_XFER_GET);
@@ -365,7 +399,8 @@ ca_bincmp_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
365
399
  else {
366
400
  /* Same-shape right: full slab pull or leaf-opt. */
367
401
  char *right_inplace = NULL;
368
- if ( ca_bincmp_try_leaf_inplace(bc->right, bc->common_dt,
402
+ if ( is_contig &&
403
+ ca_bincmp_try_leaf_inplace(bc->right, bc->common_dt,
369
404
  starts, counts, operand_bytes,
370
405
  &right_inplace) ) {
371
406
  right_scratch = right_inplace;
@@ -373,15 +408,11 @@ ca_bincmp_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
373
408
  ca_bincmp_leaf_inplace_count++;
374
409
  }
375
410
  else {
376
- ca_size_t right_strides[CA_RANK_MAX];
377
- ca_size_t s = operand_bytes;
378
- for ( k = bc->ndim - 1; k >= 0; k-- ) {
379
- right_strides[k] = s;
380
- s *= counts[k];
381
- }
382
- right_scratch = ca_lazy_arena_acquire(slab_n * operand_bytes);
411
+ right_scratch = ( bc->common_dt == CA_OBJECT )
412
+ ? ca_lazy_arena_acquire_object(slab_n)
413
+ : ca_lazy_arena_acquire(slab_n * operand_bytes);
383
414
  ca_bincmp_scratch_acquire_count++;
384
- ca_xfer_stride(bc->right, starts, counts, right_strides, right_scratch,
415
+ ca_xfer_stride(bc->right, starts, counts, operand_strides, right_scratch,
385
416
  CA_XFER_GET);
386
417
  }
387
418
  right_step = 1;
@@ -644,10 +675,9 @@ rb_ca_bincmp_build (VALUE l_cary, VALUE r_cary, uint16_t op_id, double eps)
644
675
  TypedData_Get_Struct(l_resolved, CArray, &carray_data_type, l);
645
676
  }
646
677
  else {
678
+ /* Unreachable from Ruby; see the note in ca_obj_binop.c. */
647
679
  rb_raise(rb_eArgError,
648
- "CABinCmp: shape mismatch (%lld vs %lld) — only same-"
649
- "ndim size-1 broadcast is supported; cross-ndim "
650
- "promotion is not adopted in CArray",
680
+ "CABinCmp: element count mismatch (%lld vs %lld)",
651
681
  (long long) l->elements, (long long) r->elements);
652
682
  }
653
683
  }