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
@@ -186,9 +186,7 @@ ca_iter_classify_source (CArray *src)
186
186
  if ( attach == ca_window_func.attach ) return CA_ITER_SRC_DESCRIPTOR; /* + CAShift */
187
187
 
188
188
  /* Step 9: SRC_ATTACH 5 view. Each view-specific attach materialises
189
- src->ptr via per-element transform; kernel sees a flat contig slab.
190
- CAUnboundRepeat shares ca_stride_func.attach so it was already
191
- classified as SRC_CASTRIDE above (prep doc §2.6). */
189
+ src->ptr via per-element transform; kernel sees a flat contig slab. */
192
190
  if ( attach == ca_fake_func.attach ) return CA_ITER_SRC_ATTACH;
193
191
  if ( attach == ca_byte_swap_func.attach ) return CA_ITER_SRC_ATTACH;
194
192
  if ( attach == ca_bitfield_func.attach ) return CA_ITER_SRC_ATTACH;
@@ -526,18 +524,97 @@ ca_iter_validate_inputs (ca_iter_state *st,
526
524
  return CA_ITER_OK;
527
525
  }
528
526
 
527
+ /* Storage-identical wrapper strip for the kernel-compute entry.
528
+
529
+ A Face layers a semantic identifier and a CALazyMarker layers "read this
530
+ as the leaf of a lazy chain"; neither changes the storage, so routing and
531
+ alias decisions belong to what they wrap. ca_strip_face answers this for
532
+ Face alone and is read that way in ~60 other places, so the marker is
533
+ added here rather than inside it.
534
+
535
+ The marker is NOT classified as a source in its own right. Routing it to
536
+ CA_ITER_SRC_ATTACH would work but would allocate elements * bytes and pull
537
+ the whole array through ca_xfer_all first -- a.lazy.sum(axis: 0) would copy
538
+ what a.sum(axis: 0) aliases. Descending instead lets the parent be
539
+ classified on its own merits, which costs nothing.
540
+
541
+ Unlike Face, nothing re-wraps the result: a reduction over a lazy marker
542
+ yields a plain entity, not a lazy view.
543
+
544
+ CAREFUL: a marker carries CA_FLAG_READ_ONLY and its parent usually does
545
+ not, so the WRITE rejection has to happen against the wrapper. The caller
546
+ does that before stripping. */
547
+ static CArray *
548
+ ca_iter_strip_storage_wrapper (CArray *src)
549
+ {
550
+ while ( src && ( ca_is_face(src) || ca_is_lazy_marker(src) ) ) {
551
+ src = ((CAView *) src)->parent;
552
+ }
553
+ return src;
554
+ }
555
+
529
556
  int
