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
data/ext/ca_obj_monop.c CHANGED
@@ -222,6 +222,7 @@ ca_monop_setup (CAMonOp *ca, CArray *parent, uint16_t op_id)
222
222
  CAMonOp *
223
223
  ca_monop_new (CArray *parent, uint16_t op_id)
224
224
  {
225
+ /* check: the setup checks nothing */
225
226
  CAMonOp *ca = (CAMonOp *) ca_array_alloc(CA_OBJ_MONOP, parent->ndim);
226
227
  ca_monop_setup(ca, parent, op_id);
227
228
  return ca;
@@ -282,7 +283,7 @@ ca_monop_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
282
283
  /* PUT: only writable-view op_ids accept writes (cast, or byte_swap
283
284
  involution). */
284
285
  if ( ! ca_monop_is_writable_view(ca->op_id) ) {
285
- rb_raise(rb_eRuntimeError, "CAMonOp is read-only (xfer_index PUT)");
286
+ ca_refuse_read_only();
286
287
  }
287
288
  {
288
289
  volatile VALUE holder;
@@ -337,16 +338,14 @@ ca_size_t ca_monop_materialise_call_count = 0;
337
338
  #define CA_MAX_LAZY_DEPTH 256
338
339
  #endif
339
340
 
340
- /* Whether an op can raise on a cell, so that a masked cell must be
341
- skipped rather than computed: any op on an object cell (it calls Ruby),
342
- and an integer reciprocal (zero raises). */
341
+ /* Whether a cast from `from` to `to` raises on a NaN cell: a float or
342
+ complex value has no integer for NaN. */
343
343
  static int
344
- ca_monop_is_trapping (uint16_t op_id, int8_t dt)
344
+ ca_monop_cast_refuses_nan (int8_t from, int8_t to)
345
345
  {
346
- if ( dt == CA_OBJECT ) {
347
- return 1;
348
- }
349
- return ( op_id == CA_MONOP_RCP && dt >= CA_INT8 && dt <= CA_UINT64 );
346
+ int from_float = ( from == CA_FLOAT32 || from == CA_FLOAT64 ||
347
+ from == CA_CMPLX64 || from == CA_CMPLX128 );
348
+ return from_float && to >= CA_INT8 && to <= CA_UINT64;
350
349
  }
351
350
 
352
351
  /* Apply a single op in-place on `data` (src == dst, unit element stride).
@@ -476,7 +475,27 @@ ca_monop_eval (CAMonOp *mo, const ca_lazy_req_t *req, void *data, int dir)
476
475
  v = ALLOCV_N(char, holder, n * parent_bytes);
477
476
  if ( dir == CA_XFER_GET ) {
478
477
  ca_lazy_req_pull(mo->parent, req, v, CA_XFER_GET);
479
- if ( is_cast ) {
478
+ if ( is_cast && ca_monop_cast_refuses_nan(mo->parent->data_type,
479
+ mo->data_type) ) {
480
+ /* The cast raises on a NaN cell it is asked to convert; a masked
481
+ cell (mask_invalid keeps the NaN under the mask) is skipped.
482
+ The mask is copied off the arena first so a raise leaves no
483
+ arena slot held. */
484
+ CArray *parent = mo->parent;
485
+ boolean8_t *arena_mask = ca_lazy_req_mask(req, 1, &parent);
486
+ boolean8_t *m = NULL;
487
+ volatile VALUE mholder = 0;
488
+ if ( arena_mask ) {
489
+ m = ALLOCV_N(boolean8_t, mholder, n);
490
+ memcpy(m, arena_mask, n);
491
+ ca_lazy_arena_release(arena_mask);
492
+ }
493
+ ca_cast_block_with_mask(n, mo->parent, v, (CArray *) mo, data, m);
494
+ if ( m ) {
495
+ ALLOCV_END(mholder);
496
+ }
497
+ }
498
+ else if ( is_cast ) {
480
499
  ca_cast_block(n, mo->parent, v, (CArray *) mo, data);
481
500
  }
482
501
  else {
@@ -503,7 +522,7 @@ ca_monop_eval (CAMonOp *mo, const ca_lazy_req_t *req, void *data, int dir)
503
522
  /* Writable-view op_id PUT/GET is handled above. Below: non-writable
504
523
  monop/monfunc. PUT is read-only. */
505
524
  if ( dir != CA_XFER_GET ) {
506
- rb_raise(rb_eRuntimeError, "CAMonOp is read-only (xfer_stride PUT)");
525
+ ca_refuse_read_only();
507
526
  }
508
527
  ca_monop_materialise_call_count++;
509
528
 
@@ -573,12 +592,12 @@ ca_monop_eval (CAMonOp *mo, const ca_lazy_req_t *req, void *data, int dir)
573
592
  ca_lazy_arena_release(scratch);
574
593
  cur_dt = target_dt;
575
594
  } else {
576
- if ( ! mask_taken && ca_monop_is_trapping(op_id, cur_dt) ) {
595
+ if ( ! mask_taken && ca_op_raises_on_cell(CA_OP_KIND_MONOP, op_id, cur_dt) ) {
577
596
  slab_mask = ca_lazy_req_mask(req, 1, &leaf);
578
597
  mask_taken = 1;
579
598
  }
580
599
  apply_monop_in_place(op_id, cur_dt, slab_n, data,
581
- ca_monop_is_trapping(op_id, cur_dt) ? slab_mask : NULL);
600
+ ca_op_raises_on_cell(CA_OP_KIND_MONOP, op_id, cur_dt) ? slab_mask : NULL);
582
601
  cur_dt = ca_lazy_promote_monop(op_id, cur_dt);
583
602
  }
584
603
  }
