carray 3.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -1
  3. data/CHANGELOG.md +845 -3
  4. data/{NEWS.md → CHANGELOG.v1.md} +1 -1
  5. data/README.md +33 -33
  6. data/carray.gemspec +12 -8
  7. data/ext/ca_axis_dispatch.c +33 -4
  8. data/ext/ca_axis_group.c +256 -114
  9. data/ext/ca_binop_dispatch.c +5 -6
  10. data/ext/ca_binop_dispatch.h +0 -7
  11. data/ext/ca_categorical_iterator.c +121 -67
  12. data/ext/ca_for_each_element.h +6 -8
  13. data/ext/ca_group_iter.c +1 -1
  14. data/ext/ca_kernel_iterator.c +386 -74
  15. data/ext/ca_kernel_iterator.h +145 -45
  16. data/ext/ca_obj_array.c +62 -20
  17. data/ext/ca_obj_bincmp.c +57 -27
  18. data/ext/ca_obj_binop.c +86 -30
  19. data/ext/ca_obj_block.c +4 -4
  20. data/ext/ca_obj_const_string.c +88 -27
  21. data/ext/ca_obj_face.c +104 -0
  22. data/ext/ca_obj_face.h +48 -1
  23. data/ext/ca_obj_fake.c +11 -0
  24. data/ext/ca_obj_fixlen_string.c +21 -6
  25. data/ext/ca_obj_grid.c +11 -3
  26. data/ext/ca_obj_meld.c +125 -22
  27. data/ext/ca_obj_moncmp.c +33 -14
  28. data/ext/ca_obj_monop.c +19 -7
  29. data/ext/ca_obj_object.c +107 -4
  30. data/ext/ca_obj_record.c +3 -1
  31. data/ext/ca_obj_refer.c +30 -6
  32. data/ext/ca_obj_roll.c +13 -5
  33. data/ext/ca_obj_select.c +49 -34
  34. data/ext/ca_obj_select_axis.c +16 -6
  35. data/ext/ca_obj_shift.c +3 -3
  36. data/ext/ca_obj_stack.c +3 -8
  37. data/ext/ca_obj_stride.c +178 -24
  38. data/ext/ca_obj_string.c +11 -5
  39. data/ext/ca_obj_tile.c +13 -5
  40. data/ext/ca_obj_time.c +3 -1
  41. data/ext/ca_obj_timedelta.c +3 -1
  42. data/ext/ca_obj_transpose.c +1 -1
  43. data/ext/ca_obj_triop.c +62 -23
  44. data/ext/ca_obj_window.c +94 -19
  45. data/ext/ca_op_cmplx64.h +123 -0
  46. data/ext/ca_op_ipower.c +1 -5
  47. data/ext/ca_rng_normal.h +42 -0
  48. data/ext/ca_rng_xoshiro256pp.h +105 -0
  49. data/ext/ca_sort_kernels.h +5 -5
  50. data/ext/ca_sweep_engine.c +362 -155
  51. data/ext/ca_sweep_engine.h +39 -9
  52. data/ext/ca_transform_common.c +7 -0
  53. data/ext/carray.h +148 -132
  54. data/ext/carray_access.c +83 -49
  55. data/ext/carray_address_basis.c +590 -0
  56. data/ext/carray_bincount.c +8 -8
  57. data/ext/carray_broadcast.c +102 -9
  58. data/ext/carray_build_flags.h +3 -0
  59. data/ext/carray_call_cfunc.c +2010 -335
  60. data/ext/carray_call_cfunc.h +153 -0
  61. data/ext/carray_cast.c +184 -72
  62. data/ext/carray_conversion.c +22 -22
  63. data/ext/carray_copy.c +67 -32
  64. data/ext/carray_core.c +140 -13
  65. data/ext/carray_count.c +10 -11
  66. data/ext/carray_element.c +7 -7
  67. data/ext/carray_factorize.c +72 -51
  68. data/ext/carray_hold.c +1 -1
  69. data/ext/carray_index_classifier.c +7 -21
  70. data/ext/carray_internal.h +36 -3
  71. data/ext/carray_kernels_bincmp.c +1 -0
  72. data/ext/carray_kernels_binop.c +320 -298
  73. data/ext/carray_kernels_init.c +1095 -0
  74. data/ext/carray_kernels_map.c +1 -0
  75. data/ext/carray_kernels_moncmp.c +1 -0
  76. data/ext/carray_kernels_monop.c +412 -411
  77. data/ext/carray_kernels_reduce_aggregate.c +256 -87
  78. data/ext/carray_kernels_reduce_boolean.c +1 -0
  79. data/ext/carray_kernels_reduce_cumulative.c +324 -54
  80. data/ext/carray_kernels_reduce_extreme.c +605 -58
  81. data/ext/carray_kernels_reduce_variance.c +1 -0
  82. data/ext/carray_kernels_scan.c +5 -4
  83. data/ext/carray_kernels_search.c +187 -106
  84. data/ext/carray_kernels_sort.c +1 -0
  85. data/ext/carray_kernels_triop.c +1 -0
  86. data/ext/carray_lazy.c +175 -5
  87. data/ext/carray_loop.c +7 -1
  88. data/ext/carray_mask.c +29 -13
  89. data/ext/carray_median_percentile.c +56 -1
  90. data/ext/carray_memory_view.c +46 -21
  91. data/ext/carray_operator.c +50 -53
  92. data/ext/carray_order.c +1 -1
  93. data/ext/carray_partition.c +4 -4
  94. data/ext/carray_random.c +391 -47
  95. data/ext/carray_scatter.c +1 -1
  96. data/ext/carray_slab.c +13 -0
  97. data/ext/carray_sort.c +22 -24
  98. data/ext/carray_sort_kernel.c +10 -10
  99. data/ext/carray_test.c +1 -1
  100. data/ext/extconf.rb +18 -0
  101. data/ext/mk_call_cfunc.rb +323 -145
  102. data/ext/mkkernel.rb +651 -129
  103. data/ext/ruby_carray.c +28 -17
  104. data/ext/version.h +4 -4
  105. data/lib/carray/autoload_carray.rb +24 -3
  106. data/lib/carray/autoload_method_extension.rb +14 -1
  107. data/lib/carray/axis_group.rb +84 -7
  108. data/lib/carray/basics.rb +63 -59
  109. data/lib/carray/bincount_nd.rb +12 -12
  110. data/lib/carray/block_iterator.rb +111 -25
  111. data/lib/carray/categorical.rb +154 -37
  112. data/lib/carray/categorical_iterator.rb +300 -151
  113. data/lib/carray/conditional.rb +14 -14
  114. data/lib/carray/const_string.rb +131 -27
  115. data/lib/carray/construct.rb +61 -0
  116. data/lib/carray/core_extensions.rb +62 -5
  117. data/lib/carray/data_type_extension.rb +21 -1
  118. data/lib/carray/data_type_limits.rb +91 -0
  119. data/lib/carray/fixlen_string.rb +1 -1
  120. data/lib/carray/frame/concat.rb +3 -3
  121. data/lib/carray/frame/convert.rb +1 -1
  122. data/lib/carray/frame/csv_parser.rb +11 -4
  123. data/lib/carray/frame/frame.rb +85 -21
  124. data/lib/carray/frame/group.rb +36 -3
  125. data/lib/carray/frame/io.rb +224 -15
  126. data/lib/carray/frame/records.rb +18 -4
  127. data/lib/carray/frame/sort.rb +1 -1
  128. data/lib/carray/frame/verbs.rb +32 -12
  129. data/lib/carray/fuse_source.rb +123 -0
  130. data/lib/carray/fusion.rb +218 -0
  131. data/lib/carray/histogram.rb +16 -16
  132. data/lib/carray/inspect.rb +43 -16
  133. data/lib/carray/iterator.rb +147 -3
  134. data/lib/carray/lazy.rb +88 -73
  135. data/lib/carray/mask_gap_fill.rb +3 -1
  136. data/lib/carray/meld_reduce.rb +2 -2
  137. data/lib/carray/methods/align_addr.rb +1 -1
  138. data/lib/carray/methods/composition.rb +1 -1
  139. data/lib/carray/methods/discovery_along.rb +74 -0
  140. data/lib/carray/methods/factorize.rb +50 -0
  141. data/lib/carray/methods/is_in.rb +25 -14
  142. data/lib/carray/methods/locate_addr.rb +81 -3
  143. data/lib/carray/methods/mask_duplicates.rb +36 -2
  144. data/lib/carray/methods/meshgrid.rb +4 -5
  145. data/lib/carray/methods/mode.rb +2 -2
  146. data/lib/carray/methods/nunique.rb +23 -2
  147. data/lib/carray/methods/repeat.rb +110 -0
  148. data/lib/carray/methods/snap.rb +7 -2
  149. data/lib/carray/methods/unique.rb +44 -4
  150. data/lib/carray/methods/value_counts.rb +2 -2
  151. data/lib/carray/rng.rb +86 -0
  152. data/lib/carray/runtime.rb +0 -19
  153. data/lib/carray/slab_iterator.rb +77 -19
  154. data/lib/carray/string_operation_extension.rb +10 -6
  155. data/lib/carray/time.rb +906 -491
  156. data/lib/carray/window_iterator.rb +421 -27
  157. data/lib/carray.rb +7 -5
  158. data/yard-stubs/ca_obj_array.rb +385 -0
  159. data/yard-stubs/ca_obj_bitarray.rb +38 -0
  160. data/yard-stubs/ca_obj_bitfield.rb +43 -0
  161. data/yard-stubs/ca_obj_block.rb +68 -0
  162. data/yard-stubs/ca_obj_byte_swap.rb +56 -0
  163. data/yard-stubs/ca_obj_fake.rb +31 -0
  164. data/yard-stubs/ca_obj_farray.rb +32 -0
  165. data/yard-stubs/ca_obj_field.rb +45 -0
  166. data/yard-stubs/ca_obj_grid.rb +35 -0
  167. data/yard-stubs/ca_obj_refer.rb +72 -0
  168. data/yard-stubs/ca_obj_roll.rb +45 -0
  169. data/yard-stubs/ca_obj_shift.rb +43 -0
  170. data/yard-stubs/ca_obj_stride.rb +181 -0
  171. data/yard-stubs/ca_obj_tile.rb +29 -0
  172. data/yard-stubs/ca_obj_transpose.rb +40 -0
  173. data/yard-stubs/ca_obj_window.rb +57 -0
  174. data/yard-stubs/carray_access.rb +131 -0
  175. data/yard-stubs/carray_attribute.rb +246 -0
  176. data/yard-stubs/carray_broadcast.rb +37 -0
  177. data/yard-stubs/carray_cast.rb +489 -0
  178. data/yard-stubs/carray_class.rb +65 -0
  179. data/yard-stubs/carray_conversion.rb +76 -0
  180. data/yard-stubs/carray_copy.rb +79 -0
  181. data/yard-stubs/carray_core.rb +34 -0
  182. data/yard-stubs/carray_count.rb +84 -0
  183. data/yard-stubs/carray_element.rb +108 -0
  184. data/yard-stubs/carray_generate.rb +66 -0
  185. data/yard-stubs/carray_lazy.rb +228 -0
  186. data/yard-stubs/carray_loop.rb +140 -0
  187. data/yard-stubs/carray_mask.rb +259 -0
  188. data/yard-stubs/carray_math.rb +1615 -0
  189. data/yard-stubs/carray_mathfunc.rb +45 -0
  190. data/yard-stubs/carray_median_percentile.rb +103 -0
  191. data/yard-stubs/carray_memory_view.rb +163 -0
  192. data/yard-stubs/carray_order.rb +252 -0
  193. data/yard-stubs/carray_random.rb +89 -0
  194. data/yard-stubs/carray_scatter.rb +106 -0
  195. data/yard-stubs/carray_slab.rb +59 -0
  196. data/yard-stubs/carray_sort.rb +165 -0
  197. data/yard-stubs/carray_test.rb +85 -0
  198. data/yard-stubs/carray_undef.rb +64 -0
  199. data/yard-stubs/carray_utils.rb +97 -0
  200. data/yard-stubs/ruby_carray.rb +193 -0
  201. metadata +67 -10
  202. data/ext/ca_obj_unbound_repeat.c +0 -496
