carray 3.0.0 → 3.0.1

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 (180) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -1
  3. data/CHANGELOG.md +325 -3
  4. data/{NEWS.md → CHANGELOG.v1.md} +1 -1
  5. data/README.md +32 -32
  6. data/carray.gemspec +12 -8
  7. data/ext/ca_axis_group.c +55 -19
  8. data/ext/ca_binop_dispatch.c +5 -6
  9. data/ext/ca_binop_dispatch.h +0 -7
  10. data/ext/ca_categorical_iterator.c +13 -13
  11. data/ext/ca_for_each_element.h +6 -8
  12. data/ext/ca_group_iter.c +1 -1
  13. data/ext/ca_kernel_iterator.c +69 -23
  14. data/ext/ca_kernel_iterator.h +3 -10
  15. data/ext/ca_obj_bincmp.c +57 -27
  16. data/ext/ca_obj_binop.c +86 -30
  17. data/ext/ca_obj_const_string.c +3 -1
  18. data/ext/ca_obj_face.c +80 -0
  19. data/ext/ca_obj_face.h +33 -1
  20. data/ext/ca_obj_fake.c +11 -0
  21. data/ext/ca_obj_fixlen_string.c +3 -1
  22. data/ext/ca_obj_grid.c +11 -3
  23. data/ext/ca_obj_meld.c +13 -8
  24. data/ext/ca_obj_moncmp.c +33 -14
  25. data/ext/ca_obj_monop.c +19 -7
  26. data/ext/ca_obj_object.c +99 -4
  27. data/ext/ca_obj_record.c +3 -1
  28. data/ext/ca_obj_refer.c +30 -6
  29. data/ext/ca_obj_roll.c +13 -5
  30. data/ext/ca_obj_select_axis.c +16 -6
  31. data/ext/ca_obj_shift.c +3 -3
  32. data/ext/ca_obj_stride.c +106 -23
  33. data/ext/ca_obj_string.c +3 -1
  34. data/ext/ca_obj_tile.c +13 -5
  35. data/ext/ca_obj_time.c +3 -1
  36. data/ext/ca_obj_timedelta.c +3 -1
  37. data/ext/ca_obj_transpose.c +1 -1
  38. data/ext/ca_obj_triop.c +62 -23
  39. data/ext/ca_obj_window.c +86 -17
  40. data/ext/ca_op_cmplx64.h +123 -0
  41. data/ext/ca_op_ipower.c +0 -3
  42. data/ext/ca_sort_kernels.h +5 -5
  43. data/ext/ca_sweep_engine.c +78 -35
  44. data/ext/ca_sweep_engine.h +13 -4
  45. data/ext/ca_transform_common.c +7 -0
  46. data/ext/carray.h +127 -130
  47. data/ext/carray_access.c +56 -34
  48. data/ext/carray_bincount.c +8 -8
  49. data/ext/carray_broadcast.c +99 -6
  50. data/ext/carray_build_flags.h +3 -0
  51. data/ext/carray_call_cfunc.c +1491 -0
  52. data/ext/carray_call_cfunc.h +153 -0
  53. data/ext/carray_cast.c +69 -31
  54. data/ext/carray_conversion.c +22 -22
  55. data/ext/carray_copy.c +12 -2
  56. data/ext/carray_core.c +57 -10
  57. data/ext/carray_count.c +1 -1
  58. data/ext/carray_element.c +7 -7
  59. data/ext/carray_factorize.c +28 -28
  60. data/ext/carray_hold.c +1 -1
  61. data/ext/carray_index_classifier.c +7 -21
  62. data/ext/carray_internal.h +19 -3
  63. data/ext/carray_kernels_bincmp.c +1 -0
  64. data/ext/carray_kernels_binop.c +320 -298
  65. data/ext/carray_kernels_init.c +1095 -0
  66. data/ext/carray_kernels_map.c +1 -0
  67. data/ext/carray_kernels_moncmp.c +1 -0
  68. data/ext/carray_kernels_monop.c +412 -411
  69. data/ext/carray_kernels_reduce_aggregate.c +88 -87
  70. data/ext/carray_kernels_reduce_boolean.c +1 -0
  71. data/ext/carray_kernels_reduce_cumulative.c +54 -53
  72. data/ext/carray_kernels_reduce_extreme.c +51 -50
  73. data/ext/carray_kernels_reduce_variance.c +1 -0
  74. data/ext/carray_kernels_scan.c +1 -0
  75. data/ext/carray_kernels_search.c +93 -92
  76. data/ext/carray_kernels_sort.c +1 -0
  77. data/ext/carray_kernels_triop.c +1 -0
  78. data/ext/carray_lazy.c +175 -5
  79. data/ext/carray_mask.c +6 -5
  80. data/ext/carray_median_percentile.c +1 -1
  81. data/ext/carray_memory_view.c +46 -21
  82. data/ext/carray_operator.c +46 -49
  83. data/ext/carray_partition.c +4 -4
  84. data/ext/carray_random.c +7 -7
  85. data/ext/carray_scatter.c +1 -1
  86. data/ext/carray_sort.c +3 -3
  87. data/ext/carray_sort_kernel.c +10 -10
  88. data/ext/carray_test.c +1 -1
  89. data/ext/extconf.rb +18 -0
  90. data/ext/mk_call_cfunc.rb +243 -52
  91. data/ext/mkkernel.rb +354 -100
  92. data/ext/ruby_carray.c +18 -16
  93. data/ext/version.h +4 -4
  94. data/lib/carray/autoload_carray.rb +19 -0
  95. data/lib/carray/autoload_method_extension.rb +2 -1
  96. data/lib/carray/axis_group.rb +7 -7
  97. data/lib/carray/basics.rb +59 -59
  98. data/lib/carray/bincount_nd.rb +12 -12
  99. data/lib/carray/block_iterator.rb +24 -14
  100. data/lib/carray/categorical.rb +4 -4
  101. data/lib/carray/categorical_iterator.rb +98 -76
  102. data/lib/carray/conditional.rb +14 -14
  103. data/lib/carray/construct.rb +21 -0
  104. data/lib/carray/core_extensions.rb +62 -5
  105. data/lib/carray/data_type_extension.rb +18 -1
  106. data/lib/carray/frame/concat.rb +3 -3
  107. data/lib/carray/frame/convert.rb +1 -1
  108. data/lib/carray/frame/frame.rb +4 -11
  109. data/lib/carray/frame/io.rb +159 -2
  110. data/lib/carray/frame/sort.rb +1 -1
  111. data/lib/carray/frame/verbs.rb +18 -1
  112. data/lib/carray/fuse_source.rb +123 -0
  113. data/lib/carray/fusion.rb +218 -0
  114. data/lib/carray/histogram.rb +16 -16
  115. data/lib/carray/inspect.rb +1 -7
  116. data/lib/carray/iterator.rb +4 -3
  117. data/lib/carray/lazy.rb +125 -73
  118. data/lib/carray/meld_reduce.rb +2 -2
  119. data/lib/carray/methods/align_addr.rb +1 -1
  120. data/lib/carray/methods/composition.rb +1 -1
  121. data/lib/carray/methods/is_in.rb +12 -12
  122. data/lib/carray/methods/locate_addr.rb +6 -1
  123. data/lib/carray/methods/mask_duplicates.rb +1 -1
  124. data/lib/carray/methods/meshgrid.rb +4 -5
  125. data/lib/carray/methods/mode.rb +2 -2
  126. data/lib/carray/methods/nunique.rb +1 -1
  127. data/lib/carray/methods/snap.rb +7 -2
  128. data/lib/carray/methods/unique.rb +3 -3
  129. data/lib/carray/methods/value_counts.rb +2 -2
  130. data/lib/carray/runtime.rb +0 -19
  131. data/lib/carray/slab_iterator.rb +20 -7
  132. data/lib/carray/string_operation_extension.rb +5 -5
  133. data/lib/carray/time.rb +890 -491
  134. data/lib/carray/window_iterator.rb +285 -13
  135. data/lib/carray.rb +5 -5
  136. data/yard-stubs/ca_obj_array.rb +385 -0
  137. data/yard-stubs/ca_obj_bitarray.rb +38 -0
  138. data/yard-stubs/ca_obj_bitfield.rb +43 -0
  139. data/yard-stubs/ca_obj_block.rb +73 -0
  140. data/yard-stubs/ca_obj_byte_swap.rb +56 -0
  141. data/yard-stubs/ca_obj_fake.rb +31 -0
  142. data/yard-stubs/ca_obj_farray.rb +32 -0
  143. data/yard-stubs/ca_obj_field.rb +45 -0
  144. data/yard-stubs/ca_obj_grid.rb +35 -0
  145. data/yard-stubs/ca_obj_refer.rb +72 -0
  146. data/yard-stubs/ca_obj_roll.rb +45 -0
  147. data/yard-stubs/ca_obj_shift.rb +43 -0
  148. data/yard-stubs/ca_obj_stride.rb +181 -0
  149. data/yard-stubs/ca_obj_tile.rb +29 -0
  150. data/yard-stubs/ca_obj_transpose.rb +40 -0
  151. data/yard-stubs/ca_obj_window.rb +49 -0
  152. data/yard-stubs/carray_access.rb +131 -0
  153. data/yard-stubs/carray_attribute.rb +246 -0
  154. data/yard-stubs/carray_broadcast.rb +37 -0
  155. data/yard-stubs/carray_cast.rb +489 -0
  156. data/yard-stubs/carray_class.rb +65 -0
  157. data/yard-stubs/carray_conversion.rb +76 -0
  158. data/yard-stubs/carray_copy.rb +79 -0
  159. data/yard-stubs/carray_core.rb +114 -0
  160. data/yard-stubs/carray_count.rb +79 -0
  161. data/yard-stubs/carray_element.rb +108 -0
  162. data/yard-stubs/carray_generate.rb +66 -0
  163. data/yard-stubs/carray_lazy.rb +23 -0
  164. data/yard-stubs/carray_loop.rb +140 -0
  165. data/yard-stubs/carray_mask.rb +259 -0
  166. data/yard-stubs/carray_math.rb +132 -0
  167. data/yard-stubs/carray_mathfunc.rb +45 -0
  168. data/yard-stubs/carray_median_percentile.rb +89 -0
  169. data/yard-stubs/carray_memory_view.rb +163 -0
  170. data/yard-stubs/carray_order.rb +312 -0
  171. data/yard-stubs/carray_random.rb +89 -0
  172. data/yard-stubs/carray_scatter.rb +106 -0
  173. data/yard-stubs/carray_slab.rb +57 -0
  174. data/yard-stubs/carray_sort.rb +163 -0
  175. data/yard-stubs/carray_test.rb +85 -0
  176. data/yard-stubs/carray_undef.rb +64 -0
  177. data/yard-stubs/carray_utils.rb +97 -0
  178. data/yard-stubs/ruby_carray.rb +193 -0
  179. metadata +59 -10
  180. data/ext/ca_obj_unbound_repeat.c +0 -496
