carray 3.0.3 → 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 (212) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -0
  3. data/README.md +3 -2
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_descriptor.h +27 -24
  6. data/ext/ca_axis_dispatch.c +1 -6
  7. data/ext/ca_axis_group.c +27 -27
  8. data/ext/ca_binop_dispatch.c +1 -29
  9. data/ext/ca_binop_dispatch.h +0 -1
  10. data/ext/ca_categorical_iterator.c +205 -134
  11. data/ext/ca_compare.c +7 -18
  12. data/ext/ca_group_iter.c +16 -17
  13. data/ext/ca_kernel_iterator.c +1234 -1994
  14. data/ext/ca_kernel_iterator.h +391 -437
  15. data/ext/ca_monop_dispatch.c +82 -59
  16. data/ext/ca_monop_dispatch.h +1 -0
  17. data/ext/ca_obj_array.c +96 -39
  18. data/ext/ca_obj_bincmp.c +24 -21
  19. data/ext/ca_obj_binop.c +15 -26
  20. data/ext/ca_obj_bitarray.c +3 -2
  21. data/ext/ca_obj_bitfield.c +5 -6
  22. data/ext/ca_obj_block.c +18 -9
  23. data/ext/ca_obj_byte_swap.c +5 -6
  24. data/ext/ca_obj_const_string.c +173 -19
  25. data/ext/ca_obj_face.c +238 -57
  26. data/ext/ca_obj_face.h +19 -0
  27. data/ext/ca_obj_fake.c +15 -29
  28. data/ext/ca_obj_farray.c +6 -3
  29. data/ext/ca_obj_field.c +17 -5
  30. data/ext/ca_obj_fixlen_string.c +2 -3
  31. data/ext/ca_obj_grid.c +406 -132
  32. data/ext/ca_obj_meld.c +172 -34
  33. data/ext/ca_obj_moncmp.c +23 -8
  34. data/ext/ca_obj_monop.c +67 -57
  35. data/ext/ca_obj_object.c +157 -51
  36. data/ext/ca_obj_record.c +12 -7
  37. data/ext/ca_obj_reduce.c +13 -8
  38. data/ext/ca_obj_refer.c +25 -27
  39. data/ext/ca_obj_remap.c +30 -9
  40. data/ext/ca_obj_repeat.c +9 -10
  41. data/ext/ca_obj_roll.c +6 -5
  42. data/ext/ca_obj_select.c +14 -9
  43. data/ext/ca_obj_select_axis.c +15 -47
  44. data/ext/ca_obj_shift.c +11 -1
  45. data/ext/ca_obj_stack.c +179 -34
  46. data/ext/ca_obj_stride.c +162 -115
  47. data/ext/ca_obj_string.c +71 -4
  48. data/ext/ca_obj_tile.c +6 -5
  49. data/ext/ca_obj_time.c +22 -6
  50. data/ext/ca_obj_timedelta.c +22 -6
  51. data/ext/ca_obj_transpose.c +5 -3
  52. data/ext/ca_obj_triop.c +23 -32
  53. data/ext/ca_obj_window.c +190 -26
  54. data/ext/ca_sweep_engine.c +2 -2
  55. data/ext/caframe_csv_reader.c +80 -1
  56. data/ext/carray.h +181 -75
  57. data/ext/carray_access.c +184 -96
  58. data/ext/carray_address_basis.c +343 -87
  59. data/ext/carray_attribute.c +89 -6
  60. data/ext/carray_bincount.c +10 -8
  61. data/ext/carray_broadcast.c +13 -8
  62. data/ext/carray_build_flags.h +3 -0
  63. data/ext/carray_call_cfunc.c +108 -0
  64. data/ext/carray_cast.c +142 -24
  65. data/ext/carray_cast_func.rb +46 -6
  66. data/ext/carray_class.c +4 -3
  67. data/ext/carray_config.h +28 -0
  68. data/ext/carray_conversion.c +16 -7
  69. data/ext/carray_copy.c +51 -14
  70. data/ext/carray_core.c +147 -55
  71. data/ext/carray_count.c +50 -20
  72. data/ext/carray_element.c +76 -33
  73. data/ext/carray_factorize.c +118 -64
  74. data/ext/carray_generate.c +191 -50
  75. data/ext/carray_histogram.c +62 -17
  76. data/ext/carray_hold.c +12 -15
  77. data/ext/carray_index_classifier.c +388 -176
  78. data/ext/carray_internal.h +336 -29
  79. data/ext/carray_kernels_bincmp.c +4453 -0
  80. data/ext/carray_kernels_binop.c +11368 -0
  81. data/ext/carray_kernels_init.c +1734 -0
  82. data/ext/carray_kernels_moncmp.c +2104 -0
  83. data/ext/carray_kernels_monop.c +18369 -0
  84. data/ext/carray_kernels_reduce_aggregate.c +23818 -0
  85. data/ext/carray_kernels_reduce_boolean.c +406 -0
  86. data/ext/carray_kernels_reduce_cumulative.c +10086 -0
  87. data/ext/carray_kernels_reduce_extreme.c +17634 -0
  88. data/ext/carray_kernels_reduce_variance.c +3849 -0
  89. data/ext/carray_kernels_scan.c +4302 -0
  90. data/ext/carray_kernels_search.c +33829 -0
  91. data/ext/carray_kernels_sort.c +10906 -0
  92. data/ext/carray_kernels_triop.c +1864 -0
  93. data/ext/carray_key_windows.c +216 -0
  94. data/ext/carray_lazy.c +170 -111
  95. data/ext/carray_mask.c +292 -72
  96. data/ext/carray_median_percentile.c +82 -28
  97. data/ext/carray_memory_view.c +669 -561
  98. data/ext/carray_operator.c +124 -47
  99. data/ext/carray_order.c +384 -76
  100. data/ext/carray_partition.c +28 -27
  101. data/ext/carray_random.c +67 -13
  102. data/ext/carray_round.c +273 -0
  103. data/ext/carray_scatter.c +63 -38
  104. data/ext/carray_segment.c +3 -3
  105. data/ext/carray_slab.c +91 -28
  106. data/ext/carray_sort.c +35 -35
  107. data/ext/carray_struct.c +19 -18
  108. data/ext/carray_test.c +26 -5
  109. data/ext/carray_undef.c +4 -0
  110. data/ext/carray_utils.c +246 -45
  111. data/ext/mk_call_cfunc.rb +19 -2
  112. data/ext/mkkernel.rb +198 -240
  113. data/ext/ruby_carray.c +10 -4
  114. data/ext/version.h +4 -4
  115. data/lib/carray/attribute.rb +126 -22
  116. data/lib/carray/autoload_carray.rb +8 -31
  117. data/lib/carray/autoload_method_extension.rb +14 -5
  118. data/lib/carray/axis_group.rb +255 -106
  119. data/lib/carray/basics.rb +78 -10
  120. data/lib/carray/bincount_nd.rb +12 -2
  121. data/lib/carray/block_iterator.rb +126 -23
  122. data/lib/carray/boolean_reduce.rb +17 -17
  123. data/lib/carray/categorical.rb +165 -43
  124. data/lib/carray/categorical_iterator.rb +130 -27
  125. data/lib/carray/complex.rb +5 -5
  126. data/lib/carray/conditional.rb +67 -18
  127. data/lib/carray/const_string.rb +14 -5
  128. data/lib/carray/construct.rb +15 -11
  129. data/lib/carray/data_type_extension.rb +1 -0
  130. data/lib/carray/distance_limit.rb +77 -0
  131. data/lib/carray/fixlen_string.rb +7 -1
  132. data/lib/carray/frame/column_verbs.rb +70 -0
  133. data/lib/carray/frame/convert.rb +69 -0
  134. data/lib/carray/frame/csv_parser.rb +111 -17
  135. data/lib/carray/frame/describe.rb +3 -2
  136. data/lib/carray/frame/frame.rb +246 -21
  137. data/lib/carray/frame/group.rb +510 -95
  138. data/lib/carray/frame/io.rb +115 -28
  139. data/lib/carray/frame/join.rb +312 -51
  140. data/lib/carray/frame/records.rb +83 -7
  141. data/lib/carray/frame/reshape.rb +42 -35
  142. data/lib/carray/frame/sort.rb +7 -19
  143. data/lib/carray/frame/verbs.rb +188 -48
  144. data/lib/carray/frame/windows.rb +117 -0
  145. data/lib/carray/frame.rb +2 -0
  146. data/lib/carray/fuse_source.rb +11 -4
  147. data/lib/carray/fusion.rb +40 -16
  148. data/lib/carray/histogram.rb +6 -1
  149. data/lib/carray/inspect.rb +31 -8
  150. data/lib/carray/iterator.rb +85 -7
  151. data/lib/carray/key_windows.rb +620 -0
  152. data/lib/carray/lazy.rb +182 -183
  153. data/lib/carray/mask_gap_fill.rb +63 -57
  154. data/lib/carray/meld_reduce.rb +12 -14
  155. data/lib/carray/methods/bin.rb +26 -18
  156. data/lib/carray/methods/bincount.rb +16 -0
  157. data/lib/carray/methods/bit_string.rb +1 -1
  158. data/lib/carray/methods/choose.rb +16 -3
  159. data/lib/carray/methods/composition.rb +3 -1
  160. data/lib/carray/methods/gather_nd.rb +14 -5
  161. data/lib/carray/methods/insert_block.rb +2 -2
  162. data/lib/carray/methods/is_between.rb +59 -0
  163. data/lib/carray/methods/is_in.rb +53 -36
  164. data/lib/carray/methods/join.rb +1 -6
  165. data/lib/carray/methods/lag.rb +49 -0
  166. data/lib/carray/methods/locate_addr.rb +47 -51
  167. data/lib/carray/methods/mode.rb +1 -1
  168. data/lib/carray/methods/nunique.rb +11 -6
  169. data/lib/carray/methods/resize.rb +3 -3
  170. data/lib/carray/methods/value_counts.rb +4 -11
  171. data/lib/carray/record_order.rb +3 -3
  172. data/lib/carray/rng.rb +3 -0
  173. data/lib/carray/runtime.rb +53 -8
  174. data/lib/carray/segment_iterator.rb +313 -82
  175. data/lib/carray/serialize.rb +24 -10
  176. data/lib/carray/slab_iterator.rb +50 -0
  177. data/lib/carray/string.rb +105 -5
  178. data/lib/carray/string_operation_extension.rb +8 -1
  179. data/lib/carray/struct.rb +9 -21
  180. data/lib/carray/struct_builder.rb +19 -8
  181. data/lib/carray/time.rb +726 -104
  182. data/lib/carray/window_iterator.rb +122 -16
  183. data/lib/carray.rb +7 -0
  184. data/yard-stubs/ca_obj_bitfield.rb +1 -1
  185. data/yard-stubs/ca_obj_field.rb +4 -0
  186. data/yard-stubs/ca_obj_grid.rb +21 -10
  187. data/yard-stubs/ca_obj_refer.rb +2 -1
  188. data/yard-stubs/ca_obj_roll.rb +2 -2
  189. data/yard-stubs/ca_obj_shift.rb +5 -1
  190. data/yard-stubs/ca_obj_tile.rb +1 -1
  191. data/yard-stubs/ca_obj_window.rb +8 -3
  192. data/yard-stubs/carray_access.rb +6 -1
  193. data/yard-stubs/carray_attribute.rb +0 -5
  194. data/yard-stubs/carray_cast.rb +10 -0
  195. data/yard-stubs/carray_conversion.rb +2 -1
  196. data/yard-stubs/carray_copy.rb +4 -2
  197. data/yard-stubs/carray_count.rb +4 -4
  198. data/yard-stubs/carray_element.rb +10 -4
  199. data/yard-stubs/carray_generate.rb +21 -1
  200. data/yard-stubs/carray_mask.rb +26 -2
  201. data/yard-stubs/carray_math.rb +27 -1
  202. data/yard-stubs/carray_median_percentile.rb +5 -2
  203. data/yard-stubs/carray_memory_view.rb +40 -23
  204. data/yard-stubs/carray_order.rb +29 -18
  205. data/yard-stubs/carray_random.rb +13 -4
  206. data/yard-stubs/carray_scatter.rb +14 -9
  207. data/yard-stubs/carray_slab.rb +9 -2
  208. data/yard-stubs/carray_sort.rb +6 -5
  209. data/yard-stubs/carray_test.rb +5 -3
  210. metadata +25 -3
  211. data/lib/carray/arrow_tensor.rb +0 -401
  212. data/lib/carray/table.rb +0 -108
