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
data/ext/ca_obj_monop.c CHANGED
@@ -33,6 +33,7 @@
33
33
  #include "carray.h"
34
34
  #include "carray_internal.h" /* ca_lazy_arena_*, ca_is_lazy_view */
35
35
  #include "ca_monop_dispatch.h"
36
+ #include "carray_math_kernel.h" /* ca_monop_not_implement */
36
37
 
37
38
  /* ca_cast_block (carray_cast.c) */
38
39
  extern void ca_cast_block (ca_size_t n, void *ap1, void *ptr1,
@@ -221,6 +222,7 @@ ca_monop_setup (CAMonOp *ca, CArray *parent, uint16_t op_id)
221
222
  CAMonOp *
222
223
  ca_monop_new (CArray *parent, uint16_t op_id)
223
224
  {
225
+ /* check: the setup checks nothing */
224
226
  CAMonOp *ca = (CAMonOp *) ca_array_alloc(CA_OBJ_MONOP, parent->ndim);
225
227
  ca_monop_setup(ca, parent, op_id);
226
228
  return ca;
@@ -281,14 +283,13 @@ ca_monop_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
281
283
  /* PUT: only writable-view op_ids accept writes (cast, or byte_swap
282
284
  involution). */
283
285
  if ( ! ca_monop_is_writable_view(ca->op_id) ) {
284
- rb_raise(rb_eRuntimeError, "CAMonOp is read-only (xfer_index PUT)");
286
+ ca_refuse_read_only();
285
287
  }
286
288
  {
287
- char scratch[32]; /* max sizeof for any built-in numeric data_type */
289
+ volatile VALUE holder;
288
290
  ca_size_t bytes_per_cell = (ca_monop_is_cast(ca->op_id))
289
291
  ? ca->parent->bytes : ca->bytes;
290
- char *p = (bytes_per_cell <= 32) ? scratch
291
- : (char *) ALLOC_N(char, bytes_per_cell);
292
+ char *p = ALLOCV_N(char, holder, bytes_per_cell);
292
293
  if ( ca_monop_is_cast(ca->op_id) ) {
293
294
  /* Reverse-cast view-data_type `data` to parent data_type scratch, then
294
295
  store into parent at idx[]. */
@@ -301,7 +302,7 @@ ca_monop_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
301
302
  ca_byte_swap_buffer(ca->data_type, ca->bytes, 1, p);
302
303
  }
303
304
  ca_store_index(ca->parent, idx, p);
304
- if ( p != scratch ) xfree(p);
305
+ ALLOCV_END(holder);
305
306
  }
306
307
  return;
307
308
  }
@@ -316,38 +317,6 @@ ca_monop_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
316
317
  ca_monop_func_xfer_stride(ca, starts, counts, strides, data, CA_XFER_GET);
317
318
  }
318
319
 
319
- static void
320
- ca_monop_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
321
- void *data, int dir)
322
- {
323
- CAMonOp *ca = (CAMonOp *) ap;
324
- ca_size_t i;
325
- char *out = (char *) data;
326
-
327
- if ( dir != CA_XFER_GET ) {
328
- /* PUT: only writable-view op_ids accept writes. */
329
- if ( ! ca_monop_is_writable_view(ca->op_id) ) {
330
- rb_raise(rb_eRuntimeError, "CAMonOp is read-only (xfer_addrs PUT)");
331
- }
332
- /* Per-cell loop: forward to xfer_index PUT (handles cast/byte_swap
333
- branch internally). */
334
- for ( i = 0; i < n; i++ ) {
335
- ca_size_t idx[CA_RANK_MAX];
336
- ca_addr2index((CArray *)ca, addrs[i], idx);
337
- ca_monop_func_xfer_index(ca, idx, out + i * ca->bytes, CA_XFER_PUT);
338
- }
339
- return;
340
- }
341
-
342
- /* Naive per-addr loop: convert addr → idx, then xfer_index. This path
343
- is not optimised. */
344
- for ( i = 0; i < n; i++ ) {
345
- ca_size_t idx[CA_RANK_MAX];
346
- ca_addr2index((CArray *)ca, addrs[i], idx);
347
- ca_monop_func_xfer_index(ca, idx, out + i * ca->bytes, CA_XFER_GET);
348
- }
349
- }
350
-
351
320
  /* ------------------------------------------------------------------- */
