carray 3.0.1 → 3.0.3

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 (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +824 -0
  3. data/README.md +5 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +258 -4
  6. data/ext/ca_axis_group.c +368 -184
  7. data/ext/ca_binop_dispatch.c +54 -32
  8. data/ext/ca_binop_dispatch.h +1 -0
  9. data/ext/ca_categorical_iterator.c +90 -41
  10. data/ext/ca_compare.c +8 -2
  11. data/ext/ca_for_buffer.h +21 -68
  12. data/ext/ca_group_iter.c +27 -11
  13. data/ext/ca_kernel_iterator.c +960 -245
  14. data/ext/ca_kernel_iterator.h +352 -181
  15. data/ext/ca_monop_dispatch.c +4 -4
  16. data/ext/ca_monop_dispatch.h +4 -0
  17. data/ext/ca_obj_array.c +194 -42
  18. data/ext/ca_obj_bincmp.c +152 -121
  19. data/ext/ca_obj_binop.c +107 -210
  20. data/ext/ca_obj_bitarray.c +37 -24
  21. data/ext/ca_obj_bitfield.c +95 -62
  22. data/ext/ca_obj_block.c +5 -4
  23. data/ext/ca_obj_byte_swap.c +40 -40
  24. data/ext/ca_obj_const_string.c +180 -50
  25. data/ext/ca_obj_face.c +428 -62
  26. data/ext/ca_obj_face.h +107 -47
  27. data/ext/ca_obj_fake.c +21 -50
  28. data/ext/ca_obj_farray.c +1 -0
  29. data/ext/ca_obj_field.c +16 -3
  30. data/ext/ca_obj_fixlen_string.c +39 -26
  31. data/ext/ca_obj_grid.c +181 -124
  32. data/ext/ca_obj_meld.c +235 -55
  33. data/ext/ca_obj_moncmp.c +61 -61
  34. data/ext/ca_obj_monop.c +125 -107
  35. data/ext/ca_obj_object.c +551 -233
  36. data/ext/ca_obj_record.c +51 -16
  37. data/ext/ca_obj_reduce.c +5 -8
  38. data/ext/ca_obj_refer.c +117 -33
  39. data/ext/ca_obj_remap.c +57 -17
  40. data/ext/ca_obj_repeat.c +21 -5
  41. data/ext/ca_obj_roll.c +115 -11
  42. data/ext/ca_obj_select.c +88 -75
  43. data/ext/ca_obj_select_axis.c +58 -60
  44. data/ext/ca_obj_shift.c +22 -102
  45. data/ext/ca_obj_stack.c +223 -123
  46. data/ext/ca_obj_stride.c +267 -35
  47. data/ext/ca_obj_string.c +25 -22
  48. data/ext/ca_obj_tile.c +32 -16
  49. data/ext/ca_obj_time.c +23 -25
  50. data/ext/ca_obj_timedelta.c +21 -23
  51. data/ext/ca_obj_transpose.c +12 -3
  52. data/ext/ca_obj_triop.c +110 -117
  53. data/ext/ca_obj_window.c +157 -29
  54. data/ext/ca_op_ipower.c +25 -13
  55. data/ext/ca_op_powi.h +57 -30
  56. data/ext/ca_pow5_table.h +656 -0
  57. data/ext/ca_rng_normal.h +42 -0
  58. data/ext/ca_rng_xoshiro256pp.h +105 -0
  59. data/ext/ca_sort_kernels.h +70 -0
  60. data/ext/ca_sweep_engine.c +385 -198
  61. data/ext/ca_sweep_engine.h +31 -23
  62. data/ext/ca_triop_dispatch.c +4 -1
  63. data/ext/ca_triop_dispatch.h +5 -2
  64. data/ext/caframe_csv_reader.c +463 -0
  65. data/ext/carray.h +90 -28
  66. data/ext/carray_access.c +487 -231
  67. data/ext/carray_address_basis.c +731 -0
  68. data/ext/carray_attribute.c +45 -3
  69. data/ext/carray_bincount.c +32 -2
  70. data/ext/carray_broadcast.c +49 -17
  71. data/ext/carray_call_cfunc.c +667 -511
  72. data/ext/carray_cast.c +1556 -247
  73. data/ext/carray_cast_func.rb +34 -21
  74. data/ext/carray_conversion.c +197 -73
  75. data/ext/carray_copy.c +84 -38
  76. data/ext/carray_core.c +591 -119
  77. data/ext/carray_count.c +148 -96
  78. data/ext/carray_element.c +18 -27
  79. data/ext/carray_factorize.c +604 -291
  80. data/ext/carray_generate.c +76 -40
  81. data/ext/carray_histogram.c +152 -94
  82. data/ext/carray_hold.c +2 -1
  83. data/ext/carray_index_classifier.c +63 -96
  84. data/ext/carray_internal.h +400 -1
  85. data/ext/carray_lazy.c +834 -79
  86. data/ext/carray_loop.c +88 -66
  87. data/ext/carray_mask.c +705 -324
  88. data/ext/carray_math_kernel.h +1 -0
  89. data/ext/carray_median_percentile.c +263 -63
  90. data/ext/carray_memory_view.c +254 -248
  91. data/ext/carray_operator.c +1093 -363
  92. data/ext/carray_order.c +133 -69
  93. data/ext/carray_partition.c +102 -56
  94. data/ext/carray_random.c +582 -204
  95. data/ext/carray_scatter.c +308 -65
  96. data/ext/carray_segment.c +257 -0
  97. data/ext/carray_slab.c +183 -70
  98. data/ext/carray_sort.c +235 -240
  99. data/ext/carray_sort_kernel.c +8 -1
  100. data/ext/carray_struct.c +99 -21
  101. data/ext/carray_test.c +167 -64
  102. data/ext/carray_time_text.c +1182 -0
  103. data/ext/carray_utils.c +363 -98
  104. data/ext/extconf.rb +27 -2
  105. data/ext/mk_call_cfunc.rb +103 -118
  106. data/ext/mk_pow5_table.rb +35 -0
  107. data/ext/mkkernel.rb +1349 -851
  108. data/ext/ruby_carray.c +17 -2
  109. data/ext/version.h +4 -4
  110. data/ext/version.rb +4 -2
  111. data/lib/carray/attribute.rb +39 -0
  112. data/lib/carray/autoload_carray.rb +24 -3
  113. data/lib/carray/autoload_method_extension.rb +18 -0
  114. data/lib/carray/axis_group.rb +245 -20
  115. data/lib/carray/basics.rb +95 -25
  116. data/lib/carray/bincount_nd.rb +6 -0
  117. data/lib/carray/block_iterator.rb +200 -54
  118. data/lib/carray/boolean_reduce.rb +21 -29
  119. data/lib/carray/categorical.rb +244 -59
  120. data/lib/carray/categorical_iterator.rb +391 -757
  121. data/lib/carray/complex.rb +36 -29
  122. data/lib/carray/conditional.rb +94 -7
  123. data/lib/carray/const_string.rb +177 -27
  124. data/lib/carray/construct.rb +40 -0
  125. data/lib/carray/data_type_extension.rb +3 -0
  126. data/lib/carray/data_type_limits.rb +91 -0
  127. data/lib/carray/fixlen_string.rb +1 -1
  128. data/lib/carray/frame/concat.rb +157 -6
  129. data/lib/carray/frame/convert.rb +13 -7
  130. data/lib/carray/frame/csv_parser.rb +590 -76
  131. data/lib/carray/frame/describe.rb +79 -0
  132. data/lib/carray/frame/frame.rb +205 -35
  133. data/lib/carray/frame/group.rb +199 -40
  134. data/lib/carray/frame/io.rb +452 -60
  135. data/lib/carray/frame/join.rb +134 -46
  136. data/lib/carray/frame/records.rb +54 -19
  137. data/lib/carray/frame/reshape.rb +448 -0
  138. data/lib/carray/frame/sort.rb +26 -2
  139. data/lib/carray/frame/verbs.rb +726 -56
  140. data/lib/carray/frame.rb +2 -0
  141. data/lib/carray/fuse_source.rb +111 -2
  142. data/lib/carray/fusion.rb +229 -38
  143. data/lib/carray/inspect.rb +71 -20
  144. data/lib/carray/iterator.rb +157 -2
  145. data/lib/carray/lazy.rb +54 -67
  146. data/lib/carray/mask_gap_fill.rb +66 -50
  147. data/lib/carray/math.rb +32 -19
  148. data/lib/carray/meld_reduce.rb +40 -7
  149. data/lib/carray/methods/align_addr.rb +8 -4
  150. data/lib/carray/methods/bincount.rb +2 -2
  151. data/lib/carray/methods/bit_string.rb +14 -3
  152. data/lib/carray/methods/composition.rb +16 -10
  153. data/lib/carray/methods/discovery_along.rb +74 -0
  154. data/lib/carray/methods/factorize.rb +50 -0
  155. data/lib/carray/methods/gather_nd.rb +13 -2
  156. data/lib/carray/methods/insert_block.rb +9 -14
  157. data/lib/carray/methods/is_in.rb +130 -6
  158. data/lib/carray/methods/join.rb +1 -1
  159. data/lib/carray/methods/locate_addr.rb +235 -21
  160. data/lib/carray/methods/mask_duplicates.rb +35 -1
  161. data/lib/carray/methods/mode.rb +18 -1
  162. data/lib/carray/methods/nunique.rb +22 -1
  163. data/lib/carray/methods/repeat.rb +91 -0
  164. data/lib/carray/methods/resize.rb +21 -23
  165. data/lib/carray/methods/snap.rb +34 -12
  166. data/lib/carray/methods/unique.rb +52 -3
  167. data/lib/carray/record_order.rb +208 -0
  168. data/lib/carray/rng.rb +86 -0
  169. data/lib/carray/runtime.rb +14 -11
  170. data/lib/carray/segment_iterator.rb +990 -0
  171. data/lib/carray/serialize.rb +197 -27
  172. data/lib/carray/slab_iterator.rb +109 -40
  173. data/lib/carray/stack.rb +10 -4
  174. data/lib/carray/string_operation_extension.rb +9 -4
  175. data/lib/carray/struct.rb +142 -51
  176. data/lib/carray/struct_builder.rb +110 -14
  177. data/lib/carray/table.rb +2 -2
  178. data/lib/carray/time.rb +535 -105
  179. data/lib/carray/window_iterator.rb +259 -82
  180. data/lib/carray.rb +4 -2
  181. data/yard-stubs/ca_obj_array.rb +4 -4
  182. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  183. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  184. data/yard-stubs/ca_obj_block.rb +2 -7
  185. data/yard-stubs/ca_obj_fake.rb +1 -1
  186. data/yard-stubs/ca_obj_field.rb +1 -1
  187. data/yard-stubs/ca_obj_refer.rb +3 -3
  188. data/yard-stubs/ca_obj_roll.rb +0 -1
  189. data/yard-stubs/ca_obj_stride.rb +10 -7
  190. data/yard-stubs/ca_obj_transpose.rb +1 -1
  191. data/yard-stubs/ca_obj_window.rb +11 -4
  192. data/yard-stubs/carray_access.rb +6 -5
  193. data/yard-stubs/carray_broadcast.rb +2 -2
  194. data/yard-stubs/carray_cast.rb +78 -43
  195. data/yard-stubs/carray_conversion.rb +13 -1
  196. data/yard-stubs/carray_copy.rb +8 -3
  197. data/yard-stubs/carray_core.rb +3 -83
  198. data/yard-stubs/carray_count.rb +16 -2
  199. data/yard-stubs/carray_lazy.rb +210 -1
  200. data/yard-stubs/carray_loop.rb +4 -1
  201. data/yard-stubs/carray_mask.rb +34 -10
  202. data/yard-stubs/carray_math.rb +1499 -4
  203. data/yard-stubs/carray_median_percentile.rb +26 -8
  204. data/yard-stubs/carray_order.rb +43 -82
  205. data/yard-stubs/carray_scatter.rb +10 -3
  206. data/yard-stubs/carray_segment.rb +72 -0
  207. data/yard-stubs/carray_slab.rb +11 -8
  208. data/yard-stubs/carray_sort.rb +50 -8
  209. data/yard-stubs/carray_test.rb +10 -0
  210. data/yard-stubs/carray_utils.rb +0 -26
  211. metadata +19 -19
  212. data/ext/ca_for_each_element.h +0 -239
  213. data/ext/carray_build_flags.h +0 -3
  214. data/ext/carray_config.h +0 -28
  215. data/ext/carray_kernels_bincmp.c +0 -4446
  216. data/ext/carray_kernels_binop.c +0 -11001
  217. data/ext/carray_kernels_init.c +0 -1131
  218. data/ext/carray_kernels_map.c +0 -3467
  219. data/ext/carray_kernels_moncmp.c +0 -2097
  220. data/ext/carray_kernels_monop.c +0 -18313
  221. data/ext/carray_kernels_reduce_aggregate.c +0 -25837
  222. data/ext/carray_kernels_reduce_boolean.c +0 -330
  223. data/ext/carray_kernels_reduce_cumulative.c +0 -14593
  224. data/ext/carray_kernels_reduce_extreme.c +0 -16948
  225. data/ext/carray_kernels_reduce_variance.c +0 -3910
  226. data/ext/carray_kernels_scan.c +0 -3693
  227. data/ext/carray_kernels_search.c +0 -32138
  228. data/ext/carray_kernels_sort.c +0 -10626
  229. data/ext/carray_kernels_triop.c +0 -1392
@@ -12,6 +12,7 @@
12
12
 
13
13
  #include "carray.h"
14
14
  #include "carray_internal.h" /* ca_lazy_arena_* */
15
+ #include "ca_binop_dispatch.h" /* ca_binop_is_trapping */
15
16
 
16
17
  /* Operand scratch for a chunked / gathered kernel run.
17
18
 
@@ -30,6 +31,21 @@ ca_op_acquire_operand_scratch (CArray *ca, ca_size_t n_elements)
30
31
 
31
32
  #include "ca_obj_face.h" /* ca_face_reconcile_comparison (comparison Face gate) */
32
33
 
34
+ /* A kernel table with no entry for the operand's data_type. Name the
35
+ method and the type, or the Face, rather than leave it to the table's
36
+ stub, which knows neither. */
37
+ NORETURN(static void ca_op_refuse_unimplemented (VALUE operand, CArray *ca,
38
+ VALUE eclass));
39
+
40
+ static void
41
+ ca_op_refuse_unimplemented (VALUE operand, CArray *ca, VALUE eclass)
42
+ {
43
+ const char *name = ca_calling_method_name();
44
+ ca_face_refuse_numeric(operand, name, eclass);
45
+ rb_raise(eclass, "%s: not defined for data_type %s",
46
+ name, ca_type_name[ca->data_type]);
47
+ }
48
+
33
49
  VALUE rb_mCAMath;
34
50
 
35
51
  extern ca_binop_func_t ca_binop_mul[CA_NTYPE];
@@ -52,8 +68,8 @@ ca_zerodiv (void)
52
68
 
53
69
  These three helpers (ca_chunk_inner_size / ca_chunk_compute_n /
54
70
  ca_chunked_gather) are extern so ca_sweep_engine.c can reuse the same
55
- chunk policy + region-gather mechanism for the sweep ELEMENT macro
56
- family without duplicating the implementation. */
71
+ chunk policy + region-gather mechanism for the chunked slab family
72
+ (ca_call_cslab_*) without duplicating the implementation. */
57
73
 
58
74
  #define CA_CHUNK_TARGET_ELEMENTS 4096
59
75
 
@@ -133,6 +149,360 @@ ca_chunked_gather (CArray *ca, ca_size_t off, ca_size_t n, void *dest)
133
149
  ca_xfer_stride(ca, starts, counts, strides, dest, CA_XFER_GET);
134
150
  }