@@ -12,7 +12,8 @@
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
+ #include "ca_binop_dispatch.h" /* ca_binop_op_id_of_table */
16
+ #include "ca_monop_dispatch.h" /* ca_monop_op_id_of_table */
16
17
 
17
18
  /* Operand scratch for a chunked / gathered kernel run.
18
19
 
@@ -31,19 +32,17 @@ ca_op_acquire_operand_scratch (CArray *ca, ca_size_t n_elements)
31
32
 
32
33
  #include "ca_obj_face.h" /* ca_face_reconcile_comparison (comparison Face gate) */
33
34
 
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)
35
+ /* A kernel table with no entry for `data_type`, the type the operator
36
+ would run in. Name the method and the type, or the Face, rather than
37
+ leave it to the table's stub, which knows neither. The eager operators
38
+ and the lazy builders refuse through here, so the two say the same. */
39
+ void
40
+ ca_op_refuse_unimplemented (VALUE operand, int8_t data_type, VALUE eclass)
42
41
  {
43
42
  const char *name = ca_calling_method_name();
44
43
  ca_face_refuse_numeric(operand, name, eclass);
45
44
  rb_raise(eclass, "%s: not defined for data_type %s",
46
- name, ca_type_name[ca->data_type]);
45
+ name, ca_type_name[data_type]);
47
46
  }