352
321
  /* In-place chain eval (scratch = 0) */
353
322
  /* ------------------------------------------------------------------- */
@@ -369,27 +338,28 @@ ca_size_t ca_monop_materialise_call_count = 0;
369
338
  #define CA_MAX_LAZY_DEPTH 256
370
339
  #endif
371
340
 
341
+ /* Whether a cast from `from` to `to` raises on a NaN cell: a float or
342
+ complex value has no integer for NaN. */
343
+ static int
344
+ ca_monop_cast_refuses_nan (int8_t from, int8_t to)
345
+ {
346
+ int from_float = ( from == CA_FLOAT32 || from == CA_FLOAT64 ||
347
+ from == CA_CMPLX64 || from == CA_CMPLX128 );
348
+ return from_float && to >= CA_INT8 && to <= CA_UINT64;
349
+ }
350
+
372
351
  /* Apply a single op in-place on `data` (src == dst, unit element stride).
373
352
  Raises if the kernel is not implemented for the given input data_type.
374
353
 
375
- Unmask fast path:
376
- We always pass `mask == NULL` to the kernel, which selects the SIMD-
377
- friendly "no mask" branch in the existing eager kernels (see e.g.
378
- ca_monop_sqrt_float64_t in carray_math.c — `if (m) { ... } else
379
- { ...SIMD loop... }`). Mask cells contain garbage after evaluation,
380
- but they are unobservable: the output mask is built separately via
381
- the attach lifecycle (ca_monop_func_create_mask + parent.mask
382
- CARefer), so reads of masked cells return UNDEF regardless of byte
383
- contents. The mask marks cells as undefined; it does not guard their
384
- bytes, so writing garbage into a masked cell is licensed.
385
-
386
- A "partial mask slow path" (= a cell-wise branch to skip masked-cell
387
- compute) is a possible future micro-optimisation; it is not done
388
- because masked-cell compute does not affect correctness and the SIMD
389
- path is faster overall for typical mask densities. */
354
+ `mask` is NULL except for an op that can raise on a cell: the kernel
355
+ then takes the SIMD-friendly "no mask" branch and computes the masked
356
+ cells too. Their bytes are unobservable -- the output mask is the
357
+ parent's (ca_monop_func_create_mask) -- and the mask marks cells as
358
+ undefined rather than guarding their bytes, so writing anything into a
359
+ masked cell is licensed. */
390
360
  static void
391
361
  apply_monop_in_place (uint16_t op_id, int8_t cur_dt,
392
- ca_size_t slab_n, void *data)
362
+ ca_size_t slab_n, void *data, boolean8_t *mask)
393
363
  {
394
364
  ca_monop_func_t fn = ca_monop_kernel_lookup(op_id, cur_dt);
395
365
  if ( fn == NULL ) {
@@ -397,7 +367,7 @@ apply_monop_in_place (uint16_t op_id, int8_t cur_dt,
397
367
  "CAMonOp: kernel not implemented (op_id=%u data_type=%d)",
398
368
  (unsigned) op_id, (int) cur_dt);
399
369
  }
400
- fn(slab_n, NULL, (char *) data, 1, (char *) data, 1);
370
+ fn(slab_n, mask, (char *) data, 1, (char *) data, 1);
401
371
  }
402
372
 
403
373
  /* Minimal CArray-shaped stub used as ca_cast_block's data_type-only carrier.
@@ -434,38 +404,25 @@ typedef struct {
434
404
  static int8_t
435
405
  pull_leaf_with_optional_cast (CArray *leaf, uint16_t innermost_op,
436
406
  int has_cast_at_leaf, int8_t out_bytes,
437
- ca_size_t slab_n,
438
- ca_size_t *starts, ca_size_t *counts,
439
- ca_size_t *strides, void *data)
407
+ const ca_lazy_req_t *req, void *data)
440
408
  {
441
409
  ca_size_t leaf_bytes = leaf->bytes;
410
+ ca_size_t slab_n = req->n;
442
411
 
443
412
  /* Path 1: no size mismatch, no cast — direct pull. */