data/ext/ca_obj_binop.c CHANGED
@@ -70,11 +70,21 @@ typedef struct CABinOp {
70
70
  collapse and attach lifecycle traversal) */
71
71
  uint32_t attach;
72
72
  uint8_t nosync;
73
+ /* ---- CAMultiParent conformance (CA_FLAG_MULTI_PARENTS): n_parents and
74
+ parents[] sit immediately after the CAView header, as the layout
75
+ convention in carray.h requires, so generic code that would walk a
76
+ single ->parent folds over both operands instead. What this buys
77
+ here is ca_has_mask: it answers for a two-parent view by asking both
78
+ and creating the mask on demand, which lets the mask stay unbuilt
79
+ until something reads it. ---- */
80
+ int32_t n_parents; /* always 2 */
81
+ CArray **parents; /* = &operands[0]; no separate allocation */
73
82
  /* CABinOp-specific tail */
74
83
  CArray *right;
75
84
  uint16_t op_id;
76
85
  uint8_t right_is_scalar; /* 1 ⇒ right has elements == 1, walk
77
86
  with element-stride 0 (= broadcast) */
87
+ CArray *operands[2]; /* {left, right}; what parents points at */
78
88
  } CABinOp;
79
89
 
80
90
  static size_t
@@ -125,7 +135,7 @@ ca_binop_setup (CABinOp *ca, CArray *left, CArray *right, uint16_t op_id)
125
135
 