48
47
 
49
48
  VALUE rb_mCAMath;
@@ -276,10 +275,11 @@ ca_chunked_run (ca_chunked_run_t *run)
276
275
  /* ---- Kernels that can raise -----------------------------------------
277
276
 
278
277
  An operator kernel runs with its alias operands attached. Most kernels
279
- cannot raise and are called directly. Three kinds can:
278
+ cannot raise and are called directly. Two kinds can:
280
279
 
281
- - an object lane, which calls Ruby for every cell
282
- - an integer division by zero (div, mod, fmod, rcp, rcp_mul)
280
+ - a kernel that can raise on a cell (ca_op_raises_on_cell: an object
281
+ lane, which calls Ruby for every cell; an integer division, remainder
282
+ or reciprocal by zero; an integer power of zero to a negative power)
283
283
  - a data type the operator has no kernel for (the not_implement slot)
284
284
 
285
285
  Those run through ca_op_guarded, which releases the operands the call
@@ -293,41 +293,71 @@ ca_op_is_integer (int8_t dt)
293
293
  return ( dt >= CA_INT8 && dt <= CA_UINT64 );
294
294
  }
295
295
 
296
+ int
297
+ ca_op_raises_on_cell (int kind, int op_id, int8_t dt)
298
+ {
299
+ if ( dt == CA_OBJECT ) {
300
+ return 1;
301
+ }
302
+ if ( ! ca_op_is_integer(dt) ) {
303
+ return 0;
304
+ }
305
+ switch ( kind ) {
306
+ case CA_OP_KIND_MONOP:
307
+ return op_id == CA_MONOP_RCP;
308
+ case CA_OP_KIND_BINOP:
309
+ switch ( op_id ) {
310
+ case CA_BINOP_DIV:
311
+ case CA_BINOP_MOD:
312
+ case CA_BINOP_QUO:
313
+ case CA_BINOP_FMOD:
314
+ case CA_BINOP_RCP_MUL:
315
+ case CA_BINOP_POW:
316
+ case CA_BINOP_IPOWER:
317
+ return 1;
318
+ default:
319
+ return 0;
320
+ }
321
+ default:
322
+ return 0;
323
+ }
324
+ }
325
+
326
+ /* The eager forms: a kernel that can raise on a cell
327
+ (ca_op_raises_on_cell), or a data type the operator has no kernel for. */
296
328
  static int
