carray 3.0.2 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +568 -0
  3. data/README.md +6 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_descriptor.h +27 -24
  6. data/ext/ca_axis_dispatch.c +220 -0
  7. data/ext/ca_axis_group.c +201 -123
  8. data/ext/ca_binop_dispatch.c +46 -52
  9. data/ext/ca_binop_dispatch.h +1 -1
  10. data/ext/ca_categorical_iterator.c +249 -183
  11. data/ext/ca_compare.c +7 -12
  12. data/ext/ca_for_buffer.h +21 -68
  13. data/ext/ca_group_iter.c +38 -23
  14. data/ext/ca_kernel_iterator.c +1765 -2076
  15. data/ext/ca_kernel_iterator.h +584 -566
  16. data/ext/ca_monop_dispatch.c +86 -63
  17. data/ext/ca_monop_dispatch.h +5 -0
  18. data/ext/ca_obj_array.c +223 -56
  19. data/ext/ca_obj_bincmp.c +172 -138
  20. data/ext/ca_obj_binop.c +116 -230
  21. data/ext/ca_obj_bitarray.c +40 -26
  22. data/ext/ca_obj_bitfield.c +100 -68
  23. data/ext/ca_obj_block.c +19 -9
  24. data/ext/ca_obj_byte_swap.c +45 -46
  25. data/ext/ca_obj_const_string.c +269 -44
  26. data/ext/ca_obj_face.c +612 -89
  27. data/ext/ca_obj_face.h +111 -47
  28. data/ext/ca_obj_fake.c +36 -79
  29. data/ext/ca_obj_farray.c +7 -3
  30. data/ext/ca_obj_field.c +33 -8
  31. data/ext/ca_obj_fixlen_string.c +23 -24
  32. data/ext/ca_obj_grid.c +541 -210
  33. data/ext/ca_obj_meld.c +331 -111
  34. data/ext/ca_obj_moncmp.c +80 -65
  35. data/ext/ca_obj_monop.c +177 -149
  36. data/ext/ca_obj_object.c +664 -248
  37. data/ext/ca_obj_record.c +63 -23
  38. data/ext/ca_obj_reduce.c +18 -16
  39. data/ext/ca_obj_refer.c +140 -58
  40. data/ext/ca_obj_remap.c +85 -24
  41. data/ext/ca_obj_repeat.c +24 -9
  42. data/ext/ca_obj_roll.c +121 -16
  43. data/ext/ca_obj_select.c +61 -58
  44. data/ext/ca_obj_select_axis.c +73 -107
  45. data/ext/ca_obj_shift.c +32 -102
  46. data/ext/ca_obj_stack.c +372 -122
  47. data/ext/ca_obj_stride.c +348 -140
  48. data/ext/ca_obj_string.c +88 -22
  49. data/ext/ca_obj_tile.c +38 -21
  50. data/ext/ca_obj_time.c +45 -31
  51. data/ext/ca_obj_timedelta.c +43 -29
  52. data/ext/ca_obj_transpose.c +17 -6
  53. data/ext/ca_obj_triop.c +125 -141
  54. data/ext/ca_obj_window.c +330 -44
  55. data/ext/ca_op_ipower.c +24 -11
  56. data/ext/ca_op_powi.h +57 -30
  57. data/ext/ca_pow5_table.h +656 -0
  58. data/ext/ca_sort_kernels.h +70 -0
  59. data/ext/ca_sweep_engine.c +104 -81
  60. data/ext/ca_sweep_engine.h +7 -20
  61. data/ext/ca_triop_dispatch.c +4 -1
  62. data/ext/ca_triop_dispatch.h +5 -2
  63. data/ext/caframe_csv_reader.c +542 -0
  64. data/ext/carray.h +241 -92
  65. data/ext/carray_access.c +608 -276
  66. data/ext/carray_address_basis.c +494 -97
  67. data/ext/carray_attribute.c +129 -4
  68. data/ext/carray_bincount.c +39 -7
  69. data/ext/carray_broadcast.c +56 -19
  70. data/ext/carray_build_flags.h +1 -1
  71. data/ext/carray_call_cfunc.c +108 -28
  72. data/ext/carray_cast.c +1576 -223
  73. data/ext/carray_cast_func.rb +77 -24
  74. data/ext/carray_class.c +4 -3
  75. data/ext/carray_conversion.c +208 -75
  76. data/ext/carray_copy.c +82 -24
  77. data/ext/carray_core.c +646 -162
  78. data/ext/carray_count.c +171 -88
  79. data/ext/carray_element.c +94 -60
  80. data/ext/carray_factorize.c +670 -324
  81. data/ext/carray_generate.c +260 -83
  82. data/ext/carray_histogram.c +209 -106
  83. data/ext/carray_hold.c +14 -16
  84. data/ext/carray_index_classifier.c +422 -243
  85. data/ext/carray_internal.h +692 -3
  86. data/ext/carray_kernels_bincmp.c +7 -0
  87. data/ext/carray_kernels_binop.c +487 -120
  88. data/ext/carray_kernels_init.c +653 -50
  89. data/ext/carray_kernels_moncmp.c +15 -8
  90. data/ext/carray_kernels_monop.c +99 -43
  91. data/ext/carray_kernels_reduce_aggregate.c +3925 -6112
  92. data/ext/carray_kernels_reduce_boolean.c +112 -36
  93. data/ext/carray_kernels_reduce_cumulative.c +2127 -6903
  94. data/ext/carray_kernels_reduce_extreme.c +3032 -2892
  95. data/ext/carray_kernels_reduce_variance.c +439 -500
  96. data/ext/carray_kernels_scan.c +1097 -488
  97. data/ext/carray_kernels_search.c +3787 -2176
  98. data/ext/carray_kernels_sort.c +1200 -920
  99. data/ext/carray_kernels_triop.c +479 -7
  100. data/ext/carray_key_windows.c +216 -0
  101. data/ext/carray_lazy.c +967 -153
  102. data/ext/carray_loop.c +81 -65
  103. data/ext/carray_mask.c +910 -324
  104. data/ext/carray_math_kernel.h +1 -0
  105. data/ext/carray_median_percentile.c +299 -100
  106. data/ext/carray_memory_view.c +872 -758
  107. data/ext/carray_operator.c +1171 -364
  108. data/ext/carray_order.c +505 -133
  109. data/ext/carray_partition.c +112 -65
  110. data/ext/carray_random.c +327 -239
  111. data/ext/carray_round.c +273 -0
  112. data/ext/carray_scatter.c +346 -78
  113. data/ext/carray_segment.c +257 -0
  114. data/ext/carray_slab.c +249 -86
  115. data/ext/carray_sort.c +224 -227
  116. data/ext/carray_sort_kernel.c +8 -1
  117. data/ext/carray_struct.c +118 -39
  118. data/ext/carray_test.c +193 -69
  119. data/ext/carray_time_text.c +1182 -0
  120. data/ext/carray_undef.c +4 -0
  121. data/ext/carray_utils.c +579 -113
  122. data/ext/extconf.rb +27 -2
  123. data/ext/mk_call_cfunc.rb +19 -4
  124. data/ext/mk_pow5_table.rb +35 -0
  125. data/ext/mkkernel.rb +1202 -1014
  126. data/ext/ruby_carray.c +17 -5
  127. data/ext/version.h +4 -4
  128. data/ext/version.rb +4 -2
  129. data/lib/carray/attribute.rb +160 -17
  130. data/lib/carray/autoload_carray.rb +23 -27
  131. data/lib/carray/autoload_method_extension.rb +20 -5
  132. data/lib/carray/axis_group.rb +360 -63
  133. data/lib/carray/basics.rb +162 -28
  134. data/lib/carray/bincount_nd.rb +18 -2
  135. data/lib/carray/block_iterator.rb +231 -58
  136. data/lib/carray/boolean_reduce.rb +34 -42
  137. data/lib/carray/categorical.rb +242 -52
  138. data/lib/carray/categorical_iterator.rb +437 -827
  139. data/lib/carray/complex.rb +41 -34
  140. data/lib/carray/conditional.rb +150 -14
  141. data/lib/carray/const_string.rb +59 -4
  142. data/lib/carray/construct.rb +15 -11
  143. data/lib/carray/data_type_extension.rb +1 -0
  144. data/lib/carray/distance_limit.rb +77 -0
  145. data/lib/carray/fixlen_string.rb +7 -1
  146. data/lib/carray/frame/column_verbs.rb +70 -0
  147. data/lib/carray/frame/concat.rb +157 -6
  148. data/lib/carray/frame/convert.rb +82 -7
  149. data/lib/carray/frame/csv_parser.rb +688 -87
  150. data/lib/carray/frame/describe.rb +80 -0
  151. data/lib/carray/frame/frame.rb +369 -45
  152. data/lib/carray/frame/group.rb +631 -90
  153. data/lib/carray/frame/io.rb +506 -79
  154. data/lib/carray/frame/join.rb +411 -62
  155. data/lib/carray/frame/records.rb +116 -19
  156. data/lib/carray/frame/reshape.rb +455 -0
  157. data/lib/carray/frame/sort.rb +30 -18
  158. data/lib/carray/frame/verbs.rb +868 -61
  159. data/lib/carray/frame/windows.rb +117 -0
  160. data/lib/carray/frame.rb +4 -0
  161. data/lib/carray/fuse_source.rb +118 -2
  162. data/lib/carray/fusion.rb +257 -42
  163. data/lib/carray/histogram.rb +6 -1
  164. data/lib/carray/inspect.rb +54 -13
  165. data/lib/carray/iterator.rb +99 -9
  166. data/lib/carray/key_windows.rb +620 -0
  167. data/lib/carray/lazy.rb +216 -193
  168. data/lib/carray/mask_gap_fill.rb +112 -92
  169. data/lib/carray/math.rb +32 -19
  170. data/lib/carray/meld_reduce.rb +52 -21
  171. data/lib/carray/methods/align_addr.rb +8 -4
  172. data/lib/carray/methods/bin.rb +26 -18
  173. data/lib/carray/methods/bincount.rb +18 -2
  174. data/lib/carray/methods/bit_string.rb +15 -4
  175. data/lib/carray/methods/choose.rb +16 -3
  176. data/lib/carray/methods/composition.rb +18 -10
  177. data/lib/carray/methods/factorize.rb +2 -2
  178. data/lib/carray/methods/gather_nd.rb +25 -5
  179. data/lib/carray/methods/insert_block.rb +9 -14
  180. data/lib/carray/methods/is_between.rb +59 -0
  181. data/lib/carray/methods/is_in.rb +140 -10
  182. data/lib/carray/methods/join.rb +2 -7
  183. data/lib/carray/methods/lag.rb +49 -0
  184. data/lib/carray/methods/locate_addr.rb +182 -45
  185. data/lib/carray/methods/mode.rb +18 -1
  186. data/lib/carray/methods/nunique.rb +11 -6
  187. data/lib/carray/methods/repeat.rb +1 -20
  188. data/lib/carray/methods/resize.rb +21 -23
  189. data/lib/carray/methods/snap.rb +34 -12
  190. data/lib/carray/methods/unique.rb +11 -2
  191. data/lib/carray/methods/value_counts.rb +4 -11
  192. data/lib/carray/record_order.rb +208 -0
  193. data/lib/carray/rng.rb +3 -0
  194. data/lib/carray/runtime.rb +65 -17
  195. data/lib/carray/segment_iterator.rb +1221 -0
  196. data/lib/carray/serialize.rb +214 -30
  197. data/lib/carray/slab_iterator.rb +101 -27
  198. data/lib/carray/stack.rb +10 -4
  199. data/lib/carray/string.rb +105 -5
  200. data/lib/carray/string_operation_extension.rb +12 -4
  201. data/lib/carray/struct.rb +137 -58
  202. data/lib/carray/struct_builder.rb +124 -17
  203. data/lib/carray/time.rb +1222 -186
  204. data/lib/carray/window_iterator.rb +242 -81
  205. data/lib/carray.rb +9 -2
  206. data/yard-stubs/ca_obj_array.rb +4 -4
  207. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  208. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  209. data/yard-stubs/ca_obj_fake.rb +1 -1
  210. data/yard-stubs/ca_obj_field.rb +5 -1
  211. data/yard-stubs/ca_obj_grid.rb +21 -10
  212. data/yard-stubs/ca_obj_refer.rb +5 -4
  213. data/yard-stubs/ca_obj_roll.rb +2 -3
  214. data/yard-stubs/ca_obj_shift.rb +5 -1
  215. data/yard-stubs/ca_obj_stride.rb +10 -7
  216. data/yard-stubs/ca_obj_tile.rb +1 -1
  217. data/yard-stubs/ca_obj_transpose.rb +1 -1
  218. data/yard-stubs/ca_obj_window.rb +9 -5
  219. data/yard-stubs/carray_access.rb +11 -5
  220. data/yard-stubs/carray_attribute.rb +0 -5
  221. data/yard-stubs/carray_broadcast.rb +1 -1
  222. data/yard-stubs/carray_cast.rb +88 -43
  223. data/yard-stubs/carray_conversion.rb +15 -2
  224. data/yard-stubs/carray_copy.rb +12 -5
  225. data/yard-stubs/carray_core.rb +3 -3
  226. data/yard-stubs/carray_count.rb +9 -0
  227. data/yard-stubs/carray_element.rb +10 -4
  228. data/yard-stubs/carray_generate.rb +21 -1
  229. data/yard-stubs/carray_lazy.rb +5 -1
  230. data/yard-stubs/carray_loop.rb +4 -1
  231. data/yard-stubs/carray_mask.rb +60 -12
  232. data/yard-stubs/carray_math.rb +52 -14
  233. data/yard-stubs/carray_median_percentile.rb +15 -8
  234. data/yard-stubs/carray_memory_view.rb +40 -23
  235. data/yard-stubs/carray_order.rb +61 -29
  236. data/yard-stubs/carray_random.rb +13 -4
  237. data/yard-stubs/carray_scatter.rb +21 -9
  238. data/yard-stubs/carray_segment.rb +72 -0
  239. data/yard-stubs/carray_slab.rb +10 -2
  240. data/yard-stubs/carray_sort.rb +49 -8
  241. data/yard-stubs/carray_test.rb +15 -3
  242. data/yard-stubs/carray_utils.rb +0 -26
  243. metadata +19 -5
  244. data/ext/ca_for_each_element.h +0 -239
  245. data/ext/carray_kernels_map.c +0 -3467
  246. data/lib/carray/arrow_tensor.rb +0 -401
  247. data/lib/carray/table.rb +0 -108