135
151
 
152
+ /* The chunked kernel run shared by the binop / triop / bincmp drivers.
153
+
154
+ Each input operand is acquired once and read chunk by chunk:
155
+ - a scalar (step 0) is gathered once, or aliased
156
+ - an alias array is read through its own ptr at the chunk offset
157
+ - any other array is gathered into an arena scratch every chunk
158
+
159
+ The acquires, the gathers and the kernel itself can all raise (a view
160
+ that refuses the transfer, an object lane calling Ruby), so the run
161
+ goes through ca_lazy_arena_protect and the release -- scratches back to
162
+ the arena, aliases detached -- happens however it ends. */
163
+
164
+ #define CA_CHUNKED_MAX_OPERANDS 3
165
+
166
+ typedef struct ca_chunked_run {
167
+ int n_op;
168
+ CArray *ca[CA_CHUNKED_MAX_OPERANDS];
169
+ ca_size_t step[CA_CHUNKED_MAX_OPERANDS];
170
+ char *src[CA_CHUNKED_MAX_OPERANDS];
171
+ void *scratch[CA_CHUNKED_MAX_OPERANDS];
172
+ int per_chunk[CA_CHUNKED_MAX_OPERANDS];
173
+ int attached[CA_CHUNKED_MAX_OPERANDS];
174
+ ca_size_t n_total;
175
+ ca_size_t chunk_n;
176
+ /* Calls the kernel on cells [off, off + n) with p[k] the chunk of
177
+ operand k. */
178
+ void (*kernel)(struct ca_chunked_run *run, ca_size_t off, ca_size_t n,
179
+ char **p);
180
+ void *data; /* the driver's kernel table and output */
181
+ } ca_chunked_run_t;
182
+
183
+ static void
184
+ ca_chunked_run_init (ca_chunked_run_t *run, ca_size_t n_total,
185
+ ca_size_t chunk_n)
186
+ {
187
+ MEMZERO(run, ca_chunked_run_t, 1);
188
+ run->n_total = n_total;
189
+ run->chunk_n = chunk_n;
190
+ }
191
+
192
+ static void
193
+ ca_chunked_run_add (ca_chunked_run_t *run, CArray *ca, ca_size_t step)
194
+ {
195
+ run->ca[run->n_op] = ca;
196
+ run->step[run->n_op] = step;
197
+ run->n_op++;
198
+ }
199
+
200
+ static void
201
+ ca_chunked_run_acquire (ca_chunked_run_t *run, int k)
202
+ {
203
+ CArray *ca = run->ca[k];
204
+ if ( ca_attach_is_alias(ca) ) {
205
+ ca_attach(ca); /* window: released by ca_chunked_run on the way out */
206
+ run->attached[k] = 1;
207
+ run->src[k] = (char *) ca->ptr;
208
+ }
209
+ else if ( run->step[k] == 0 ) {
210
+ run->scratch[k] = ca_op_acquire_operand_scratch(ca, 1);
211
+ ca_xfer_all(ca, run->scratch[k], CA_XFER_GET);
212
+ run->src[k] = (char *) run->scratch[k];
213
+ }
214
+ else {
215
+ run->scratch[k] = ca_op_acquire_operand_scratch(ca, run->chunk_n);
216
+ run->src[k] = (char *) run->scratch[k];
217
+ run->per_chunk[k] = 1;
218
+ }
219
+ }
220
+
221
+ static VALUE
222
+ ca_chunked_run_body (VALUE arg)
223
+ {
224
+ ca_chunked_run_t *run = (ca_chunked_run_t *) arg;
225
+ char *p[CA_CHUNKED_MAX_OPERANDS];
226
+ ca_size_t off;
227
+ int k;
228
+
229
+ for ( k = 0; k < run->n_op; k++ ) {
230
+ ca_chunked_run_acquire(run, k);
231
+ }
232
+ for ( off = 0; off < run->n_total; off += run->chunk_n ) {
233
+ ca_size_t n = ( off + run->chunk_n > run->n_total )
234
+ ? run->n_total - off : run->chunk_n;
235
+ for ( k = 0; k < run->n_op; k++ ) {
236
+ if ( run->per_chunk[k] ) {
237
+ ca_chunked_gather(run->ca[k], off, n, run->scratch[k]);
238
+ p[k] = run->src[k];
239
+ }
240
+ else {
241
+ p[k] = run->src[k] + ( run->step[k] ? off * run->ca[k]->bytes : 0 );
242
+ }
243
+ }
244
+ run->kernel(run, off, n, p);
245
+ }
246
+ return Qnil;
247
+ }
248
+
249
+ static VALUE
250
+ ca_chunked_run_release (VALUE arg)
251
+ {
252
+ ca_chunked_run_t *run = (ca_chunked_run_t *) arg;
253
+ int k;
254
+ for ( k = run->n_op - 1; k >= 0; k-- ) {
255
+ if ( run->scratch[k] ) {
256
+ ca_lazy_arena_release(run->scratch[k]);
257
+ run->scratch[k] = NULL;
258
+ }
259
+ }
260
+ for ( k = run->n_op - 1; k >= 0; k-- ) {
261
+ if ( run->attached[k] ) {
262
+ ca_detach(run->ca[k]);
263
+ run->attached[k] = 0;
264
+ }
265
+ }
266
+ return Qnil;
267
+ }
268
+
269
+ static void
270
+ ca_chunked_run (ca_chunked_run_t *run)
271
+ {
272
+ ca_lazy_arena_protect(ca_chunked_run_body, ca_chunked_run_release,
273
+ (VALUE) run);
274
+ }
275
+
276
+ /* ---- Kernels that can raise -----------------------------------------
277
+
278
+ An operator kernel runs with its alias operands attached. Most kernels
279
+ cannot raise and are called directly. Three kinds can:
280
+
281
+ - an object lane, which calls Ruby for every cell
282
+ - an integer division by zero (div, mod, fmod, rcp, rcp_mul)
283
+ - a data type the operator has no kernel for (the not_implement slot)
284
+
285
+ Those run through ca_op_guarded, which releases the operands the call
286
+ holds if the kernel raises. An operand the kernel writes in place (the
287
+ receiver of a bang form) is synced first, so the cells written reach
288
+ its parent as they would for an entity. */
289
+
290
+ static int
291
+ ca_op_is_integer (int8_t dt)
292
+ {
293
+ return ( dt >= CA_INT8 && dt <= CA_UINT64 );
294
+ }
295
+
296
+ static int
297
+ ca_monop_may_raise (ca_monop_func_t func[], int8_t dt)
298
+ {
299
+ return dt == CA_OBJECT
300
+ || func[dt] == ca_monop_not_implement
301
+ || ( func == ca_monop_rcp && ca_op_is_integer(dt) );
302
+ }
303
+
304
+ /* Which ops raise on a cell is ca_binop_is_trapping's list, the one the
305
+ lazy views skip masked cells by. */
306
+ static int
307
+ ca_binop_may_raise (ca_binop_func_t func[], int8_t dt)
308
+ {
309
+ int op_id;
310
+ if ( dt == CA_OBJECT || func[dt] == ca_binop_not_implement ) return 1;
311
+ op_id = ca_binop_op_id_of_table(func);
312
+ return op_id >= 0 && ca_binop_is_trapping((uint16_t) op_id, dt);
313
+ }
314
+
315
+ static int
316
+ ca_triop_may_raise (ca_triop_func_t func[], int8_t dt)
317
+ {
318
+ return dt == CA_OBJECT || func[dt] == ca_triop_not_implement;
319
+ }
320
+
321
+ static int
322
+ ca_moncmp_may_raise (ca_moncmp_func_t func[], int8_t dt)
323
+ {
324
+ return dt == CA_OBJECT || func[dt] == ca_moncmp_not_implement;
325
+ }
326
+
327
+ static int
328
+ ca_bincmp_may_raise (ca_bincmp_func_t func[], int8_t dt)
329
+ {
330
+ return dt == CA_OBJECT || func[dt] == ca_bincmp_not_implement;
331
+ }
332
+
333
+ /* One kernel call, packed so that it can run under rb_ensure. */
334
+
335
+ typedef struct {
336
+ ca_monop_func_t f;
337
+ ca_size_t n; boolean8_t *m;
338
+ char *p1; ca_size_t i1;
339
+ char *p2; ca_size_t i2;
340
+ } ca_monop_call_t;
341
+
342
+ static VALUE
343
+ ca_monop_call (VALUE arg)
344
+ {
345
+ ca_monop_call_t *k = (ca_monop_call_t *) arg;
346
+ k->f(k->n, k->m, k->p1, k->i1, k->p2, k->i2);
347
+ return Qnil;
348
+ }
349
+
350
+ typedef struct {
351
+ ca_binop_func_t f;
352
+ ca_size_t n; boolean8_t *m;
353
+ char *p1; ca_size_t i1;
354
+ char *p2; ca_size_t i2;
355
+ char *p3; ca_size_t i3;
356
+ } ca_binop_call_t;
357
+
358
+ static VALUE
359
+ ca_binop_call (VALUE arg)
360
+ {
361
+ ca_binop_call_t *k = (ca_binop_call_t *) arg;
362
+ k->f(k->n, k->m, k->p1, k->i1, k->p2, k->i2, k->p3, k->i3);
363
+ return Qnil;
364
+ }
365
+
366
+ typedef struct {
367
+ ca_triop_func_t f;
368
+ ca_size_t n; boolean8_t *m;
369
+ char *p1; ca_size_t i1;
370
+ char *p2; ca_size_t i2;
371
+ char *p3; ca_size_t i3;
372
+ char *p4; ca_size_t i4;
373
+ } ca_triop_call_t;
374
+
375
+ static VALUE
376
+ ca_triop_call (VALUE arg)
377
+ {
378
+ ca_triop_call_t *k = (ca_triop_call_t *) arg;
379
+ k->f(k->n, k->m, k->p1, k->i1, k->p2, k->i2, k->p3, k->i3, k->p4, k->i4);
380
+ return Qnil;
381
+ }
382
+
383
+ typedef struct {
384
+ ca_moncmp_func_t f;
385
+ ca_size_t n; boolean8_t *m;
386
+ char *p1; ca_size_t i1;
387
+ boolean8_t *p2; ca_size_t i2;
388
+ } ca_moncmp_call_t;
389
+
390
+ static VALUE
391
+ ca_moncmp_call (VALUE arg)
392
+ {
393
+ ca_moncmp_call_t *k = (ca_moncmp_call_t *) arg;
394
+ k->f(k->n, k->m, k->p1, k->i1, k->p2, k->i2);
395
+ return Qnil;
396
+ }
397
+
398
+ typedef struct {
399
+ ca_bincmp_func_t f;
400
+ ca_size_t n; boolean8_t *m;
401
+ char *p1; ca_size_t b1; ca_size_t i1;
402
+ char *p2; ca_size_t b2; ca_size_t i2;
403
+ char *p3; ca_size_t b3; ca_size_t i3;
404
+ double tol;
405
+ } ca_bincmp_call_t;
406
+
407
+ static VALUE
408
+ ca_bincmp_call (VALUE arg)
409
+ {
410
+ ca_bincmp_call_t *k = (ca_bincmp_call_t *) arg;
411
+ k->f(k->n, k->m, k->p1, k->b1, k->i1, k->p2, k->b2, k->i2,
412
+ k->p3, k->b3, k->i3, k->tol);
413
+ return Qnil;
414
+ }
415
+
416
+ typedef struct {
417
+ VALUE (*run)(VALUE);
418
+ VALUE arg;
419
+ int done;
420
+ int n;
421
+ CArray **held;
422
+ const char *mode; /* per held array: 'r' read, 'w' written in place */
423
+ } ca_op_guard_t;
424
+
425
+ static VALUE
426
+ ca_op_guard_body (VALUE varg)
427
+ {
428
+ ca_op_guard_t *g = (ca_op_guard_t *) varg;
429
+ g->run(g->arg);
430
+ g->done = 1;
431
+ return Qnil;
432
+ }
433
+
434
+ static VALUE
435
+ ca_op_guard_sync (VALUE varg)
436
+ {
437
+ ca_sync((void *) varg);
438
+ return Qnil;
439
+ }
440
+
441
+ /* Runs only after a raise: the caller closes the operands itself when the
442
+ kernel returns. rb_ensure keeps the kernel's exception across this, so
443
+ a sync failing in here is dropped rather than let replace it. */
444
+ static VALUE
445
+ ca_op_guard_release (VALUE varg)
446
+ {
447
+ ca_op_guard_t *g = (ca_op_guard_t *) varg;
448
+ int k;
449
+ if ( g->done ) {
450
+ return Qnil;
451
+ }
452
+ for ( k = g->n - 1; k >= 0; k-- ) {
453
+ CArray *ca = g->held[k];
454
+ if ( ! ca || ! ca->ptr ) {
455
+ continue;
456
+ }
457
+ if ( g->mode[k] == 'w' ) {
458
+ int tag = 0;
459
+ rb_protect(ca_op_guard_sync, (VALUE) ca, &tag);
460
+ rb_set_errinfo(Qnil);
461
+ }
462
+ ca_detach(ca);
463
+ }
464
+ return Qnil;
465
+ }
466
+
467
+ /* Runs the kernel packed in `call` with `held` attached; see above. NULL
468
+ entries in held are skipped. */
469
+ static void
470
+ ca_op_guarded (VALUE (*run)(VALUE), void *call,
471
+ int n, CArray **held, const char *mode)
472
+ {
473
+ ca_op_guard_t g;
474
+ g.run = run;
475
+ g.arg = (VALUE) call;
476
+ g.done = 0;
477
+ g.n = n;
478
+ g.held = held;
479
+ g.mode = mode;
480
+ rb_ensure(ca_op_guard_body, (VALUE) &g, ca_op_guard_release, (VALUE) &g);
481
+ }
482
+
483
+ /* ORs one operand's mask into ma through an arena scratch. The scratch
484
+ is acquired inside the bracket: the transfer below opens a bracket of
485
+ its own, and an entry from depth 0 treats every slot as free. */
486
+ typedef struct {
487
+ void *scratch; /* first: released by ca_lazy_arena_release_held */
488
+ CArray *mask;
489
+ boolean8_t *ma;
490
+ ca_size_t elements;
491
+ } ca_mask_overlay_or_t;
492
+
493
+ static VALUE
494
+ ca_mask_overlay_or_body (VALUE arg)
495
+ {
496
+ ca_mask_overlay_or_t *o = (ca_mask_overlay_or_t *) arg;
497
+ boolean8_t *ms;
498
+ ca_size_t j;
499
+ o->scratch = ca_lazy_arena_acquire(o->elements);
500
+ ms = (boolean8_t *) o->scratch;
501
+ ca_xfer_all(o->mask, o->scratch, CA_XFER_GET);
502
+ for ( j = 0; j < o->elements; j++ ) o->ma[j] |= ms[j];
503
+ return Qnil;
504
+ }
505
+
136
506
  /* Operand mask overlay without calling ca_attach on the operand masks.
137
507
 
138
508
  `ca_copy_mask_overlay` (carray_mask.c) attaches each operand mask via
@@ -180,7 +550,6 @@ ca_mask_overlay_safe (CArray *ca_out, int n, ...)
180
550
 
181
551
  boolean8_t *ma = (boolean8_t *) ca_out->mask->ptr;
182
552
  ca_size_t elements = ca_out->elements;
183
- ca_size_t j;
184
553
 
185
554
  /* Fresh mask → zero-init. Existing mask (= bang variant where
186
555
  ca_out IS one of the operands) → preserve as initial accumulator
@@ -200,16 +569,59 @@ ca_mask_overlay_safe (CArray *ca_out, int n, ...)
200
569
  ca_xfer_all(cs->mask, &bit, CA_XFER_GET);
201
570
  if ( bit ) memset(ma, 1, elements);
202
571
  } else {
203
- void *scratch = ca_lazy_arena_acquire(elements);
204
- boolean8_t *ms = (boolean8_t *) scratch;
205
- ca_xfer_all(cs->mask, scratch, CA_XFER_GET);
206
- for ( j = 0; j < elements; j++ ) ma[j] |= ms[j];
207
- ca_lazy_arena_release(scratch);
572
+ ca_mask_overlay_or_t o;
573
+ o.scratch = NULL;
574
+ o.mask = cs->mask;
575
+ o.ma = ma;
576
+ o.elements = elements;
577
+ ca_lazy_arena_protect(ca_mask_overlay_or_body,
578
+ ca_lazy_arena_release_held, (VALUE) &o);
208
579
  }
209
580
  }
210
581
  }
211
582
  }
212
583
 
584
+ /* ca_mask_overlay_safe into a bang operation's self, which is attached:
585
+ reading an operand's mask can call Ruby (a CAObject's mask hooks) and
586
+ raise, and self is then detached before the raise goes on. Nothing has
587
+ been written to self's data yet, so there is nothing to sync. */
588
+ typedef struct {
589
+ CArray *out;
590
+ CArray *src[3];
591
+ int n;
592
+ } ca_overlay_args_t;
593
+
594
+ static VALUE
595
+ ca_overlay_attached_body (VALUE arg)
596
+ {
597
+ ca_overlay_args_t *a = (ca_overlay_args_t *) arg;
598
+ if ( a->n == 2 ) {
599
+ ca_mask_overlay_safe(a->out, 2, a->src[0], a->src[1]);
600
+ }
601
+ else {
602
+ ca_mask_overlay_safe(a->out, 3, a->src[0], a->src[1], a->src[2]);
603
+ }
604
+ return Qnil;
605
+ }
606
+
607
+ static void
608
+ ca_mask_overlay_attached (CArray *out, int n,
609
+ CArray *s0, CArray *s1, CArray *s2)
610
+ {
611
+ ca_overlay_args_t a;
612
+ int tag = 0;
613
+ a.out = out;
614
+ a.n = n;
615
+ a.src[0] = s0;
616
+ a.src[1] = s1;
617
+ a.src[2] = s2;
618
+ rb_protect(ca_overlay_attached_body, (VALUE) &a, &tag);
619
+ if ( tag ) {
620
+ ca_detach(out);
621
+ rb_jump_tag(tag);
622
+ }
623
+ }
624
+
213
625
  /* Monop driver. ca1 is input-only (the driver does not attach it); ca2
214
626
  is the output (a new entity, attach legit). fast = ca_attach_is_alias(ca1)
215
627
  → 1-shot; slow = ALLOCV + ca_xfer_all without ca_func[X].attach. */