444
413
  if ( leaf_bytes == out_bytes && ! has_cast_at_leaf ) {
445
- ca_xfer_stride(leaf, starts, counts, strides, data, CA_XFER_GET);
414
+ ca_lazy_req_pull(leaf, req, data, CA_XFER_GET);
446
415
  return leaf->data_type;
447
416
  }
448
417
 
449
- /* Paths 2 & 3 both need a leaf-sized scratch + leaf-byte strides
450
- for the leaf pull. */
451
- ca_size_t parent_strides[CA_RANK_MAX];
418
+ /* Paths 2 & 3 both pull the leaf into a leaf-sized scratch. */
452
419
  ca_size_t total_bytes = slab_n * leaf_bytes;
453
420
  void *scratch;
454
- volatile VALUE holder;
455
- int8_t k;
456
- {
457
- ca_size_t s = leaf_bytes;
458
- for ( k = leaf->ndim - 1; k >= 0; k-- ) {
459
- parent_strides[k] = s;
460
- s *= counts[k];
461
- }
462
- }
463
- (void) holder;
464
421
  scratch = ( leaf->data_type == CA_OBJECT )
465
422
  ? ca_lazy_arena_acquire_object(slab_n)
466
423
  : ca_lazy_arena_acquire(total_bytes);
467
424
  ca_monop_scratch_acquire_count++;
468
- ca_xfer_stride(leaf, starts, counts, parent_strides, scratch, CA_XFER_GET);
425
+ ca_lazy_req_pull(leaf, req, scratch, CA_XFER_GET);
469
426
 
470
427
  int8_t result_dt;