@@ -129,4 +129,74 @@ void ca_sort_merge_pair_u64 (ca_pair_u64 *a, ca_pair_u64 *aux, ca_size_t n);
129
129
  void ca_sort_merge_pair_f32 (ca_pair_f32 *a, ca_pair_f32 *aux, ca_size_t n);
130
130
  void ca_sort_merge_pair_f64 (ca_pair_f64 *a, ca_pair_f64 *aux, ca_size_t n);
131
131
 
132
+ /* Runs of this many elements are insertion-sorted before the merge passes. */
133
+ #define CA_SORT_INSERTION_THRESHOLD 16
134
+
135
+ /* The body of a stable bottom-up mergesort over TYPE, ordered by cmp alone
136
+ (the element type has no `<`), for a function taking
137
+ (TYPE *a, TYPE *aux, ca_size_t n, int (*cmp)(const void *, const void *))
138
+ aux holds n elements. cmp may raise -- an object comparator calls <=> --
139
+ and the raise unwinds through this code's frames, which hold nothing;
140
+ libc's qsort / mergesort would have to be unwound instead.
141
+
142
+ A comparison is costly here (a call into Ruby) and a move is cheap, so
143
+ the runs are insertion-sorted with each insertion point found by binary
144
+ search: log2(k) comparisons where a linear walk takes k/2. Merge passes
145
+ ping-pong between a and aux and skip a pair of runs already in order.
146
+ Stable: an element lands after every one that does not compare greater,
147
+ and a merge takes from the right run only on a strictly smaller one. */
148
+ #define CA_SORT_MERGE_BY_CMP(TYPE) \
149
+ { \
150
+ if ( n <= 1 ) return; \
151
+ for ( ca_size_t lo = 0; lo < n; lo += CA_SORT_INSERTION_THRESHOLD ) { \
152
+ ca_size_t hi = lo + CA_SORT_INSERTION_THRESHOLD; \
153
+ if ( hi > n ) hi = n; \
154
+ for ( ca_size_t k = lo + 1; k < hi; k++ ) { \
155
+ TYPE v = a[k]; \
156
+ ca_size_t l = lo, r = k; \
157
+ while ( l < r ) { \
158
+ ca_size_t mid = l + (r - l) / 2; \
159
+ if ( cmp(&a[mid], &v) > 0 ) r = mid; \
160
+ else l = mid + 1; \
161
+ } \
162
+ if ( l < k ) { \
163
+ memmove(a + l + 1, a + l, (size_t)(k - l) * sizeof(TYPE)); \
164
+ a[l] = v; \
165
+ } \
166
+ } \
167
+ } \
168
+ TYPE *cur = a; \
169
+ TYPE *next = aux; \
170
+ for ( ca_size_t width = CA_SORT_INSERTION_THRESHOLD; width < n; \
171
+ width <<= 1 ) { \
172
+ for ( ca_size_t lo = 0; lo < n; lo += (width << 1) ) { \
173
+ ca_size_t mid = lo + width; \
174
+ ca_size_t hi = lo + (width << 1); \
175
+ if ( mid > n ) mid = n; \
176
+ if ( hi > n ) hi = n; \
177
+ if ( mid >= hi || cmp(&cur[mid - 1], &cur[mid]) <= 0 ) { \
178
+ memcpy(next + lo, cur + lo, (size_t)(hi - lo) * sizeof(TYPE)); \
179
+ continue; \
180
+ } \
181
+ ca_size_t i = lo, j = mid, k = lo; \
182
+ while ( i < mid && j < hi ) { \
183
+ if ( cmp(&cur[j], &cur[i]) < 0 ) next[k++] = cur[j++]; \
184
+ else next[k++] = cur[i++]; \
185
+ } \
186
+ while ( i < mid ) next[k++] = cur[i++]; \
187
+ while ( j < hi ) next[k++] = cur[j++]; \
188
+ } \
189
+ { TYPE *t = cur; cur = next; next = t; } \
190
+ } \
191
+ if ( cur != a ) { \
192
+ memcpy(a, cur, (size_t) n * sizeof(TYPE)); \
193
+ } \
194
+ }
195
+
196
+ /* (VALUE, index) pairs, as the object lane of the sort kernels holds them. */
197
+ typedef struct ca_pair_object { VALUE v; ca_size_t i; } ca_pair_object;
198
+ void ca_sort_merge_pair_object (ca_pair_object *a, ca_pair_object *aux,
199
+ ca_size_t n,
200
+ int (*cmp)(const void *, const void *));
201
+
132
202
  #endif /* CA_SORT_KERNELS_H */