126
136
  ca->obj_type = CA_OBJ_BINOP;
127
137
  ca->data_type = out_dt;
128
- ca->flags = CA_FLAG_READ_ONLY;
138
+ ca->flags = CA_FLAG_READ_ONLY | CA_FLAG_MULTI_PARENTS;
129
139
  ca->ndim = left->ndim;
130
140
  ca->bytes = out_bytes;
131
141
  ca->elements = left->elements;
@@ -139,6 +149,10 @@ ca_binop_setup (CABinOp *ca, CArray *left, CArray *right, uint16_t op_id)
139
149
  ca->nosync = 0;
140
150
  ca->right = right;
141
151
  ca->op_id = op_id;
152
+ ca->operands[0] = left;
153
+ ca->operands[1] = right;
154
+ ca->parents = ca->operands;
155
+ ca->n_parents = 2;
142
156
  /* right_is_scalar fast path: when right holds a single element,
143
157
  xfer_stride pulls only that cell into scratch and walks the
144
158
  kernel with element-stride 0 (broadcast). The symmetric
@@ -148,9 +162,10 @@ ca_binop_setup (CABinOp *ca, CArray *left, CArray *right, uint16_t op_id)
148
162
 
149
163
  memcpy(ca->dim, left->dim, left->ndim * sizeof(ca_size_t));
150
164
 
151
- if ( ca_has_mask(left) || ca_has_mask(right) ) {
152
- ca_create_mask(ca);
153
- }
165
+ /* The mask is NOT built here. ca_has_mask folds over parents[] for a
166
+ multi-parent view and creates it on demand, so an expression whose mask
167
+ nobody reads never allocates one -- and a chain of masked nodes builds
168
+ one array at the end rather than one per node. */
154
169
 
155
170
  if ( ca_is_scalar(left) && ca_is_scalar(right) ) {
156
171
  ca_set_flag(ca, CA_FLAG_SCALAR);
@@ -291,25 +306,25 @@ ca_binop_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
291
306
  one_counts[k] = 1;
292
307
  one_strides[k] = right_bytes;
293
308
  }
294
- scratch = ca_lazy_arena_acquire(right_bytes);
309
+ scratch = ( bo->data_type == CA_OBJECT )
310
+ ? ca_lazy_arena_acquire_object(1)
311
+ : ca_lazy_arena_acquire(right_bytes);
295
312
  ca_binop_scratch_acquire_count++;
296
313
  ca_xfer_stride(bo->right, one_starts, one_counts, one_strides,
297
314
  scratch, CA_XFER_GET);
298
315
  right_step = 0;
299
316
  }
300
317
  else {
301
- /* Same-shape right. Pull with the same byte layout the caller
302
- requested for the output; right.bytes matches output.bytes
303
- because right.data_type == out.data_type by cast-before. */
304
- ca_size_t right_strides[CA_RANK_MAX];
305
- ca_size_t s = right_bytes;
306
- for ( k = bo->ndim - 1; k >= 0; k-- ) {
307
- right_strides[k] = s;
308
- s *= counts[k];
309
- }
310
- scratch = ca_lazy_arena_acquire(slab_n * right_bytes);
318
+ /* Same-shape right. `strides` is the step through the operand, the
319
+ same one the left was pulled with -- recomputing a packed one here
320
+ reads the right operand's cells 0, 1, 2 against the left's 0, 2, 4.
321
+ right.bytes matches output.bytes because right.data_type ==
322
+ out.data_type by cast-before, so the same steps serve both. */
323
+ scratch = ( bo->data_type == CA_OBJECT )
324
+ ? ca_lazy_arena_acquire_object(slab_n)
325
+ : ca_lazy_arena_acquire(slab_n * right_bytes);
311
326
  ca_binop_scratch_acquire_count++;
312
- ca_xfer_stride(bo->right, starts, counts, right_strides, scratch,
327
+ ca_xfer_stride(bo->right, starts, counts, strides, scratch,
313
328
  CA_XFER_GET);
314
329
  right_step = 1;
315
330
  }
@@ -438,6 +453,14 @@ ca_binop_func_allocate (void *ap)
438
453
  {
439
454
  CABinOp *ca = (CABinOp *) ap;
440
455
  ca->ptr = xmalloc(ca_length(ca));
456
+ /* CA_OBJECT cells are VALUEs and this buffer is about to be marked as
457
+ soon as the view is, so it must not be handed to the GC as raw
458
+ xmalloc garbage. */
459
+ if ( ca->data_type == CA_OBJECT ) {
460
+ VALUE *p = (VALUE *) ca->ptr;
461
+ ca_size_t i;
462
+ for ( i = 0; i < ca->elements; i++ ) *p++ = Qnil;
463
+ }
441
464
  }
442
465
 
443
466
  static void
@@ -459,6 +482,14 @@ ca_binop_func_attach (void *ap)
459
482
  s = ca->bytes;
460
483
  for ( k = ca->ndim - 1; k >= 0; k-- ) { native[k] = s; s *= ca->dim[k]; }
461
484
  for ( k = 0; k < ca->ndim; k++ ) starts[k] = 0;
485
+ /* CA_OBJECT cells are VALUEs and this buffer is about to be marked as
486
+ soon as the view is, so it must not be handed to the GC as raw
487
+ xmalloc garbage. */
488
+ if ( ca->data_type == CA_OBJECT ) {
489
+ VALUE *p = (VALUE *) ca->ptr;
490
+ ca_size_t i;
491
+ for ( i = 0; i < ca->elements; i++ ) *p++ = Qnil;
492
+ }
462
493
  ca_binop_func_xfer_stride(ca, starts, ca->dim, native, ca->ptr, CA_XFER_GET);
463
494
  }