471
428
  if ( has_cast_at_leaf ) {
@@ -486,20 +443,21 @@ pull_leaf_with_optional_cast (CArray *leaf, uint16_t innermost_op,
486
443
  return result_dt;
487
444
  }
488
445
 
446
+ /* Evaluate (GET) or write (PUT, writable views only) the cells a request
447
+ names; `data` holds them packed. */
489
448
  static void
490
- ca_monop_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
491
- ca_size_t *strides, void *data, int dir)
449
+ ca_monop_eval (CAMonOp *mo, const ca_lazy_req_t *req, void *data, int dir)
492
450
  {
493
- CAMonOp *mo = (CAMonOp *) ap;
494
451
  uint16_t op_chain[CA_MAX_LAZY_DEPTH];
495
452
  int chain_len = 0;
496
453
  CArray *leaf;
497
- ca_size_t slab_n;
498
- int8_t k;
454
+ ca_size_t slab_n = req->n;
499
455
  int8_t cur_dt;
500
456
  int i;
501
457
  int has_cast_at_leaf = 0;
502
458
  int chain_apply_count;
459
+ boolean8_t *slab_mask = NULL;
460
+ int mask_taken = 0;
503
461
 
504
462
  /* Writable-view op_ids (cast / byte_swap) use CAFake-style xfer_stride
505
463
  for both GET and PUT. This avoids the chain-materialise inline path
@@ -510,22 +468,34 @@ ca_monop_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
510
468
  - byte_swap: same data_type → pstrides = strides; involution apply. */
511
469
  if ( ca_monop_is_writable_view(mo->op_id) ) {
512
470
  int is_cast = ca_monop_is_cast(mo->op_id);
513
- ca_size_t pstrides[CA_RANK_MAX];
514
- ca_size_t n = 1;
471
+ ca_size_t n = slab_n;
515
472
  ca_size_t parent_bytes = is_cast ? mo->parent->bytes : mo->bytes;
516
- int8_t kk;
517
473
  char *v;
518
474
  volatile VALUE holder;
519
- for ( kk = 0; kk < mo->ndim; kk++ ) {
520
- n *= counts[kk];
521
- pstrides[kk] = is_cast
522
- ? (strides[kk] / mo->bytes * mo->parent->bytes)
523
- : strides[kk];
524
- }
525
475
  v = ALLOCV_N(char, holder, n * parent_bytes);
526
476
  if ( dir == CA_XFER_GET ) {
527
- ca_xfer_stride(mo->parent, starts, counts, pstrides, v, CA_XFER_GET);
528
- if ( is_cast ) {
477
+ ca_lazy_req_pull(mo->parent, req, v, CA_XFER_GET);
478
+ if ( is_cast && ca_monop_cast_refuses_nan(mo->parent->data_type,
479
+ mo->data_type) ) {
480
+ /* The cast raises on a NaN cell it is asked to convert; a masked
481
+ cell (mask_invalid keeps the NaN under the mask) is skipped.
482
+ The mask is copied off the arena first so a raise leaves no
483
+ arena slot held. */
484
+ CArray *parent = mo->parent;
485
+ boolean8_t *arena_mask = ca_lazy_req_mask(req, 1, &parent);
486
+ boolean8_t *m = NULL;
487
+ volatile VALUE mholder = 0;
488
+ if ( arena_mask ) {
489
+ m = ALLOCV_N(boolean8_t, mholder, n);
490
+ memcpy(m, arena_mask, n);
491
+ ca_lazy_arena_release(arena_mask);
492
+ }
493
+ ca_cast_block_with_mask(n, mo->parent, v, (CArray *) mo, data, m);
494
+ if ( m ) {
495
+ ALLOCV_END(mholder);
496
+ }
497
+ }
498
+ else if ( is_cast ) {
529
499
  ca_cast_block(n, mo->parent, v, (CArray *) mo, data);
530
500
  }
531
501
  else {
@@ -543,7 +513,7 @@ ca_monop_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
543
513
  memcpy(v, data, n * mo->bytes);
544
514
  ca_byte_swap_buffer(mo->data_type, mo->bytes, n, v);
545
515
  }
546
- ca_xfer_stride(mo->parent, starts, counts, pstrides, v, CA_XFER_PUT);
516
+ ca_lazy_req_pull(mo->parent, req, v, CA_XFER_PUT);
547
517
  }
548
518
  ALLOCV_END(holder);
549
519
  return;
@@ -552,7 +522,7 @@ ca_monop_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
552
522
  /* Writable-view op_id PUT/GET is handled above. Below: non-writable
553
523
  monop/monfunc. PUT is read-only. */
554
524
  if ( dir != CA_XFER_GET ) {
555
- rb_raise(rb_eRuntimeError, "CAMonOp is read-only (xfer_stride PUT)");
525
+ ca_refuse_read_only();
556
526
  }
557
527
  ca_monop_materialise_call_count++;
558
528
 
@@ -564,8 +534,8 @@ ca_monop_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
564
534
  while ( leaf->obj_type == CA_OBJ_MONOP ) {
565
535
  CAMonOp *node = (CAMonOp *) leaf;
566
536
  if ( chain_len >= CA_MAX_LAZY_DEPTH ) {
567
- rb_raise(rb_eRuntimeError,
568
- "CAMonOp chain depth exceeds CA_MAX_LAZY_DEPTH (%d)",
537
+ rb_raise(rb_eArgError,
538
+ "lazy expression too deep: more than %d operations in a chain",
569
539
  CA_MAX_LAZY_DEPTH);
570
540
  }
571
541
  op_chain[chain_len++] = node->op_id;
@@ -586,16 +556,12 @@ ca_monop_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
586
556
  }
587
557
 
588
558
  /* === 2. pull leaf into output buffer (with optional cast) === */
589
- slab_n = 1;
590
- for ( k = 0; k < mo->ndim; k++ ) {
591
- slab_n *= counts[k];
592
- }
593
559
  cur_dt = pull_leaf_with_optional_cast(
594
560
  leaf,
595
561
  has_cast_at_leaf ? op_chain[chain_len - 1] : 0,
596
562
  has_cast_at_leaf,
597
563
  (int8_t) mo->bytes,
598
- slab_n, starts, counts, strides, data
564
+ req, data
599
565
  );
600
566
 
601
567
  /* === 3. apply remaining chain in reverse (leaf side → outermost) ===
@@ -626,10 +592,38 @@ ca_monop_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
626
592
  ca_lazy_arena_release(scratch);
627
593
  cur_dt = target_dt;
628
594
  } else {
629
- apply_monop_in_place(op_id, cur_dt, slab_n, data);
595
+ if ( ! mask_taken && ca_op_raises_on_cell(CA_OP_KIND_MONOP, op_id, cur_dt) ) {
596
+ slab_mask = ca_lazy_req_mask(req, 1, &leaf);
597
+ mask_taken = 1;
598
+ }
599
+ apply_monop_in_place(op_id, cur_dt, slab_n, data,
600
+ ca_op_raises_on_cell(CA_OP_KIND_MONOP, op_id, cur_dt) ? slab_mask : NULL);
630
601
  cur_dt = ca_lazy_promote_monop(op_id, cur_dt);
631
602
  }
632
603
  }
604
+ if ( slab_mask ) {
605
+ ca_lazy_arena_release(slab_mask);
606
+ }
607
+ }
608
+
609
+ static void
610
+ ca_monop_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
611
+ ca_size_t *strides, void *data, int dir)
612
+ {
613
+ if ( ca_region_is_empty(((CArray *) ap)->ndim, counts) ) return;
614
+ ca_lazy_req_t req;
615
+ ca_lazy_req_region(&req, ap, starts, counts, strides);
616
+ ca_monop_eval((CAMonOp *) ap, &req, data, dir);
617
+ }
618
+
619
+ /* An address list is evaluated in one pass, as a region is. */
620
+ static void
621
+ ca_monop_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
622
+ void *data, int dir)
623
+ {
624
+ ca_lazy_req_t req;
625
+ ca_lazy_req_addrs(&req, n, addrs);
626
+ ca_monop_eval((CAMonOp *) ap, &req, data, dir);
633
627
  }
634
628
 
635
629
  static void
@@ -663,16 +657,7 @@ ca_monop_func_allocate (void *ap)
663
657
  if ( ca_monop_is_writable_view(ca->op_id) ) {
664
658
  ca_attach(ca->parent);
665
659
  }
666
- ca->ptr = xmalloc(ca_length(ca));
667
- /* CA_OBJECT initialisation for GC safety. */
668
- if ( ca->data_type == CA_OBJECT ) {
669
- VALUE *p = (VALUE *) ca->ptr;
670
- VALUE zero = SIZE2NUM(0);
671
- ca_size_t i;
672
- for ( i = 0; i < ca->elements; i++ ) {
673
- *p++ = zero;
674
- }
675
- }
660
+ ca->ptr = ca_alloc_cells((CArray *) ca);
676
661
  }
677
662
 
678
663
  static void
@@ -701,22 +686,19 @@ ca_monop_func_attach (void *ap)
701
686
  Mirrors CAFake.attach. */
702
687
  if ( ca_monop_is_writable_view(ca->op_id) ) {
703
688
  ca_attach(ca->parent);
704
- ca->ptr = xmalloc(ca_length(ca));
705
- /* CA_OBJECT initialisation for GC safety (mirrors CAFake.allocate):
706
- zero-init the VALUE cells before any potential GC can observe
707
- them. */
708
- if ( ca->data_type == CA_OBJECT ) {
709
- VALUE *p = (VALUE *) ca->ptr;
710
- VALUE zero = SIZE2NUM(0);
711
- ca_size_t i;
712
- for ( i = 0; i < ca->elements; i++ ) {
713
- *p++ = zero;
714
- }
715
- }
689
+ ca->ptr = ca_alloc_cells((CArray *) ca);
716
690
  if ( ca_monop_is_cast(ca->op_id) ) {
717
- /* Cast parent.ptr (parent data_type) → ca.ptr (cast target data_type). */
718
- ca_cast_block(ca->elements, ca->parent, ca->parent->ptr,
719
- (CArray *) ca, ca->ptr);
691
+ /* Cast parent.ptr (parent data_type) → ca.ptr (cast target data_type).
692
+ A cast that raises on NaN skips the masked cells; the parent's
693
+ mask is attached with the parent. A raise here is undone by
694
+ ca_attach, which calls the detach slot once ca->ptr is set. */
695
+ boolean8_t *m = NULL;
696
+ if ( ca_monop_cast_refuses_nan(ca->parent->data_type, ca->data_type)
697
+ && ca_has_mask(ca->parent) ) {
698
+ m = (boolean8_t *) ca->parent->mask->ptr;
699
+ }
700
+ ca_cast_block_with_mask(ca->elements, ca->parent, ca->parent->ptr,
701
+ (CArray *) ca, ca->ptr, m);
720
702
  }
721
703
  else {
722
704
  /* byte_swap: memcpy parent.ptr → ca.ptr (same data_type same bytes),
@@ -727,19 +709,11 @@ ca_monop_func_attach (void *ap)
727
709
  return;
728
710
  }
729
711
 
730
- ca->ptr = xmalloc(ca_length(ca));
712
+ ca->ptr = ca_alloc_cells((CArray *) ca);
731
713
 
732
714
  s = ca->bytes;
733
715
  for ( k = ca->ndim - 1; k >= 0; k-- ) { native[k] = s; s *= ca->dim[k]; }
734
716
  for ( k = 0; k < ca->ndim; k++ ) starts[k] = 0;
735
- /* CA_OBJECT cells are VALUEs and this buffer is about to be marked as
736
- soon as the view is, so it must not be handed to the GC as raw
737
- xmalloc garbage. */
738
- if ( ca->data_type == CA_OBJECT ) {
739
- VALUE *p = (VALUE *) ca->ptr;
740
- ca_size_t i;
741
- for ( i = 0; i < ca->elements; i++ ) *p++ = Qnil;
742
- }
743
717
  ca_monop_func_xfer_stride(ca, starts, ca->dim, native, ca->ptr, CA_XFER_GET);
744
718
  }
745
719
 
@@ -774,10 +748,12 @@ ca_monop_func_sync (void *ap)
774
748
  }
775
749
  }
776
750
  else {
777
- /* byte_swap (involution): apply on ca.ptr in place (re-swap), then
778
- memcpy ca.ptr → parent.ptr. Mirrors CAByteSwap sync. */
779
- ca_byte_swap_buffer(ca->data_type, ca->bytes, ca->elements, ca->ptr);
751
+ /* byte_swap (involution): swap a copy back to the parent's byte order.
752
+ Not ca.ptr in place -- the window stays open after a sync, and a
753
+ second sync (a stack holding this view twice) would swap it again. */
780
754
  memcpy(ca->parent->ptr, ca->ptr, ca_length(ca));
755
+ ca_byte_swap_buffer(ca->data_type, ca->bytes, ca->elements,
756
+ ca->parent->ptr);
781
757
  }
782
758
  ca_sync(ca->parent);
783
759
  }