@@ -42,9 +42,9 @@ ca_sweep_refuse_shapes (CArray *a, CArray *b)
42
42
  volatile VALUE sa = ca_sweep_shape_str(a);
43
43
  volatile VALUE sb = ca_sweep_shape_str(b);
44
44
  rb_raise(rb_eArgError,
45
- "shape mismatch between operands (%s and %s); a scalar pairs "
45
+ "%s: shape mismatch between operands (%s and %s); a scalar pairs "
46
46
  "with any array, two arrays only when their shapes agree",
47
- StringValueCStr(sa), StringValueCStr(sb));
47
+ ca_calling_method_name(), StringValueCStr(sa), StringValueCStr(sb));
48
48
  }
49
49
 
50
50
  int
@@ -85,6 +85,26 @@ ca_sweep_pair_operands (ca_sweep_state_t *st)
85
85
  return donor;
86
86
  }
87
87
 
88
+ /* Non-zero when INPUT k_op lives in the same root as an OUTPUT other
89
+ * than itself. Read in place, or chunk by chunk after earlier chunks
90
+ * were written, it would show the kernel cells already written; such an
91
+ * input is read whole before the walk. An input that is the output
92
+ * itself is read in place: each cell is read before it is written. */
93
+ static int
94
+ ca_sweep_input_overlaps_output (ca_sweep_state_t *st, int k_op)
95
+ {
96
+ int j;
97
+ CArray *ca = st->cx[k_op];
98
+ if (st->fsync[k_op] != '0' || ca_is_scalar(ca)) return 0;
99
+ for (j = 0; j < st->n_ops; j++) {
100
+ if (st->fsync[j] == '1' && st->cx[j] != ca &&
101
+ ca_shares_root(ca, st->cx[j])) {
102
+ return 1;
103
+ }
104
+ }
105
+ return 0;
106
+ }
107
+
88
108
  /* Non-zero when any INPUT operand carries a mask. Asks, allocates
89
109
  * nothing. */