data/ext/ca_obj_bincmp.c CHANGED
@@ -9,7 +9,7 @@
9
9
  ca_bincmp_kernel_lookup from ca_bincmp_dispatch.h.
10
10
 
11
11
  Dispatch by data_type:
12
- numeric (i8..f64, +complex) -> ca_bincmp_<op>_<dtype> kernel
12
+ numeric (i8..f64, +complex) -> ca_bincmp_<op>_<type> kernel
13
13
  boolean / fixlen / object -> not implemented (raise)
14
14
 
15
15
  CAREFUL: the binop in-place trick (= pull left into the output
@@ -60,6 +60,12 @@ typedef struct CABinCmp {
60
60
  CArray *parent; /* = left */
61
61
  uint32_t attach;
62
62
  uint8_t nosync;
63
+ /* ---- CAMultiParent conformance (CA_FLAG_MULTI_PARENTS): n_parents and
64
+ parents[] sit immediately after the CAView header, as carray.h's
65
+ layout convention requires, so ca_has_mask can fold over both
66
+ operands and build the mask on demand instead of at setup. ---- */
67
+ int32_t n_parents; /* always 2 */
68
+ CArray **parents; /* = &operands[0]; no separate allocation */
63
69
  /* CABinCmp-specific tail */
64
70
  CArray *right;
65
71
  uint16_t op_id;
@@ -72,6 +78,7 @@ typedef struct CABinCmp {
72
78
  `eps` name is retained to keep the
73
79
  `__eps__` Ruby accessor stable across
74
80
  the dual purpose. */
81
+ CArray *operands[2]; /* {left, right}; what parents points at */
75
82
  } CABinCmp;
76
83
 
77
84
  static size_t
@@ -119,7 +126,7 @@ ca_bincmp_setup (CABinCmp *ca, CArray *left, CArray *right, uint16_t op_id,
119
126
  {
120
127
  ca->obj_type = CA_OBJ_BINCMP;
121
128
  ca->data_type = CA_BOOLEAN; /* output is always boolean */
122
- ca->flags = CA_FLAG_READ_ONLY;
129
+ ca->flags = CA_FLAG_READ_ONLY | CA_FLAG_MULTI_PARENTS;
123
130
  ca->ndim = left->ndim;
124
131
  ca->bytes = 1; /* boolean8_t */
125
132
  ca->elements = left->elements;
@@ -137,12 +144,16 @@ ca_bincmp_setup (CABinCmp *ca, CArray *left, CArray *right, uint16_t op_id,
137
144
  /* Builder has already cast both operands to common data_type. */
138
145
  ca->common_dt = left->data_type;
139
146
  ca->eps = eps;
147
+ ca->operands[0] = left;
148
+ ca->operands[1] = right;
149
+ ca->parents = ca->operands;
150
+ ca->n_parents = 2;
140
151
 
141
152
  memcpy(ca->dim, left->dim, left->ndim * sizeof(ca_size_t));
142
153
 
143
- if ( ca_has_mask(left) || ca_has_mask(right) ) {
144
- ca_create_mask(ca);
145
- }
154
+ /* The mask is NOT built here. ca_has_mask folds over parents[] for a
155
+ multi-parent view and creates it on demand, so an expression whose mask
156
+ nobody reads never allocates one. */
146
157
 
147
158
  if ( ca_is_scalar(left) && ca_is_scalar(right) ) {
148
159
  ca_set_flag(ca, CA_FLAG_SCALAR);
@@ -294,7 +305,9 @@ ca_bincmp_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
294
305
  int8_t k;
295
306
  void *left_scratch, *right_scratch;
296
307
  ca_size_t operand_bytes;
308
+ ca_size_t operand_strides[CA_RANK_MAX];
297
309
  ca_size_t right_step;
310
+ int is_contig;
298
311
  int left_is_inplace = 0; /* skip release for leaf-opt path */
299
312
  int right_is_inplace = 0;
300
313
 
@@ -308,10 +321,33 @@ ca_bincmp_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
308
321
 
309
322
  operand_bytes = ca_sizeof[bc->common_dt];
310
323
 
324
+ /* An operand cell is operand_bytes wide where this view's is one byte
325
+ (CA_BOOLEAN), so the caller's strides cannot be handed to the operand
326
+ pull the way a binop hands them on -- they have to be restated at the
327
+ operand's cell size. strides[k] / bytes is the index step, which is
328
+ what the two spaces share.
329
+
330
+ is_contig says the caller asked for the row-major slab that xfer_all
331
+ and attach send. Only then does an operand's own buffer hold the
332
+ wanted cells in the packed order the kernel reads them in, so it is
333
+ also the only case the leaf in-place path is good for. */
334
+ {
335
+ ca_size_t native = bc->bytes;
336
+ is_contig = 1;
337
+ for ( k = bc->ndim - 1; k >= 0; k-- ) {
338
+ operand_strides[k] = strides[k] / bc->bytes * operand_bytes;
339
+ if ( strides[k] != native ) {
340
+ is_contig = 0;
341
+ }
342
+ native *= bc->dim[k];
343
+ }
344
+ }
345
+
311
346
  /* === 1. pull LEFT (leaf in-place read or arena scratch) === */
312
347
  {
313
348
  char *left_inplace = NULL;
314
- if ( ca_bincmp_try_leaf_inplace(bc->parent, bc->common_dt,
349
+ if ( is_contig &&
350
+ ca_bincmp_try_leaf_inplace(bc->parent, bc->common_dt,
315
351
  starts, counts, operand_bytes,
316
352
  &left_inplace) ) {
317
353
  /* Leaf-opt path: use parent->ptr + byte_offset directly. */
@@ -320,15 +356,11 @@ ca_bincmp_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
320
356
  ca_bincmp_leaf_inplace_count++;
321
357
  }
322
358
  else {
323
- ca_size_t left_strides[CA_RANK_MAX];
324
- ca_size_t s = operand_bytes;
325
- for ( k = bc->ndim - 1; k >= 0; k-- ) {
326
- left_strides[k] = s;
327
- s *= counts[k];
328
- }
329
- left_scratch = ca_lazy_arena_acquire(slab_n * operand_bytes);
359
+ left_scratch = ( bc->common_dt == CA_OBJECT )
360
+ ? ca_lazy_arena_acquire_object(slab_n)
361
+ : ca_lazy_arena_acquire(slab_n * operand_bytes);
330
362
  ca_bincmp_scratch_acquire_count++;
331
- ca_xfer_stride(bc->parent, starts, counts, left_strides, left_scratch,
363
+ ca_xfer_stride(bc->parent, starts, counts, operand_strides, left_scratch,
332
364
  CA_XFER_GET);
333
365
  }
334
366
  }
@@ -355,7 +387,9 @@ ca_bincmp_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
355
387
  else {
356
388
  ca_size_t one_strides[CA_RANK_MAX];
357
389
  for ( k = 0; k < bc->right->ndim; k++ ) one_strides[k] = operand_bytes;
358
- right_scratch = ca_lazy_arena_acquire(operand_bytes);
390
+ right_scratch = ( bc->common_dt == CA_OBJECT )
391
+ ? ca_lazy_arena_acquire_object(1)
392
+ : ca_lazy_arena_acquire(operand_bytes);
359
393
  ca_bincmp_scratch_acquire_count++;
360
394
  ca_xfer_stride(bc->right, one_starts, one_counts, one_strides,
361
395
  right_scratch, CA_XFER_GET);
@@ -365,7 +399,8 @@ ca_bincmp_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
365
399
  else {
366
400
  /* Same-shape right: full slab pull or leaf-opt. */
367
401
  char *right_inplace = NULL;
368
- if ( ca_bincmp_try_leaf_inplace(bc->right, bc->common_dt,
402
+ if ( is_contig &&
403
+ ca_bincmp_try_leaf_inplace(bc->right, bc->common_dt,
369
404
  starts, counts, operand_bytes,
370
405
  &right_inplace) ) {
371
406
  right_scratch = right_inplace;
@@ -373,15 +408,11 @@ ca_bincmp_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
373
408
  ca_bincmp_leaf_inplace_count++;
374
409
  }
375
410
  else {
376
- ca_size_t right_strides[CA_RANK_MAX];
377
- ca_size_t s = operand_bytes;
378
- for ( k = bc->ndim - 1; k >= 0; k-- ) {
379
- right_strides[k] = s;
380
- s *= counts[k];
381
- }
382
- right_scratch = ca_lazy_arena_acquire(slab_n * operand_bytes);
411
+ right_scratch = ( bc->common_dt == CA_OBJECT )
412
+ ? ca_lazy_arena_acquire_object(slab_n)
413
+ : ca_lazy_arena_acquire(slab_n * operand_bytes);
383
414
  ca_bincmp_scratch_acquire_count++;
384
- ca_xfer_stride(bc->right, starts, counts, right_strides, right_scratch,
415
+ ca_xfer_stride(bc->right, starts, counts, operand_strides, right_scratch,
385
416
  CA_XFER_GET);
386
417
  }
387
418
  right_step = 1;
@@ -644,10 +675,9 @@ rb_ca_bincmp_build (VALUE l_cary, VALUE r_cary, uint16_t op_id, double eps)
644
675
  TypedData_Get_Struct(l_resolved, CArray, &carray_data_type, l);
645
676
  }
646
677
  else {
678
+ /* Unreachable from Ruby; see the note in ca_obj_binop.c. */
647
679
  rb_raise(rb_eArgError,
648
- "CABinCmp: shape mismatch (%lld vs %lld) — only same-"
649
- "ndim size-1 broadcast is supported; cross-ndim "
650
- "promotion is not adopted in CArray",
680
+ "CABinCmp: element count mismatch (%lld vs %lld)",
651
681
  (long long) l->elements, (long long) r->elements);
652
682
  }
653
683
  }
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",
@@ -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
  /* ------------------------------------------------------------------- */
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
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
data/ext/ca_obj_fake.c CHANGED
@@ -462,6 +462,17 @@ rb_ca_fake_new (VALUE cary, int8_t data_type, ca_size_t bytes)
462
462
  CAFake *ca;
463
463
  rb_check_carray_object(cary);
464
464
  TypedData_Get_Struct(cary, CArray, &carray_data_type, parent);
465
+ if ( ca_is_face(parent) && ( data_type == CA_OBJECT
466
+ || parent->data_type == CA_FIXLEN ) ) {
467
+ /* A Face's cells do not mean their storage bytes, so reading them
468
+ under another data_type hands back what the surface exists to hide.
469
+ Both ways down stay open and say which one they are. A Numeric Face
470
+ is not one of these: its surface is its storage. */
471
+ rb_raise(rb_eTypeError,
472
+ "%s has no view of its values in another data_type: "
473
+ "#to_type gives the values, #parent.fake the raw storage",
474
+ rb_obj_classname(cary));
475
+ }
465
476
  ca = ca_fake_new(parent, data_type, bytes);
466
477
  obj = ca_wrap_struct(ca);
467
478
  rb_ca_set_parent(obj, cary);
@@ -202,7 +202,9 @@ ca_operation_function_t ca_fixlen_string_func = {
202
202
  ca_face_xfer_addrs,
203
203
  NULL, /* fold_stride: identity Face is not foldable */
204
204
  ca_face_xfer_stride,
205
- ca_face_xfer_all
205
+ ca_face_xfer_all,
206
+ .fill_addrs = ca_face_fill_addrs,
207
+ .fill_stride = ca_face_fill_stride,
206
208
  };
207
209
 
208
210
  /* ------------------------------------------------------------------- */
data/ext/ca_obj_grid.c CHANGED
@@ -478,9 +478,17 @@ ca_grid_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
478
478
  transposed leaf breaks this (cross-axis / non-multiple strides); fall back
479
479
  to per-cell delivery (correct, still no whole-view attach). The wiring
480
480
  guards ndim == grid->ndim, so counts/strides have ndim entries here. */
481
- for (k = 0; k < ndim; k++) {
482
- if (strides[k] % gnative[k] != 0) { aligned = 0; break; }
483
- src_step[k] = strides[k] / gnative[k];
481
+ /* The request is over the view's addresses, so a transposed / flat request
482
+ is legal and must not be composed axis-by-axis; see
483
+ ca_xfer_stride_request_is_axis_box (carray.h). */
484
+ if ( ! ca_xfer_stride_request_is_axis_box(g, starts, counts, strides) ) {
485
+ aligned = 0;
486
+ }
487
+ else {
488
+ for (k = 0; k < ndim; k++) {
489
+ if (strides[k] % gnative[k] != 0) { aligned = 0; break; }
490
+ src_step[k] = strides[k] / gnative[k];
491
+ }
484
492
  }
485
493
 
486
494
  if (!aligned) {
data/ext/ca_obj_meld.c CHANGED
@@ -89,7 +89,7 @@ static ID id_parents;
89
89
  /* uniform check */
90
90
  /* ------------------------------------------------------------------- */
91
91
 
92
- /* Parents must share dtype, ndim, bytes, and all dims except meld_axis;
92
+ /* Parents must share data type, ndim, bytes, and all dims except meld_axis;
93
93
  meld_axis lengths are the ragged dimension (may differ). */
94
94
  static void
95
95
  ca_meld_check_uniform (int32_t n_parents, CArray **parents, int8_t meld_axis)
@@ -598,19 +598,24 @@ ca_meld_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
598
598
  int8_t i;
599
599
  int structural = 1;
600
600
 
601
- /* Bound check on meld_axis */
601
+ s = ca->bytes;
602
+ for ( i = ca->ndim - 1; i >= 0; i-- ) { native[i] = s; s *= ca->dim[i]; }
603
+
604
+ /* Bound check on meld_axis. The step along the axis is strides[ma] /
605
+ native[ma] and it can be negative -- a reversed read starts at the far
606
+ end and walks down -- so the region runs between the first and last
607
+ index, which is not the same as starts[ma] .. starts[ma] + counts[ma]. */
602
608
  {
603
- ca_size_t req_lo = starts[ma];
604
- ca_size_t req_hi = starts[ma] + counts[ma];
605
- if ( req_lo < 0 || req_hi > ca->dim[ma] ) {
609
+ ca_size_t step = strides[ma] / native[ma];
610
+ ca_size_t last = starts[ma] + (counts[ma] - 1) * step;
611
+ ca_size_t req_lo = ( last < starts[ma] ) ? last : starts[ma];
612
+ ca_size_t req_hi = (( last < starts[ma] ) ? starts[ma] : last) + 1;
613
+ if ( counts[ma] > 0 && ( req_lo < 0 || req_hi > ca->dim[ma] ) ) {
606
614
  rb_raise(rb_eIndexError,
607
615
  "CAMeld xfer_stride meld_axis (axis %d) [%lld, %lld) out of range [0, %lld)",
608
616
  (int) ma, (long long) req_lo, (long long) req_hi, (long long) ca->dim[ma]);
609
617
  }
610
618
  }
611
-
612
- s = ca->bytes;
613
- for ( i = ca->ndim - 1; i >= 0; i-- ) { native[i] = s; s *= ca->dim[i]; }
614
619
  for ( i = 0; i < ca->ndim; i++ ) {
615
620
  if ( strides[i] != native[i] ) { structural = 0; break; }
616
621
  }