464
495
 
@@ -517,6 +548,19 @@ ca_binop_func_create_mask (void *ap)
517
548
  ( bo->op_id == CA_BINOP_BIT_AND || bo->op_id == CA_BINOP_BIT_OR ) );
518
549
  is_or = ( bo->op_id == CA_BINOP_BIT_OR );
519
550
 
551
+ /* One masked operand and no Kleene resolution: the answer is that
552
+ operand's mask, cell for cell. Share it the way CAMonOp shares its
553
+ parent's, rather than allocating a copy per node -- a chain of k
554
+ binops over one masked array then holds one boolean array, not k. */
555
+ if ( ! kleene && ( has_l != has_r ) ) {
556
+ CArray *src = has_l ? l : r;
557
+ if ( src->elements == bo->elements ) {
558
+ bo->mask = (CArray *) ca_refer_new(src->mask, CA_BOOLEAN,
559
+ bo->ndim, bo->dim, 0, 0);
560
+ return;
561
+ }
562
+ }
563
+
520
564
  bo->mask = (CArray *) carray_new(CA_BOOLEAN, bo->ndim, bo->dim, 0, NULL);
521
565
  dst = (boolean8_t *) bo->mask->ptr;
522
566
  n = bo->elements;
@@ -524,8 +568,18 @@ ca_binop_func_create_mask (void *ap)
524
568
  /* Kleene needs operand values even where an operand is unmasked. */
525
569
  need_l = has_l || kleene;
526
570
  need_r = has_r || kleene;
527
- if ( need_l ) ca_attach(l);
528
- if ( need_r ) ca_attach(r);
571
+ /* Only Kleene reads operand values. Every other op reads the operand
572
+ masks alone, and attaching the operand to reach its mask materialises
573
+ the whole subexpression under it -- so a chain of k masked binops
574
+ evaluates its own subtree k times over. Attach what is read. */
575
+ if ( kleene ) {
576
+ if ( need_l ) ca_attach(l);
577
+ if ( need_r ) ca_attach(r);
578
+ }
579
+ else {
580
+ if ( has_l ) ca_attach(l->mask);
581
+ if ( has_r ) ca_attach(r->mask);
582
+ }
529
583
 
530
584
  lm = has_l ? (boolean8_t *) l->mask->ptr : NULL;
531
585
  rm = has_r ? (boolean8_t *) r->mask->ptr : NULL;
@@ -550,8 +604,14 @@ ca_binop_func_create_mask (void *ap)
550
604
  dst[i] = m;
551
605
  }
552
606
 
553
- if ( need_l ) ca_detach(l);
554
- if ( need_r ) ca_detach(r);
607
+ if ( kleene ) {
608
+ if ( need_r ) ca_detach(r);
609
+ if ( need_l ) ca_detach(l);
610
+ }
611
+ else {
612
+ if ( has_r ) ca_detach(r->mask);
613
+ if ( has_l ) ca_detach(l->mask);
614
+ }
555
615
  }
556
616
 
557
617
  ca_operation_function_t ca_binop_func = {
@@ -682,8 +742,7 @@ rb_ca_binop_build (VALUE l_cary, VALUE r_cary, uint16_t op_id)
682
742
 
683
743
  /* === Step 2: broadcast via ca_broadcast_pair (same-ndim size-1
684
744
  expansion). No-op when shapes already match or either operand
685
- is scalar; also a no-op for incompatible shapes, which the
686
- final elements-mismatch check below catches. */
745
+ is scalar; refuses a pair that cannot be brought to one shape. */
687
746
  ca_broadcast_pair(&l_resolved, &r_resolved);
688
747
  TypedData_Get_Struct(l_resolved, CArray, &carray_data_type, l);
689
748
  TypedData_Get_Struct(r_resolved, CArray, &carray_data_type, r);
@@ -735,14 +794,12 @@ rb_ca_binop_build (VALUE l_cary, VALUE r_cary, uint16_t op_id)
735
794
  }
736
795
  }
737
796
  else {
738
- /* Cross-ndim or otherwise incompatible shapes; see the
739
- builder header comment for why CArray rejects implicit
740
- cross-ndim promotion. */
797
+ /* Unreachable from Ruby: ca_broadcast_pair above refuses a pair
798
+ it cannot bring to one shape, and states the rule there. Kept
799
+ so a caller reaching this builder by another route still finds
800
+ the kernel guarded. */
741
801
  rb_raise(rb_eArgError,
742
- "CABinOp: shape mismatch (%lld vs %lld) — only same-"
743
- "ndim size-1 broadcast is supported; cross-ndim "
744
- "promotion is not adopted in CArray "
745
- "(reshape explicitly)",
802
+ "CABinOp: element count mismatch (%lld vs %lld)",
746
803
  (long long) l->elements, (long long) r->elements);
747
804
  }
748
805
  }
@@ -866,7 +923,6 @@ Init_ca_obj_binop (void)
866
923
  rb_define_const(rb_cCABinOp, "OP_AND", INT2NUM(CA_BINOP_AND));
867
924
  rb_define_const(rb_cCABinOp, "OP_OR", INT2NUM(CA_BINOP_OR));
868
925
  rb_define_const(rb_cCABinOp, "OP_XOR", INT2NUM(CA_BINOP_XOR));
869
- rb_define_const(rb_cCABinOp, "OP_REMINDER", INT2NUM(CA_BINOP_REMINDER));
870
926
 
871
927
  rb_define_alloc_func(rb_cCABinOp, rb_ca_binop_s_allocate);
872
928
  rb_define_method(rb_cCABinOp, "initialize_copy",
data/ext/ca_obj_block.c CHANGED
@@ -269,8 +269,10 @@ rb_cb_initialize_copy (VALUE self, VALUE other)
269
269
  }
270
270
 
271
271
  /* Per-axis accessor macro: defines a method that returns the named
272
- CABlock tail array (size0 / start / step / count) as a Ruby Array.
273
- `offset` is a single Integer and uses its own accessor below. */
272
+ CABlock tail array (size0 / start / step) as a Ruby Array.
273
+ `offset` is a single Integer and uses its own accessor below.
274
+ `count` has no accessor: it is what `shape` already answers, and the
275
+ name belongs to CArray#count. */
274
276
  #define rb_cb_get_attr_ary(name) \