297
329
  ca_monop_may_raise (ca_monop_func_t func[], int8_t dt)
298
330
  {
299
- return dt == CA_OBJECT
300
- || func[dt] == ca_monop_not_implement
301
- || ( func == ca_monop_rcp && ca_op_is_integer(dt) );
331
+ return func[dt] == ca_monop_not_implement
332
+ || ca_op_raises_on_cell(CA_OP_KIND_MONOP, ca_monop_op_id_of_table(func), dt);
302
333
  }
303
334
 
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
335
  static int
307
336
  ca_binop_may_raise (ca_binop_func_t func[], int8_t dt)
308
337
  {
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);
338
+ return func[dt] == ca_binop_not_implement
339
+ || ca_op_raises_on_cell(CA_OP_KIND_BINOP, ca_binop_op_id_of_table(func), dt);
313
340
  }
314
341
 
315
342
  static int
316
343
  ca_triop_may_raise (ca_triop_func_t func[], int8_t dt)
317
344
  {
318
- return dt == CA_OBJECT || func[dt] == ca_triop_not_implement;
345
+ return func[dt] == ca_triop_not_implement
346
+ || ca_op_raises_on_cell(CA_OP_KIND_TRIOP, -1, dt);
319
347
  }
320
348
 
321
349
  static int