90
110
  static int
@@ -97,18 +117,6 @@ ca_sweep_any_input_mask (ca_sweep_state_t *st)
97
117
  return 0;
98
118
  }
99
119
 
100
- NORETURN(static void ca_sweep_refuse_masked_input (ca_sweep_state_t *st,
101
- const char *fallback));
102
-
103
- static void
104
- ca_sweep_refuse_masked_input (ca_sweep_state_t *st, const char *fallback)
105
- {
106
- rb_raise(rb_eRuntimeError,
107
- "%s: masked INPUT not allowed in NO_MASK form "
108
- "(use the *_MASKED form to handle masked cells explicitly)",
109
- st->src_label ? st->src_label : fallback);
110
- }
111
-
112
120
  /* Give back everything acquire has taken so far, without syncing: detach
113
121
  * attached operands, free owned scratch (xmalloc on the whole-buffer path,
114
122
  * arena on the chunked one) and the mask buffers. Called when a read
@@ -158,12 +166,14 @@ ca_sweep_acquire_body (VALUE arg)
158
166
  /* Per-operand acquire:
159
167
  * OUTPUT (fsync == '1') -> ca_attach + base = ca->ptr
160
168
  * INPUT alias -> ca_attach + base = ca->ptr
161
- * INPUT non-alias non-scalar -> xmalloc scratch + ca_xfer_all
169
+ * INPUT non-alias non-scalar,
170
+ * or sharing an OUTPUT's root -> xmalloc scratch + ca_xfer_all
162
171
  * INPUT non-alias never attaches the operand itself; the scratch
163
172
  * copy carries the values into the kernel. */