275
277
  rb_cb_ ## name (VALUE self) \
276
278
  { \
@@ -288,7 +290,6 @@ rb_cb_ ## name (VALUE self) \
288
290
  static VALUE rb_cb_get_attr_ary(size0)
289
291
  static VALUE rb_cb_get_attr_ary(start)
290
292
  static VALUE rb_cb_get_attr_ary(step)
291
- static VALUE rb_cb_get_attr_ary(count)
292
293
 
293
294
  static VALUE
294
295
  rb_cb_offset (VALUE self)
@@ -376,7 +377,6 @@ Init_ca_obj_block (void)
376
377
  rb_define_method(rb_cCABlock, "size0", rb_cb_size0, 0);
377
378
  rb_define_method(rb_cCABlock, "start", rb_cb_start, 0);
378
379
  rb_define_method(rb_cCABlock, "step", rb_cb_step, 0);
379
- rb_define_method(rb_cCABlock, "count", rb_cb_count, 0);
380
380
  rb_define_method(rb_cCABlock, "offset", rb_cb_offset, 0);
381
381
 
382
382
  rb_define_method(rb_cCABlock, "idx2addr0", rb_cb_idx2addr0, -1);
@@ -38,10 +38,10 @@
38
38
  typedef struct {
39
39
  /* === CAView prefix === */
40
40
  int16_t obj_type;
41
- int8_t data_type; /* CA_FIXLEN surface (numeric gate); storage is int64 */
41
+ int8_t data_type; /* CA_FIXLEN surface (numeric gate); storage is fixlen-16 */
42
42
  int8_t ndim;
43
43
  int32_t flags; /* CA_FLAG_IS_FACE set */
44
- ca_size_t bytes; /* sizeof(int64_t) = 8 (one offset per element) */
44
+ ca_size_t bytes; /* 2 * sizeof(int64_t) = 16 (one (start,end) pair per element) */
45
45
  ca_size_t elements;
46
46
  ca_size_t *dim;
47
47
  char *ptr;
@@ -227,7 +227,9 @@ ca_operation_function_t ca_const_string_func = {
227
227
  ca_face_xfer_addrs,
228
228
  NULL, /* fold_stride: identity Face is not foldable */
229
229
  ca_face_xfer_stride,
230
- ca_face_xfer_all
230
+ ca_face_xfer_all,
231
+ .fill_addrs = ca_face_fill_addrs,
232
+ .fill_stride = ca_face_fill_stride,
231
233
  };
232
234
 
233
235
  /* ------------------------------------------------------------------- */
@@ -297,6 +299,54 @@ rb_ca_const_string_wrap (VALUE parent_val, VALUE buffer, int encoding_id)
297
299
  rb_raise(rb_eTypeError, "CAConstString.wrap: buffer: must be a String");
298
300
  }
299
301
 
302
+ /* This is the one door through which already-built offsets enter, so it
303
+ validates rather than assumes. Everything downstream -- the per-cell
304
+ decode, the native byte scans, the sort comparator -- reads `buf + start`
305
+ for `end - start` bytes on the strength of these pairs; an out-of-range
306
+ one is an out-of-bounds read of the heap, not a wrong answer. Refuse it
307
+ here, while the input is still in the caller's hands, rather than letting
308
+ it surface as an IndexError from a decode or as a crash from a scan.
309
+
310
+ A masked cell is exempt: its bytes may be anything at all, that being the
311
+ CArray mask contract, and no reader dereferences them (the scan skips on
312
+ the mask before touching an offset; `.value`, the explicit strip, raises
313
+ from the decode). */
314
+ {
315
+ ca_size_t i, n;
316
+ int64_t *pair;
317
+ boolean8_t *m;
318
+
319
+ ca_attach(parent);
320
+ ca_update_mask(parent);
321
+ n = parent->elements;
322
+ pair = (int64_t *) parent->ptr;
323
+ m = parent->mask ? (boolean8_t *) parent->mask->ptr : NULL;
324
+ for ( i = 0; i < n; i++ ) {
325
+ int64_t start = pair[2 * i], end = pair[2 * i + 1];
326
+ if ( m && m[i] ) {
327
+ continue;
328
+ }
329
+ if ( start < 0 || end < start || end > (int64_t) RSTRING_LEN(buffer) ) {
330
+ ca_detach(parent);
331
+ rb_raise(rb_eArgError,
332
+ "CAConstString.wrap: element %lld has range [%lld,%lld), "
333
+ "outside the %ld-byte buffer",
334
+ (long long) i, (long long) start, (long long) end,
335
+ (long) RSTRING_LEN(buffer));
336
+ }
337
+ }
338
+ ca_detach(parent);
339
+ }
340
+
341
+ /* Take ownership of the offsets, the way CACategorical.from_codes takes
342
+ ownership of its codes. Without this the Face is read-only but its
343
+ storage is not, so `cs.parent[i] = <anything>` walks straight past the
344
+ check above and back into an out-of-bounds read. The flag rather than
345
+ #freeze, for the reason from_codes gives: freeze would propagate through
346
+ views and Faces. A caller that wants to keep a mutable entity of its own
347
+ passes `.copy`. */
348
+ ca_set_flag(parent, CA_FLAG_READ_ONLY);
349
+
300
350
  ca = ca_const_string_new(parent, rb_str_freeze(buffer), encoding_id);
301
351
  obj = TypedData_Wrap_Struct(rb_cCAConstString, &catext_data_type, ca);
302
352
  rb_ca_set_parent(obj, parent_val); /* pin parent VALUE for GC */
@@ -395,10 +445,13 @@ rb_ca_const_string_encoding (VALUE self)
395
445
  return rb_enc_from_encoding(rb_enc_from_index(ca->encoding_id));
396
446
  }
397
447
 
398
- /* `CAConstString#buffer` — returns the frozen internal byte buffer
399
- (length-prefix format). Exposes the defining tail state for
400
- introspection and the Arrow-boundary component-buffer export
401
- path. */
448
+ /* `CAConstString#buffer` — returns the frozen internal byte buffer: a pure
449
+ concatenation of the element bytes, with no per-record length prefix (=
450
+ the Arrow values buffer). Exposes the defining tail state for
451
+ introspection and the Arrow-boundary component-buffer export path. The
452
+ ranges that index it live in the storage, one (start,end) int64 pair per
453
+ element; `wrap` is where a hand-built pair array is checked against a
454
+ buffer. */
402
455
  static VALUE