322
350
  ca_moncmp_may_raise (ca_moncmp_func_t func[], int8_t dt)
323
351
  {
324
- return dt == CA_OBJECT || func[dt] == ca_moncmp_not_implement;
352
+ return func[dt] == ca_moncmp_not_implement
353
+ || ca_op_raises_on_cell(CA_OP_KIND_MONCMP, -1, dt);
325
354
  }
326
355
 
327
356
  static int
328
357
  ca_bincmp_may_raise (ca_bincmp_func_t func[], int8_t dt)
329
358
  {
330
- return dt == CA_OBJECT || func[dt] == ca_bincmp_not_implement;
359
+ return func[dt] == ca_bincmp_not_implement
360
+ || ca_op_raises_on_cell(CA_OP_KIND_BINCMP, -1, dt);
331
361
  }
332
362
 
333
363
  /* One kernel call, packed so that it can run under rb_ensure. */
@@ -456,6 +486,7 @@ ca_op_guard_release (VALUE varg)
456
486
  }
457
487
  if ( g->mode[k] == 'w' ) {
458
488
  int tag = 0;
489
+ /* protect: a sync that raises is dropped so that every held array is detached */
459
490
  rb_protect(ca_op_guard_sync, (VALUE) ca, &tag);
460
491
  rb_set_errinfo(Qnil);
461
492
  }
@@ -495,11 +526,16 @@ ca_mask_overlay_or_body (VALUE arg)
495
526
  {
496
527
  ca_mask_overlay_or_t *o = (ca_mask_overlay_or_t *) arg;
497
528
  boolean8_t *ms;
529
+ /* Locals, not o->ma / o->elements: a store through a boolean8_t may alias
530
+ *o, so reading them through o reloads both every iteration and keeps
531
+ the loop from vectorizing. */
532
+ boolean8_t *ma = o->ma;
533
+ ca_size_t elements = o->elements;
498
534
  ca_size_t j;
499
- o->scratch = ca_lazy_arena_acquire(o->elements);
535
+ o->scratch = ca_lazy_arena_acquire(elements);
500
536
  ms = (boolean8_t *) o->scratch;
501
537
  ca_xfer_all(o->mask, o->scratch, CA_XFER_GET);
502
- for ( j = 0; j < o->elements; j++ ) o->ma[j] |= ms[j];
538
+ for ( j = 0; j < elements; j++ ) ma[j] |= ms[j];
503
539
  return Qnil;
504
540
  }
505
541
 
@@ -515,9 +551,10 @@ ca_mask_overlay_or_body (VALUE arg)
515
551
  Memory peak: one mask scratch at a time (= elements bytes, 1 B/cell),
516
552
  released between operands.
517
553
 
518
- CAREFUL: `ca_out` must be a freshly templated entity (driver allocates
519
- via ca_template_safe), so ca_out->mask->ptr is directly writable
520
- without attach.
554
+ CAREFUL: ca_out's mask is written through ca_out->mask->ptr, so ca_out
555
+ is an entity (a fresh one from ca_template_safe) or an attached view.
556
+ Each operand other than a scalar has ca_out's number of elements
557
+ (ArgumentError otherwise); at most 8 operands.
521
558
 
522
559
  This gathers the full mask once; a per-chunk mask gather inside the
523
560
  chunked branch is a possible future optimisation. */
@@ -534,10 +571,22 @@ ca_mask_overlay_safe (CArray *ca_out, int n, ...)
534
571
  va_start(ap, n);
535
572
  for ( i = 0; i < n; i++ ) {
536
573
  slist[i] = va_arg(ap, CArray *);
537
- if ( slist[i] && ca_has_mask(slist[i]) ) any_mask = 1;
538
574
  }
539
575
  va_end(ap);
540
576
 
577
+ /* An array operand's mask is gathered over ca_out's cells. */
578
+ for ( i = 0; i < n; i++ ) {
579
+ CArray *cs = slist[i];
580
+ if ( cs && ! ca_is_scalar(cs) && cs->elements != ca_out->elements ) {
581
+ rb_raise(rb_eArgError,
582
+ "mask operand has %lld elements, the output %lld",
583
+ (long long) cs->elements, (long long) ca_out->elements);
584
+ }
585
+ }
586
+ for ( i = 0; i < n; i++ ) {
587
+ if ( slist[i] && ca_has_mask(slist[i]) ) any_mask = 1;
588
+ }
589
+
541
590
  if ( ! any_mask ) return;