164
173
  for (k_op = 0; k_op < st->n_ops; k_op++) {
165
174
  CArray *ca = st->cx[k_op];
166
- if (st->fsync[k_op] == '1' || ca_attach_is_alias(ca)) {
175
+ if (st->fsync[k_op] == '1' ||
176
+ (ca_attach_is_alias(ca) && ! ca_sweep_input_overlaps_output(st, k_op))) {
167
177
  ca_attach(ca);
168
178
  st->attached[k_op] = 1;
169
179
  st->base[k_op] = (char *) ca->ptr;
@@ -204,8 +214,7 @@ ca_sweep_acquire_body (VALUE arg)
204
214
  for (k_op = 0; k_op < st->n_ops; k_op++) {
205
215
  CArray *ca = st->cx[k_op];
206
216
  if (st->fsync[k_op] != '1') continue;
207
- ca_update_mask(ca);
208
- if (!ca->mask) ca_create_mask(ca);
217
+ ca_create_mask(ca);
209
218
  memcpy(ca->mask->ptr, st->m0, st->n_kernel);
210
219
  }
211
220
  }
@@ -232,17 +241,12 @@ ca_sweep_acquire (ca_sweep_state_t *st)
232
241
  st->attached[k_op] = 0;
233
242
  }
234
243
  st->m0 = NULL;
235
- /* CAREFUL: do not reset st->no_mask here — caller sets it before
236
- * acquire and the NO_MASK guard below consumes it. */
237
244
 
238
245
  /* Refusals come first, while nothing is held. */
239
246
  ca_sweep_pair_operands(st);
240
247
  ctx.st = st;
241
248
  ctx.ms = NULL;
242
249
  ctx.any_input_mask = ca_sweep_any_input_mask(st);
243
- if (ctx.any_input_mask && st->no_mask) {
244
- ca_sweep_refuse_masked_input(st, "ca_sweep_acquire");
245
- }
246
250
 
247
251
  rb_protect(ca_sweep_acquire_body, (VALUE) &ctx, &tag);
248
252
  if (ctx.ms) xfree(ctx.ms);
@@ -280,14 +284,60 @@ ca_sweep_run_chunked (ca_sweep_state_t *st, VALUE (*walk)(VALUE), VALUE arg)
280
284
  ca_sweep_release_chunked(st);
281
285
  }
282
286
 
287
+ static VALUE
288
+ ca_sweep_sync_one (VALUE arg)
289
+ {
290
+ ca_sync((CArray *) arg);
291
+ return Qnil;
292
+ }
293
+
294
+ /* Sync the OUTPUTs (reverse order). Writing back through a view's slots
295
+ * can raise (a CAObject's store hook); the raise is held so that release
296
+ * still detaches and frees everything, and ca_sweep_reraise propagates
297
+ * it afterwards. An entity's sync does nothing that can raise, so it is
298
+ * called directly: the outputs this library allocates itself pay nothing. */
299
+ static int
300
+ ca_sweep_sync_outputs (ca_sweep_state_t *st, volatile VALUE *error)
301
+ {
302
+ int k_op, first = 0;
303
+ for (k_op = st->n_ops - 1; k_op >= 0; k_op--) {
304
+ CArray *ca = st->cx[k_op];
305
+ if (st->fsync[k_op] != '1') {
306
+ continue;
307
+ }
308
+ if (ca_is_entity(ca)) {
309
+ ca_sync(ca);
310
+ }
311
+ else {
312
+ int tag = 0;
313
+ rb_protect(ca_sweep_sync_one, (VALUE) ca, &tag);
314
+ if (tag) {
315
+ if (! first) {
316
+ first = tag;
317
+ *error = rb_errinfo();
318
+ }
319
+ rb_set_errinfo(Qnil);
320
+ }
321
+ }
322
+ }
323
+ return first;
324
+ }
325
+
326
+ static void
327
+ ca_sweep_reraise (int tag, VALUE error)
328
+ {
329
+ if (tag) {
330
+ rb_set_errinfo(error);
331
+ rb_jump_tag(tag);
332
+ }
333
+ }
334
+
283
335
  void
284
336
  ca_sweep_release (ca_sweep_state_t *st)