403
456
  rb_ca_const_string_buffer (VALUE self)
404
457
  {
@@ -538,7 +591,7 @@ ca_const_string_scan_end (ca_const_string_scan_t *s)
538
591
  so element i occupies s->off[2*i] (start) .. s->off[2*i+1] (end).
539
592
  The buffer is a pure concatenation; length = end - start. */
540
593
  static inline int
541
- ca_const_string_record (ca_const_string_scan_t *s, ca_size_t i, const char **pp, int32_t *plen)
594
+ ca_const_string_record (ca_const_string_scan_t *s, ca_size_t i, const char **pp, int64_t *plen)
542
595
  {
543
596
  int64_t start, end;
544
597
  if ( s->m && s->m[i] ) {
@@ -547,7 +600,7 @@ ca_const_string_record (ca_const_string_scan_t *s, ca_size_t i, const char **pp,
547
600
  start = s->off[2 * i];
548
601
  end = s->off[2 * i + 1];
549
602
  *pp = s->buf + start;
550
- *plen = (int32_t) (end - start);
603
+ *plen = (int64_t) (end - start);
551
604
  return 1;
552
605
  }
553
606
 
@@ -596,7 +649,7 @@ rb_ca_const_string_byte_length (VALUE self)
596
649
  CArray *co;
597
650
  int64_t *op;
598
651
  const char *p;
599
- int32_t len;
652
+ int64_t len;
600
653
  ca_size_t i;
601
654
 
602
655
  ca_const_string_scan_begin(self, &s);
@@ -631,7 +684,7 @@ ca_const_string_predicate (VALUE self, VALUE query, int op)
631
684
  boolean8_t *out;
632
685
  const char *qp, *p;
633
686
  long qlen;
634
- int32_t len;
687
+ int64_t len;
635
688
  ca_size_t i;
636
689
 
637
690
  if ( TYPE(query) != T_STRING ) {
@@ -684,7 +737,7 @@ rb_ca_const_string_eq (VALUE self, VALUE other)
684
737
  they are read only when both records are valid, but GCC cannot prove
685
738
  that -- initialize to keep -Wmaybe-uninitialized quiet and defensive. */
686
739
  const char *pa = NULL, *pb = NULL;
687
- int32_t la = 0, lb = 0;
740
+ int64_t la = 0, lb = 0;
688
741
  ca_size_t i;
689
742
 
690
743
  ca_const_string_scan_begin(self, &a);
@@ -715,7 +768,7 @@ rb_ca_const_string_count (VALUE self, VALUE query)
715
768
  ca_const_string_scan_t s;
716
769
  const char *qp, *p;
717
770
  long qlen;
718
- int32_t len;
771
+ int64_t len;
719
772
  ca_size_t i, c = 0;
720
773
 
721
774
  if ( TYPE(query) != T_STRING ) {
@@ -745,7 +798,7 @@ rb_ca_const_string_search (VALUE self, VALUE query)
745
798
  ca_const_string_scan_t s;
746
799
  const char *qp, *p;
747
800
  long qlen;
748
- int32_t len;
801
+ int64_t len;
749
802
  ca_size_t i;
750
803
  VALUE result = Qnil;
751
804
 
@@ -784,7 +837,7 @@ ca_const_string_byte_cmp (const void *a, const void *b)
784
837
  /* sort raises on masked input, so record() always sets these here; the
785
838
  initializers keep -Wmaybe-uninitialized quiet and stay defensive. */
786
839
  const char *pa = NULL, *pb = NULL;
787
- int32_t la = 0, lb = 0;
840
+ int64_t la = 0, lb = 0;
788
841
  int c;
789
842
  ca_const_string_record(ca_const_string_sort_ctx, ia, &pa, &la);
790
843
  ca_const_string_record(ca_const_string_sort_ctx, ib, &pb, &lb);
@@ -834,9 +887,15 @@ rb_ca_const_string_sort_index (VALUE self)
834
887
  return vout;
835
888
  }
836
889
 
837
- /* CAConstString#sort is defined in Ruby (lib/carray/const_string.rb) as `self[sort_index]`,
838
- i.e. a no-copy view over the offset source (buffer + offsets shared, only
839
- the gather order changes) — consistent with CArray#sort being a view. */
890
+ /* The ordering surface is defined in Ruby (lib/carray/const_string.rb) on
891
+ top of these three: a storage cell is a byte range, so storage order is
892
+ insertion order rather than string order, and every member has to read
893
+ the bytes. What is native here is the flat form, which is the common one
894
+ for a string column and which a byte scan does an order of magnitude
895
+ faster than decoding a Ruby String per cell; the per-axis forms go
896
+ through #to_string. These are spelled as internals because the public
897
+ names carry CArray's meanings: what the permutation below answers is
898
+ sort_addr (view-flat addresses in sorted order), not sort_index. */
840
899
 
841
900
  /* CAConstString#min / #max → the byte-min / byte-max element as a frozen String,
842
901
  skipping masked elements; nil if empty or all masked. */
@@ -845,7 +904,7 @@ ca_const_string_extremum (VALUE self, int want_max)
845
904
  {
846
905
  ca_const_string_scan_t s;
847
906
  const char *p, *bestp = NULL;
848
- int32_t len, bestlen = 0;
907
+ int64_t len, bestlen = 0;
849
908
  ca_size_t i;
850
909
  int found = 0;
851
910
 
@@ -892,9 +951,9 @@ rb_ca_const_string_initialize_copy (VALUE self, VALUE other)
892
951
  CAConstString *ca, *cs;
893
952
  TypedData_Get_Struct(self, CAConstString, &catext_data_type, ca);
894
953
  TypedData_Get_Struct(other, CAConstString, &catext_data_type, cs);
895
- /* T.0/T.1: shallow re-setup (shares buffer + offset-source). T.3 will
896
- replace this with compacting deep copy (rebased offsets + compacted
897
- buffer + detach). */
954
+ /* Shallow re-setup: shares the buffer and the offset source, which is the
955
+ documented `dup` semantics for a view. `copy` is the compacting deep
956
+ copy (rebased offsets, compacted buffer, detached). */
898
957
  if ( ca_func[CA_OBJ_CONST_STRING].pool_init ) {
899
958
  ca_array_pool_alloc(ca, CA_OBJ_CONST_STRING, cs->parent->ndim);
900
959
  }
@@ -947,11 +1006,13 @@ Init_ca_obj_const_string (void)
947
1006
  rb_define_method(rb_cCAConstString, "search", rb_ca_const_string_search, 1);
948
1007
  rb_define_method(rb_cCAConstString, "find_value_index", rb_ca_const_string_search, 1);
949
1008
 
950
- /* native sort_index / min / max (§3.7), byte-memcmp comparator.
951
- sort / sort_copy are defined in Ruby on top of sort_index. */
952
- rb_define_method(rb_cCAConstString, "sort_index", rb_ca_const_string_sort_index, 0);
953
- rb_define_method(rb_cCAConstString, "min", rb_ca_const_string_min, 0);
954
- rb_define_method(rb_cCAConstString, "max", rb_ca_const_string_max, 0);
1009
+ /* Native flat sort permutation / min / max (§3.7), byte-memcmp
1010
+ comparator. The public ordering surface is built on these in
1011
+ lib/carray/const_string.rb; see the comment above their definitions. */
1012
+ rb_define_method(rb_cCAConstString, "__sort_addr_bytes__",
1013
+ rb_ca_const_string_sort_index, 0);
1014
+ rb_define_method(rb_cCAConstString, "__min_bytes__", rb_ca_const_string_min, 0);
1015
+ rb_define_method(rb_cCAConstString, "__max_bytes__", rb_ca_const_string_max, 0);
955
1016
 
956
1017
  /* Y-pilot: Face-local C-level fast path for per-cell scalar fetch. */
957
1018
  ca_face_register_storage_to_scalar(CA_OBJ_CONST_STRING, rb_ca_const_string_storage_to_scalar);
data/ext/ca_obj_face.c CHANGED
@@ -66,6 +66,21 @@ ca_face_fill_data (void *ap, void *ptr)
66
66
  ca_fill(cav->parent, ptr);
67
67
  }
68
68
 
69
+ void
70
+ ca_face_fill_addrs (void *ap, ca_size_t n, ca_size_t *addrs, void *ptr)
71
+ {
72
+ CAView *cav = (CAView *) ap;
73
+ ca_fill_addrs(cav->parent, n, addrs, ptr);
74
+ }
75
+
76
+ void
77
+ ca_face_fill_stride (void *ap, ca_size_t base, int8_t ndim,
78
+ ca_size_t *counts, ca_size_t *steps, void *ptr)
79
+ {
80
+ CAView *cav = (CAView *) ap;
81
+ ca_fill_stride(cav->parent, base, ndim, counts, steps, ptr);
82
+ }
83
+
69
84
  void
70
85
  ca_face_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
71
86
  {
@@ -270,6 +285,71 @@ ca_face_lift (VALUE view, VALUE face_parent)
270
285
  return lifted;
271
286
  }
272
287
 
288
+ /* ca_lazy_marker_lift(view, marker) — the CALazyMarker half of
289
+ * ca_wrapper_lift. Same invariant as the Face lift (wrapper on top, view
290
+ * built against what the wrapper wraps) reached by a shorter route: a
291
+ * marker carries no subclass state, so there is nothing for
292
+ * rb_ca_face_template's struct copy to preserve, and building a fresh
293
+ * marker over the view gets data_type, bytes, shape, mask and flags right
294
+ * by construction. That last part is load-bearing for `refer(type)`,
295
+ * where the view's data_type differs from the one the old marker copied
296
+ * off the entity.
297
+ *
298
+ * The one piece it shares with the Face lift is the one-level swap: the
299
+ * builder handed us a view whose parent is the marker, and leaving it
300
+ * there would stack a redundant marker in the middle. */
301
+ static VALUE
302
+ ca_lazy_marker_lift (VALUE view, VALUE marker)
303
+ {
304
+ CArray *view_ca, *mp;
305
+ extern VALUE rb_ca_lazy_marker_new (VALUE cary);
306
+
307
+ TypedData_Get_Struct(view, CArray, &carray_data_type, view_ca);
308
+
309
+ /* Idempotent: a builder that wrapped internally already returned a
310
+ marker (mirrors the same guard in ca_face_lift). */
311
+ if ( ca_is_lazy_marker(view_ca) ) {
312
+ return view;
313
+ }
314
+
315
+ TypedData_Get_Struct(marker, CArray, &carray_data_type, mp);
316
+
317
+ /* One-level swap (CAFace.md section 8.3, same reasoning): move the view
318
+ off the marker and onto what the marker wraps. Guarded on pointer
319
+ equality so only a view built directly on this marker fires. */
320
+ if ( ca_is_view(view_ca)
321
+ && ((CAView *) view_ca)->parent == mp
322
+ && ! ca_test_flag(view_ca, CA_FLAG_MULTI_PARENTS) ) {
323
+ ((CAView *) view_ca)->parent = ((CAView *) mp)->parent; /* C pointer */
324
+ rb_ca_set_parent(view, rb_ca_parent(marker)); /* @parent ivar */
325
+ }
326
+
327
+ return rb_ca_lazy_marker_new(view);
328
+ }
329
+
330
+ /* ca_wrapper_lift(view, wrapper, wrapper_ca) — dispatch for
331
+ * CA_WRAPPER_LIFT. The macro has already established that wrapper_ca
332
+ * carries one of the two flags and that view is a CArray. */
333
+ VALUE
334
+ ca_wrapper_lift (VALUE view, VALUE wrapper, void *wrapper_ca)
335
+ {
336
+ CArray *ca = (CArray *) wrapper_ca;
337
+
338
+ if ( ca_is_face(ca) ) {
339
+ CArray *view_ca;
340
+ TypedData_Get_Struct(view, CArray, &carray_data_type, view_ca);
341
+ /* Some builders (select / repeat / ...) lift their own result; a
342
+ second lift here would double-wrap. Mirrors the guard the `[]`
343
+ call site used to carry inline. */
344
+ if ( ca_is_face(view_ca) ) {
345
+ return view;
346
+ }
347
+ return ca_face_lift(view, wrapper);
348
+ }
349
+
350
+ return ca_lazy_marker_lift(view, wrapper);
351
+ }
352
+
273
353
  /* ca_strip_face(src) — walk down through Face parents to the storage
274
354
  * CArray. Called at kernel_iterator entry and lazy chain endpoints
275
355
  * where downstream code needs to observe the storage layout, not the
@@ -515,6 +595,30 @@ ca_face_class_has_portable_method (VALUE klass)
515
595
  return ( owner != rb_singleton_class(rb_cCArray) ) ? 1 : 0;
516
596
  }
517
597
 
598
+ VALUE
599
+ ca_face_operand_descend (VALUE operand, const char *name)
600
+ {
601
+ CArray *op;
602
+ if ( ! RTEST(rb_obj_is_carray(operand)) ) {
603
+ return operand;
604
+ }
605
+ TypedData_Get_Struct(operand, CArray, &carray_data_type, op);
606
+ if ( ! ca_is_face(op) ) {
607
+ return operand;
608
+ }
609
+ if ( ca_test_flag(op, CA_FLAG_FACE_COMPARABLE_STORAGE) ) {
610
+ return rb_ca_strip_face_value(operand);
611
+ }
612
+ rb_raise(rb_eArgError,
613
+ "%s: cannot take a %s operand: its storage is not its surface "
614
+ "(a cell encodes the value rather than being it), so comparing "
615
+ "the storage would compare the encoding. Convert the operand to "
616
+ "the receiver's space first -- #to_string for a string Face -- "
617
+ "or pass .parent on both sides to work in storage space",
618
+ name, rb_obj_classname(operand));
619
+ return Qnil; /* not reached */
620
+ }
621
+
518
622
  int
519
623
  ca_face_state_portable (int obj_type, VALUE klass)
520
624
  {
data/ext/ca_obj_face.h CHANGED
@@ -34,7 +34,8 @@
34
34
  via parent)
35
35
  - detach: release ptr alias, parent detach
36
36
  - xfer_*: delegate to parent's xfer_* (= identical shape / index space)
37
- - fill_data: delegate to parent's ca_fill (= identical storage bytes)
37
+ - fill_data / fill_*: delegate to parent (= identical storage bytes,
38
+ and the identity layout makes the parent's addresses ours)
38
39
 
39
40
  Subclasses may override as needed (= default policy, not an invariant;
40
41
  does not preclude a future transform Face). */
@@ -43,6 +44,9 @@ void ca_face_attach (void *ap);
43
44
  void ca_face_sync (void *ap);
44
45
  void ca_face_detach (void *ap);
45
46
  void ca_face_fill_data (void *ap, void *ptr);
47
+ void ca_face_fill_addrs (void *ap, ca_size_t n, ca_size_t *addrs, void *ptr);
48
+ void ca_face_fill_stride(void *ap, ca_size_t base, int8_t ndim,
49
+ ca_size_t *counts, ca_size_t *steps, void *ptr);
46
50
  void ca_face_xfer_index (void *ap, ca_size_t *idx,
47
51
  void *data, int dir);
48
52
  void ca_face_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
@@ -112,6 +116,34 @@ VALUE rb_ca_record_get_data_class (CArray *ca);
112
116
  } \
113
117
  } while (0)
114
118
 
119
+ /* -- wrapper lift (Face or CALazyMarker) --
120
+
121
+ A Face and a CALazyMarker are both storage-identical wrappers: they add
122
+ an interpretation over their parent's bytes without changing them. Both
123
+ want the same invariant at a view-creating method -- the wrapper stays on
124
+ top, and the view is built against what the wrapper wraps. For a Face
125
+ that keeps `dt.shift(1)` a CATime; for a marker it keeps a fuse block's
126
+ expression lazy instead of dropping out of the chain.
127
+
128
+ The flag test comes first and covers both bits in one mask, so an
129
+ ordinary array leaves through a single AND. That matters: `[]` is the
130
+ hottest method this is deployed on (see devel/bench_index_percall.rb).
131
+
132
+ NOT interchangeable with CA_FACE_LIFT_IF_FACE. Face is lifted at ~24
133
+ sites, including ones a marker must not follow it through -- `copy` owns
134
+ its data, `sort` reorders values, `value` and `strip_mask` change what
135
+ the mask means. Deploy this only where the result is a view whose shape
136
+ was fixed at construction and which only moves positions
137
+ (PROPOSAL_LAZY_MARKER_LIFT section 4). */
138
+ #define CA_WRAPPER_LIFT(obj, self, ca) do { \
139
+ if ( ca_test_flag((ca), CA_FLAG_IS_FACE | CA_FLAG_IS_LAZY_MARKER) \
140
+ && rb_obj_is_kind_of((obj), rb_cCArray) ) { \
141
+ (obj) = ca_wrapper_lift((obj), (self), (ca)); \
142
+ } \
143
+ } while (0)
144
+
145
+ VALUE ca_wrapper_lift (VALUE view, VALUE wrapper, void *wrapper_ca);
146
+
115
147
  /* -- Scalar fetch decode hook (storage -> surface) --
116
148
  On the scalar-return path (= tail of rb_ca_fetch_index /
117
149
  rb_ca_fetch_addr), if a Face-derived subclass defines a
@@ -227,6 +259,21 @@ void ca_face_register_state_portable (int obj_type, int portable);
227
259
  (= falls through to default). */
228
260
  int ca_face_state_portable (int obj_type, VALUE klass);
229
261
 
262
+ /* Bring a Face *operand* into the reference's storage space, or refuse.
263
+
264
+ The gate asks `fz_face_descend`'s question -- "is your storage your
265
+ surface?" -- of the receiver, and for a long time asked nothing at all of
266
+ the operand: a COMPARABLE receiver stripped any Face handed to it, and got
267
+ back that Face's encoding rather than its values. Where the two encodings
268
+ happened to be the same width the comparison then answered, wrongly and
269
+ silently. Call this instead of stripping an operand by hand.
270
+
271
+ A Face passes only if it declares COMPARABLE_STORAGE, i.e. its storage
272
+ cells *are* the values it shows. Anything else -- a byte range, a code, a
273
+ tick -- is refused rather than compared as bytes. Non-Face operands are
274
+ returned untouched. `name` opens the message. */
275
+ VALUE ca_face_operand_descend (VALUE operand, const char *name);
276
+
230
277
  #define CA_FACE_STORAGE_TO_SCALAR_IF_FACE(obj, self, ca) do { \
231
278
  if ( ca_is_face(ca) && (obj) != CA_UNDEF && (obj) != Qnil \
232
279
  && ! rb_obj_is_kind_of((obj), rb_cCArray) ) { \