530
- ca_iter_state_init_l1 (ca_iter_state *st,
557
+ ca_iter_check_init (int rc)
558
+ {
559
+ const char *why;
560
+
561
+ if ( rc == CA_ITER_OK ) return rc;
562
+
563
+ switch ( rc ) {
564
+ case CA_ITER_ERR_NOT_CHEAP:
565
+ why = "the source cannot be read without materialising it";
566
+ break;
567
+ case CA_ITER_ERR_POLICY:
568
+ why = "the slab policy or the axes given do not apply to this source";
569
+ break;
570
+ case CA_ITER_ERR_FLAGS:
571
+ why = "this flag combination is not supported for this source";
572
+ break;
573
+ case CA_ITER_ERR_READONLY:
574
+ why = "the array is read-only and the kernel asked to write";
575
+ break;
576
+ case CA_ITER_ERR_MASK:
577
+ why = "the source carries a mask";
578
+ break;
579
+ case CA_ITER_ERR_MASK_NOT_ALLOWED:
580
+ why = "the kernel declared CA_KERNEL_NO_MASK and the source carries a mask";
581
+ break;
582
+ default:
583
+ why = "the request was refused";
584
+ break;
585
+ }
586
+ rb_raise(rb_eRuntimeError, "kernel iterator: %s (rc=%d)", why, rc);
587
+ return rc; /* not reached */
588
+ }
589
+
590
+ static int
591
+ ca_iter_state_init_l1_unprotected (ca_iter_state *st,
531
592
  struct _CArray *src,
532
593
  ca_slab_policy_t policy,
533
594
  int8_t *axes,
534
595
  int8_t naxes,
535
596
  uint32_t flags)
536
597
  {
537
- /* PROPOSAL_CAFACE_PHASE_2 F.2.6: Face strip at entry (= same rationale as
538
- init_l2 above). Strip before validate_inputs. */
539
- if ( ca_is_face(src) ) {
540
- src = ca_strip_face(src);
598
+ /* Leave the state defined before anything can return. Every error
599
+ return below hands control back to a caller that may not look at the
600
+ code, so what it is left holding has to be a state that iterates zero
601
+ times rather than whatever its stack frame happened to contain. */
602
+ if ( st == NULL ) return CA_ITER_ERR_FLAGS;
603
+ memset(st, 0, sizeof(*st));
604
+
605
+ /* PROPOSAL_CAFACE_PHASE_2 F.2.6 + PROPOSAL_LAZY_MARKER_LIFT Phase 0:
606
+ storage-identical wrapper strip at entry (= same rationale as init_l2
607
+ below). Strip before validate_inputs.
608
+
609
+ The WRITE rejection is taken against the wrapper, not what it wraps: a
610
+ CALazyMarker is read-only while its parent is not, and stripping first
611
+ would let a destructive kernel through to the parent. validate_inputs
612
+ re-checks the stripped source, which is harmless. */
613
+ if ( (flags & CA_KERNEL_WRITE) && ca_is_readonly(src) ) {
614
+ return CA_ITER_ERR_READONLY;
615
+ }
616
+ if ( ca_is_face(src) || ca_is_lazy_marker(src) ) {
617
+ src = ca_iter_strip_storage_wrapper(src);
541
618
  }
542
619
 
543
620
  int rc = ca_iter_validate_inputs(st, src, policy, flags);
@@ -550,7 +627,6 @@ ca_iter_state_init_l1 (ca_iter_state *st,
550
627
 
551
628
  uint8_t src_kind = ca_iter_classify_source(src);
552
629
 
553
- memset(st, 0, sizeof(*st));
554
630
  st->src = src;
555
631
  st->src_kind = src_kind;
556
632
  st->level = 1;
@@ -704,24 +780,42 @@ ca_iter_state_init_l1 (ca_iter_state *st,
704
780
  return CA_ITER_OK;
705
781
  }
706
782
 
707
- int
708
- ca_iter_state_init_l2 (ca_iter_state *st,
783
+ static int
784
+ ca_iter_state_init_l2_unprotected (ca_iter_state *st,
709
785
  struct _CArray *src,
710
786
  ca_slab_policy_t policy,
711
787
  int8_t *axes,
712
788
  int8_t naxes,
713
789
  uint32_t flags)
714
790
  {
715
- /* PROPOSAL_CAFACE_PHASE_2 F.2.6 (= MEMO §3.5 kernel_iterator entry strip):
716
- Face only layers a semantic identifier; storage is identical to parent,
717
- so strip at the kernel-compute entry and descend to parent. The Face
718
- identifier is re-wrapped onto the result by the caller's lift hook
719
- (= primary operators / reductions / etc.). Because Face is an identity
720
- mask, routing / alias decisions should be based on parent.
721
- Strip before validate_inputs — `classify_source` would reject Face as
722
- knows-no. */
723
- if ( ca_is_face(src) ) {
724
- src = ca_strip_face(src);
791
+ /* Leave the state defined before anything can return. Every error
792
+ return below hands control back to a caller that may not look at the
793
+ code, so what it is left holding has to be a state that iterates zero
794
+ times rather than whatever its stack frame happened to contain. */
795
+ if ( st == NULL ) return CA_ITER_ERR_FLAGS;
796
+ memset(st, 0, sizeof(*st));
797
+
798
+ /* PROPOSAL_CAFACE_PHASE_2 F.2.6 (= MEMO §3.5 kernel_iterator entry strip)
799
+ + PROPOSAL_LAZY_MARKER_LIFT Phase 0.
800
+
801
+ A Face only layers a semantic identifier and a CALazyMarker only layers
802
+ "leaf of a lazy chain"; storage is identical to the parent either way, so
803
+ strip at the kernel-compute entry and descend. Routing and alias
804
+ decisions belong to the parent. Strip before validate_inputs —
805
+ classify_source rejects both as knows-no.
806
+
807
+ The Face identifier is re-wrapped onto the result by the caller's lift
808
+ hook (= primary operators / reductions / etc.). The marker is not:
809
+ a reduction over a lazy marker yields a plain entity.
810
+
811
+ WRITE is rejected against the wrapper, before the strip — a marker is
812
+ read-only while its parent is not. validate_inputs re-checks the
813
+ stripped source, which is harmless. */
814
+ if ( (flags & CA_KERNEL_WRITE) && ca_is_readonly(src) ) {
815
+ return CA_ITER_ERR_READONLY;
816
+ }
817
+ if ( ca_is_face(src) || ca_is_lazy_marker(src) ) {
818
+ src = ca_iter_strip_storage_wrapper(src);
725
819
  }
726
820
 
727
821
  int rc = ca_iter_validate_inputs(st, src, policy, flags);
@@ -781,7 +875,6 @@ ca_iter_state_init_l2 (ca_iter_state *st,
781
875
  if ( ca_iter_should_per_fiber_fused(src, src_kind, fiber_ax,
782
876
  row_byte_strides[fiber_ax],
783
877
  flags) ) {
784
- memset(st, 0, sizeof(*st));
785
878
  st->src = src;
786
879
  st->src_kind = src_kind;
787
880
  st->level = 2;
@@ -875,7 +968,6 @@ ca_iter_state_init_l2 (ca_iter_state *st,
875
968
  return CA_ITER_ERR_FLAGS;
876
969
  }
877
970
 
878
- memset(st, 0, sizeof(*st));
879
971
  st->src = src;
880
972
  st->src_kind = CA_ITER_SRC_DESCRIPTOR;
881
973
  st->level = 2;
@@ -1028,8 +1120,8 @@ ca_iter_state_init_l2 (ca_iter_state *st,
1028
1120
  }
1029
1121
  /* slab is all-STRIDE: fall through to B.1.5 / Phase B paths. */
1030
1122
  /* Phase B.1.5: outer SHIFT axis → materialise downgrade. Alias
1031
- can't deliver OOB cells (= need fill_value), so per the
1032
- delivery principle (CLAUDE.md §"deliver" + D1.3 confirmed),
1123
+ can't deliver OOB cells (= need fill_value), and the view-family
1124
+ surface prioritises delivering the cells over avoiding a copy, so
1033
1125
  we materialise the entire view into a row-major scratch
1034
1126
  buffer via ca_axis_dispatch_attach (= same engine as the
1035
1127
  existing SRC_DESCRIPTOR L2 NONE path), then walk it with
@@ -1044,7 +1136,6 @@ ca_iter_state_init_l2 (ca_iter_state *st,
1044
1136
  }
1045
1137
 
1046
1138
  if ( outer_has_shift ) {
1047
- memset(st, 0, sizeof(*st));
1048
1139
  st->src = src;
1049
1140
  st->src_kind = CA_ITER_SRC_DESCRIPTOR;
1050
1141
  st->level = 2;
@@ -1138,7 +1229,6 @@ ca_iter_state_init_l2 (ca_iter_state *st,
1138
1229
  return CA_ITER_OK;
1139
1230
  }
1140
1231
 
1141
- memset(st, 0, sizeof(*st));
1142
1232
  st->src = src;
1143
1233
  st->src_kind = CA_ITER_SRC_DESCRIPTOR; /* Phase B alias path */
1144
1234
  st->level = 2;
@@ -1286,7 +1376,6 @@ ca_iter_state_init_l2 (ca_iter_state *st,
1286
1376
  in_slab[ax] = 1;
1287
1377
  }
1288
1378
 
1289
- memset(st, 0, sizeof(*st));
1290
1379
  st->src = src;
1291
1380
  st->src_kind = CA_ITER_SRC_ATTACH;
1292
1381
  st->level = 2;
@@ -1339,6 +1428,7 @@ ca_iter_state_init_l2 (ca_iter_state *st,
1339
1428
  k_axis..N-2]; either way outer_idx[m] = parent axis m). */
1340
1429
  if ( ca_func[src->obj_type].attach == ca_stack_func.attach
1341
1430
  && !ca_has_mask(src)
1431
+ && !(flags & CA_KERNEL_WRITE)
1342
1432
  && naxes == 1 && axes[0] == ((CAStack *) src)->k_axis ) {
1343
1433
  CAStack *stack = (CAStack *) src;
1344
1434
  int8_t parent_ndim = src->ndim - 1;
@@ -1454,6 +1544,7 @@ ca_iter_state_init_l2 (ca_iter_state *st,
1454
1544
  s - 1); only the K stack axis (s == k_axis) has no parent
1455
1545
  counterpart. */
1456
1546
  if ( ca_func[src->obj_type].attach == ca_stack_func.attach
1547
+ && !(flags & CA_KERNEL_WRITE)
1457
1548
  && naxes >= 1 && !in_slab[((CAStack *) src)->k_axis] ) {
1458
1549
  CAStack *stack = (CAStack *) src;
1459
1550
  int8_t k_axis = stack->k_axis;
@@ -1674,7 +1765,6 @@ ca_iter_state_init_l2 (ca_iter_state *st,
1674
1765
  / CATile / CARoll). Same shape as L1 SRC_ATTACH: iterator-owns
1675
1766
  scratch + xfer_all GET/PUT. Yield as one 1-D strided slab. */
1676
1767
  if ( src_kind == CA_ITER_SRC_ATTACH ) {
1677
- memset(st, 0, sizeof(*st));
1678
1768
  st->src = src;
1679
1769
  st->src_kind = CA_ITER_SRC_ATTACH;
1680
1770
  st->level = 2;
@@ -1726,7 +1816,6 @@ ca_iter_state_init_l2 (ca_iter_state *st,
1726
1816
  int8_t nd = raw_ndim;
1727
1817
  int8_t k;
1728
1818
 
1729
- memset(st, 0, sizeof(*st));
1730
1819
  st->src = src;
1731
1820
  st->src_kind = CA_ITER_SRC_DESCRIPTOR_L2_ALIASABLE;
1732
1821
  st->level = 2;
@@ -1804,9 +1893,9 @@ ca_iter_state_init_l2 (ca_iter_state *st,
1804
1893
  return CA_ITER_OK;
1805
1894
  }
1806
1895
 
1807
- /* Sub-step 5.3: L2 dispatch over descriptor sources. Per the
1808
- delivery principle (CLAUDE.md "view-family surface prioritises delivery",
1809
- PROPOSAL_T1_STEP5_DESCRIPTOR.md §0): always materialise into a
1896
+ /* Sub-step 5.3: L2 dispatch over descriptor sources. The view-family
1897
+ surface prioritises delivering the cells over avoiding a copy: always
1898
+ materialise into a
1810
1899
  scratch buffer via ca_axis_dispatch_attach and yield a single
1811
1900
  strided slab (stride = bytes). CASelect/CAMapping always reach
1812
1901
  here, CSA/CAGrid/CAWindow/CAShift when INDEX/SHIFT axes are
@@ -1817,7 +1906,6 @@ ca_iter_state_init_l2 (ca_iter_state *st,
1817
1906
  /* raw_descs / raw_pdims / raw_ndim were populated by route_source
1818
1907
  above so we skip the local describe_axes call. Kept locals
1819
1908
  named raw_* to match the original code. */
1820
- memset(st, 0, sizeof(*st));
1821
1909
  st->src = src;
1822
1910
  st->src_kind = CA_ITER_SRC_DESCRIPTOR;
1823
1911
  st->level = 2;
@@ -1891,7 +1979,6 @@ ca_iter_state_init_l2 (ca_iter_state *st,
1891
1979
  }
1892
1980
 
1893
1981
  /* === CAStride / entity L2 path === */
1894
- memset(st, 0, sizeof(*st));
1895
1982
  st->src = src;
1896
1983
  st->src_kind = CA_ITER_SRC_CASTRIDE;
1897
1984
  st->level = 2;
@@ -2092,8 +2179,8 @@ ca_iter_state_init_l2 (ca_iter_state *st,
2092
2179
  return CA_ITER_OK;
2093
2180
  }
2094
2181
 
2095
- int
2096
- ca_iter_state_next_slab (ca_iter_state *st,
2182
+ static int
2183
+ ca_iter_state_next_slab_unprotected (ca_iter_state *st,
2097
2184
  char **out_ptr,
2098
2185
  boolean8_t **out_mask,
2099
2186
  ca_size_t *out_n)
@@ -2115,8 +2202,8 @@ ca_iter_state_next_slab (ca_iter_state *st,
2115
2202
  return 1;
2116
2203
  }
2117
2204
 
2118
- int
2119
- ca_iter_state_next_slab_strided (ca_iter_state *st,
2205
+ static int
2206
+ ca_iter_state_next_slab_strided_unprotected (ca_iter_state *st,
2120
2207
  char **out_ptr,
2121
2208
  boolean8_t **out_mask,
2122
2209
  ca_size_t *out_n,
@@ -2184,8 +2271,8 @@ ca_iter_state_next_slab_strided (ca_iter_state *st,
2184
2271
  return 1;
2185
2272
  }
2186
2273
 
2187
- int
2188
- ca_iter_state_next_slab_axes (ca_iter_state *st,
2274
+ static int
2275
+ ca_iter_state_next_slab_axes_unprotected (ca_iter_state *st,
2189
2276
  char **out_ptr,
2190
2277
  boolean8_t **out_mask)
2191
2278
  {
@@ -2695,38 +2782,22 @@ ca_iter_state_next_slab_axes (ca_iter_state *st,
2695
2782
  return 1;
2696
2783
  }
2697
2784
 
2698
- void
2699
- ca_iter_state_sync_slab (ca_iter_state *st)
2785
+ static void
2786
+ ca_iter_state_sync_slab_unprotected (ca_iter_state *st)
2700
2787
  {
2701
2788
  /* READ walk: nothing to sync. */
2702
2789
  if ( st == NULL || !(st->flags & CA_KERNEL_WRITE) ) return;
2703
2790
 
2704
- /* PROPOSAL_CASTACK_LOOP_INTERCHANGE Vector A rev2: STACK path is
2705
- READ-only in initial scope. Kernel writes into scratch would not
2706
- be valid to scatter back via xfer_all PUT (= scratch is slab-sized
2707
- not whole-view), so the SRC_ATTACH PUT below would be a semantic
2708
- mismatch. Per-slab scatter via direct per-parent memcpy is a
2709
- future extension once a WRITE-using kernel materialises. */
2791
+ /* Neither stack mode can be reached with WRITE set: both are declined
2792
+ at init, which sends a write destination down the generic SRC_ATTACH
2793
+ path instead. They cannot serve a write themselves -- ALIAS_STACK
2794
+ hands out a tile cache that finish frees, and STACK_OUTER_K aliases
2795
+ parents[k]->ptr, which is the attach buffer rather than the parent's
2796
+ own memory whenever the parent is a view. Scattering per parent
2797
+ would let the fast paths carry writes too; until then this branch is
2798
+ just the assertion that they are not asked to. */
2710
2799
  if ( st->alias_mode == CA_ITER_ALIAS_STACK
2711
2800
  || st->alias_mode == CA_ITER_ALIAS_STACK_OUTER_K ) {
2712
- /* STACK_OUTER_K (P.2 Case A) is READ-only scope: slabs are direct
2713
- aliases into parents[k]->ptr, kernel WRITE would scatter into
2714
- parent memory which is out of scope (see proposal R1). Skip
2715
- sync. */
2716
- st->write_dirty = 0;
2717
- return;
2718
- }
2719
-
2720
- /* SRC_ATTACH path (step 9 + 2026-05-31 refactor): kernel wrote into
2721
- iterator-owned scratch (= scratch_ptr). Push back via xfer_all PUT
2722
- which routes through the view's xfer_all slot -- handles CAFake
2723
- (cast back), CAByteSwap (swap back), CABitfield/CABitarray (bit
2724
- pack back), CAReduce (broadcast across reduce window). Inherits
2725
- transform-fused / partial materialise / etc. automatically. */
2726
- if ( st->src_kind == CA_ITER_SRC_ATTACH ) {
2727
- if ( st->src->elements > 0 ) {
2728
- ca_xfer_all(st->src, st->scratch_ptr, CA_XFER_PUT);
2729
- }
2730
2801
  st->write_dirty = 0;
2731
2802
  return;
2732
2803
  }
@@ -2805,12 +2876,32 @@ ca_iter_state_sync_slab (ca_iter_state *st)
2805
2876
  ca_stride_scatter_run(dst, st->fiber_data_scratch,
2806
2877
  bytes, n, data_step);
2807
2878
  }
2808
- /* Fall through to any subsequent src_kind scatter (= harmless: for
2809
- the alias paths reached here, scratch_ptr is NULL and the switch
2810
- below early-returns). But for SRC_DESCRIPTOR / SRC_ATTACH that
2811
- use the per-slab materialise path, fiber_data_scratch stays NULL
2812
- (those paths use scratch_ptr and the PER_SLAB(_HOIST) yield), so
2813
- this block does not fire. */
2879
+ /* Fall through. The gather above is not specific to a src_kind --
2880
+ it fires for any source whose fiber is not innermost-contig -- so
2881
+ for SRC_ATTACH this has just written the fiber back into the
2882
+ whole-view scratch, and the push-back below carries it to the
2883
+ source. For the alias paths scratch_ptr is NULL and the switch
2884
+ below early-returns. */
2885
+ }
2886
+
2887
+ /* SRC_ATTACH path (step 9 + 2026-05-31 refactor): kernel wrote into
2888
+ iterator-owned scratch (= scratch_ptr). Push back via xfer_all PUT
2889
+ which routes through the view's xfer_all slot -- handles CAFake
2890
+ (cast back), CAByteSwap (swap back), CABitfield/CABitarray (bit
2891
+ pack back), CAReduce (broadcast across reduce window). Inherits
2892
+ transform-fused / partial materialise / etc. automatically.
2893
+
2894
+ This has to stay below the two per-fiber blocks: consulting
2895
+ src_kind first would push back the whole-view buffer while the
2896
+ fiber the author actually wrote sat unscattered in
2897
+ fiber_data_scratch, and would push back a NULL one for
2898
+ PER_FIBER_FUSED, which owns no whole-view buffer at all. */
2899
+ if ( st->src_kind == CA_ITER_SRC_ATTACH ) {
2900
+ if ( st->src->elements > 0 ) {
2901
+ ca_xfer_all(st->src, st->scratch_ptr, CA_XFER_PUT);
2902
+ }
2903
+ st->write_dirty = 0;
2904
+ return;
2814
2905
  }
2815
2906
 
2816
2907
  /* alias path: kernel wrote through alias_ptr into parent directly
@@ -2949,6 +3040,228 @@ ca_iter_state_finish (ca_iter_state *st)
2949
3040
  st->alias_ptr = NULL;
2950
3041
  }
2951
3042
 
3043
+ /* ---- Raise-safe entry points ----------------------------------------
3044
+ *
3045
+ * A walk reads its source, and a source that converts as it is read (a
3046
+ * lazy view over an object array, say) raises when it meets a cell it
3047
+ * cannot convert. The caller's loop then never reaches
3048
+ * ca_iter_state_finish -- the block macros put it in the `for`
3049
+ * increment, which a raise jumps over, and it is past the raise in
3050
+ * hand-written walks too. So the walk finishes itself on the way out.
3051
+ *
3052
+ * Every entry point that can raise is called through rb_protect here,
3053
+ * and ca_iter_state_finish is idempotent (it clears st->src), so the
3054
+ * caller's own finish after an ordinary walk stays correct.
3055
+ *
3056
+ * Not covered: a kernel body that raises. It runs in the caller's
3057
+ * frame, between two calls of these functions, where the engine has no
3058
+ * hold on it. An object-lane kernel calling back into Ruby is the case
3059
+ * to watch.
3060
+ *
3061
+ * init runs protected always -- once per walk, next to a materialise.
3062
+ * next_slab / sync_slab are called once per slab, in walks whose whole
3063
+ * per-fiber cost can be a few nanoseconds, so they are protected only
3064
+ * when this walk can raise at all: when it gathers each fiber through
3065
+ * the source's own transfer slots, or when it writes scratch back
3066
+ * through them. Every other shape hands out pointers into memory that
3067
+ * init already materialised, and moves it with memcpy. */
3068
+
3069
+ /* Private flags bit, kept in ca_iter_state.flags rather than a field of
3070
+ its own: the state is stack-allocated by ext authors through the block
3071
+ macros, so growing it would overrun states built against an older
3072
+ header. */
3073
+ #define CA_ITER_FLAG_WALK_MAY_RAISE 0x80000000u
3074
+
3075
+ typedef struct {
3076
+ ca_iter_state *st;
3077
+ CArray *src;
3078
+ ca_slab_policy_t policy;
3079
+ int8_t *axes;
3080
+ int8_t naxes;
3081
+ uint32_t flags;
3082
+ char **out_ptr;
3083
+ boolean8_t **out_mask;
3084
+ ca_size_t *out_n;
3085
+ ca_size_t *out_stride_bytes;
3086
+ int rc;
3087
+ } ca_iter_call_t;
3088
+
3089
+ /* Whether next_slab / sync_slab on this walk need the protection. */
3090
+ static void
3091
+ ca_iter_mark_walk_may_raise (ca_iter_state *st)
3092
+ {
3093
+ if ( st == NULL ) return;
3094
+ if ( st->alias_mode == CA_ITER_ALIAS_PER_FIBER_FUSED ) {
3095
+ /* gathers each fiber through the source's xfer_stride slot */
3096
+ st->flags |= CA_ITER_FLAG_WALK_MAY_RAISE;
3097
+ }
3098
+ else if ( (st->flags & CA_KERNEL_WRITE)
3099
+ && (st->scratch_ptr != NULL
3100
+ || st->src_kind == CA_ITER_SRC_ATTACH) ) {
3101
+ /* writes back out of iterator-owned scratch, through the source's
3102
+ xfer_all / sync_data slot. A write into aliased memory needs no
3103
+ push-back at all: sync_slab returns at once. */
3104
+ st->flags |= CA_ITER_FLAG_WALK_MAY_RAISE;
3105
+ }
3106
+ }
3107
+
3108
+ static void
3109
+ ca_iter_protect (ca_iter_state *st, VALUE (*body)(VALUE), VALUE arg)
3110
+ {
3111
+ int tag = 0;
3112
+ rb_protect(body, arg, &tag);
3113
+ if ( tag ) {
3114
+ ca_iter_state_finish(st);
3115
+ rb_jump_tag(tag);
3116
+ }
3117
+ }
3118
+
3119
+ static VALUE
3120
+ ca_iter_call_init_l1 (VALUE arg)
3121
+ {
3122
+ ca_iter_call_t *c = (ca_iter_call_t *) arg;
3123
+ c->rc = ca_iter_state_init_l1_unprotected(c->st, c->src, c->policy,
3124
+ c->axes, c->naxes, c->flags);
3125
+ return Qnil;
3126
+ }
3127
+
3128
+ static VALUE
3129
+ ca_iter_call_init_l2 (VALUE arg)
3130
+ {
3131
+ ca_iter_call_t *c = (ca_iter_call_t *) arg;
3132
+ c->rc = ca_iter_state_init_l2_unprotected(c->st, c->src, c->policy,
3133
+ c->axes, c->naxes, c->flags);
3134
+ return Qnil;
3135
+ }
3136
+
3137
+ static VALUE
3138
+ ca_iter_call_next_slab (VALUE arg)
3139
+ {
3140
+ ca_iter_call_t *c = (ca_iter_call_t *) arg;
3141
+ c->rc = ca_iter_state_next_slab_unprotected(c->st, c->out_ptr,
3142
+ c->out_mask, c->out_n);
3143
+ return Qnil;
3144
+ }
3145
+
3146
+ static VALUE
3147
+ ca_iter_call_next_slab_strided (VALUE arg)
3148
+ {
3149
+ ca_iter_call_t *c = (ca_iter_call_t *) arg;
3150
+ c->rc = ca_iter_state_next_slab_strided_unprotected(c->st, c->out_ptr,
3151
+ c->out_mask, c->out_n,
3152
+ c->out_stride_bytes);
3153
+ return Qnil;
3154
+ }
3155
+
3156
+ static VALUE
3157
+ ca_iter_call_next_slab_axes (VALUE arg)
3158
+ {
3159
+ ca_iter_call_t *c = (ca_iter_call_t *) arg;
3160
+ c->rc = ca_iter_state_next_slab_axes_unprotected(c->st, c->out_ptr,
3161
+ c->out_mask);
3162
+ return Qnil;
3163
+ }
3164
+
3165
+ static VALUE
3166
+ ca_iter_call_sync_slab (VALUE arg)
3167
+ {
3168
+ ca_iter_call_t *c = (ca_iter_call_t *) arg;
3169
+ ca_iter_state_sync_slab_unprotected(c->st);
3170
+ return Qnil;
3171
+ }
3172
+
3173
+ int
3174
+ ca_iter_state_init_l1 (ca_iter_state *st,
3175
+ struct _CArray *src,
3176
+ ca_slab_policy_t policy,
3177
+ int8_t *axes,
3178
+ int8_t naxes,
3179
+ uint32_t flags)
3180
+ {
3181
+ ca_iter_call_t c;
3182
+ c.st = st; c.src = src; c.policy = policy;
3183
+ c.axes = axes; c.naxes = naxes; c.flags = flags; c.rc = CA_ITER_OK;
3184
+ ca_iter_protect(st, ca_iter_call_init_l1, (VALUE) &c);
3185
+ if ( c.rc == CA_ITER_OK ) ca_iter_mark_walk_may_raise(st);
3186
+ return c.rc;
3187
+ }
3188
+
3189
+ int
3190
+ ca_iter_state_init_l2 (ca_iter_state *st,
3191
+ struct _CArray *src,
3192
+ ca_slab_policy_t policy,
3193
+ int8_t *axes,
3194
+ int8_t naxes,
3195
+ uint32_t flags)
3196
+ {
3197
+ ca_iter_call_t c;
3198
+ c.st = st; c.src = src; c.policy = policy;
3199
+ c.axes = axes; c.naxes = naxes; c.flags = flags; c.rc = CA_ITER_OK;
3200
+ ca_iter_protect(st, ca_iter_call_init_l2, (VALUE) &c);
3201
+ if ( c.rc == CA_ITER_OK ) ca_iter_mark_walk_may_raise(st);
3202
+ return c.rc;
3203
+ }
3204
+
3205
+ int
3206
+ ca_iter_state_next_slab (ca_iter_state *st,
3207
+ char **out_ptr,
3208
+ boolean8_t **out_mask,
3209
+ ca_size_t *out_n)
3210
+ {
3211
+ ca_iter_call_t c;
3212
+ if ( st == NULL || !(st->flags & CA_ITER_FLAG_WALK_MAY_RAISE) ) {
3213
+ return ca_iter_state_next_slab_unprotected(st, out_ptr, out_mask, out_n);
3214
+ }
3215
+ c.st = st; c.out_ptr = out_ptr; c.out_mask = out_mask; c.out_n = out_n;
3216
+ c.rc = 0;
3217
+ ca_iter_protect(st, ca_iter_call_next_slab, (VALUE) &c);
3218
+ return c.rc;
3219
+ }
3220
+
3221
+ int
3222
+ ca_iter_state_next_slab_strided (ca_iter_state *st,
3223
+ char **out_ptr,
3224
+ boolean8_t **out_mask,
3225
+ ca_size_t *out_n,
3226
+ ca_size_t *out_stride_bytes)
3227
+ {
3228
+ ca_iter_call_t c;
3229
+ if ( st == NULL || !(st->flags & CA_ITER_FLAG_WALK_MAY_RAISE) ) {
3230
+ return ca_iter_state_next_slab_strided_unprotected(st, out_ptr, out_mask,
3231
+ out_n, out_stride_bytes);
3232
+ }
3233
+ c.st = st; c.out_ptr = out_ptr; c.out_mask = out_mask; c.out_n = out_n;
3234
+ c.out_stride_bytes = out_stride_bytes; c.rc = 0;
3235
+ ca_iter_protect(st, ca_iter_call_next_slab_strided, (VALUE) &c);
3236
+ return c.rc;
3237
+ }
3238
+
3239
+ int
3240
+ ca_iter_state_next_slab_axes (ca_iter_state *st,
3241
+ char **out_ptr,
3242
+ boolean8_t **out_mask)
3243
+ {
3244
+ ca_iter_call_t c;
3245
+ if ( st == NULL || !(st->flags & CA_ITER_FLAG_WALK_MAY_RAISE) ) {
3246
+ return ca_iter_state_next_slab_axes_unprotected(st, out_ptr, out_mask);
3247
+ }
3248
+ c.st = st; c.out_ptr = out_ptr; c.out_mask = out_mask; c.rc = 0;
3249
+ ca_iter_protect(st, ca_iter_call_next_slab_axes, (VALUE) &c);
3250
+ return c.rc;
3251
+ }
3252
+
3253
+ void
3254
+ ca_iter_state_sync_slab (ca_iter_state *st)
3255
+ {
3256
+ ca_iter_call_t c;
3257
+ if ( st == NULL || !(st->flags & CA_ITER_FLAG_WALK_MAY_RAISE) ) {
3258
+ ca_iter_state_sync_slab_unprotected(st);
3259
+ return;
3260
+ }
3261
+ c.st = st;
3262
+ ca_iter_protect(st, ca_iter_call_sync_slab, (VALUE) &c);
3263
+ }
3264
+
2952
3265
  #ifdef CARRAY_DEV_BUILD
2953
3266
  /* ============================================================
2954
3267
  * smoke surface (dev-only, stripped in release)
@@ -4316,6 +4629,5 @@ Init_ca_kernel_iterator (void)
4316
4629
  rb_define_const(rb_cCArray, "T1_ITER_ALIAS_CONTIG", INT2NUM(CA_ITER_ALIAS_CONTIG));
4317
4630
  rb_define_const(rb_cCArray, "T1_ITER_ALIAS_STRIDED", INT2NUM(CA_ITER_ALIAS_STRIDED));
4318
4631
  rb_define_const(rb_cCArray, "T1_ITER_ALIAS_ATTACH", INT2NUM(CA_ITER_ALIAS_ATTACH));
4319
- rb_define_const(rb_cCArray, "T1_ITER_ERR_UNBOUND_SHAPE", INT2NUM(CA_ITER_ERR_UNBOUND_SHAPE));
4320
4632
  #endif /* CARRAY_DEV_BUILD */
4321
4633
  }