@@ -805,14 +781,13 @@ ca_monop_func_fill_data (void *ap, void *ptr)
805
781
  data_type (= same bytes for byte_swap, reverse-cast for cast).
806
782
  Non-writable monop/monfunc remains read-only. */
807
783
  if ( ! ca_monop_is_writable_view(ca->op_id) ) {
808
- rb_raise(rb_eRuntimeError, "CAMonOp is read-only (fill_data)");
784
+ ca_refuse_read_only();
809
785
  }
810
786
  {
811
787
  int is_cast = ca_monop_is_cast(ca->op_id);
812
788
  ca_size_t parent_bytes = is_cast ? ca->parent->bytes : ca->bytes;
813
- char stack_v[32];
814
- char *v = (parent_bytes <= 32) ? stack_v
815
- : (char *) xmalloc(parent_bytes);
789
+ volatile VALUE holder;
790
+ char *v = ALLOCV_N(char, holder, parent_bytes);
816
791
  if ( is_cast ) {
817
792
  ca_ptr2ptr((CArray *) ca, ptr, ca->parent, v);
818
793
  }
@@ -821,7 +796,7 @@ ca_monop_func_fill_data (void *ap, void *ptr)
821
796
  ca_byte_swap_buffer(ca->data_type, ca->bytes, 1, v);
822
797
  }