285
337
  {
286
338
  int k_op;
287
- /* sync OUTPUTs (reverse order) */
288
- for (k_op = st->n_ops - 1; k_op >= 0; k_op--) {
289
- if (st->fsync[k_op] == '1') ca_sync(st->cx[k_op]);
290
- }
339
+ volatile VALUE error = Qnil;
340
+ int tag = ca_sweep_sync_outputs(st, &error);
291
341
  /* detach attached / xfree owned buffers (reverse order) */
292
342
  for (k_op = st->n_ops - 1; k_op >= 0; k_op--) {
293
343
  if (st->attached[k_op]) {
@@ -301,6 +351,7 @@ ca_sweep_release (ca_sweep_state_t *st)
301
351
  xfree(st->m0);
302
352
  st->m0 = NULL;
303
353
  }
354
+ ca_sweep_reraise(tag, error);
304
355
  }
305
356
 
306
357
  /* ===== Chunked path implementation ===== */
@@ -315,13 +366,14 @@ ca_sweep_acquire_chunked_body (VALUE arg)
315
366
  /* per-operand acquire:
316
367
  * OUTPUT (fsync == '1') : ca_attach + base_orig = ca->ptr (legitimate)
317
368
  * INPUT alias : ca_attach + base_orig = ca->ptr (zero-copy)
318
- * INPUT non-alias non-scalar : arena chunk scratch of chunk_n_max bytes
369
+ * INPUT non-alias non-scalar,
370
+ * or sharing an OUTPUT's root : arena chunk scratch of chunk_n_max bytes
319
371
  * INPUT scalar : ca_attach + base_orig = ca->ptr (1 cell)
320
372
  */
321
373
  for (k_op = 0; k_op < st->n_ops; k_op++) {
322
374
  CArray *ca = st->cx[k_op];
323
375
  if (st->fsync[k_op] == '1' || ca_is_scalar(ca) ||
324
- ca_attach_is_alias(ca)) {
376
+ (ca_attach_is_alias(ca) && ! ca_sweep_input_overlaps_output(st, k_op))) {
325
377
  ca_attach(ca);
326
378
  st->attached[k_op] = 1;
327
379
  st->base_orig[k_op] = (char *) ca->ptr;
@@ -352,8 +404,7 @@ ca_sweep_acquire_chunked_body (VALUE arg)
352
404
  for (k_op = 0; k_op < st->n_ops; k_op++) {
353
405
  CArray *ca = st->cx[k_op];
354
406
  if (st->fsync[k_op] != '1') continue;
355
- ca_update_mask(ca);
356
- if (!ca->mask) ca_create_mask(ca);
407
+ ca_create_mask(ca);
357
408
  }
358
409
  }
359
410
 
@@ -410,13 +461,20 @@ ca_sweep_acquire_chunked (ca_sweep_state_t *st)
410
461
  st->chunk_n_max = 1;
411
462
  }
412
463
 
464
+ /* An INPUT sharing an OUTPUT's root is read whole before anything is
465
+ written: the walk becomes a single chunk, so its data and its mask
466
+ are gathered before the first flush. */
467
+ for (k_op = 0; k_op < st->n_ops; k_op++) {
468
+ if (ca_sweep_input_overlaps_output(st, k_op) && st->n_kernel > 0) {
469
+ st->chunk_n_max = st->n_kernel;
470
+ break;
471
+ }
472
+ }
473
+
413
474
  /* Refusals come first, while nothing is held. */
414
475
  ctx.st = st;
415
476
  ctx.ms = NULL;
416
477
  ctx.any_input_mask = ca_sweep_any_input_mask(st);
417
- if (ctx.any_input_mask && st->no_mask) {
418
- ca_sweep_refuse_masked_input(st, "ca_sweep_acquire_chunked");
419
- }
420
478
 
421
479
  rb_protect(ca_sweep_acquire_chunked_body, (VALUE) &ctx, &tag);
422
480
  if (tag) {
@@ -454,9 +512,7 @@ ca_sweep_gather_chunk_mask (ca_sweep_state_t *st, ca_size_t off, ca_size_t n)
454
512
  }
455
513
  }
456
514
 
457
- /* Copy the chunk just finished out to every OUTPUT operand's mask. Runs
458
- * after the author's loop over that chunk, so m_out writes into m0 are
459
- * carried through. */
515
+ /* Copy the chunk just finished out to every OUTPUT operand's mask. */
460
516
  static void
461
517
  ca_sweep_flush_chunk_mask (ca_sweep_state_t *st)