@@ -229,6 +641,9 @@ rb_ca_call_monop (VALUE self, ca_monop_func_t func[])
229
641
  self = rb_ca_wrap_readonly(self, INT2NUM(CA_INT64));
230
642
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca1);
231
643
  }
644
+ if ( func[ca1->data_type] == ca_monop_not_implement ) {
645
+ ca_op_refuse_unimplemented(self, ca1, rb_eCADataTypeError);
646
+ }
232
647
 
233
648
  ca2 = ca_has_mask(ca1) ? ca_template_safe(ca1) : ca_template(ca1);
234
649
  out = ca_wrap_struct(ca2);
@@ -236,11 +651,19 @@ rb_ca_call_monop (VALUE self, ca_monop_func_t func[])
236
651
  ca_mask_overlay_safe(ca2, 1, ca1);
237
652
 
238
653
  if ( ca_attach_is_alias(ca1) ) {
239
- ca_attach(ca1);
240
- func[ca1->data_type](ca1->elements,
241
- ( ca2->mask ) ? (boolean8_t *)ca2->mask->ptr : NULL,
242
- ca1->ptr, 1,
243
- ca2->ptr, 1);
654
+ ca_monop_call_t k;
655
+ k.f = func[ca1->data_type];
656
+ k.n = ca1->elements;
657
+ k.m = ( ca2->mask ) ? (boolean8_t *)ca2->mask->ptr : NULL;
658
+ k.p2 = ca2->ptr; k.i2 = 1;
659
+ ca_attach(ca1); /* window: the kernel runs through ca_op_guarded when it can raise */
660
+ k.p1 = ca1->ptr; k.i1 = 1;
661
+ if ( ca_monop_may_raise(func, ca1->data_type) ) {
662
+ ca_op_guarded(ca_monop_call, &k, 1, &ca1, "r");
663
+ }
664
+ else {
665
+ ca_monop_call((VALUE) &k);
666
+ }
244
667
  ca_detach(ca1);
245
668
  }
246
669
  else {
@@ -351,14 +774,26 @@ rb_ca_call_monop_bang (VALUE self, ca_monop_func_t func[])
351
774
  rb_ca_modify(self);
352
775
 
353
776
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca1);
777
+ if ( func[ca1->data_type] == ca_monop_not_implement ) {
778
+ ca_op_refuse_unimplemented(self, ca1, rb_eCADataTypeError);
779
+ }
354
780
 
355
- ca_attach(ca1);
356
- func[ca1->data_type](ca1->elements,
357
- ( ca1->mask ) ? (boolean8_t *)ca1->mask->ptr : NULL,
358
- ca1->ptr, 1,
359
- ca1->ptr, 1);
360
- ca_sync(ca1);
361
- ca_detach(ca1);
781
+ ca_attach(ca1); /* window: the kernel runs through ca_op_guarded when it can raise */
782
+ {
783
+ ca_monop_call_t k;
784
+ k.f = func[ca1->data_type];
785
+ k.n = ca1->elements;
786
+ k.m = ( ca1->mask ) ? (boolean8_t *)ca1->mask->ptr : NULL;
787
+ k.p1 = ca1->ptr; k.i1 = 1;
788
+ k.p2 = ca1->ptr; k.i2 = 1;
789
+ if ( ca_monop_may_raise(func, ca1->data_type) ) {
790
+ ca_op_guarded(ca_monop_call, &k, 1, &ca1, "w");
791
+ }
792
+ else {
793
+ ca_monop_call((VALUE) &k);
794
+ }
795
+ }
796
+ ca_sync_detach(ca1);
362
797
 
363
798
  return self;
364
799
  }