542
591
 
543
592
  ca_update_mask(ca_out);
@@ -609,17 +658,12 @@ ca_mask_overlay_attached (CArray *out, int n,
609
658
  CArray *s0, CArray *s1, CArray *s2)
610
659
  {
611
660
  ca_overlay_args_t a;
612
- int tag = 0;
613
661
  a.out = out;
614
662
  a.n = n;
615
663
  a.src[0] = s0;
616
664
  a.src[1] = s1;
617
665
  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
- }
666
+ ca_fill_or_detach(1, &out, ca_overlay_attached_body, (VALUE) &a);
623
667
  }
624
668
 
625
669
  /* Monop driver. ca1 is input-only (the driver does not attach it); ca2
@@ -642,7 +686,7 @@ rb_ca_call_monop (VALUE self, ca_monop_func_t func[])
642
686
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca1);
643
687
  }
644
688
  if ( func[ca1->data_type] == ca_monop_not_implement ) {
645
- ca_op_refuse_unimplemented(self, ca1, rb_eCADataTypeError);
689
+ ca_op_refuse_unimplemented(self, ca1->data_type, rb_eCADataTypeError);
646
690
  }
647
691
 
648
692
  ca2 = ca_has_mask(ca1) ? ca_template_safe(ca1) : ca_template(ca1);
@@ -650,6 +694,10 @@ rb_ca_call_monop (VALUE self, ca_monop_func_t func[])
650
694
 
651
695
  ca_mask_overlay_safe(ca2, 1, ca1);
652
696
 
697
+ /* No cell to compute: the strided walks below hand the kernel a first
698
+ row before they look at the outer counts. */
699
+ if ( ca2->elements == 0 ) return out;
700
+
653
701
  if ( ca_attach_is_alias(ca1) ) {
654
702
  ca_monop_call_t k;
655
703
  k.f = func[ca1->data_type];
@@ -724,6 +772,7 @@ rb_ca_call_monop (VALUE self, ca_monop_func_t func[])
724
772
  ca2->ptr, 1);
725
773
  }
726
774
  else {
775
+ /* empty: the function returns on a result with no cells, above */
727
776
  while ( 1 ) {
728
777
  ca_size_t off1 = e1_base;
729
778
  for ( k = 0; k < ndim - 1; k++ ) {
@@ -763,6 +812,8 @@ rb_ca_call_monop (VALUE self, ca_monop_func_t func[])
763
812
  }
764
813
  }
765
814
 
815
+ /* the INT64 view of a boolean operand is reached only through ca1 */
816
+ RB_GC_GUARD(self);
766
817
  return out;
767
818
  }
768
819
 
@@ -775,7 +826,7 @@ rb_ca_call_monop_bang (VALUE self, ca_monop_func_t func[])
775
826
 
776
827
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca1);
777
828
  if ( func[ca1->data_type] == ca_monop_not_implement ) {
778
- ca_op_refuse_unimplemented(self, ca1, rb_eCADataTypeError);
829
+ ca_op_refuse_unimplemented(self, ca1->data_type, rb_eCADataTypeError);
779
830
  }
780
831
 
781
832
  ca_attach(ca1); /* window: the kernel runs through ca_op_guarded when it can raise */