462
518
  {
@@ -510,7 +566,6 @@ ca_sweep_next_chunk (ca_sweep_state_t *st)
510
566
  st->chunk_off = 0;
511
567
  st->chunked_state = 1;
512
568
  } else {
513
- /* the chunk that just finished is the author's last word on its mask */
514
569
  ca_sweep_flush_chunk_mask(st);
515
570
  /* advance */
516
571
  st->chunk_off += st->chunk_n;
@@ -543,16 +598,15 @@ void
543
598
  ca_sweep_release_chunked (ca_sweep_state_t *st)
544
599
  {
545
600
  int k_op;
601
+ volatile VALUE error = Qnil;
602
+ int tag;
546
603
  /* a walk that raised has already given everything back */
547
604
  if (st->chunked_state == 3) return;
548
605
  /* The final chunk has no next_chunk call to flush it, so it is flushed
549
- * here. For INOUT_MASKED forms this is what captures the author's
550
- * per-cell m_out writes over that last chunk. */
606
+ * here. */
551
607
  ca_sweep_flush_chunk_mask(st);
552
- /* sync OUTPUTs (reverse order, regardless of whether chunk loop ran) */
553
- for (k_op = st->n_ops - 1; k_op >= 0; k_op--) {
554
- if (st->fsync[k_op] == '1') ca_sync(st->cx[k_op]);
555
- }
608
+ /* sync OUTPUTs (regardless of whether chunk loop ran) */
609
+ tag = ca_sweep_sync_outputs(st, &error);
556
610
  /* detach attached / release arena scratch buffers (reverse order) */
557
611
  for (k_op = st->n_ops - 1; k_op >= 0; k_op--) {
558
612
  if (st->attached[k_op]) {
@@ -570,13 +624,16 @@ ca_sweep_release_chunked (ca_sweep_state_t *st)
570
624
  ca_lazy_arena_release(st->mask_scratch);
571
625
  st->mask_scratch = NULL;
572
626
  }
627
+ if (tag) {
628
+ st->chunked_state = 3; /* everything has been given back */
629
+ }
630
+ ca_sweep_reraise(tag, error);
573
631
  }
574
632
 
575
633
  /* ===== WHOLE_BUFFER function form (rb_ensure-protected) ===== */
576
634
 
577
635
  typedef struct {
578
636
  CArray *ca;
579
- int writable;
580
637
  ca_with_buffer_body_fn body_fn;
581
638
  void *user_data;
582
639
  } ca_with_buffer_ctx_t;
@@ -589,49 +646,15 @@ ca_with_buffer_body_trampoline (VALUE ctx_val)
589
646
  return Qnil;
590
647
  }
591
648
 
592
- static VALUE
593
- ca_with_buffer_ensure_trampoline (VALUE ctx_val)
594
- {
595
- ca_with_buffer_ctx_t *ctx = (ca_with_buffer_ctx_t *) ctx_val;
596
- if (ctx->writable) ca_sync(ctx->ca);
597
- ca_detach(ctx->ca);
598
- return Qnil;
599
- }
600
-
601
649
  void
602
650
  rb_ca_call_with_buffer (VALUE r_ca, int writable,
603
651
  ca_with_buffer_body_fn body, void *user_data)
604
652
  {
605
653
  ca_with_buffer_ctx_t ctx;
606
654
  TypedData_Get_Struct(r_ca, CArray, &carray_data_type, ctx.ca);
607
- /* CAREFUL: attach outside rb_ensure. If attach raises, we are not
608
- * yet inside the ensure scope, so the ensure trampoline will not
609
- * fire on a not-attached ca (which would try to detach and crash). */
610
- ca_attach(ctx.ca);
611
- ctx.writable = writable;
612
655
  ctx.body_fn = body;
613
656
  ctx.user_data = user_data;
614
- rb_ensure(ca_with_buffer_body_trampoline, (VALUE) &ctx,
615
- ca_with_buffer_ensure_trampoline, (VALUE) &ctx);
657
+ ca_attach_window(1, &ctx.ca, writable ? "w" : "r",
658
+ ca_with_buffer_body_trampoline, (VALUE) &ctx);
616
659
  }
617
660
 
618
- void
619
- ca_sweep_check_same_shape (CArray *ca_in, CArray *ca_out, const char *src_label)
620
- {
621
- int k;
622
- const char *lbl = src_label ? src_label : "ca_sweep_check_same_shape";
623
- if (ca_in->ndim != ca_out->ndim) {
624
- rb_raise(rb_eRuntimeError,
625
- "%s: shape mismatch (ndim %d vs %d)",
626
- lbl, ca_in->ndim, ca_out->ndim);
627
- }
628
- for (k = 0; k < ca_in->ndim; k++) {
629
- if (ca_in->dim[k] != ca_out->dim[k]) {
630
- rb_raise(rb_eRuntimeError,
631
- "%s: shape mismatch at dim[%d] (%lld vs %lld)",
632
- lbl, k,
633
- (long long) ca_in->dim[k],
634
- (long long) ca_out->dim[k]);
635
- }
636
- }
637
- }
@@ -5,8 +5,7 @@
5
5
  * Established in PROPOSAL_L0_AUTHOR_SURFACE L0.1 (2026-06-11). Extracts
6
6
  * the xfer_all-aware lifecycle template previously duplicated 7-times in
7
7
  * ext/carray_call_cfunc.c (= one body per arity 1..7) into a single shared
8
- * helper. Same helper will back the forthcoming CA_FOR_EACH_ELEMENT macro
9
- * family (L0.2a) and the WHOLE_BUFFER family (L0.2c).
8
+ * helper, also used by the chunked slab family (ca_call_cslab_*).
10
9
  *
11
10
  * Lifecycle template:
12
11
  * (1) per-operand acquire
@@ -64,15 +63,10 @@ typedef struct ca_sweep_state {
64
63
  boolean8_t *m0;
65
64
  ca_size_t n_kernel;
66
65
  const char *src_label;
67
- /* no_mask: if non-zero, ca_sweep_acquire raises when any INPUT operand has
68
- * a mask. Used by the NO_MASK macro forms (CA_FOR_EACH_ELEMENT etc.) to
69
- * implement Q3=(ii) runtime error policy. Default 0 (= masked INPUT OK,
70
- * m0 built as usual). Caller sets before calling ca_sweep_acquire. */
71
- int no_mask;
72
66
 
73
67
  /* === chunked path fields (L0.1 chunk, PROPOSAL_L0_AUTHOR_SURFACE) === */
74
68
  /* Used by ca_sweep_acquire_chunked / ca_sweep_next_chunk / ca_sweep_release_chunked
75
- * (= the sweep ELEMENT macro family). Unused by the whole-materialise path
69
+ * (= the chunked slab family, ca_call_cslab_*). Unused by the whole-materialise path
76
70
  * (ca_sweep_acquire / ca_sweep_release) — cfunc and other whole-buffer callers
77
71
  * leave these zero. */
78
72
  char **base_orig; /* per-op original ptr (= ca->ptr for
@@ -93,7 +87,7 @@ typedef struct ca_sweep_state {
93
87
 
94
88
  /* Validate fsync length, pair the operands (shape), acquire per-op
95
89
  * buffers, build mask m0, propagate mask to OUTPUTs. May raise: refusals
96
- * (fsync length, shape mismatch, masked INPUT in a NO_MASK form) before
90
+ * (fsync length, shape mismatch) before
97
91
  * anything is held, and a raising operand read after -- in which case
98
92
  * everything acquired so far is detached / freed before the raise
99
93
  * propagates, so the caller must not call release. The chunked path
@@ -120,13 +114,7 @@ void ca_sweep_run_chunked (ca_sweep_state_t *st, VALUE (*walk)(VALUE), VALUE arg
120
114
  int ca_sweep_same_shape (CArray *a, CArray *b);
121
115
  NORETURN(void ca_sweep_refuse_shapes (CArray *a, CArray *b));
122
116
 
123
- /* Strict full-shape equality check used by INOUT macros (AC5: silent-
124
- * corruption seam prevention). Raises if ndim differs or any dim[k]
125
- * differs. Returns void; raises on mismatch. */
126
- void ca_sweep_check_same_shape (CArray *ca_in, CArray *ca_out,
127
- const char *src_label);
128
-
129
- /* ===== Chunked path API (sweep ELEMENT macro family, L0.1 chunk) =====
117
+ /* ===== Chunked path API (ca_call_cslab_*) =====
130
118
  *
131
119
  * Unlike ca_sweep_acquire / ca_sweep_release (= whole-buffer materialise used
132
120
  * by cfunc), the chunked path keeps INPUT memory peak bounded to chunk
@@ -134,7 +122,7 @@ void ca_sweep_check_same_shape (CArray *ca_in, CArray *ca_out,
134
122
  * memory peak guarantee: shrinking views materialise into chunk scratch
135
123
  * sized to inner-axis multiples, not full operand size.
136
124
  *
137
- * Usage shape (inside a macro):
125
+ * Usage shape:
138
126
  * ca_sweep_acquire_chunked(&st);
139
127
  * while (ca_sweep_next_chunk(&st)) {
140
128
  * for (k = 0; k < st.chunk_n; k++) {
@@ -162,9 +150,8 @@ void ca_sweep_check_same_shape (CArray *ca_in, CArray *ca_out,
162
150
  * indexed within the chunk, NOT by the flat cell index. (This differs
163
151
  * from the whole-buffer path, where m0 spans n_kernel.)
164
152
  *
165
- * An author may WRITE m0[k] during the chunk loop (= the INOUT_MASKED
166
- * forms' m_out). Those writes are flushed into the OUTPUT operands'
167
- * masks when the chunk finishes -- at the top of the following
153
+ * The chunk's m0 is flushed into the OUTPUT operands' masks when the
154
+ * chunk finishes -- at the top of the following
168
155
  * ca_sweep_next_chunk, and once more in ca_sweep_release_chunked for
169
156
  * the last chunk -- so the OUTPUT masks are created up front in
170
157
  * acquire_chunked rather than propagated in one go at release.
@@ -27,6 +27,7 @@ ca_triop_kernel_lookup_vvv (uint16_t op_id, int8_t common_dt)
27
27
  case CA_TRIOP_FMA: return ca_triop_fma [common_dt];
28
28
  case CA_TRIOP_FMS: return ca_triop_fms [common_dt];
29
29
  case CA_TRIOP_CLIP: return ca_triop_clip [common_dt];
30
+ case CA_TRIOP_SELECT: return ca_triop_select [common_dt];
30
31
  default: return NULL;
31
32
  }
32
33
  }
@@ -38,7 +39,9 @@ ca_triop_kernel_lookup_vvv (uint16_t op_id, int8_t common_dt)
38
39
  int8_t
39
40
  ca_lazy_promote_triop (uint16_t op_id, int8_t dt1, int8_t dt2, int8_t dt3)
40
41
  {
41
- (void) op_id; /* uniform 3-way promote across all currently-defined triops */
42
+ if ( op_id == CA_TRIOP_SELECT ) {
43
+ return (int8_t) ca_promote_type(dt2, dt3);
44
+ }
42
45
  return (int8_t) ca_promote_type(ca_promote_type(dt1, dt2), dt3);
43
46
  }
44
47
 
@@ -11,7 +11,7 @@
11
11
  runs (cast-before invariant, mirrors CABinOp). Output data_type is
12
12
  always the common data_type (preserve).
13
13
 
14
- Scope: fma / fms / clip (see ext/mkkernel.rb :fma / :fms / :clip).
14
+ Scope: fma / fms / clip / select (see ext/mkkernel.rb).
15
15
  None of the currently-defined triops trap on integer zero divisor.
16
16
 
17
17
  ---------------------------------------------------------------------------- */
@@ -29,6 +29,7 @@ enum {
29
29
  CA_TRIOP_FMA = 0, /* a * b + c (single-rounding on float) */
30
30
  CA_TRIOP_FMS = 1, /* a * b - c (single-rounding on float) */
31
31
  CA_TRIOP_CLIP = 2, /* min(max(a, lo), hi), NaN-preserving */
32
+ CA_TRIOP_SELECT = 3, /* a ? b : c (lazy then_else) */
32
33
 
33
34
  CA_TRIOP_COUNT
34
35
  };
@@ -40,6 +41,7 @@ enum {
40
41
  extern ca_triop_func_t ca_triop_fma [CA_NTYPE];
41
42
  extern ca_triop_func_t ca_triop_fms [CA_NTYPE];
42
43
  extern ca_triop_func_t ca_triop_clip [CA_NTYPE];
44
+ extern ca_triop_func_t ca_triop_select [CA_NTYPE];
43
45
 
44
46
  /* ------------------------------------------------------------------- */
45
47
  /* Dispatch API */
@@ -49,7 +51,8 @@ extern ca_triop_func_t ca_triop_clip [CA_NTYPE];
49
51
  not implemented at the requested data_type. */
50
52
  ca_triop_func_t ca_triop_kernel_lookup_vvv (uint16_t op_id, int8_t common_dt);
51
53
 
52
- /* 3-way promotion: common data_type of (dt1, dt2, dt3). */
54
+ /* 3-way promotion: common data_type of (dt1, dt2, dt3). For select it is
55
+ that of the two branches (dt2, dt3); the condition is converted to it. */
53
56
  int8_t ca_lazy_promote_triop (uint16_t op_id, int8_t dt1, int8_t dt2, int8_t dt3);
54
57
 
55
58
  /* Each operand's cast-target data_type (= common data_type for all three,