@@ -380,9 +815,7 @@ rb_ca_call_monop_typed (VALUE self, ca_monop_func_t func[],
380
815
 
381
816
  out_dt = out_data_types[ca1->data_type];
382
817
  if ( out_dt < 0 ) {
383
- rb_raise(rb_eCADataTypeError,
384
- "data_type-changing monop not implemented for input data_type %d",
385
- ca1->data_type);
818
+ ca_op_refuse_unimplemented(self, ca1, rb_eCADataTypeError);
386
819
  }
387
820
 
388
821
  /* Allocate output with the per-op output data_type. Same shape as input. */
@@ -393,11 +826,19 @@ rb_ca_call_monop_typed (VALUE self, ca_monop_func_t func[],
393
826
  ca_mask_overlay_safe(ca2, 1, ca1);
394
827
 
395
828
  if ( ca_attach_is_alias(ca1) ) {
396
- ca_attach(ca1);
397
- func[ca1->data_type](ca1->elements,
398
- ( ca2->mask ) ? (boolean8_t *)ca2->mask->ptr : NULL,
399
- ca1->ptr, 1,
400
- ca2->ptr, 1);
829
+ ca_monop_call_t k;
830
+ k.f = func[ca1->data_type];
831
+ k.n = ca1->elements;
832
+ k.m = ( ca2->mask ) ? (boolean8_t *)ca2->mask->ptr : NULL;
833
+ k.p2 = ca2->ptr; k.i2 = 1;
834
+ ca_attach(ca1); /* window: the kernel runs through ca_op_guarded when it can raise */
835
+ k.p1 = ca1->ptr; k.i1 = 1;
836
+ if ( ca_monop_may_raise(func, ca1->data_type) ) {
837
+ ca_op_guarded(ca_monop_call, &k, 1, &ca1, "r");
838
+ }
839
+ else {
840
+ ca_monop_call((VALUE) &k);
841
+ }
401
842
  ca_detach(ca1);
402
843
  }
403
844
  else {
@@ -562,7 +1003,7 @@ VALUE
562
1003
  ca_kleene_bool_fixup (VALUE vout, VALUE vself, VALUE vother, int is_or)
563
1004
  {
564
1005
  CArray *out, *a, *b;
565
- volatile VALUE va, vb;
1006
+ volatile VALUE va, vb, holder;
566
1007
  boolean8_t *ov, *om, *av, *am, *bv, *bm;
567
1008
  ca_size_t n, i;
568
1009
 
@@ -582,9 +1023,12 @@ ca_kleene_bool_fixup (VALUE vout, VALUE vself, VALUE vother, int is_or)
582
1023
  TypedData_Get_Struct(va, CArray, &carray_data_type, a);
583
1024
  TypedData_Get_Struct(vb, CArray, &carray_data_type, b);
584
1025
 
1026
+ /* One temporary of Ruby's for all four: reading an operand can raise. */
585
1027
  n = out->elements;
586
- av = ALLOC_N(boolean8_t, n); am = ALLOC_N(boolean8_t, n);
587
- bv = ALLOC_N(boolean8_t, n); bm = ALLOC_N(boolean8_t, n);
1028
+ av = ALLOCV_N(boolean8_t, holder, 4 * (n > 0 ? n : 1));
1029
+ am = av + n;
1030
+ bv = am + n;
1031
+ bm = bv + n;
588
1032
  kleene_gather_bool(a, av, am, n);
589
1033
  kleene_gather_bool(b, bv, bm, n);
590
1034
 
@@ -608,7 +1052,7 @@ ca_kleene_bool_fixup (VALUE vout, VALUE vself, VALUE vother, int is_or)
608
1052
  }
609
1053
  }
610
1054
 
611
- xfree(av); xfree(am); xfree(bv); xfree(bm);
1055
+ ALLOCV_END(holder);
612
1056
  return vout;
613
1057
  }
614
1058
 
@@ -622,6 +1066,71 @@ ca_kleene_bool_fixup (VALUE vout, VALUE vself, VALUE vother, int is_or)
622
1066
  CATile expansion, attach-hostile roots, and unattachable test
623
1067
  fixtures working.
624
1068
  Output `ca3` is always a new entity (write target, attach legitimate). */
1069
+ typedef struct {
1070
+ ca_binop_func_t *func;
1071
+ int8_t dt;
1072
+ CArray *out;
1073
+ ca_size_t step_out;
1074
+ } ca_binop_chunk_t;
1075
+
1076
+ static void
1077
+ ca_binop_chunk_kernel (ca_chunked_run_t *run, ca_size_t off, ca_size_t n,
1078
+ char **p)
1079
+ {
1080
+ ca_binop_chunk_t *d = (ca_binop_chunk_t *) run->data;
1081
+ ca_size_t o = d->step_out ? off : 0;
1082
+ d->func[d->dt](n,
1083
+ d->out->mask ? ((boolean8_t *) d->out->mask->ptr) + o : NULL,
1084
+ p[0], run->step[0],
1085
+ p[1], run->step[1],
1086
+ (char *) d->out->ptr + o * d->out->bytes, d->step_out);
1087
+ }
1088
+
1089
+ /* A boolean array as an object array of true / false. The ordinary cast
1090
+ gives 1 / 0, which a logical operator on an object array reads as two
1091
+ true values (0 is true in Ruby). Masked cells stay masked. */
1092
+ static VALUE
1093
+ ca_boolean_as_truth_objects (VALUE vb)
1094
+ {
1095
+ CArray *cb;
1096
+ ca_size_t i;
1097
+ VALUE out;
1098
+ TypedData_Get_Struct(vb, CArray, &carray_data_type, cb);
1099
+ out = rb_carray_new(CA_OBJECT, cb->ndim, cb->dim, 0, NULL);
1100
+ for ( i = 0; i < cb->elements; i++ ) {
1101
+ rb_ca_store_addr(out, i, rb_ca_fetch_addr(vb, i));
1102
+ }
1103
+ return out;
1104
+ }
1105
+
1106
+ static int
1107
+ ca_value_is_type (VALUE v, int8_t data_type)
1108
+ {
1109
+ CArray *ca;
1110
+ if ( ! rb_obj_is_carray(v) ) return 0;
1111
+ TypedData_Get_Struct(v, CArray, &carray_data_type, ca);
1112
+ return ca->data_type == data_type;
1113
+ }
1114
+
1115
+ /* A logical operator (`logical`: one with a boolean kernel) between a
1116
+ boolean array and an object array reads the boolean cells as true /
1117
+ false. The eager binop and the lazy builder both call this before
1118
+ anything else decides the operands' types, so both read them alike. */
1119
+ void
1120
+ ca_binop_read_boolean_as_truth (int logical, volatile VALUE *pa,
1121
+ volatile VALUE *pb)
1122
+ {
1123
+ if ( ! logical ) {
1124
+ return;
1125
+ }
1126
+ if ( ca_value_is_type(*pa, CA_BOOLEAN) && ca_value_is_type(*pb, CA_OBJECT) ) {
1127
+ *pa = ca_boolean_as_truth_objects(*pa);
1128
+ }
1129
+ else if ( ca_value_is_type(*pb, CA_BOOLEAN) && ca_value_is_type(*pa, CA_OBJECT) ) {
1130
+ *pb = ca_boolean_as_truth_objects(*pb);
1131
+ }
1132
+ }
1133
+
625
1134
  VALUE
626
1135
  rb_ca_call_binop (volatile VALUE self, volatile VALUE other,
627
1136
  ca_binop_func_t func[])
@@ -633,7 +1142,10 @@ rb_ca_call_binop (volatile VALUE self, volatile VALUE other,
633
1142
  ca_size_t i1, i2, i3;
634
1143
  int fast_path;
635
1144
 
636
- /* do implicit casting and resolving unbound repeat array */
1145
+ ca_binop_read_boolean_as_truth(func[CA_BOOLEAN] != ca_binop_not_implement,
1146
+ &self, &other);
1147
+
1148
+ /* do implicit casting */
637
1149
  rb_ca_cast_self_or_other(&self, &other);
638
1150
 
639
1151
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca1);
@@ -652,6 +1164,10 @@ rb_ca_call_binop (volatile VALUE self, volatile VALUE other,
652
1164
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca1);
653
1165
  TypedData_Get_Struct(other, CArray, &carray_data_type, ca2);
654
1166
  }
1167
+ if ( func[ca1->data_type] == ca_binop_not_implement ) {
1168
+ ca_op_refuse_unimplemented(ca_is_face(ca1) ? self : other, ca1,
1169
+ rb_eCADataTypeError);
1170
+ }
655
1171
 
656
1172
  self_is_scalar = RTEST(rb_obj_is_cscalar(self));
657
1173
  other_is_scalar = RTEST(rb_obj_is_cscalar(other));