@@ -638,16 +657,7 @@ ca_monop_func_allocate (void *ap)
638
657
  if ( ca_monop_is_writable_view(ca->op_id) ) {
639
658
  ca_attach(ca->parent);
640
659
  }
641
- ca->ptr = xmalloc(ca_length(ca));
642
- /* CA_OBJECT initialisation for GC safety. */
643
- if ( ca->data_type == CA_OBJECT ) {
644
- VALUE *p = (VALUE *) ca->ptr;
645
- VALUE zero = SIZE2NUM(0);
646
- ca_size_t i;
647
- for ( i = 0; i < ca->elements; i++ ) {
648
- *p++ = zero;
649
- }
650
- }
660
+ ca->ptr = ca_alloc_cells((CArray *) ca);
651
661
  }
652
662
 
653
663
  static void
@@ -676,22 +686,19 @@ ca_monop_func_attach (void *ap)
676
686
  Mirrors CAFake.attach. */
677
687
  if ( ca_monop_is_writable_view(ca->op_id) ) {
678
688
  ca_attach(ca->parent);
679
- ca->ptr = xmalloc(ca_length(ca));
680
- /* CA_OBJECT initialisation for GC safety (mirrors CAFake.allocate):
681
- zero-init the VALUE cells before any potential GC can observe
682
- them. */
683
- if ( ca->data_type == CA_OBJECT ) {
684
- VALUE *p = (VALUE *) ca->ptr;
685
- VALUE zero = SIZE2NUM(0);
686
- ca_size_t i;
687
- for ( i = 0; i < ca->elements; i++ ) {
688
- *p++ = zero;
689
- }
690
- }
689
+ ca->ptr = ca_alloc_cells((CArray *) ca);
691
690
  if ( ca_monop_is_cast(ca->op_id) ) {
692
- /* Cast parent.ptr (parent data_type) → ca.ptr (cast target data_type). */
693
- ca_cast_block(ca->elements, ca->parent, ca->parent->ptr,
694
- (CArray *) ca, ca->ptr);
691
+ /* Cast parent.ptr (parent data_type) → ca.ptr (cast target data_type).
692
+ A cast that raises on NaN skips the masked cells; the parent's
693
+ mask is attached with the parent. A raise here is undone by
694
+ ca_attach, which calls the detach slot once ca->ptr is set. */
695
+ boolean8_t *m = NULL;
696
+ if ( ca_monop_cast_refuses_nan(ca->parent->data_type, ca->data_type)
697
+ && ca_has_mask(ca->parent) ) {
698
+ m = (boolean8_t *) ca->parent->mask->ptr;
699
+ }
700
+ ca_cast_block_with_mask(ca->elements, ca->parent, ca->parent->ptr,
701
+ (CArray *) ca, ca->ptr, m);
695
702
  }
696
703
  else {
697
704
  /* byte_swap: memcpy parent.ptr → ca.ptr (same data_type same bytes),
@@ -702,19 +709,11 @@ ca_monop_func_attach (void *ap)
702
709
  return;
703
710
  }
704
711
 
705
- ca->ptr = xmalloc(ca_length(ca));
712
+ ca->ptr = ca_alloc_cells((CArray *) ca);
706
713
 
707
714
  s = ca->bytes;
708
715
  for ( k = ca->ndim - 1; k >= 0; k-- ) { native[k] = s; s *= ca->dim[k]; }
709
716
  for ( k = 0; k < ca->ndim; k++ ) starts[k] = 0;
710
- /* CA_OBJECT cells are VALUEs and this buffer is about to be marked as
711
- soon as the view is, so it must not be handed to the GC as raw
712
- xmalloc garbage. */
713
- if ( ca->data_type == CA_OBJECT ) {
714
- VALUE *p = (VALUE *) ca->ptr;
715
- ca_size_t i;
716
- for ( i = 0; i < ca->elements; i++ ) *p++ = Qnil;
717
- }
718
717
  ca_monop_func_xfer_stride(ca, starts, ca->dim, native, ca->ptr, CA_XFER_GET);
719
718
  }
720
719
 
@@ -782,7 +781,7 @@ ca_monop_func_fill_data (void *ap, void *ptr)
782
781
  data_type (= same bytes for byte_swap, reverse-cast for cast).
783
782
  Non-writable monop/monfunc remains read-only. */
784
783
  if ( ! ca_monop_is_writable_view(ca->op_id) ) {
785
- rb_raise(rb_eRuntimeError, "CAMonOp is read-only (fill_data)");
784
+ ca_refuse_read_only();
786
785
  }
787
786
  {
788
787
  int is_cast = ca_monop_is_cast(ca->op_id);
@@ -915,8 +914,9 @@ rb_ca_monop_build (VALUE cary, uint16_t op_id)
915
914
  with no boolean kernel reads the 0/1 storage in int64. */
916
915
  if ( parent_dt == CA_BOOLEAN && ! ca_monop_is_writable_view(op_id) ) {
917
916
  if ( ca_monop_is_widening(op_id) ) {
918
- rb_raise(rb_eCADataTypeError,
919
- "invalid data type for monop (not implemented)");
917
+ /* the eager monop reads a boolean with no kernel as int64 (0/1), and
918
+ names that type in its refusal */
919
+ ca_op_refuse_unimplemented(cary, CA_INT64, rb_eCADataTypeError);
920
920
  }
921
921
  if ( ca_monop_kernel_lookup(op_id, CA_BOOLEAN) == ca_monop_not_implement ) {
922
922
  target = rb_ca_monop_new(target, CA_MONOP_CAST_BASE + CA_INT64);
@@ -934,8 +934,13 @@ rb_ca_monop_build (VALUE cary, uint16_t op_id)
934
934
  ( ca_monop_kernel_lookup(op_id, kernel_in_dt) == ca_monop_not_implement ||
935
935
  ( parent_dt == CA_FIXLEN &&
936
936
  ca_monop_kernel_lookup(op_id, CA_FIXLEN) == ca_monop_not_implement ) ) ) {
937
- rb_raise(rb_eCADataTypeError,
938
- "invalid data type for monop (not implemented)");
937
+ /* the eager monop takes an integer to float64 before it looks for the
938
+ kernel, and names the type it looked in */
939
+ ca_op_refuse_unimplemented(cary,
940
+ parent_dt == CA_FIXLEN ? CA_FIXLEN
941
+ : RTEST(rb_ca_is_integer_type(cary)) ? CA_FLOAT64
942
+ : kernel_in_dt,
943
+ rb_eCADataTypeError);
939
944
  }
940
945
 
941
946
  /* Insert cast node if the kernel wants a different input data_type. */
@@ -952,6 +957,7 @@ rb_ca_monop_build (VALUE cary, uint16_t op_id)
952
957
  static VALUE
953
958
  rb_ca_monop_s_build (VALUE klass, VALUE cary, VALUE op_id_val)
954
959
  {
960
+ /* fits: an operation number from the library's own table */
955
961
  uint16_t op_id = (uint16_t) NUM2UINT(op_id_val);
956
962
  (void) klass;
957
963
  return rb_ca_monop_build(cary, op_id);
@@ -976,7 +982,7 @@ rb_ca_monop_trapping (VALUE self)
976
982
  CAMonOp *mo;
977
983
  TypedData_Get_Struct(self, CAMonOp, &camonop_data_type, mo);
978
984
  return ( ! ca_monop_is_cast(mo->op_id)
979
- && ca_monop_is_trapping(mo->op_id, mo->parent->data_type) )
985
+ && ca_op_raises_on_cell(CA_OP_KIND_MONOP, mo->op_id, mo->parent->data_type) )
980
986
  ? Qtrue : Qfalse;
981
987
  }
982
988
 
@@ -996,6 +1002,7 @@ rb_ca_monop_op_id (VALUE self)
996
1002
  because in-place chain eval allocates no scratch; only attach (xfer_all
997
1003
  → output) and the parent's own xfer_stride alloc, neither of which we
998
1004
  count here. */
1005
+ #ifdef CARRAY_DEV_BUILD
999
1006
  static VALUE
1000
1007
  rb_ca_monop_s_reset_scratch_counter (VALUE klass)
1001
1008
  {
@@ -1021,6 +1028,7 @@ rb_ca_monop_s_materialise_count (VALUE klass)
1021
1028
  {
1022
1029
  return SIZE2NUM(ca_monop_materialise_call_count);
1023
1030
  }
1031
+ #endif /* CARRAY_DEV_BUILD */
1024
1032
 
1025
1033
  static VALUE
1026
1034
  rb_ca_monop_s_allocate (VALUE klass)
@@ -1119,10 +1127,10 @@ Init_ca_obj_monop (void)
1119
1127
  rb_define_singleton_method(rb_cCAMonOp, "__build__",
1120
1128
  rb_ca_monop_s_build, 2);
1121
1129
 
1122
- /* Lazy-view predicate exposed on CArray (covers CAMonOp and
1123
- CALazyMarker). */
1124
- rb_define_method(rb_cCArray, "__lazy_view__?",
1125
- rb_ca_is_lazy_view_p, 0);
1130
+ /* Lazy-view predicate on CArray (covers CAMonOp and CALazyMarker).
1131
+ Protected: the lazy dispatch asks it of the other operands. */
1132
+ rb_define_protected_method(rb_cCArray, "lazy_view?",
1133
+ rb_ca_is_lazy_view_p, 0);
1126
1134
 
1127
1135
  /* op_id accessor for inspect / dump_tree consumers in Ruby. */
1128
1136
  rb_define_method(rb_cCAMonOp, "__op_id__",
@@ -1130,6 +1138,7 @@ Init_ca_obj_monop (void)
1130
1138
  rb_define_method(rb_cCAMonOp, "__trapping__", rb_ca_monop_trapping, 0);
1131
1139
 
1132
1140
  /* Test instrumentation (not user-facing API). */
1141
+ #ifdef CARRAY_DEV_BUILD
1133
1142
  rb_define_singleton_method(rb_cCAMonOp, "__reset_scratch_counter__",
1134
1143
  rb_ca_monop_s_reset_scratch_counter, 0);
1135
1144
  rb_define_singleton_method(rb_cCAMonOp, "__scratch_count__",
@@ -1138,4 +1147,5 @@ Init_ca_obj_monop (void)
1138
1147
  rb_ca_monop_s_reset_materialise_counter, 0);
1139
1148
  rb_define_singleton_method(rb_cCAMonOp, "__materialise_count__",
1140
1149
  rb_ca_monop_s_materialise_count, 0);
1150
+ #endif
1141
1151
  }
data/ext/ca_obj_object.c CHANGED
@@ -60,6 +60,15 @@ ca_object_pool_init (void *ap, int8_t ndim)
60
60
  ca->dim = (ca_size_t *) ca->_pool;
61
61
  }
62
62
 
63
+ /* ca->self is the CAObject's own VALUE, which compaction can move; the
64
+ hooks call back through it, so follow the object to its new slot. */
65
+ static void
66
+ ca_object_compact (void *ap)
67
+ {
68
+ CAObject *ca = (CAObject *) ap;
69
+ ca->self = rb_gc_location(ca->self);
70
+ }
71
+
63
72
  const rb_data_type_t caobject_data_type = {
64
73
  .parent = &caview_data_type,
65
74
  .wrap_struct_name = "CAObject",
@@ -67,7 +76,7 @@ const rb_data_type_t caobject_data_type = {
67
76
  .dmark = ca_mark,
68
77
  .dfree = ca_free,
69
78
  .dsize = ca_object_dsize,
70
- .dcompact = NULL
79
+ .dcompact = ca_object_compact
71
80
  },
72
81
  .flags = RUBY_TYPED_FREE_IMMEDIATELY
73
82
  };
@@ -84,15 +93,23 @@ ca_objectmask_dsize (const void *ap)
84
93
  }
85
94
 
86
95
  /* The mask reads and writes through its CAObject, and its cache is the
87
- CAObject's; it keeps the CAObject alive. */
96
+ CAObject's; it keeps the CAObject alive, and follows it when compaction
97
+ moves it. */
88
98
  static void
89
99
  ca_objmask_mark (void *ap)
90
100
  {
91
101
  CAObjectMask *ca = (CAObjectMask *) ap;
92
- rb_gc_mark(ca->array);
102
+ rb_gc_mark_movable(ca->array);
93
103
  ca_mark(ap);
94
104
  }
95
105
 
106
+ static void
107
+ ca_objmask_compact (void *ap)
108
+ {
109
+ CAObjectMask *ca = (CAObjectMask *) ap;
110
+ ca->array = rb_gc_location(ca->array);
111
+ }
112
+
96
113
  const rb_data_type_t caobjectmask_data_type = {
97
114
  .parent = &carray_data_type,
98
115
  .wrap_struct_name = "CAObjectMask",
@@ -100,7 +117,7 @@ const rb_data_type_t caobjectmask_data_type = {
100
117
  .dmark = ca_objmask_mark,
101
118
  .dfree = ca_free,
102
119
  .dsize = ca_objectmask_dsize,
103
- .dcompact = NULL
120
+ .dcompact = ca_objmask_compact
104
121
  },
105
122
  .flags = RUBY_TYPED_FREE_IMMEDIATELY
106
123
  };
@@ -111,6 +128,7 @@ VALUE rb_cCAObjectMask;
111
128
  static CAObjectMask *
112
129
  ca_objmask_new (VALUE array, int8_t ndim, ca_size_t *dim)
113
130
  {
131
+ /* check: the mask takes the CAObject's own shape, already checked */
114
132
  CAObjectMask *ca = ALLOC(CAObjectMask);
115
133
  /* CA_OBJ_OBJECT_MASK registers no pool_init, so it lives on the legacy
116
134
  ALLOC_N path (dim allocated separately, freed via free_ca_wrap's else
@@ -135,50 +153,74 @@ ca_objmask_mask_data (void *ap)
135
153
  rb_intern("mask"), 0);
136
154
  }
137
155
 
138
- /* Calls recv.mid(arg(ap)) with *slot pointing at a caller's buffer for
139
- the duration, and puts *slot back however the call leaves. The bulk
140
- bridge lends the buffer this way so that copy_data / sync_data write
141
- straight into it; a raise that skipped the restore would leave the
142
- array pointing at a buffer its caller is about to free. */
156
+ /* A wrap handed to a hook points at a buffer the caller frees once the
157
+ hook returns. Taking it back leaves an array of no cells: a hook that
158
+ kept it holds something that raises when read, not a pointer into freed
159
+ memory. */
160
+ static char ca_object_no_cells[16];
161
+
162
+ static void
163
+ ca_object_empty_wrap (CArray *w)
164
+ {
165
+ int8_t k;
166
+ /* Not NULL: an entity whose pointer is NULL reads as detached. */
167
+ w->ptr = ca_object_no_cells;
168
+ w->elements = 0;
169
+ for ( k = 0; k < w->ndim; k++ ) {
170
+ w->dim[k] = 0;
171
+ }
172
+ }
173
+
174
+ /* Calls copy_data / sync_data with a wrap of a caller's buffer, shaped as
175
+ the object, and takes the buffer back however the call leaves. The wrap
176
+ shares the object's mask (the data's), and whatever mask it holds after
177
+ the call becomes the data's: a hook that writes masked values masks the
178
+ object's cells. The data points at the buffer for the duration too, for
179
+ a hook that reads the object back while it runs. */
143
180
 
144
181
  typedef struct {
145
- char **slot;
146
- char *saved;
147
- VALUE recv;
148
- ID mid;
149
- VALUE (*arg)(void *);
150
- void *ap;
182
+ CAObject *ca;
183
+ char *saved;
184
+ ID mid;
185
+ VALUE wrap;
151
186
  } ca_object_lend_t;
152
187
 
153
188
  static VALUE
154
189
  ca_object_lend_body (VALUE varg)
155
190
  {
156
191
  ca_object_lend_t *l = (ca_object_lend_t *) varg;
157
- return rb_funcall(l->recv, l->mid, 1, l->arg(l->ap));
192
+ return rb_funcall(l->ca->self, l->mid, 1, l->wrap);
158
193
  }
159
194
 
160
195
  static VALUE
161
196
  ca_object_lend_restore (VALUE varg)
162
197
  {
163
198
  ca_object_lend_t *l = (ca_object_lend_t *) varg;
164
- *l->slot = l->saved;
199
+ CArray *w = (CArray *) DATA_PTR(l->wrap);
200
+ l->ca->data->mask = w->mask;
201
+ w->mask = NULL;
202
+ ca_object_empty_wrap(w);
203
+ l->ca->data->ptr = l->saved;
165
204
  return Qnil;
166
205
  }
167
206
 
168
207
  static void
169
- ca_object_call_lending (char **slot, void *ptr, VALUE recv, const char *mid,
170
- VALUE (*arg)(void *), void *ap)
208
+ ca_object_call_lending (CAObject *ca, void *ptr, const char *mid)
171
209
  {
172
210
  ca_object_lend_t l;
173
- l.slot = slot;
174
- l.saved = *slot;
175
- l.recv = recv;
211
+ CArray *data = ca->data;
212
+ CAWrap *w;
213
+ w = ca_wrap_new(data->data_type, data->ndim, data->dim, data->bytes,
214
+ NULL, (char *) ptr);
215
+ l.wrap = ca_wrap_struct(w);
216
+ w->mask = data->mask;
217
+ l.ca = ca;
218
+ l.saved = data->ptr;
176
219
  l.mid = rb_intern(mid);
177
- l.arg = arg;
178
- l.ap = ap;
179
- *slot = (char *) ptr;
220
+ data->ptr = (char *) ptr;
180
221
  rb_ensure(ca_object_lend_body, (VALUE) &l,
181
222
  ca_object_lend_restore, (VALUE) &l);
223
+ RB_GC_GUARD(l.wrap);
182
224
  }
183
225
 
184
226
  /* Calls recv.mid(*argv), where some of argv are transient wraps of a
@@ -187,8 +229,7 @@ ca_object_call_lending (char **slot, void *ptr, VALUE recv, const char *mid,
187
229
  when it returns, and a wrap outlives the call: the collector scans the
188
230
  machine stack conservatively and still finds it there, and an object
189
231
  wrap is marked cell by cell -- through a buffer that is gone. A wrap
190
- taken back points at nothing; a hook that kept it holds an empty
191
- handle, not a stale one. */
232
+ taken back has no cells (ca_object_empty_wrap). */
192
233
 
193
234
  typedef struct {
194
235
  VALUE recv;
@@ -212,7 +253,7 @@ ca_object_hook_release (VALUE varg)
212
253
  ca_object_hook_t *h = (ca_object_hook_t *) varg;
213
254
  int i;
214
255
  for ( i = 0; i < h->ntrans; i++ ) {
215
- ((CArray *) DATA_PTR(h->trans[i]))->ptr = NULL;
256
+ ca_object_empty_wrap((CArray *) DATA_PTR(h->trans[i]));
216
257
  }
217
258
  return Qnil;
218
259
  }
@@ -233,11 +274,6 @@ ca_object_call_transient (VALUE recv, const char *mid,
233
274
  ca_object_hook_release, (VALUE) &h);
234
275
  }
235
276
 
236
- static VALUE
237
- ca_object_data_of (void *ap)
238
- {
239
- return rb_ivar_get(((CAObject *) ap)->self, rb_intern("__data__"));
240
- }
241
277
 
242
278
 
243
279
  /* Forward declaration: ca_object_wrap_transient (defined further down,
@@ -710,10 +746,16 @@ ca_object_setup (CAObject *ca,
710
746
 
711
747
  /* check arguments */
712
748
 
713
- CA_CHECK_DATA_TYPE(data_type);
714
- CA_CHECK_RANK(ndim);
715
- CA_CHECK_DIM(ndim, dim);
716
- CA_CHECK_BYTES(data_type, bytes);
749
+ CA_CHECK_DATA_TYPE_GIVEN(data_type);
750
+ CA_CHECK_RANK_GIVEN(ndim);
751
+ CA_CHECK_DIM_GIVEN(ndim, dim);
752
+ CA_CHECK_BYTES_GIVEN(data_type, bytes);
753
+
754
+ /* ca == NULL asks for the checks above only, so that a constructor can
755
+ run them before it allocates. */
756
+ if ( ca == NULL ) {
757
+ return 0;
758
+ }
717
759
 
718
760
  /* calculate total number of elements */
719
761
 
@@ -745,7 +787,9 @@ ca_object_setup (CAObject *ca,
745
787
  static CAObject *
746
788
  ca_object_new (int8_t data_type, int8_t ndim, ca_size_t *dim, ca_size_t bytes)
747
789
  {
748
- CAObject *ca = (CAObject *) ca_array_alloc(CA_OBJ_OBJECT, ndim);
790
+ CAObject *ca;
791
+ ca_object_setup(NULL, data_type, ndim, dim, bytes);
792
+ ca = (CAObject *) ca_array_alloc(CA_OBJ_OBJECT, ndim);
749
793
  ca_object_setup(ca, data_type, ndim, dim, bytes);
750
794
  return ca;
751
795
  }
@@ -755,7 +799,12 @@ free_ca_object (void *ap)
755
799
  {
756
800
  CAObject *ca = (CAObject *) ap;
757
801
  if ( ca != NULL ) {
758
- /* ca->mask will be GC-ed by Ruby interpreter */
802
+ /* A Face's mask is a view over its parent's mask (made by
803
+ ca_view_func_create_mask) and belongs to this struct. Otherwise
804
+ the mask is a Ruby object held in @mask, and the GC frees it. */
805
+ if ( ca_is_face(ca) ) {
806
+ ca_free(ca->mask);
807
+ }
759
808
  if ( ca->_pool ) {
760
809
  ca_array_free(ca); /* dim lives in _pool */
761
810
  }
@@ -789,6 +838,19 @@ ca_object_mask_bytes (CAObject *ca)
789
838
  return (boolean8_t *) ca->mask->ptr;
790
839
  }
791
840
 
841
+ /* Whether the mask of this object comes from its mask_* hooks. Then a
842
+ fetched value says nothing about the mask: a cell the hooks mask stays
843
+ masked when its value is read, and only a fetcher answering UNDEF marks
844
+ one. Without hooks the mask is what the fetches have answered, so a
845
+ value fetched clears the cell. */
846
+ static int
847
+ ca_object_mask_from_hooks (CAObject *ca)
848
+ {
849
+ return rb_obj_respond_to(ca->self, rb_intern("mask_fetch_addr"), Qtrue)
850
+ || rb_obj_respond_to(ca->self, rb_intern("mask_fetch_index"), Qtrue)
851
+ || rb_obj_respond_to(ca->self, rb_intern("mask_copy_data"), Qtrue);
852
+ }
853
+
792
854
  /* per-cell cores: the addr- and index-primary Ruby-callback bridge logic,
793
855
  direction-unified. The fetch_* / store_* slots below are thin
794
856
  forwarders onto these. */
@@ -809,7 +871,7 @@ ca_object_xfer_addr_one (CAObject *ca, ca_size_t addr, void *ptr, int dir)
809
871
  }
810
872
  }
811
873
  else {
812
- if ( ca_has_mask(ca) ) {
874
+ if ( ! ca_object_mask_from_hooks(ca) && ca_has_mask(ca) ) {
813
875
  ca_object_mask_bytes(ca)[addr] = 0;
814
876
  }
815
877
  rb_ca_obj2ptr(ca->self, rval, ptr);
@@ -831,7 +893,7 @@ ca_object_xfer_addr_one (CAObject *ca, ca_size_t addr, void *ptr, int dir)
831
893
  }
832
894
  }
833
895
  else {
834
- if ( ca_has_mask(ca) ) {
896
+ if ( ! ca_object_mask_from_hooks(ca) && ca_has_mask(ca) ) {
835
897
  ca_object_mask_bytes(ca)[ca_index2addr(ca->mask, idx)] = 0;
836
898
  }
837
899
  rb_ca_obj2ptr(ca->self, rval, ptr);
@@ -877,7 +939,7 @@ ca_object_xfer_index_one (CAObject *ca, ca_size_t *idx, void *ptr, int dir)
877
939
  }
878
940
  }
879
941
  else {
880
- if ( ca_has_mask(ca) ) {
942
+ if ( ! ca_object_mask_from_hooks(ca) && ca_has_mask(ca) ) {
881
943
  ca_object_mask_bytes(ca)[ca_index2addr(ca->mask, idx)] = 0;
882
944
  }
883
945
  rb_ca_obj2ptr(ca->self, rval, ptr);
@@ -895,7 +957,7 @@ ca_object_xfer_index_one (CAObject *ca, ca_size_t *idx, void *ptr, int dir)
895
957
  }
896
958
  }
897
959
  else {
898
- if ( ca_has_mask(ca) ) {
960
+ if ( ! ca_object_mask_from_hooks(ca) && ca_has_mask(ca) ) {
899
961
  ca_object_mask_bytes(ca)[addr] = 0;
900
962
  }
901
963
  rb_ca_obj2ptr(ca->self, rval, ptr);
@@ -924,8 +986,8 @@ static void
924
986
  ca_object_func_allocate (void *ap)
925
987
  {
926
988
  CAObject *ca = (CAObject *) ap;
927
- /* ca->data->ptr = ALLOC_N(char, ca_length(ca)); */
928
- ca->data->ptr = xmalloc(ca_length(ca));
989
+ /* cells: seeded with 0, which a copy_data that skips cells leaves visible */
990
+ ca->data->ptr = xmalloc(ca_length(ca));
929
991
  if ( ca_is_object_type(ca->data) ) { /* GC safe */
930
992
  VALUE *p = (VALUE *) ca->data->ptr;
931
993
  VALUE zero = INT2NUM(0);
@@ -951,12 +1013,51 @@ ca_object_func_allocate (void *ap)
951
1013
  arithmetic all work via the slow per-element fallback. The fast bulk
952
1014
  path is opt-in by defining copy_data / sync_data / fill_data. */
953
1015
 
1016
+ /* copy_data of an object whose mask comes from its hooks: the array it is
1017
+ handed must not carry the mask's cache, or a store of its values into it
1018
+ (d[] = src) would clear the cells the hooks mask. It is handed without a
1019
+ mask, and a mask the call makes on it is dropped. */
1020
+ typedef struct {
1021
+ CAObject *ca;
1022
+ void *ptr;
1023
+ CArray *mask;
1024
+ } ca_object_copy_bare_t;
1025
+
1026
+ static VALUE
1027
+ ca_object_copy_bare_body (VALUE arg)
1028
+ {
1029
+ ca_object_copy_bare_t *c = (ca_object_copy_bare_t *) arg;
1030
+ ca_object_call_lending(c->ca, c->ptr, "copy_data");
1031
+ return Qnil;
1032
+ }
1033
+
1034
+ static VALUE
1035
+ ca_object_copy_bare_restore (VALUE arg)
1036
+ {
1037
+ ca_object_copy_bare_t *c = (ca_object_copy_bare_t *) arg;
1038
+ CArray *made = c->ca->data->mask;
1039
+ if ( made != NULL && made != c->mask ) {
1040
+ ca_free(made);
1041
+ }
1042
+ c->ca->data->mask = c->mask;
1043
+ return Qnil;
1044
+ }
1045
+
954
1046
  static void
955
1047
  ca_object_dispatch_copy (CAObject *ca, void *ptr)
956
1048
  {
957
1049
  if ( rb_obj_respond_to(ca->self, rb_intern("copy_data"), Qtrue) ) {
958
- ca_object_call_lending(&ca->data->ptr, ptr, ca->self, "copy_data",
959
- ca_object_data_of, ca);
1050
+ if ( ca->data->mask != NULL && ca_object_mask_from_hooks(ca) ) {
1051
+ ca_object_copy_bare_t c;
1052
+ c.ca = ca;
1053
+ c.ptr = ptr;
1054
+ c.mask = ca->data->mask;
1055
+ ca->data->mask = NULL;
1056
+ rb_ensure(ca_object_copy_bare_body, (VALUE) &c,
1057
+ ca_object_copy_bare_restore, (VALUE) &c);
1058
+ return;
1059
+ }
1060
+ ca_object_call_lending(ca, ptr, "copy_data");
960
1061
  }
961
1062
  else {
962
1063
  ca_size_t addr;
@@ -971,8 +1072,7 @@ static void
971
1072
  ca_object_dispatch_sync (CAObject *ca, void *ptr)
972
1073
  {
973
1074
  if ( rb_obj_respond_to(ca->self, rb_intern("sync_data"), Qtrue) ) {
974
- ca_object_call_lending(&ca->data->ptr, ptr, ca->self, "sync_data",
975
- ca_object_data_of, ca);
1075
+ ca_object_call_lending(ca, ptr, "sync_data");
976
1076
  }
977
1077
  else {
978
1078
  ca_size_t addr;
@@ -1017,7 +1117,7 @@ ca_object_func_attach (void *ap)
1017
1117
  ca_face_attach(ap);
1018
1118
  return;
1019
1119
  }
1020
- /* ca->data->ptr = ALLOC_N(char, ca_length(ca)); */
1120
+ /* cells: seeded with 0, which a copy_data that skips cells leaves visible */
1021
1121
  ca->data->ptr = xmalloc(ca_length(ca));
1022
1122
  if ( ca_is_object_type(ca->data) ) { /* GC safe */
1023
1123
  VALUE *p = (VALUE *) ca->data->ptr;
@@ -1468,6 +1568,12 @@ rb_ca_object_initialize_copy (VALUE self, VALUE other)
1468
1568
  data = ca_wrap_struct(ca->data);
1469
1569
  rb_ivar_set(self, rb_intern("__data__"), data);
1470
1570
 
1571
+ /* A Face's mask is a C struct owned by the Face, so the copy makes its
1572
+ own from the parent when it is asked for one. */
1573
+ if ( ca_is_face(ca) ) {
1574
+ return self;
1575
+ }
1576
+
1471
1577
  ca_update_mask(cs);
1472
1578
  if ( cs->mask ) {
1473
1579
  ca->mask = cs->mask;
@@ -1567,7 +1673,7 @@ rb_ca_object_initialize (int argc, VALUE *argv, VALUE self)
1567
1673
  }
1568
1674
 
1569
1675
  Check_Type(rdim, T_ARRAY);
1570
- CA_CHECK_RANK(RARRAY_LEN(rdim)); /* before dim[] is filled */
1676
+ CA_CHECK_RANK_GIVEN(RARRAY_LEN(rdim)); /* before dim[] is filled */
1571
1677
 
1572
1678
  ndim = RARRAY_LEN(rdim);
1573
1679
  for (i=0; i<ndim; i++) {