@@ -815,7 +866,7 @@ rb_ca_call_monop_typed (VALUE self, ca_monop_func_t func[],
815
866
 
816
867
  out_dt = out_data_types[ca1->data_type];
817
868
  if ( out_dt < 0 ) {
818
- ca_op_refuse_unimplemented(self, ca1, rb_eCADataTypeError);
869
+ ca_op_refuse_unimplemented(self, ca1->data_type, rb_eCADataTypeError);
819
870
  }
820
871
 
821
872
  /* Allocate output with the per-op output data_type. Same shape as input. */
@@ -825,6 +876,10 @@ rb_ca_call_monop_typed (VALUE self, ca_monop_func_t func[],
825
876
  /* Same fast/slow pattern as rb_ca_call_monop. ca1 input-only, ca2 output. */
826
877
  ca_mask_overlay_safe(ca2, 1, ca1);
827
878
 
879
+ /* No cell to compute: the strided walks below hand the kernel a first
880
+ row before they look at the outer counts. */
881
+ if ( ca2->elements == 0 ) return out;
882
+
828
883
  if ( ca_attach_is_alias(ca1) ) {
829
884
  ca_monop_call_t k;
830
885
  k.f = func[ca1->data_type];
@@ -895,6 +950,7 @@ rb_ca_call_monop_typed (VALUE self, ca_monop_func_t func[],
895
950
  ca2->ptr, 1);
896
951
  }
897
952
  else {
953
+ /* empty: the function returns on a result with no cells, above */
898
954
  while ( 1 ) {
899
955
  ca_size_t off1 = e1_base;
900
956
  for ( k = 0; k < ndim - 1; k++ ) {
@@ -1165,7 +1221,7 @@ rb_ca_call_binop (volatile VALUE self, volatile VALUE other,
1165
1221
  TypedData_Get_Struct(other, CArray, &carray_data_type, ca2);
1166
1222
  }
1167
1223
  if ( func[ca1->data_type] == ca_binop_not_implement ) {
1168
- ca_op_refuse_unimplemented(ca_is_face(ca1) ? self : other, ca1,
1224
+ ca_op_refuse_unimplemented(ca_is_face(ca1) ? self : other, ca1->data_type,
1169
1225
  rb_eCADataTypeError);
1170
1226
  }
1171
1227
 
@@ -1210,6 +1266,10 @@ rb_ca_call_binop (volatile VALUE self, volatile VALUE other,
1210
1266
  input-only operand does not raise. */
1211
1267
  ca_mask_overlay_safe(ca3, 2, ca1, ca2);
1212
1268
 
1269
+ /* No cell to compute: the strided walks below hand the kernel a first
1270
+ row before they look at the outer counts. */
1271
+ if ( ca3->elements == 0 ) return out;
1272
+
1213
1273
  /* path decision: alias-attachable both → fast path */
1214
1274
  fast_path = ca_attach_is_alias(ca1) && ca_attach_is_alias(ca2);
1215
1275
 
@@ -1291,7 +1351,7 @@ rb_ca_call_binop (volatile VALUE self, volatile VALUE other,
1291
1351
  Output ca3 is contig entity; per-row output offset = row_idx *
1292
1352
  inner_count. The use_chunked / ALLOCV blocks below remain as a
1293
1353
  fallback (dead on the success path) but fire for views outside the
1294
- CAStride+entity family (CASelect / CAMapping / CAReduce
1354
+ CAStride+entity family (CASelect / CAGrid / CAReduce
1295
1355
  etc.). */
1296
1356
  if ( i1 == 1 && i2 == 1 && nonalias_arrays >= 1 ) {
1297
1357
  extern ca_operation_function_t ca_stride_func;
@@ -1380,6 +1440,7 @@ rb_ca_call_binop (volatile VALUE self, volatile VALUE other,
1380
1440
  ca3->ptr, i3);
1381
1441
  }
1382
1442
  else {
1443
+ /* empty: the function returns on a result with no cells, above */
1383
1444
  while ( 1 ) {
1384
1445
  ca_size_t off1 = e1_base, off2 = e2_base;
1385
1446
  for ( k = 0; k < ndim - 1; k++ ) {
@@ -1519,7 +1580,7 @@ rb_ca_call_binop_bang (VALUE self, VALUE other, ca_binop_func_t func[])
1519
1580
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca1);
1520
1581
  TypedData_Get_Struct(other, CArray, &carray_data_type, ca2);
1521
1582
  if ( func[ca1->data_type] == ca_binop_not_implement ) {
1522
- ca_op_refuse_unimplemented(self, ca1, rb_eCADataTypeError);
1583
+ ca_op_refuse_unimplemented(self, ca1->data_type, rb_eCADataTypeError);
1523
1584
  }
1524
1585
 
1525
1586
  self_is_scalar = RTEST(rb_obj_is_cscalar(self));
@@ -1589,6 +1650,8 @@ rb_ca_call_binop_bang (VALUE self, VALUE other, ca_binop_func_t func[])
1589
1650
 
1590
1651
  ca_sync_detach(ca1);
1591
1652
 
1653
+ /* a cast or broadcast view of other is reached only through ca2 */
1654
+ RB_GC_GUARD(other);
1592
1655
  return self;
1593
1656
  }
1594
1657
 
@@ -1732,7 +1795,7 @@ rb_ca_call_triop (VALUE self, VALUE other2, VALUE other3,
1732
1795
  if ( func[ca1->data_type] == ca_triop_not_implement ) {
1733
1796
  ca_op_refuse_unimplemented(ca_is_face(ca1) ? self
1734
1797
  : ca_is_face(ca2) ? other2 : other3,
1735
- ca1, rb_eCADataTypeError);
1798
+ ca1->data_type, rb_eCADataTypeError);
1736
1799
  }
1737
1800
 
1738
1801
  /* Element-count check: all non-scalar operands must agree. */
@@ -1855,6 +1918,10 @@ rb_ca_call_triop (VALUE self, VALUE other2, VALUE other3,
1855
1918
  }
1856
1919
  }
1857
1920
 
1921
+ /* the INT64 views of a boolean triop are reached only through ca1..ca3 */
1922
+ RB_GC_GUARD(self);
1923
+ RB_GC_GUARD(other2);
1924
+ RB_GC_GUARD(other3);
1858
1925
  return out;
1859
1926
  }
1860
1927
 
@@ -1883,7 +1950,7 @@ rb_ca_call_triop_bang (VALUE self, VALUE other2, VALUE other3,
1883
1950
  TypedData_Get_Struct(other2, CArray, &carray_data_type, ca2);
1884
1951
  TypedData_Get_Struct(other3, CArray, &carray_data_type, ca3);
1885
1952
  if ( func[ca1->data_type] == ca_triop_not_implement ) {
1886
- ca_op_refuse_unimplemented(self, ca1, rb_eCADataTypeError);
1953
+ ca_op_refuse_unimplemented(self, ca1->data_type, rb_eCADataTypeError);
1887
1954
  }
1888
1955
 
1889
1956
  /* self is the write target, so the destination rule applies to each
@@ -2018,6 +2085,10 @@ rb_ca_call_triop_bang (VALUE self, VALUE other2, VALUE other3,
2018
2085
 
2019
2086
  ca_sync_detach(ca1);
2020
2087
 
2088
+ /* cast or broadcast views of the operands are reached only through
2089
+ ca2 and ca3 */
2090
+ RB_GC_GUARD(other2);
2091
+ RB_GC_GUARD(other3);
2021
2092
  return self;
2022
2093
  }
2023
2094
 
@@ -2031,7 +2102,7 @@ rb_ca_call_moncmp (VALUE self, ca_moncmp_func_t func[])
2031
2102
 
2032
2103
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca1);
2033
2104
  if ( func[ca1->data_type] == ca_moncmp_not_implement ) {
2034
- ca_op_refuse_unimplemented(self, ca1, rb_eCADataTypeError);
2105
+ ca_op_refuse_unimplemented(self, ca1->data_type, rb_eCADataTypeError);
2035
2106
  }
2036
2107
 
2037
2108
  if ( ca_is_scalar(ca1) ) {
@@ -2045,6 +2116,9 @@ rb_ca_call_moncmp (VALUE self, ca_moncmp_func_t func[])
2045
2116
 
2046
2117
  ca_mask_overlay_safe(ca2, 1, ca1);
2047
2118
 
2119
+ /* No cell to compute, as in the operators above. */
2120
+ if ( ca2->elements == 0 ) return out;
2121
+
2048
2122
  /* The kernel's masked branch skips masked cells, so they would keep the
2049
2123
  uninitialised data from rb_carray_new. Zero the output when a mask is
2050
2124
  present so masked cells read as 0 (matching binop's ca_template_safe
@@ -2302,7 +2376,7 @@ rb_ca_call_bincmp (volatile VALUE self, volatile VALUE other,
2302
2376
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca1);
2303
2377
  TypedData_Get_Struct(other, CArray, &carray_data_type, ca2);
2304
2378
  if ( func[ca1->data_type] == ca_bincmp_not_implement ) {
2305
- ca_op_refuse_unimplemented(ca_is_face(ca1) ? self : other, ca1,
2379
+ ca_op_refuse_unimplemented(ca_is_face(ca1) ? self : other, ca1->data_type,
2306
2380
  rb_eTypeError);
2307
2381
  }
2308
2382
 
@@ -2338,6 +2412,9 @@ rb_ca_call_bincmp (volatile VALUE self, volatile VALUE other,
2338
2412
 
2339
2413
  ca_mask_overlay_safe(ca3, 2, ca1, ca2);
2340
2414
 
2415
+ /* No cell to compute, as in the operators above. */
2416
+ if ( ca3->elements == 0 ) return out;
2417
+
2341
2418
  /* The kernel's masked branch skips masked cells, so they would keep the
2342
2419
  uninitialised data from rb_carray_new. Zero the output when a mask is
2343
2420
  present so masked cells read as 0 (matching binop's ca_template_safe