@@ -698,13 +1214,23 @@ rb_ca_call_binop (volatile VALUE self, volatile VALUE other,
698
1214
  fast_path = ca_attach_is_alias(ca1) && ca_attach_is_alias(ca2);
699
1215
 
700
1216
  if ( fast_path ) {
701
- /* FAST PATH: zero behavioral change for the hot case. */
702
- ca_attach_n(2, ca1, ca2);
703
- func[ca1->data_type](n_kernel,
704
- ( ca3->mask ) ? (boolean8_t *) ca3->mask->ptr : NULL,
705
- ca1->ptr, i1,
706
- ca2->ptr, i2,
707
- ca3->ptr, i3);
1217
+ /* FAST PATH: both operands alias. */
1218
+ ca_binop_call_t k;
1219
+ CArray *held[2];
1220
+ k.f = func[ca1->data_type];
1221
+ k.n = n_kernel;
1222
+ k.m = ( ca3->mask ) ? (boolean8_t *) ca3->mask->ptr : NULL;
1223
+ k.p3 = ca3->ptr; k.i3 = i3;
1224
+ ca_attach_n(2, ca1, ca2); /* window: the kernel runs through ca_op_guarded when it can raise */
1225
+ k.p1 = ca1->ptr; k.i1 = i1;
1226
+ k.p2 = ca2->ptr; k.i2 = i2;
1227
+ if ( ca_binop_may_raise(func, ca1->data_type) ) {
1228
+ held[0] = ca1; held[1] = ca2;
1229
+ ca_op_guarded(ca_binop_call, &k, 2, held, "rr");
1230
+ }
1231
+ else {
1232
+ ca_binop_call((VALUE) &k);
1233
+ }
708
1234
  ca_detach_n(2, ca1, ca2);
709
1235
  }
710
1236
  else if ( ca1 == ca2 ) {
@@ -744,13 +1270,8 @@ rb_ca_call_binop (volatile VALUE self, volatile VALUE other,
744
1270
  - both array & both non-alias (= mt + mt2) → CHUNKED (peak 2x win)
745
1271
  - one non-alias array + one alias/scalar (= mt + 3.14, mt + entity)
746
1272
  → 1-shot ALLOCV */
747
- char *p1_src = NULL, *p2_src = NULL;
748
- void *s1_arena = NULL, *s2_arena = NULL;
749
- int gather_per_chunk1 = 0, gather_per_chunk2 = 0;
750
- int attached1 = 0, attached2 = 0;
751
1273
  int8_t dt = ca1->data_type;
752
1274
  ca_size_t chunk_n;
753
- ca_size_t off;
754
1275
  int nonalias_arrays;
755
1276
  int use_chunked;
756
1277
 
@@ -895,24 +1416,40 @@ rb_ca_call_binop (volatile VALUE self, volatile VALUE other,
895
1416
  int a1 = 0, a2 = 0;
896
1417
  (void) h1; (void) h2;
897
1418
 
898
- if ( ca_attach_is_alias(ca1) ) {
899
- ca_attach(ca1); p1 = (char *) ca1->ptr; a1 = 1;
900
- } else {
1419
+ /* Gathers first: a gather can raise, and nothing is attached yet. */
1420
+ if ( ! ca_attach_is_alias(ca1) ) {
901
1421
  p1 = ALLOCV_N(char, h1, ca1->elements * ca1->bytes);
902
1422
  ca_xfer_all(ca1, p1, CA_XFER_GET);
903
1423
  }
904
- if ( ca_attach_is_alias(ca2) ) {
905
- ca_attach(ca2); p2 = (char *) ca2->ptr; a2 = 1;
906
- } else {
1424
+ if ( ! ca_attach_is_alias(ca2) ) {
907
1425
  p2 = ALLOCV_N(char, h2, ca2->elements * ca2->bytes);
908
1426
  ca_xfer_all(ca2, p2, CA_XFER_GET);
909
1427
  }
1428
+ if ( ca_attach_is_alias(ca1) ) {
1429
+ ca_attach(ca1); p1 = (char *) ca1->ptr; a1 = 1; /* window: an alias attach, cannot raise */
1430
+ }
1431
+ if ( ca_attach_is_alias(ca2) ) {
1432
+ ca_attach(ca2); p2 = (char *) ca2->ptr; a2 = 1; /* window: as above */
1433
+ }
910
1434
 
911
- func[dt](n_kernel,
912
- ( ca3->mask ) ? (boolean8_t *) ca3->mask->ptr : NULL,
913
- p1, i1,
914
- p2, i2,
915
- ca3->ptr, i3);
1435
+ {
1436
+ ca_binop_call_t k;
1437
+ CArray *held[2];
1438
+ k.f = func[dt];
1439
+ k.n = n_kernel;
1440
+ k.m = ( ca3->mask ) ? (boolean8_t *) ca3->mask->ptr : NULL;
1441
+ k.p1 = p1; k.i1 = i1;
1442
+ k.p2 = p2; k.i2 = i2;
1443
+ k.p3 = ca3->ptr; k.i3 = i3;
1444
+ if ( ca_binop_may_raise(func, dt) ) {
1445
+ held[0] = a1 ? ca1 : NULL;
1446
+ held[1] = a2 ? ca2 : NULL;
1447
+ ca_op_guarded(ca_binop_call, &k, 2, held, "rr");
1448
+ }
1449
+ else {
1450
+ ca_binop_call((VALUE) &k);
1451
+ }
1452
+ }
916
1453
 
917
1454
  if ( a2 ) { ca_detach(ca2); } else { ALLOCV_END(h2); }
918
1455
  if ( a1 ) { ca_detach(ca1); } else { ALLOCV_END(h1); }
@@ -934,88 +1471,34 @@ rb_ca_call_binop (volatile VALUE self, volatile VALUE other,
934
1471
  chunk_n = ca_chunk_compute_n(n_kernel, inner, maxb);
935
1472
  }
936
1473
 
937
- ca_lazy_arena_enter();
938
-
939
- /* ca1 acquire */
940
- if ( i1 == 0 ) {
941
- /* scalar: gather 1 element once, kernel re-reads with stride 0 */
942
- if ( ca_attach_is_alias(ca1) ) {
943
- ca_attach(ca1);
944
- p1_src = (char *) ca1->ptr;
945
- attached1 = 1;
946
- } else {
947
- s1_arena = ca_op_acquire_operand_scratch(ca1, 1);
948
- ca_xfer_all(ca1, s1_arena, CA_XFER_GET);
949
- p1_src = (char *) s1_arena;
950
- }
951
- } else if ( ca_attach_is_alias(ca1) ) {
952
- ca_attach(ca1);
953
- p1_src = (char *) ca1->ptr;
954
- attached1 = 1;
955
- } else {
956
- s1_arena = ca_op_acquire_operand_scratch(ca1, chunk_n);
957
- p1_src = (char *) s1_arena;
958
- gather_per_chunk1 = 1;
959
- }
960
-
961
- /* ca2 acquire (mirror) */
962
- if ( i2 == 0 ) {
963
- if ( ca_attach_is_alias(ca2) ) {
964
- ca_attach(ca2);
965
- p2_src = (char *) ca2->ptr;
966
- attached2 = 1;
967
- } else {
968
- s2_arena = ca_op_acquire_operand_scratch(ca2, 1);
969
- ca_xfer_all(ca2, s2_arena, CA_XFER_GET);
970
- p2_src = (char *) s2_arena;
971
- }
972
- } else if ( ca_attach_is_alias(ca2) ) {
973
- ca_attach(ca2);
974
- p2_src = (char *) ca2->ptr;
975
- attached2 = 1;
976
- } else {
977
- s2_arena = ca_op_acquire_operand_scratch(ca2, chunk_n);
978
- p2_src = (char *) s2_arena;
979
- gather_per_chunk2 = 1;
980
- }
981
-
982
- /* chunk loop: walk n_kernel cells in chunk_n strides */
983
- for ( off = 0; off < n_kernel; off += chunk_n ) {
984
- ca_size_t n_done = (off + chunk_n > n_kernel) ? n_kernel - off : chunk_n;
985
- char *p1, *p2;
986
-
987
- if ( gather_per_chunk1 ) {
988
- ca_chunked_gather(ca1, off, n_done, s1_arena);
989
- p1 = (char *) s1_arena;
990
- } else {
991
- p1 = p1_src + (i1 ? off * ca1->bytes : 0);
992
- }
993
- if ( gather_per_chunk2 ) {
994
- ca_chunked_gather(ca2, off, n_done, s2_arena);
995
- p2 = (char *) s2_arena;
996
- } else {
997
- p2 = p2_src + (i2 ? off * ca2->bytes : 0);
998
- }
999
-
1000
- func[dt](n_done,
1001
- ca3->mask ? ((boolean8_t *) ca3->mask->ptr) + (i3 ? off : 0)
1002
- : NULL,
1003
- p1, i1,
1004
- p2, i2,
1005
- (char *) ca3->ptr + (i3 ? off * ca3->bytes : 0), i3);
1474
+ {
1475
+ ca_binop_chunk_t d;
1476
+ ca_chunked_run_t run;
1477
+ d.func = func; d.dt = dt; d.out = ca3; d.step_out = i3;
1478
+ ca_chunked_run_init(&run, n_kernel, chunk_n);
1479
+ ca_chunked_run_add(&run, ca1, i1);
1480
+ ca_chunked_run_add(&run, ca2, i2);
1481
+ run.kernel = ca_binop_chunk_kernel;
1482
+ run.data = &d;
1483
+ ca_chunked_run(&run);
1006
1484
  }
1007
-
1008
- if ( s2_arena ) ca_lazy_arena_release(s2_arena);
1009
- if ( s1_arena ) ca_lazy_arena_release(s1_arena);
1010
- if ( attached2 ) ca_detach(ca2);
1011
- if ( attached1 ) ca_detach(ca1);
1012
-
1013
- ca_lazy_arena_exit();
1014
1485
  }
1015
1486
 
1016
1487
  return out;
1017
1488
  }
1018
1489
 
1490
+ /* An input of a bang operation that lives in the same root as the target
1491
+ may share memory with it, and read in place it would show the kernel
1492
+ cells the kernel has already written (`a[1..5].add!(a[0..4])` would
1493
+ become a running sum). Such an input is gathered whole before the
1494
+ kernel runs. The target itself (`a.add!(a)`) reads each cell before
1495
+ writing it, so it is read in place. */
1496
+ static int
1497
+ ca_bang_input_may_overlap (CArray *target, CArray *input)
1498
+ {
1499
+ return input != target && ca_shares_root(input, target);
1500
+ }
1501
+
1019
1502
  /* Bang (in-place) variant. Invariant: the input-only operand (= other)
1020
1503
  must not be attached; self IS the output (write target, attach
1021
1504
  legitimate). Same fast/slow pattern as rb_ca_call_binop, applied to
@@ -1030,11 +1513,14 @@ rb_ca_call_binop_bang (VALUE self, VALUE other, ca_binop_func_t func[])
1030
1513
 
1031
1514
  rb_ca_modify(self);
1032
1515
 
1033
- /* do implicit casting and resolving unbound repeat array */
1516
+ /* do implicit casting */
1034
1517
  rb_ca_cast_other(&self, &other);
1035
1518
 
1036
1519
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca1);
1037
1520
  TypedData_Get_Struct(other, CArray, &carray_data_type, ca2);
1521
+ if ( func[ca1->data_type] == ca_binop_not_implement ) {
1522
+ ca_op_refuse_unimplemented(self, ca1, rb_eCADataTypeError);
1523
+ }
1038
1524
 
1039
1525
  self_is_scalar = RTEST(rb_obj_is_cscalar(self));
1040
1526
  other_is_scalar = RTEST(rb_obj_is_cscalar(other));
@@ -1042,17 +1528,11 @@ rb_ca_call_binop_bang (VALUE self, VALUE other, ca_binop_func_t func[])
1042
1528
  /* self is the write target, so its shape is the result's by definition
1043
1529
  and the destination rule applies (the same one assignment uses), not
1044
1530
  the symmetric one a binary operation is held to. */
1045
- if ( !self_is_scalar && !other_is_scalar ) {
1531
+ if ( !other_is_scalar ) {
1046
1532
  ca_broadcast_to_destination(self, &other);
1047
1533
  TypedData_Get_Struct(other, CArray, &carray_data_type, ca2);
1048
1534
  other_is_scalar = RTEST(rb_obj_is_cscalar(other));
1049
1535
  }
1050
- if ( self_is_scalar && !other_is_scalar &&
1051
- ca1->elements != ca2->elements ) {
1052
- rb_raise(rb_eRuntimeError, "elements mismatch (%" PRId64 " <-> %" PRId64 ")",
1053
- (ca_size_t) ca1->elements,
1054
- (ca_size_t) ca2->elements);
1055
- }
1056
1536
 
1057
1537
  /* kernel strides: cscalar → 0 (broadcast). ca1 is both src1 (input)
1058
1538
  and dst (output), same stride. ca2 is input only. */
@@ -1062,36 +1542,52 @@ rb_ca_call_binop_bang (VALUE self, VALUE other, ca_binop_func_t func[])
1062
1542
  /* self IS the output (= write target). Always attach + ca_sync
1063
1543
  (= legitimate per refined invariant; self can be view e.g.
1064
1544
  `arr[i,nil].add!(b)`, sync writes back to root). */
1065
- ca_attach(ca1);
1066
- ca_mask_overlay_safe(ca1, 2, ca1, ca2);
1067
-
1068
- /* other is input only: fast path if alias-cheap, else materialise
1069
- via ca_xfer_all without ca_func[X].attach. */
1070
- if ( ca_attach_is_alias(ca2) ) {
1071
- ca_attach(ca2);
1072
- func[ca1->data_type](ca1->elements,
1073
- ( ca1->mask ) ? (boolean8_t *) ca1->mask->ptr : NULL,
1074
- ca1->ptr, i1,
1075
- ca2->ptr, i2,
1076
- ca1->ptr, i1);
1077
- ca_detach(ca2);
1078
- }
1079
- else {
1545
+ {
1080
1546
  volatile VALUE h2 = Qnil;
1081
- char *p2;
1547
+ char *p2 = NULL;
1548
+ int a2 = 0;
1549
+ int in_place2 = ca_attach_is_alias(ca2) &&
1550
+ ! ca_bang_input_may_overlap(ca1, ca2);
1551
+ ca_binop_call_t k;
1552
+ CArray *held[2];
1082
1553
  (void) h2;
1083
- p2 = ALLOCV_N(char, h2, ca2->elements * ca2->bytes);
1084
- ca_xfer_all(ca2, p2, CA_XFER_GET);
1085
- func[ca1->data_type](ca1->elements,
1086
- ( ca1->mask ) ? (boolean8_t *) ca1->mask->ptr : NULL,
1087
- ca1->ptr, i1,
1088
- p2, i2,
1089
- ca1->ptr, i1);
1090
- ALLOCV_END(h2);
1554
+
1555
+ /* other is input only: materialised via ca_xfer_all when it cannot be
1556
+ read in place -- before self is attached, since a gather can raise. */
1557
+ if ( ! in_place2 ) {
1558
+ p2 = ALLOCV_N(char, h2, ca2->elements * ca2->bytes);
1559
+ ca_xfer_all(ca2, p2, CA_XFER_GET);
1560
+ }
1561
+
1562
+ ca_attach(ca1); /* window: the overlay detaches it if it raises; the
1563
+ kernel runs through ca_op_guarded when it can */
1564
+ ca_mask_overlay_attached(ca1, 2, ca1, ca2, NULL);
1565
+
1566
+ if ( in_place2 ) {
1567
+ ca_attach(ca2); /* window: an alias attach, which cannot raise */
1568
+ p2 = ca2->ptr;
1569
+ a2 = 1;
1570
+ }
1571
+
1572
+ k.f = func[ca1->data_type];
1573
+ k.n = ca1->elements;
1574
+ k.m = ( ca1->mask ) ? (boolean8_t *) ca1->mask->ptr : NULL;
1575
+ k.p1 = ca1->ptr; k.i1 = i1;
1576
+ k.p2 = p2; k.i2 = i2;
1577
+ k.p3 = ca1->ptr; k.i3 = i1;
1578
+ if ( ca_binop_may_raise(func, ca1->data_type) ) {
1579
+ held[0] = ca1;
1580
+ held[1] = a2 ? ca2 : NULL;
1581
+ ca_op_guarded(ca_binop_call, &k, 2, held, "wr");
1582
+ }
1583
+ else {
1584
+ ca_binop_call((VALUE) &k);
1585
+ }
1586
+
1587
+ if ( a2 ) { ca_detach(ca2); } else { ALLOCV_END(h2); }
1091
1588
  }
1092
1589
 
1093
- ca_sync(ca1);
1094
- ca_detach(ca1);
1590
+ ca_sync_detach(ca1);
1095
1591
 
1096
1592
  return self;
1097
1593
  }
@@ -1131,18 +1627,24 @@ rb_ca_triop_select_template (VALUE self, VALUE other2, VALUE other3,
1131
1627
  - alias-cheap operand → ca_attach (= O(1)) + use ca->ptr directly
1132
1628
  - else → ALLOCV scratch + ca_xfer_all without ca_func[X].attach
1133
1629
 
1134
- Pair ACQUIRE / RELEASE; `h` must be a volatile VALUE declared by
1135
- caller (= ALLOCV_END requires it even on alias-cheap branch where
1136
- ALLOCV_N wasn't actually called, since holder stays Qnil = no-op). */
1137
- #define EAGER_ACQUIRE_INPUT(ca_, p_, h_, attached_) do { \
1630
+ GATHER every operand before ATTACH-ing any: a gather can raise, and an
1631
+ operand attached before it would be left attached. Pair with RELEASE;
1632
+ `h` must be a volatile VALUE declared by the caller (Qnil when nothing
1633
+ was gathered, which ALLOCV_END takes as a no-op). */
1634
+ #define EAGER_GATHER_INPUT(ca_, p_, h_) do { \
1635
+ if ( ! ca_attach_is_alias(ca_) ) { \
1636
+ (p_) = ALLOCV_N(char, (h_), (ca_)->elements * (ca_)->bytes); \
1637
+ ca_xfer_all((ca_), (p_), CA_XFER_GET); \
1638
+ } \
1639
+ } while (0)
1640
+
1641
+ #define EAGER_ATTACH_INPUT(ca_, p_, attached_) do { \
1138
1642
  if ( ca_attach_is_alias(ca_) ) { \
1139
- ca_attach(ca_); \
1643
+ ca_attach(ca_); /* window: an alias attach, cannot raise */ \
1140
1644
  (p_) = (char *)(ca_)->ptr; \
1141
1645
  (attached_) = 1; \
1142
1646
  } \
1143
1647
  else { \
1144
- (p_) = ALLOCV_N(char, (h_), (ca_)->elements * (ca_)->bytes); \
1145
- ca_xfer_all((ca_), (p_), CA_XFER_GET); \
1146
1648
  (attached_) = 0; \
1147
1649
  } \
1148
1650
  } while (0)
@@ -1156,6 +1658,39 @@ rb_ca_triop_select_template (VALUE self, VALUE other2, VALUE other3,
1156
1658
  them); cao = new entity output (attach is legit). Each input
1157
1659
  independently uses fast (= alias) or slow (= ALLOCV + ca_xfer_all)
1158
1660
  path. */
1661
+ /* The triop output is always a fresh array the size of the run; for the
1662
+ bang form it is self, read and written in place. */
1663
+ typedef struct {
1664
+ ca_triop_func_t *func;
1665
+ int8_t dt;
1666
+ CArray *out;
1667
+ int in_place;
1668
+ } ca_triop_chunk_t;
1669
+
1670
+ static void
1671
+ ca_triop_chunk_kernel (ca_chunked_run_t *run, ca_size_t off, ca_size_t n,
1672
+ char **p)
1673
+ {
1674
+ ca_triop_chunk_t *d = (ca_triop_chunk_t *) run->data;
1675
+ char *out = (char *) d->out->ptr + off * d->out->bytes;
1676
+ boolean8_t *m = d->out->mask ? ((boolean8_t *) d->out->mask->ptr) + off
1677
+ : NULL;
1678
+ if ( d->in_place ) {
1679
+ d->func[d->dt](n, m,
1680
+ out, 1,
1681
+ p[0], run->step[0],
1682
+ p[1], run->step[1],
1683
+ out, 1);
1684
+ }
1685
+ else {
1686
+ d->func[d->dt](n, m,
1687
+ p[0], run->step[0],
1688
+ p[1], run->step[1],
1689
+ p[2], run->step[2],
1690
+ out, 1);
1691
+ }
1692
+ }
1693
+
1159
1694
  VALUE
1160
1695
  rb_ca_call_triop (VALUE self, VALUE other2, VALUE other3,
1161
1696
  ca_triop_func_t func[])
@@ -1194,6 +1729,11 @@ rb_ca_call_triop (VALUE self, VALUE other2, VALUE other3,
1194
1729
  TypedData_Get_Struct(other2, CArray, &carray_data_type, ca2);
1195
1730
  TypedData_Get_Struct(other3, CArray, &carray_data_type, ca3);
1196
1731
  }
1732
+ if ( func[ca1->data_type] == ca_triop_not_implement ) {
1733
+ ca_op_refuse_unimplemented(ca_is_face(ca1) ? self
1734
+ : ca_is_face(ca2) ? other2 : other3,
1735
+ ca1, rb_eCADataTypeError);
1736
+ }
1197
1737
 
1198
1738
  /* Element-count check: all non-scalar operands must agree. */
1199
1739
  {
@@ -1242,16 +1782,33 @@ rb_ca_call_triop (VALUE self, VALUE other2, VALUE other3,
1242
1782
  int attached1, attached2, attached3;
1243
1783
  (void) h1; (void) h2; (void) h3;
1244
1784
 
1245
- EAGER_ACQUIRE_INPUT(ca1, p1, h1, attached1);
1246
- EAGER_ACQUIRE_INPUT(ca2, p2, h2, attached2);
1247
- EAGER_ACQUIRE_INPUT(ca3, p3, h3, attached3);
1785
+ EAGER_GATHER_INPUT(ca1, p1, h1);
1786
+ EAGER_GATHER_INPUT(ca2, p2, h2);
1787
+ EAGER_GATHER_INPUT(ca3, p3, h3);
1788
+ EAGER_ATTACH_INPUT(ca1, p1, attached1);
1789
+ EAGER_ATTACH_INPUT(ca2, p2, attached2);
1790
+ EAGER_ATTACH_INPUT(ca3, p3, attached3);
1248
1791
 
1249
- func[ca1->data_type](cao->elements,
1250
- ( cao->mask ) ? (boolean8_t *) cao->mask->ptr : NULL,
1251
- p1, s1,
1252
- p2, s2,
1253
- p3, s3,
1254
- cao->ptr, 1);
1792
+ {
1793
+ ca_triop_call_t k;
1794
+ CArray *held[3];
1795
+ k.f = func[ca1->data_type];
1796
+ k.n = cao->elements;
1797
+ k.m = ( cao->mask ) ? (boolean8_t *) cao->mask->ptr : NULL;
1798
+ k.p1 = p1; k.i1 = s1;
1799
+ k.p2 = p2; k.i2 = s2;
1800
+ k.p3 = p3; k.i3 = s3;
1801
+ k.p4 = cao->ptr; k.i4 = 1;
1802
+ if ( ca_triop_may_raise(func, ca1->data_type) ) {
1803
+ held[0] = attached1 ? ca1 : NULL;
1804
+ held[1] = attached2 ? ca2 : NULL;
1805
+ held[2] = attached3 ? ca3 : NULL;
1806
+ ca_op_guarded(ca_triop_call, &k, 3, held, "rrr");
1807
+ }
1808
+ else {
1809
+ ca_triop_call((VALUE) &k);
1810
+ }
1811
+ }
1255
1812
 
1256
1813
  EAGER_RELEASE_INPUT(ca3, h3, attached3);
1257
1814
  EAGER_RELEASE_INPUT(ca2, h2, attached2);
@@ -1261,13 +1818,8 @@ rb_ca_call_triop (VALUE self, VALUE other2, VALUE other3,
1261
1818
  /* CHUNKED PATH: per-operand decision matrix (the binop pattern
1262
1819
  extended to 3 inputs). scalar/alias same as before; non-alias
1263
1820
  array goes through per-chunk gather. */
1264
- char *p1_src = NULL, *p2_src = NULL, *p3_src = NULL;
1265
- void *s1_arena = NULL, *s2_arena = NULL, *s3_arena = NULL;
1266
- int gpc1 = 0, gpc2 = 0, gpc3 = 0; /* gather-per-chunk flags */
1267
- int att1 = 0, att2 = 0, att3 = 0;
1268
1821
  int8_t dt = ca1->data_type;
1269
1822
  ca_size_t chunk_n;
1270
- ca_size_t off;
1271
1823
  ca_size_t n_total = cao->elements;
1272
1824
 
1273
1825
  {
@@ -1288,86 +1840,18 @@ rb_ca_call_triop (VALUE self, VALUE other2, VALUE other3,
1288
1840
  chunk_n = ca_chunk_compute_n(n_total, inner, maxb);
1289
1841
  }
1290
1842
 
1291
- ca_lazy_arena_enter();
1292
-
1293
- /* ca1 acquire */
1294
- if ( s1 == 0 ) {
1295
- if ( ca_attach_is_alias(ca1) ) {
1296
- ca_attach(ca1); p1_src = (char *) ca1->ptr; att1 = 1;
1297
- } else {
1298
- s1_arena = ca_op_acquire_operand_scratch(ca1, 1);
1299
- ca_xfer_all(ca1, s1_arena, CA_XFER_GET);
1300
- p1_src = (char *) s1_arena;
1301
- }
1302
- } else if ( ca_attach_is_alias(ca1) ) {
1303
- ca_attach(ca1); p1_src = (char *) ca1->ptr; att1 = 1;
1304
- } else {
1305
- s1_arena = ca_op_acquire_operand_scratch(ca1, chunk_n);
1306
- p1_src = (char *) s1_arena; gpc1 = 1;
1307
- }
1308
- /* ca2 acquire (mirror) */
1309
- if ( s2 == 0 ) {
1310
- if ( ca_attach_is_alias(ca2) ) {
1311
- ca_attach(ca2); p2_src = (char *) ca2->ptr; att2 = 1;
1312
- } else {
1313
- s2_arena = ca_op_acquire_operand_scratch(ca2, 1);
1314
- ca_xfer_all(ca2, s2_arena, CA_XFER_GET);
1315
- p2_src = (char *) s2_arena;
1316
- }
1317
- } else if ( ca_attach_is_alias(ca2) ) {
1318
- ca_attach(ca2); p2_src = (char *) ca2->ptr; att2 = 1;
1319
- } else {
1320
- s2_arena = ca_op_acquire_operand_scratch(ca2, chunk_n);
1321
- p2_src = (char *) s2_arena; gpc2 = 1;
1322
- }
1323
- /* ca3 acquire (mirror) */
1324
- if ( s3 == 0 ) {
1325
- if ( ca_attach_is_alias(ca3) ) {
1326
- ca_attach(ca3); p3_src = (char *) ca3->ptr; att3 = 1;
1327
- } else {
1328
- s3_arena = ca_op_acquire_operand_scratch(ca3, 1);
1329
- ca_xfer_all(ca3, s3_arena, CA_XFER_GET);
1330
- p3_src = (char *) s3_arena;
1331
- }
1332
- } else if ( ca_attach_is_alias(ca3) ) {
1333
- ca_attach(ca3); p3_src = (char *) ca3->ptr; att3 = 1;
1334
- } else {
1335
- s3_arena = ca_op_acquire_operand_scratch(ca3, chunk_n);
1336
- p3_src = (char *) s3_arena; gpc3 = 1;
1337
- }
1338
-
1339
- for ( off = 0; off < n_total; off += chunk_n ) {
1340
- ca_size_t n_done = (off + chunk_n > n_total) ? n_total - off
1341
- : chunk_n;
1342
- char *p1, *p2, *p3;
1343
-
1344
- if ( gpc1 ) { ca_chunked_gather(ca1, off, n_done, s1_arena);
1345
- p1 = (char *) s1_arena; }
1346
- else { p1 = p1_src + (s1 ? off * ca1->bytes : 0); }
1347
- if ( gpc2 ) { ca_chunked_gather(ca2, off, n_done, s2_arena);
1348
- p2 = (char *) s2_arena; }
1349
- else { p2 = p2_src + (s2 ? off * ca2->bytes : 0); }
1350
- if ( gpc3 ) { ca_chunked_gather(ca3, off, n_done, s3_arena);
1351
- p3 = (char *) s3_arena; }
1352
- else { p3 = p3_src + (s3 ? off * ca3->bytes : 0); }
1353
-
1354
- func[dt](n_done,
1355
- cao->mask ? ((boolean8_t *) cao->mask->ptr) + off
1356
- : NULL,
1357
- p1, s1,
1358
- p2, s2,
1359
- p3, s3,
1360
- (char *) cao->ptr + off * cao->bytes, 1);
1843
+ {
1844
+ ca_triop_chunk_t d;
1845
+ ca_chunked_run_t run;
1846
+ d.func = func; d.dt = dt; d.out = cao; d.in_place = 0;
1847
+ ca_chunked_run_init(&run, n_total, chunk_n);
1848
+ ca_chunked_run_add(&run, ca1, s1);
1849
+ ca_chunked_run_add(&run, ca2, s2);
1850
+ ca_chunked_run_add(&run, ca3, s3);
1851
+ run.kernel = ca_triop_chunk_kernel;
1852
+ run.data = &d;
1853
+ ca_chunked_run(&run);
1361
1854
  }
1362
-
1363
- if ( s3_arena ) ca_lazy_arena_release(s3_arena);
1364
- if ( s2_arena ) ca_lazy_arena_release(s2_arena);
1365
- if ( s1_arena ) ca_lazy_arena_release(s1_arena);
1366
- if ( att3 ) ca_detach(ca3);
1367
- if ( att2 ) ca_detach(ca2);
1368
- if ( att1 ) ca_detach(ca1);
1369
-
1370
- ca_lazy_arena_exit();
1371
1855
  }
1372
1856
  }
1373
1857
 
@@ -1377,6 +1861,13 @@ rb_ca_call_triop (VALUE self, VALUE other2, VALUE other3,
1377
1861
  /* triop_bang (in-place) driver. ca1 = self = output (write target,
1378
1862
  attach legit; keep ca_attach + ca_sync); ca2/ca3 = input only (fast/slow
1379
1863
  dispatch via EAGER_ACQUIRE/RELEASE). */
1864
+ static VALUE
1865
+ ca_triop_bang_chunked (VALUE arg)
1866
+ {
1867
+ ca_chunked_run((ca_chunked_run_t *) arg);
1868
+ return Qnil;
1869
+ }
1870
+
1380
1871
  VALUE
1381
1872
  rb_ca_call_triop_bang (VALUE self, VALUE other2, VALUE other3,
1382
1873
  ca_triop_func_t func[])
@@ -1391,6 +1882,9 @@ rb_ca_call_triop_bang (VALUE self, VALUE other2, VALUE other3,
1391
1882
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca1);
1392
1883
  TypedData_Get_Struct(other2, CArray, &carray_data_type, ca2);
1393
1884
  TypedData_Get_Struct(other3, CArray, &carray_data_type, ca3);
1885
+ if ( func[ca1->data_type] == ca_triop_not_implement ) {
1886
+ ca_op_refuse_unimplemented(self, ca1, rb_eCADataTypeError);
1887
+ }
1394
1888
 
1395
1889
  /* self is the write target, so the destination rule applies to each
1396
1890
  input operand in turn (the same one assignment uses). */
@@ -1403,10 +1897,6 @@ rb_ca_call_triop_bang (VALUE self, VALUE other2, VALUE other3,
1403
1897
  TypedData_Get_Struct(other3, CArray, &carray_data_type, ca3);
1404
1898
  }
1405
1899
 
1406
- /* self IS the output (= write target; attach legit per refined invariant) */
1407
- ca_attach(ca1);
1408
- ca_mask_overlay_safe(ca1, 3, ca1, ca2, ca3);
1409
-
1410
1900
  {
1411
1901
  ca_size_t s2 = rb_obj_is_cscalar(other2) ? 0 : 1;
1412
1902
  ca_size_t s3 = rb_obj_is_cscalar(other3) ? 0 : 1;
@@ -1414,81 +1904,119 @@ rb_ca_call_triop_bang (VALUE self, VALUE other2, VALUE other3,
1414
1904
  /* Threshold dispatch on the input-only operands (ca2 / ca3). Self
1415
1905
  (ca1) IS the output (attached + ca_sync as usual). >= 2 non-alias
1416
1906
  input arrays → chunked, else 1-shot ALLOCV. */
1907
+ /* An input that may share memory with self is gathered whole, never
1908
+ read in place or per chunk: either would read cells already
1909
+ written. */
1910
+ int overlap2 = ca_bang_input_may_overlap(ca1, ca2);
1911
+ int overlap3 = ca_bang_input_may_overlap(ca1, ca3);
1417
1912
  int nonalias_arrays = 0;
1418
1913
  int use_chunked;
1419
1914
  if ( s2 == 1 && !ca_attach_is_alias(ca2) ) nonalias_arrays++;
1420
1915
  if ( s3 == 1 && !ca_attach_is_alias(ca3) ) nonalias_arrays++;
1421
- use_chunked = (nonalias_arrays >= 2);
1916
+ use_chunked = (nonalias_arrays >= 2) && !overlap2 && !overlap3;
1422
1917
 
1423
1918
  if ( !use_chunked ) {
1424
1919
  volatile VALUE h2 = Qnil, h3 = Qnil;
1425
1920
  char *p2, *p3;
1426
1921
  int attached2, attached3;
1922
+ ca_triop_call_t k;
1923
+ CArray *held[3];
1427
1924
  (void) h2; (void) h3;
1428
1925
 
1429
- EAGER_ACQUIRE_INPUT(ca2, p2, h2, attached2);
1430
- EAGER_ACQUIRE_INPUT(ca3, p3, h3, attached3);
1926
+ int in_place2 = ca_attach_is_alias(ca2) && !overlap2;
1927
+ int in_place3 = ca_attach_is_alias(ca3) && !overlap3;
1431
1928
 
1432
- func[ca1->data_type](ca1->elements,
1433
- ( ca1->mask ) ? (boolean8_t *) ca1->mask->ptr : NULL,
1434
- ca1->ptr, 1,
1435
- p2, s2,
1436
- p3, s3,
1437
- ca1->ptr, 1);
1929
+ /* The gathers can raise, so they run before self is attached. */
1930
+ if ( ! in_place2 ) {
1931
+ p2 = ALLOCV_N(char, h2, ca2->elements * ca2->bytes);
1932
+ ca_xfer_all(ca2, p2, CA_XFER_GET);
1933
+ }
1934
+ if ( ! in_place3 ) {
1935
+ p3 = ALLOCV_N(char, h3, ca3->elements * ca3->bytes);
1936
+ ca_xfer_all(ca3, p3, CA_XFER_GET);
1937
+ }
1938
+
1939
+ /* self IS the output (= write target; attach legit per refined
1940
+ invariant) */
1941
+ ca_attach(ca1); /* window: the overlay detaches it if it raises; the
1942
+ kernel runs through ca_op_guarded */
1943
+ ca_mask_overlay_attached(ca1, 3, ca1, ca2, ca3);
1944
+
1945
+ attached2 = attached3 = 0;
1946
+ if ( in_place2 ) {
1947
+ ca_attach(ca2); /* window: an alias attach, cannot raise */
1948
+ p2 = (char *) ca2->ptr;
1949
+ attached2 = 1;
1950
+ }
1951
+ if ( in_place3 ) {
1952
+ ca_attach(ca3); /* window: as above */
1953
+ p3 = (char *) ca3->ptr;
1954
+ attached3 = 1;
1955
+ }
1956
+
1957
+ k.f = func[ca1->data_type];
1958
+ k.n = ca1->elements;
1959
+ k.m = ( ca1->mask ) ? (boolean8_t *) ca1->mask->ptr : NULL;
1960
+ k.p1 = ca1->ptr; k.i1 = 1;
1961
+ k.p2 = p2; k.i2 = s2;
1962
+ k.p3 = p3; k.i3 = s3;
1963
+ k.p4 = ca1->ptr; k.i4 = 1;
1964
+ if ( ca_triop_may_raise(func, ca1->data_type) ) {
1965
+ held[0] = ca1;
1966
+ held[1] = attached2 ? ca2 : NULL;
1967
+ held[2] = attached3 ? ca3 : NULL;
1968
+ ca_op_guarded(ca_triop_call, &k, 3, held, "wrr");
1969
+ }
1970
+ else {
1971
+ ca_triop_call((VALUE) &k);
1972
+ }
1438
1973
 
1439
1974
  EAGER_RELEASE_INPUT(ca3, h3, attached3);
1440
1975
  EAGER_RELEASE_INPUT(ca2, h2, attached2);
1441
1976
  }
1442
1977
  else {
1443
1978
  /* CHUNKED PATH: both ca2 and ca3 non-alias arrays. ca1 (= self =
1444
- output) is already attached; its ptr is contig (= ca_attach
1445
- materialise + alias for entity, or full materialise for view).
1446
- Write to ca1->ptr + off*bytes in chunks; sync at end. */
1447
- void *s2_arena = NULL, *s3_arena = NULL;
1448
- int8_t dt = ca1->data_type;
1449
- ca_size_t chunk_n;
1450
- ca_size_t off;
1451
- ca_size_t n_total = ca1->elements;
1452
-
1979
+ output) is attached; its ptr is contig (= ca_attach materialise +
1980
+ alias for entity, or full materialise for view). Write to
1981
+ ca1->ptr + off*bytes in chunks; sync at end. The run gathers
1982
+ per chunk, and any gather can raise, so the run is guarded
1983
+ whatever the kernel. */
1984
+ ca_attach(ca1); /* window: the overlay detaches it if it raises; the
1985
+ kernel runs through ca_op_guarded */
1986
+ ca_mask_overlay_attached(ca1, 3, ca1, ca2, ca3);
1453
1987
  {
1454
- ca_size_t inner = ca_chunk_inner_size(ca1);
1455
- ca_size_t inn2 = ca_chunk_inner_size(ca2);
1456
- ca_size_t inn3 = ca_chunk_inner_size(ca3);
1457
- ca_size_t maxb = ca1->bytes;
1458
- if ( ca2->bytes > maxb ) maxb = ca2->bytes;
1459
- if ( ca3->bytes > maxb ) maxb = ca3->bytes;
1460
- if ( inn2 > inner ) inner = inn2;
1461
- if ( inn3 > inner ) inner = inn3;
1462
- chunk_n = ca_chunk_compute_n(n_total, inner, maxb);
1463
- }
1988
+ int8_t dt = ca1->data_type;
1989
+ ca_size_t chunk_n;
1990
+ ca_size_t n_total = ca1->elements;
1464
1991
 
1465
- ca_lazy_arena_enter();
1466
- s2_arena = ca_op_acquire_operand_scratch(ca2, chunk_n);
1467
- s3_arena = ca_op_acquire_operand_scratch(ca3, chunk_n);
1468
-
1469
- for ( off = 0; off < n_total; off += chunk_n ) {
1470
- ca_size_t n_done = (off + chunk_n > n_total) ? n_total - off
1471
- : chunk_n;
1472
- ca_chunked_gather(ca2, off, n_done, s2_arena);
1473
- ca_chunked_gather(ca3, off, n_done, s3_arena);
1474
-
1475
- func[dt](n_done,
1476
- ca1->mask ? ((boolean8_t *) ca1->mask->ptr) + off
1477
- : NULL,
1478
- (char *) ca1->ptr + off * ca1->bytes, 1,
1479
- (char *) s2_arena, s2,
1480
- (char *) s3_arena, s3,
1481
- (char *) ca1->ptr + off * ca1->bytes, 1);
1482
- }
1992
+ {
1993
+ ca_size_t inner = ca_chunk_inner_size(ca1);
1994
+ ca_size_t inn2 = ca_chunk_inner_size(ca2);
1995
+ ca_size_t inn3 = ca_chunk_inner_size(ca3);
1996
+ ca_size_t maxb = ca1->bytes;
1997
+ if ( ca2->bytes > maxb ) maxb = ca2->bytes;
1998
+ if ( ca3->bytes > maxb ) maxb = ca3->bytes;
1999
+ if ( inn2 > inner ) inner = inn2;
2000
+ if ( inn3 > inner ) inner = inn3;
2001
+ chunk_n = ca_chunk_compute_n(n_total, inner, maxb);
2002
+ }
1483
2003
 
1484
- ca_lazy_arena_release(s3_arena);
1485
- ca_lazy_arena_release(s2_arena);
1486
- ca_lazy_arena_exit();
2004
+ {
2005
+ ca_triop_chunk_t d;
2006
+ ca_chunked_run_t run;
2007
+ d.func = func; d.dt = dt; d.out = ca1; d.in_place = 1;
2008
+ ca_chunked_run_init(&run, n_total, chunk_n);
2009
+ ca_chunked_run_add(&run, ca2, s2);
2010
+ ca_chunked_run_add(&run, ca3, s3);
2011
+ run.kernel = ca_triop_chunk_kernel;
2012
+ run.data = &d;
2013
+ ca_op_guarded(ca_triop_bang_chunked, &run, 1, &ca1, "w");
2014
+ }
2015
+ }
1487
2016
  }
1488
2017
  }
1489
2018
 
1490
- ca_sync(ca1);
1491
- ca_detach(ca1);
2019
+ ca_sync_detach(ca1);
1492
2020
 
1493
2021
  return self;
1494
2022
  }
@@ -1502,6 +2030,9 @@ rb_ca_call_moncmp (VALUE self, ca_moncmp_func_t func[])
1502
2030
  CArray *ca1, *ca2; /* ca2 = ca1.op */
1503
2031
 
1504
2032
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca1);
2033
+ if ( func[ca1->data_type] == ca_moncmp_not_implement ) {
2034
+ ca_op_refuse_unimplemented(self, ca1, rb_eCADataTypeError);
2035
+ }
1505
2036
 
1506
2037
  if ( ca_is_scalar(ca1) ) {
1507
2038
  out = rb_cscalar_new(CA_BOOLEAN, 0, NULL);
@@ -1528,11 +2059,20 @@ rb_ca_call_moncmp (VALUE self, ca_moncmp_func_t func[])
1528
2059
  int attached1;
1529
2060
  (void) h1;
1530
2061
 
1531
- EAGER_ACQUIRE_INPUT(ca1, p1, h1, attached1);
1532
- func[ca1->data_type](ca1->elements,
1533
- ( ca2->mask ) ? (boolean8_t *) ca2->mask->ptr : NULL,
1534
- p1, 1,
1535
- (boolean8_t *) ca2->ptr, 1);
2062
+ ca_moncmp_call_t k;
2063
+ EAGER_GATHER_INPUT(ca1, p1, h1);
2064
+ EAGER_ATTACH_INPUT(ca1, p1, attached1);
2065
+ k.f = func[ca1->data_type];
2066
+ k.n = ca1->elements;
2067
+ k.m = ( ca2->mask ) ? (boolean8_t *) ca2->mask->ptr : NULL;
2068
+ k.p1 = p1; k.i1 = 1;
2069
+ k.p2 = (boolean8_t *) ca2->ptr; k.i2 = 1;
2070
+ if ( attached1 && ca_moncmp_may_raise(func, ca1->data_type) ) {
2071
+ ca_op_guarded(ca_moncmp_call, &k, 1, &ca1, "r");
2072
+ }
2073
+ else {
2074
+ ca_moncmp_call((VALUE) &k);
2075
+ }
1536
2076
  EAGER_RELEASE_INPUT(ca1, h1, attached1);
1537
2077
  }
1538
2078
 
@@ -1545,6 +2085,180 @@ rb_ca_call_moncmp (VALUE self, ca_moncmp_func_t func[])
1545
2085
  UNDEF-comparison identity checks below cast to ca_bincmp_func_t
1546
2086
  explicitly. */
1547
2087
 
2088
+ typedef struct {
2089
+ ca_bincmp_func_t *func;
2090
+ int8_t dt;
2091
+ CArray *out;
2092
+ ca_size_t step_out;
2093
+ double tol;
2094
+ } ca_bincmp_chunk_t;
2095
+
2096
+ static void
2097
+ ca_bincmp_chunk_kernel (ca_chunked_run_t *run, ca_size_t off, ca_size_t n,
2098
+ char **p)
2099
+ {
2100
+ ca_bincmp_chunk_t *d = (ca_bincmp_chunk_t *) run->data;
2101
+ ca_size_t o = d->step_out ? off : 0;
2102
+ d->func[d->dt](n,
2103
+ d->out->mask ? ((boolean8_t *) d->out->mask->ptr) + o : NULL,
2104
+ p[0], run->ca[0]->bytes, run->step[0],
2105
+ p[1], run->ca[1]->bytes, run->step[1],
2106
+ (char *) d->out->ptr + o * d->out->bytes, d->out->bytes,
2107
+ d->step_out,
2108
+ d->tol);
2109
+ }
2110
+
2111
+ /* The six comparisons answer by value between integers. Promotion takes
2112
+ uint64 against int64 (and uint8 against int8, ...) to the unsigned type,
2113
+ where -7 is 2**64 - 7 and 7 < -7 would be true; and an Integer outside an
2114
+ array's type has no cell of that type to stand in for it. */
2115
+
2116
+ static int
2117
+ ca_is_unsigned_integer_dt (int8_t dt)
2118
+ {
2119
+ return dt == CA_UINT8 || dt == CA_UINT16 || dt == CA_UINT32 ||
2120
+ dt == CA_UINT64;
2121
+ }
2122
+
2123
+ static int
2124
+ ca_is_signed_integer_dt (int8_t dt)
2125
+ {
2126
+ return dt == CA_INT8 || dt == CA_INT16 || dt == CA_INT32 ||
2127
+ dt == CA_INT64;
2128
+ }
2129
+
2130
+ /* The answer of `left op right` when every right is greater than every
2131
+ left (right_greater = 1), or every left greater than every right. */
2132
+ static int
2133
+ ca_bincmp_ordered_answer (int op_id, int right_greater)
2134
+ {
2135
+ switch ( op_id ) {
2136
+ case CA_BINCMP_LT: case CA_BINCMP_LE: return right_greater;
2137
+ case CA_BINCMP_GT: case CA_BINCMP_GE: return ! right_greater;
2138
+ case CA_BINCMP_EQ: return 0;
2139
+ default: return 1; /* CA_BINCMP_NE */
2140
+ }
2141
+ }
2142
+
2143
+ /* The answer at a cell whose signed operand is negative: the unsigned
2144
+ operand is then the greater. signed_side is 1 (left) or 2 (right). */
2145
+ int
2146
+ ca_bincmp_answer_when_negative (int op_id, int signed_side)
2147
+ {
2148
+ return ca_bincmp_ordered_answer(op_id, signed_side == 1);
2149
+ }
2150
+
2151
+ static int
2152
+ ca_bincmp_plain_integer_array (VALUE v, int8_t *dt)
2153
+ {
2154
+ CArray *ca;
2155
+ if ( ! rb_obj_is_carray(v) ) return 0;
2156
+ TypedData_Get_Struct(v, CArray, &carray_data_type, ca);
2157
+ if ( ca_is_face(ca) ) return 0;
2158
+ *dt = ca->data_type;
2159
+ return ca_is_unsigned_integer_dt(*dt) || ca_is_signed_integer_dt(*dt);
2160
+ }
2161
+
2162
+ /* Prepares the operands of comparison op_id (CA_BINCMP_*). Returns
2163
+ 0 nothing to do,
2164
+ 1 every cell answers true, 2 every cell answers false
2165
+ (*self and *other are then both the array, whose mask the answer
2166
+ keeps),
2167
+ 3 the operands are integers of mixed signedness that promote to the
2168
+ unsigned type; *signed_side is 1 or 2 for the signed one. */
2169
+ int
2170
+ ca_bincmp_integer_prepare (volatile VALUE *self, volatile VALUE *other,
2171
+ int op_id, int *signed_side)
2172
+ {
2173
+ int8_t dt1, dt2;
2174
+ int side;
2175
+
2176
+ if ( op_id < CA_BINCMP_LT || op_id > CA_BINCMP_NE ) return 0;
2177
+
2178
+ if ( RB_INTEGER_TYPE_P(*other) &&
2179
+ ca_bincmp_plain_integer_array(*self, &dt1) ) {
2180
+ side = ca_integer_range_side(*other, dt1);
2181
+ if ( side == 0 ) return 0;
2182
+ *other = *self;
2183
+ return ca_bincmp_ordered_answer(op_id, side > 0) ? 1 : 2;
2184
+ }
2185
+ if ( RB_INTEGER_TYPE_P(*self) &&
2186
+ ca_bincmp_plain_integer_array(*other, &dt2) ) {
2187
+ side = ca_integer_range_side(*self, dt2);
2188
+ if ( side == 0 ) return 0;
2189
+ *self = *other;
2190
+ return ca_bincmp_ordered_answer(op_id, side < 0) ? 1 : 2;
2191
+ }
2192
+
2193
+ if ( ! ca_bincmp_plain_integer_array(*self, &dt1) ||
2194
+ ! ca_bincmp_plain_integer_array(*other, &dt2) ) {
2195
+ return 0;
2196
+ }
2197
+ if ( ca_is_unsigned_integer_dt(dt1) == ca_is_unsigned_integer_dt(dt2) ) {
2198
+ return 0;
2199
+ }
2200
+ if ( ! ca_is_unsigned_integer_dt(ca_promote_type(dt1, dt2)) ) {
2201
+ return 0;
2202
+ }
2203
+ *signed_side = ca_is_signed_integer_dt(dt1) ? 1 : 2;
2204
+ return 3;
2205
+ }
2206
+
2207
+ static int
2208
+ ca_bincmp_op_id_of (ca_bincmp_func_t *func)
2209
+ {
2210
+ if ( func == ca_bincmp_lt ) return CA_BINCMP_LT;
2211
+ if ( func == ca_bincmp_gt ) return CA_BINCMP_GT;
2212
+ if ( func == ca_bincmp_le ) return CA_BINCMP_LE;
2213
+ if ( func == ca_bincmp_ge ) return CA_BINCMP_GE;
2214
+ if ( func == ca_bincmp_eq ) return CA_BINCMP_EQ;
2215
+ if ( func == ca_bincmp_ne ) return CA_BINCMP_NE;
2216
+ return -1;
2217
+ }
2218
+
2219
+ /* Compares the unsigned operand as uint64 with the signed one taken to
2220
+ uint64 modulo 2**64, then answers the cells where the signed operand is
2221
+ negative. */
2222
+ static VALUE
2223
+ ca_bincmp_mixed_sign (VALUE self, VALUE other, ca_bincmp_func_t func[],
2224
+ int op_id, int signed_side)
2225
+ {
2226
+ volatile VALUE l, r, su, out;
2227
+ CArray *cs, *co;
2228
+ int64_t *sp;
2229
+ boolean8_t *op, *m;
2230
+ ca_size_t i, step;
2231
+ int answer = ca_bincmp_answer_when_negative(op_id, signed_side);
2232
+
2233
+ l = rb_funcall(self, rb_intern("to_type"), 1,
2234
+ INT2NUM(( signed_side == 1 ) ? CA_INT64 : CA_UINT64));
2235
+ r = rb_funcall(other, rb_intern("to_type"), 1,
2236
+ INT2NUM(( signed_side == 2 ) ? CA_INT64 : CA_UINT64));
2237
+ ca_broadcast_pair(&l, &r);
2238
+
2239
+ /* A fresh uint64 entity holding the signed operand modulo 2**64; read
2240
+ as int64 it still says which cells are negative. */
2241
+ su = rb_funcall(( signed_side == 1 ) ? l : r, rb_intern("to_type"), 1,
2242
+ INT2NUM(CA_UINT64));
2243
+ out = ( signed_side == 1 ) ? rb_ca_call_bincmp(su, r, func, 0.0)
2244
+ : rb_ca_call_bincmp(l, su, func, 0.0);
2245
+
2246
+ TypedData_Get_Struct(su, CArray, &carray_data_type, cs);
2247
+ TypedData_Get_Struct(out, CArray, &carray_data_type, co);
2248
+ sp = (int64_t *) cs->ptr;
2249
+ op = (boolean8_t *) co->ptr;
2250
+ m = co->mask ? (boolean8_t *) co->mask->ptr : NULL;
2251
+ step = ( cs->elements == 1 ) ? 0 : 1;
2252
+ for ( i = 0; i < co->elements; i++ ) {
2253
+ if ( ( ! m || ! m[i] ) && sp[i * step] < 0 ) {
2254
+ op[i] = (boolean8_t) answer;
2255
+ }
2256
+ }
2257
+ RB_GC_GUARD(l);
2258
+ RB_GC_GUARD(r);
2259
+ return out;
2260
+ }
2261
+
1548
2262
  VALUE
1549
2263
  rb_ca_call_bincmp (volatile VALUE self, volatile VALUE other,
1550
2264
  ca_bincmp_func_t func[],
@@ -1562,7 +2276,7 @@ rb_ca_call_bincmp (volatile VALUE self, volatile VALUE other,
1562
2276
  return rb_ca_is_not_masked(self);
1563
2277
  }
1564
2278
  else {
1565
- rb_raise(rb_eRuntimeError, "array can not be compared with UNDEF");
2279
+ rb_raise(rb_eArgError, "array can not be compared with UNDEF");
1566
2280
  }
1567
2281
  }
1568
2282
 
@@ -1572,11 +2286,25 @@ rb_ca_call_bincmp (volatile VALUE self, volatile VALUE other,
1572
2286
  for fixlen-storage Faces (memcmp is already correct there). */
1573
2287
  ca_face_reconcile_comparison(&self, &other);
1574
2288
 
1575
- /* do implicit casting and resolving unbound repeat array */
2289
+ {
2290
+ int op_id = ca_bincmp_op_id_of(func);
2291
+ int signed_side = 0;
2292
+ switch ( ca_bincmp_integer_prepare(&self, &other, op_id, &signed_side) ) {
2293
+ case 1: return rb_ca_call_bincmp(self, self, ca_bincmp_eq, 0.0);
2294
+ case 2: return rb_ca_call_bincmp(self, self, ca_bincmp_ne, 0.0);
2295
+ case 3: return ca_bincmp_mixed_sign(self, other, func, op_id, signed_side);
2296
+ }
2297
+ }
2298
+
2299
+ /* do implicit casting */
1576
2300
  rb_ca_cast_self_or_other(&self, &other);
1577
2301
 
1578
2302
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca1);
1579
2303
  TypedData_Get_Struct(other, CArray, &carray_data_type, ca2);
2304
+ if ( func[ca1->data_type] == ca_bincmp_not_implement ) {
2305
+ ca_op_refuse_unimplemented(ca_is_face(ca1) ? self : other, ca1,
2306
+ rb_eTypeError);
2307
+ }
1580
2308
 
1581
2309
  /* Same fast/slow shape as rb_ca_call_binop, but the output data_type is
1582
2310
  fixed boolean (rb_ca_call_binop's output data_type matches its input). */
@@ -1651,15 +2379,29 @@ rb_ca_call_bincmp (volatile VALUE self, volatile VALUE other,
1651
2379
  int attached1, attached2;
1652
2380
  (void) h1; (void) h2;
1653
2381
 
1654
- EAGER_ACQUIRE_INPUT(ca1, p1, h1, attached1);
1655
- EAGER_ACQUIRE_INPUT(ca2, p2, h2, attached2);
1656
-
1657
- func[ca1->data_type](n_kernel,
1658
- ( ca3->mask ) ? (boolean8_t *) ca3->mask->ptr : NULL,
1659
- p1, ca1->bytes, i1,
1660
- p2, ca2->bytes, i2,
1661
- ca3->ptr, ca3->bytes, i3,
1662
- tol);
2382
+ ca_bincmp_call_t k;
2383
+ CArray *held[2];
2384
+ EAGER_GATHER_INPUT(ca1, p1, h1);
2385
+ EAGER_GATHER_INPUT(ca2, p2, h2);
2386
+ EAGER_ATTACH_INPUT(ca1, p1, attached1);
2387
+ EAGER_ATTACH_INPUT(ca2, p2, attached2);
2388
+
2389
+ k.f = func[ca1->data_type];
2390
+ k.n = n_kernel;
2391
+ k.m = ( ca3->mask ) ? (boolean8_t *) ca3->mask->ptr : NULL;
2392
+ k.p1 = p1; k.b1 = ca1->bytes; k.i1 = i1;
2393
+ k.p2 = p2; k.b2 = ca2->bytes; k.i2 = i2;
2394
+ k.p3 = ca3->ptr; k.b3 = ca3->bytes; k.i3 = i3;
2395
+ k.tol = tol;
2396
+ if ( ( attached1 || attached2 ) &&
2397
+ ca_bincmp_may_raise(func, ca1->data_type) ) {
2398
+ held[0] = attached1 ? ca1 : NULL;
2399
+ held[1] = attached2 ? ca2 : NULL;
2400
+ ca_op_guarded(ca_bincmp_call, &k, 2, held, "rr");
2401
+ }
2402
+ else {
2403
+ ca_bincmp_call((VALUE) &k);
2404
+ }
1663
2405
 
1664
2406
  EAGER_RELEASE_INPUT(ca2, h2, attached2);
1665
2407
  EAGER_RELEASE_INPUT(ca1, h1, attached1);
@@ -1667,10 +2409,9 @@ rb_ca_call_bincmp (volatile VALUE self, volatile VALUE other,
1667
2409
  else {
1668
2410
  /* CHUNKED PATH (both operands non-alias array): per-chunk gather
1669
2411
  into arena scratch. Mirrors the binop chunked branch. */
1670
- void *s1_arena = NULL, *s2_arena = NULL;
1671
2412
  ca_size_t b1 = ca1->bytes, b2 = ca2->bytes, b3 = ca3->bytes;
1672
2413
  int8_t dt = ca1->data_type;
1673
- ca_size_t chunk_n, off;
2414
+ ca_size_t chunk_n;
1674
2415
 
1675
2416
  {
1676
2417
  ca_size_t inner1 = ca_chunk_inner_size(ca1);
@@ -1681,28 +2422,17 @@ rb_ca_call_bincmp (volatile VALUE self, volatile VALUE other,
1681
2422
  chunk_n = ca_chunk_compute_n(n_kernel, inner, maxb);
1682
2423
  }
1683
2424
 
1684
- ca_lazy_arena_enter();
1685
- s1_arena = ca_op_acquire_operand_scratch(ca1, chunk_n);
1686
- s2_arena = ca_op_acquire_operand_scratch(ca2, chunk_n);
1687
-
1688
- for ( off = 0; off < n_kernel; off += chunk_n ) {
1689
- ca_size_t n_done = (off + chunk_n > n_kernel) ? n_kernel - off
1690
- : chunk_n;
1691
- ca_chunked_gather(ca1, off, n_done, s1_arena);
1692
- ca_chunked_gather(ca2, off, n_done, s2_arena);
1693
-
1694
- func[dt](n_done,
1695
- ca3->mask ? ((boolean8_t *) ca3->mask->ptr) + off
1696
- : NULL,
1697
- (char *) s1_arena, b1, i1,
1698
- (char *) s2_arena, b2, i2,
1699
- (char *) ca3->ptr + off * b3, b3, i3,
1700
- tol);
2425
+ {
2426
+ ca_bincmp_chunk_t d;
2427
+ ca_chunked_run_t run;
2428
+ d.func = func; d.dt = dt; d.out = ca3; d.step_out = i3; d.tol = tol;
2429
+ ca_chunked_run_init(&run, n_kernel, chunk_n);
2430
+ ca_chunked_run_add(&run, ca1, i1);
2431
+ ca_chunked_run_add(&run, ca2, i2);
2432
+ run.kernel = ca_bincmp_chunk_kernel;
2433
+ run.data = &d;
2434
+ ca_chunked_run(&run);
1701
2435
  }
1702
-
1703
- ca_lazy_arena_release(s2_arena);
1704
- ca_lazy_arena_release(s1_arena);
1705
- ca_lazy_arena_exit();
1706
2436
  }
1707
2437
  }
1708
2438
  }
@@ -1774,7 +2504,7 @@ ca_math_call (VALUE mod, VALUE arg, ID id)
1774
2504
  return rb_funcall(rb_mMath, id, 1, arg);
1775
2505
  }
1776
2506
  else {
1777
- rb_raise(rb_eRuntimeError, "unknown method for Math");
2507
+ rb_raise(rb_eTypeError, "unknown method for Math");
1778
2508
  }
1779
2509
  }
1780
2510
  #endif
@@ -1784,7 +2514,7 @@ ca_math_call (VALUE mod, VALUE arg, ID id)
1784
2514
  return rb_funcall(rb_mMath, id, 1, arg);
1785
2515
  }
1786
2516
  else {
1787
- rb_raise(rb_eRuntimeError, "unknown method for Math");
2517
+ rb_raise(rb_eTypeError, "unknown method for Math");
1788
2518
  }
1789
2519
  }
1790
2520
  }
@@ -1804,7 +2534,7 @@ rb_ca_coerce (VALUE self, VALUE other)
1804
2534
  return rb_ca_coerce(self, rb_funcall(other,rb_intern("to_ca"),0));
1805
2535
  }
1806
2536
  else {
1807
- /* do implicit casting and resolving unbound repeat array */
2537
+ /* do implicit casting */
1808
2538
  rb_ca_cast_self_or_other(&self, &other);
1809
2539
  return rb_assoc_new(other, self);
1810
2540
  }