823
798
  ca_fill(ca->parent, v);
824
- if ( v != stack_v ) xfree(v);
799
+ ALLOCV_END(holder);
825
800
  }
826
801
  }
827
802
 
@@ -829,12 +804,12 @@ static void
829
804
  ca_monop_func_create_mask (void *ap)
830
805
  {
831
806
  CAMonOp *ca = (CAMonOp *) ap;
832
- if ( ! ca->parent->mask ) {
833
- ca_create_mask(ca->parent);
807
+ ca_view_func_create_mask(ca);
808
+ /* A cast view writes through to its parent, mask included; any other
809
+ operation is read-only and so is its mask. */
810
+ if ( ca_test_flag(ca, CA_FLAG_READ_ONLY) ) {
811
+ ca_set_flag(ca->mask, CA_FLAG_READ_ONLY);
834
812
  }
835
- ca->mask =
836
- (CArray *) ca_refer_new(ca->parent->mask,
837
- CA_BOOLEAN, ca->ndim, ca->dim, 0, 0);
838
813
  }
839
814
 
840
815
  ca_operation_function_t ca_monop_func = {
@@ -933,8 +908,41 @@ rb_ca_monop_build (VALUE cary, uint16_t op_id)
933
908
  }
934
909
 
935
910
  parent_dt = parent->data_type;
911
+
912
+ /* A boolean parent, as rb_ca_call_monop and the data_type-changing
913
+ monops treat it: a math function wants an explicit cast, and an op
914
+ with no boolean kernel reads the 0/1 storage in int64. */
915
+ if ( parent_dt == CA_BOOLEAN && ! ca_monop_is_writable_view(op_id) ) {
916
+ if ( ca_monop_is_widening(op_id) ) {
917
+ /* the eager monop reads a boolean with no kernel as int64 (0/1), and
918
+ names that type in its refusal */
919
+ ca_op_refuse_unimplemented(cary, CA_INT64, rb_eCADataTypeError);
920
+ }
921
+ if ( ca_monop_kernel_lookup(op_id, CA_BOOLEAN) == ca_monop_not_implement ) {
922
+ target = rb_ca_monop_new(target, CA_MONOP_CAST_BASE + CA_INT64);
923
+ TypedData_Get_Struct(target, CArray, &carray_data_type, parent);
924
+ parent_dt = CA_INT64;
925
+ }
926
+ }
927
+
936
928
  kernel_in_dt = ca_monop_kernel_input_data_type(op_id, parent_dt);
937
929
 
930
+ /* No kernel for the parent, nor one a cast could reach (a fixlen cell
931
+ converts to nothing numeric): raise as the eager monop does, before
932
+ a view is built over it. */
933
+ if ( ! ca_monop_is_writable_view(op_id) &&
934
+ ( ca_monop_kernel_lookup(op_id, kernel_in_dt) == ca_monop_not_implement ||
935
+ ( parent_dt == CA_FIXLEN &&
936
+ ca_monop_kernel_lookup(op_id, CA_FIXLEN) == ca_monop_not_implement ) ) ) {
937
+ /* the eager monop takes an integer to float64 before it looks for the
938
+ kernel, and names the type it looked in */
939
+ ca_op_refuse_unimplemented(cary,
940
+ parent_dt == CA_FIXLEN ? CA_FIXLEN
941
+ : RTEST(rb_ca_is_integer_type(cary)) ? CA_FLOAT64
942
+ : kernel_in_dt,
943
+ rb_eCADataTypeError);
944
+ }
945
+
938
946
  /* Insert cast node if the kernel wants a different input data_type. */
939
947
  if ( kernel_in_dt != parent_dt ) {
940
948
  uint16_t cast_op_id = CA_MONOP_CAST_BASE + (uint16_t) kernel_in_dt;
@@ -949,6 +957,7 @@ rb_ca_monop_build (VALUE cary, uint16_t op_id)
949
957
  static VALUE
950
958
  rb_ca_monop_s_build (VALUE klass, VALUE cary, VALUE op_id_val)
951
959
  {
960
+ /* fits: an operation number from the library's own table */
952
961
  uint16_t op_id = (uint16_t) NUM2UINT(op_id_val);
953
962
  (void) klass;
954
963
  return rb_ca_monop_build(cary, op_id);
@@ -964,6 +973,19 @@ rb_ca_is_lazy_view_p (VALUE self)
964
973
  }
965
974
 
966
975
  /* Read CAMonOp's op_id for inspect/dump_tree consumers in Ruby. */
976
+ /* Whether this node's kernel skips the cells its operands mask, rather
977
+ than computing them: a kernel that can raise on a cell or calls Ruby
978
+ for it. CArray::Fusion reads it so a plan carries the same rule. */
979
+ static VALUE
980
+ rb_ca_monop_trapping (VALUE self)
981
+ {
982
+ CAMonOp *mo;
983
+ TypedData_Get_Struct(self, CAMonOp, &camonop_data_type, mo);
984
+ return ( ! ca_monop_is_cast(mo->op_id)
985
+ && ca_op_raises_on_cell(CA_OP_KIND_MONOP, mo->op_id, mo->parent->data_type) )
986
+ ? Qtrue : Qfalse;
987
+ }
988
+
967
989
  static VALUE
968
990
  rb_ca_monop_op_id (VALUE self)
969
991
  {
@@ -980,6 +1002,7 @@ rb_ca_monop_op_id (VALUE self)
980
1002
  because in-place chain eval allocates no scratch; only attach (xfer_all
981
1003
  → output) and the parent's own xfer_stride alloc, neither of which we
982
1004
  count here. */
1005
+ #ifdef CARRAY_DEV_BUILD
983
1006
  static VALUE
984
1007
  rb_ca_monop_s_reset_scratch_counter (VALUE klass)
985
1008
  {
@@ -1005,6 +1028,7 @@ rb_ca_monop_s_materialise_count (VALUE klass)
1005
1028
  {
1006
1029
  return SIZE2NUM(ca_monop_materialise_call_count);
1007
1030
  }
1031
+ #endif /* CARRAY_DEV_BUILD */
1008
1032
 
1009
1033
  static VALUE
1010
1034
  rb_ca_monop_s_allocate (VALUE klass)
@@ -1018,6 +1042,7 @@ rb_ca_monop_initialize_copy (VALUE self, VALUE other)
1018
1042
  {
1019
1043
  CAMonOp *ca, *cs;
1020
1044
  TypedData_Get_Struct(self, CAMonOp, &camonop_data_type, ca);
1045
+ ca_check_uninitialized(ca);
1021
1046
  TypedData_Get_Struct(other, CAMonOp, &camonop_data_type, cs);
1022
1047
  if ( ca_func[CA_OBJ_MONOP].pool_init ) {
1023
1048
  ca_array_pool_alloc(ca, CA_OBJ_MONOP, cs->parent->ndim);
@@ -1102,16 +1127,18 @@ Init_ca_obj_monop (void)
1102
1127
  rb_define_singleton_method(rb_cCAMonOp, "__build__",
1103
1128
  rb_ca_monop_s_build, 2);
1104
1129
 
1105
- /* Lazy-view predicate exposed on CArray (covers CAMonOp and
1106
- CALazyMarker). */
1107
- rb_define_method(rb_cCArray, "__lazy_view__?",
1108
- rb_ca_is_lazy_view_p, 0);
1130
+ /* Lazy-view predicate on CArray (covers CAMonOp and CALazyMarker).
1131
+ Protected: the lazy dispatch asks it of the other operands. */
1132
+ rb_define_protected_method(rb_cCArray, "lazy_view?",
1133
+ rb_ca_is_lazy_view_p, 0);
1109
1134
 
1110
1135
  /* op_id accessor for inspect / dump_tree consumers in Ruby. */
1111
1136
  rb_define_method(rb_cCAMonOp, "__op_id__",
1112
1137
  rb_ca_monop_op_id, 0);
1138
+ rb_define_method(rb_cCAMonOp, "__trapping__", rb_ca_monop_trapping, 0);
1113
1139
 
1114
1140
  /* Test instrumentation (not user-facing API). */
1141
+ #ifdef CARRAY_DEV_BUILD
1115
1142
  rb_define_singleton_method(rb_cCAMonOp, "__reset_scratch_counter__",
1116
1143
  rb_ca_monop_s_reset_scratch_counter, 0);
1117
1144
  rb_define_singleton_method(rb_cCAMonOp, "__scratch_count__",
@@ -1120,4 +1147,5 @@ Init_ca_obj_monop (void)
1120
1147
  rb_ca_monop_s_reset_materialise_counter, 0);
1121
1148
  rb_define_singleton_method(rb_cCAMonOp, "__materialise_count__",
1122
1149
  rb_ca_monop_s_materialise_count, 0);
1150
+ #endif
1123
1151
  }