carray 3.0.1 → 3.0.3

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 (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +824 -0
  3. data/README.md +5 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +258 -4
  6. data/ext/ca_axis_group.c +368 -184
  7. data/ext/ca_binop_dispatch.c +54 -32
  8. data/ext/ca_binop_dispatch.h +1 -0
  9. data/ext/ca_categorical_iterator.c +90 -41
  10. data/ext/ca_compare.c +8 -2
  11. data/ext/ca_for_buffer.h +21 -68
  12. data/ext/ca_group_iter.c +27 -11
  13. data/ext/ca_kernel_iterator.c +960 -245
  14. data/ext/ca_kernel_iterator.h +352 -181
  15. data/ext/ca_monop_dispatch.c +4 -4
  16. data/ext/ca_monop_dispatch.h +4 -0
  17. data/ext/ca_obj_array.c +194 -42
  18. data/ext/ca_obj_bincmp.c +152 -121
  19. data/ext/ca_obj_binop.c +107 -210
  20. data/ext/ca_obj_bitarray.c +37 -24
  21. data/ext/ca_obj_bitfield.c +95 -62
  22. data/ext/ca_obj_block.c +5 -4
  23. data/ext/ca_obj_byte_swap.c +40 -40
  24. data/ext/ca_obj_const_string.c +180 -50
  25. data/ext/ca_obj_face.c +428 -62
  26. data/ext/ca_obj_face.h +107 -47
  27. data/ext/ca_obj_fake.c +21 -50
  28. data/ext/ca_obj_farray.c +1 -0
  29. data/ext/ca_obj_field.c +16 -3
  30. data/ext/ca_obj_fixlen_string.c +39 -26
  31. data/ext/ca_obj_grid.c +181 -124
  32. data/ext/ca_obj_meld.c +235 -55
  33. data/ext/ca_obj_moncmp.c +61 -61
  34. data/ext/ca_obj_monop.c +125 -107
  35. data/ext/ca_obj_object.c +551 -233
  36. data/ext/ca_obj_record.c +51 -16
  37. data/ext/ca_obj_reduce.c +5 -8
  38. data/ext/ca_obj_refer.c +117 -33
  39. data/ext/ca_obj_remap.c +57 -17
  40. data/ext/ca_obj_repeat.c +21 -5
  41. data/ext/ca_obj_roll.c +115 -11
  42. data/ext/ca_obj_select.c +88 -75
  43. data/ext/ca_obj_select_axis.c +58 -60
  44. data/ext/ca_obj_shift.c +22 -102
  45. data/ext/ca_obj_stack.c +223 -123
  46. data/ext/ca_obj_stride.c +267 -35
  47. data/ext/ca_obj_string.c +25 -22
  48. data/ext/ca_obj_tile.c +32 -16
  49. data/ext/ca_obj_time.c +23 -25
  50. data/ext/ca_obj_timedelta.c +21 -23
  51. data/ext/ca_obj_transpose.c +12 -3
  52. data/ext/ca_obj_triop.c +110 -117
  53. data/ext/ca_obj_window.c +157 -29
  54. data/ext/ca_op_ipower.c +25 -13
  55. data/ext/ca_op_powi.h +57 -30
  56. data/ext/ca_pow5_table.h +656 -0
  57. data/ext/ca_rng_normal.h +42 -0
  58. data/ext/ca_rng_xoshiro256pp.h +105 -0
  59. data/ext/ca_sort_kernels.h +70 -0
  60. data/ext/ca_sweep_engine.c +385 -198
  61. data/ext/ca_sweep_engine.h +31 -23
  62. data/ext/ca_triop_dispatch.c +4 -1
  63. data/ext/ca_triop_dispatch.h +5 -2
  64. data/ext/caframe_csv_reader.c +463 -0
  65. data/ext/carray.h +90 -28
  66. data/ext/carray_access.c +487 -231
  67. data/ext/carray_address_basis.c +731 -0
  68. data/ext/carray_attribute.c +45 -3
  69. data/ext/carray_bincount.c +32 -2
  70. data/ext/carray_broadcast.c +49 -17
  71. data/ext/carray_call_cfunc.c +667 -511
  72. data/ext/carray_cast.c +1556 -247
  73. data/ext/carray_cast_func.rb +34 -21
  74. data/ext/carray_conversion.c +197 -73
  75. data/ext/carray_copy.c +84 -38
  76. data/ext/carray_core.c +591 -119
  77. data/ext/carray_count.c +148 -96
  78. data/ext/carray_element.c +18 -27
  79. data/ext/carray_factorize.c +604 -291
  80. data/ext/carray_generate.c +76 -40
  81. data/ext/carray_histogram.c +152 -94
  82. data/ext/carray_hold.c +2 -1
  83. data/ext/carray_index_classifier.c +63 -96
  84. data/ext/carray_internal.h +400 -1
  85. data/ext/carray_lazy.c +834 -79
  86. data/ext/carray_loop.c +88 -66
  87. data/ext/carray_mask.c +705 -324
  88. data/ext/carray_math_kernel.h +1 -0
  89. data/ext/carray_median_percentile.c +263 -63
  90. data/ext/carray_memory_view.c +254 -248
  91. data/ext/carray_operator.c +1093 -363
  92. data/ext/carray_order.c +133 -69
  93. data/ext/carray_partition.c +102 -56
  94. data/ext/carray_random.c +582 -204
  95. data/ext/carray_scatter.c +308 -65
  96. data/ext/carray_segment.c +257 -0
  97. data/ext/carray_slab.c +183 -70
  98. data/ext/carray_sort.c +235 -240
  99. data/ext/carray_sort_kernel.c +8 -1
  100. data/ext/carray_struct.c +99 -21
  101. data/ext/carray_test.c +167 -64
  102. data/ext/carray_time_text.c +1182 -0
  103. data/ext/carray_utils.c +363 -98
  104. data/ext/extconf.rb +27 -2
  105. data/ext/mk_call_cfunc.rb +103 -118
  106. data/ext/mk_pow5_table.rb +35 -0
  107. data/ext/mkkernel.rb +1349 -851
  108. data/ext/ruby_carray.c +17 -2
  109. data/ext/version.h +4 -4
  110. data/ext/version.rb +4 -2
  111. data/lib/carray/attribute.rb +39 -0
  112. data/lib/carray/autoload_carray.rb +24 -3
  113. data/lib/carray/autoload_method_extension.rb +18 -0
  114. data/lib/carray/axis_group.rb +245 -20
  115. data/lib/carray/basics.rb +95 -25
  116. data/lib/carray/bincount_nd.rb +6 -0
  117. data/lib/carray/block_iterator.rb +200 -54
  118. data/lib/carray/boolean_reduce.rb +21 -29
  119. data/lib/carray/categorical.rb +244 -59
  120. data/lib/carray/categorical_iterator.rb +391 -757
  121. data/lib/carray/complex.rb +36 -29
  122. data/lib/carray/conditional.rb +94 -7
  123. data/lib/carray/const_string.rb +177 -27
  124. data/lib/carray/construct.rb +40 -0
  125. data/lib/carray/data_type_extension.rb +3 -0
  126. data/lib/carray/data_type_limits.rb +91 -0
  127. data/lib/carray/fixlen_string.rb +1 -1
  128. data/lib/carray/frame/concat.rb +157 -6
  129. data/lib/carray/frame/convert.rb +13 -7
  130. data/lib/carray/frame/csv_parser.rb +590 -76
  131. data/lib/carray/frame/describe.rb +79 -0
  132. data/lib/carray/frame/frame.rb +205 -35
  133. data/lib/carray/frame/group.rb +199 -40
  134. data/lib/carray/frame/io.rb +452 -60
  135. data/lib/carray/frame/join.rb +134 -46
  136. data/lib/carray/frame/records.rb +54 -19
  137. data/lib/carray/frame/reshape.rb +448 -0
  138. data/lib/carray/frame/sort.rb +26 -2
  139. data/lib/carray/frame/verbs.rb +726 -56
  140. data/lib/carray/frame.rb +2 -0
  141. data/lib/carray/fuse_source.rb +111 -2
  142. data/lib/carray/fusion.rb +229 -38
  143. data/lib/carray/inspect.rb +71 -20
  144. data/lib/carray/iterator.rb +157 -2
  145. data/lib/carray/lazy.rb +54 -67
  146. data/lib/carray/mask_gap_fill.rb +66 -50
  147. data/lib/carray/math.rb +32 -19
  148. data/lib/carray/meld_reduce.rb +40 -7
  149. data/lib/carray/methods/align_addr.rb +8 -4
  150. data/lib/carray/methods/bincount.rb +2 -2
  151. data/lib/carray/methods/bit_string.rb +14 -3
  152. data/lib/carray/methods/composition.rb +16 -10
  153. data/lib/carray/methods/discovery_along.rb +74 -0
  154. data/lib/carray/methods/factorize.rb +50 -0
  155. data/lib/carray/methods/gather_nd.rb +13 -2
  156. data/lib/carray/methods/insert_block.rb +9 -14
  157. data/lib/carray/methods/is_in.rb +130 -6
  158. data/lib/carray/methods/join.rb +1 -1
  159. data/lib/carray/methods/locate_addr.rb +235 -21
  160. data/lib/carray/methods/mask_duplicates.rb +35 -1
  161. data/lib/carray/methods/mode.rb +18 -1
  162. data/lib/carray/methods/nunique.rb +22 -1
  163. data/lib/carray/methods/repeat.rb +91 -0
  164. data/lib/carray/methods/resize.rb +21 -23
  165. data/lib/carray/methods/snap.rb +34 -12
  166. data/lib/carray/methods/unique.rb +52 -3
  167. data/lib/carray/record_order.rb +208 -0
  168. data/lib/carray/rng.rb +86 -0
  169. data/lib/carray/runtime.rb +14 -11
  170. data/lib/carray/segment_iterator.rb +990 -0
  171. data/lib/carray/serialize.rb +197 -27
  172. data/lib/carray/slab_iterator.rb +109 -40
  173. data/lib/carray/stack.rb +10 -4
  174. data/lib/carray/string_operation_extension.rb +9 -4
  175. data/lib/carray/struct.rb +142 -51
  176. data/lib/carray/struct_builder.rb +110 -14
  177. data/lib/carray/table.rb +2 -2
  178. data/lib/carray/time.rb +535 -105
  179. data/lib/carray/window_iterator.rb +259 -82
  180. data/lib/carray.rb +4 -2
  181. data/yard-stubs/ca_obj_array.rb +4 -4
  182. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  183. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  184. data/yard-stubs/ca_obj_block.rb +2 -7
  185. data/yard-stubs/ca_obj_fake.rb +1 -1
  186. data/yard-stubs/ca_obj_field.rb +1 -1
  187. data/yard-stubs/ca_obj_refer.rb +3 -3
  188. data/yard-stubs/ca_obj_roll.rb +0 -1
  189. data/yard-stubs/ca_obj_stride.rb +10 -7
  190. data/yard-stubs/ca_obj_transpose.rb +1 -1
  191. data/yard-stubs/ca_obj_window.rb +11 -4
  192. data/yard-stubs/carray_access.rb +6 -5
  193. data/yard-stubs/carray_broadcast.rb +2 -2
  194. data/yard-stubs/carray_cast.rb +78 -43
  195. data/yard-stubs/carray_conversion.rb +13 -1
  196. data/yard-stubs/carray_copy.rb +8 -3
  197. data/yard-stubs/carray_core.rb +3 -83
  198. data/yard-stubs/carray_count.rb +16 -2
  199. data/yard-stubs/carray_lazy.rb +210 -1
  200. data/yard-stubs/carray_loop.rb +4 -1
  201. data/yard-stubs/carray_mask.rb +34 -10
  202. data/yard-stubs/carray_math.rb +1499 -4
  203. data/yard-stubs/carray_median_percentile.rb +26 -8
  204. data/yard-stubs/carray_order.rb +43 -82
  205. data/yard-stubs/carray_scatter.rb +10 -3
  206. data/yard-stubs/carray_segment.rb +72 -0
  207. data/yard-stubs/carray_slab.rb +11 -8
  208. data/yard-stubs/carray_sort.rb +50 -8
  209. data/yard-stubs/carray_test.rb +10 -0
  210. data/yard-stubs/carray_utils.rb +0 -26
  211. metadata +19 -19
  212. data/ext/ca_for_each_element.h +0 -239
  213. data/ext/carray_build_flags.h +0 -3
  214. data/ext/carray_config.h +0 -28
  215. data/ext/carray_kernels_bincmp.c +0 -4446
  216. data/ext/carray_kernels_binop.c +0 -11001
  217. data/ext/carray_kernels_init.c +0 -1131
  218. data/ext/carray_kernels_map.c +0 -3467
  219. data/ext/carray_kernels_moncmp.c +0 -2097
  220. data/ext/carray_kernels_monop.c +0 -18313
  221. data/ext/carray_kernels_reduce_aggregate.c +0 -25837
  222. data/ext/carray_kernels_reduce_boolean.c +0 -330
  223. data/ext/carray_kernels_reduce_cumulative.c +0 -14593
  224. data/ext/carray_kernels_reduce_extreme.c +0 -16948
  225. data/ext/carray_kernels_reduce_variance.c +0 -3910
  226. data/ext/carray_kernels_scan.c +0 -3693
  227. data/ext/carray_kernels_search.c +0 -32138
  228. data/ext/carray_kernels_sort.c +0 -10626
  229. data/ext/carray_kernels_triop.c +0 -1392
data/ext/carray_core.c CHANGED
@@ -425,7 +425,6 @@ ca_attach_is_alias (void *ap)
425
425
  CArray *ca = (CArray *) ap;
426
426
  extern ca_operation_function_t ca_stride_func;
427
427
  extern ca_operation_function_t ca_lazy_marker_func;
428
- extern int ca_stride_is_contiguous (CAStride *ca);
429
428
  extern int ca_stride_attach_aliases_root (CAStride *ca);
430
429
 
431
430
  if ( ca == NULL ) return 0;
@@ -440,21 +439,13 @@ ca_attach_is_alias (void *ap)
440
439
  return ca_attach_is_alias(((CAView *) ca)->parent);
441
440
  }
442
441
 
443
- /* CAStride family share ca_stride_func.attach (= ca_stride_func_attach).
444
- The alias-attach fast path is taken iff composed strides are
445
- row-major contiguous. ca_stride_is_contiguous checks the leaf
446
- view's own strides, which is what we want — the compose-fold to
447
- root happens during attach itself; if leaf is contig and parent
448
- chain is too (transitively, since each CAStride's strides are
449
- composed against parent's), the alias path fires.
450
-
451
- ...and iff there is parent memory to alias at the end of that fold.
452
- A non-entity root has none to lend, so attach builds its own buffer
453
- and writes through ca->ptr reach the root only via ca_sync. Saying
454
- "alias" there would let a caller write and skip the sync. */
442
+ /* CAStride family share ca_stride_func.attach, which aliases the root's
443
+ memory iff the strides composed to the root are row-major contiguous
444
+ and the root has memory to lend. Otherwise attach builds its own
445
+ buffer and writes through ca->ptr reach the root only via ca_sync, so
446
+ saying "alias" would let a caller write and skip the sync. */
455
447
  if ( ca_func[ca->obj_type].attach == ca_stride_func.attach ) {
456
- return ca_stride_is_contiguous((CAStride *) ca)
457
- && ca_stride_attach_aliases_root((CAStride *) ca);
448
+ return ca_stride_attach_aliases_root((CAStride *) ca);
458
449
  }
459
450
  return 0;
460
451
  }
@@ -598,8 +589,8 @@ rb_ca_new_reduced (VALUE self, int8_t *slab_axes, int8_t naxes, int32_t data_typ
598
589
  to NUM2SIZE) of length `count`, normalises + range-checks + duplicate-
599
590
  checks, fills out_axes[] in input order. Shared between the legacy
600
591
  variadic entry (rb_ca_parse_reduce_axes) and the kwarg entry
601
- (rb_ca_parse_reduce_axes_kw). ctx is a short label embedded in error
602
- messages so callers can disambiguate which entry raised. */
592
+ (rb_ca_parse_reduce_axes_kw). ctx names the method in error messages;
593
+ the two public entries pass the method the user called. */
603
594
  static int8_t
604
595
  parse_axes_items (const VALUE *items, int count, CArray *ca,
605
596
  int8_t *out_axes, const char *ctx)
@@ -623,12 +614,12 @@ parse_axes_items (const VALUE *items, int count, CArray *ca,
623
614
 
624
615
  for ( i = 0; i < CA_RANK_MAX; i++ ) seen[i] = 0;
625
616
  for ( i = 0; i < count; i++ ) {
626
- ca_size_t a = NUM2SIZE(items[i]);
627
- if ( a < 0 ) a += ca->ndim;
617
+ ca_size_t raw = ca_axis_integer(items[i], NULL);
618
+ ca_size_t a = ( raw < 0 ) ? raw + ca->ndim : raw;
628
619
  if ( a < 0 || a >= ca->ndim ) {
629
- rb_raise(rb_eIndexError,
630
- "%s: axis %ld out of range [0, %d)",
631
- ctx, (long) a, (int) ca->ndim);
620
+ rb_raise(rb_eArgError,
621
+ "%s: axis %ld out of range for ndim %d",
622
+ ctx, (long) raw, (int) ca->ndim);
632
623
  }
633
624
  if ( seen[a] ) {
634
625
  rb_raise(rb_eArgError,
@@ -666,7 +657,7 @@ rb_ca_parse_reduce_axes (int argc, VALUE *argv, CArray *ca, int8_t *out_axes)
666
657
  }
667
658
 
668
659
  return parse_axes_items(items, count, ca, out_axes,
669
- "rb_ca_parse_reduce_axes");
660
+ ca_calling_method_name());
670
661
  }
671
662
 
672
663
  /* Kwarg form of rb_ca_parse_reduce_axes — accepts the `axis:` value as
@@ -705,8 +696,8 @@ rb_ca_parse_reduce_axes_kw_ctx (VALUE axis_val, CArray *ca, int8_t *out_axes,
705
696
  }
706
697
 
707
698
  rb_raise(rb_eTypeError,
708
- "%s: axis: must be nil, Integer, or "
709
- "Array of Integer (got %"PRIsVALUE")",
699
+ "%s: axis must be nil, an Integer or an Array of Integer "
700
+ "(got %"PRIsVALUE")",
710
701
  ctx, rb_obj_class(axis_val));
711
702
  }
712
703
 
@@ -714,7 +705,7 @@ int8_t
714
705
  rb_ca_parse_reduce_axes_kw (VALUE axis_val, CArray *ca, int8_t *out_axes)
715
706
  {
716
707
  return rb_ca_parse_reduce_axes_kw_ctx(axis_val, ca, out_axes,
717
- "rb_ca_parse_reduce_axes_kw");
708
+ ca_calling_method_name());
718
709
  }
719
710
 
720
711
  /* ------------------------------------------------------------------- */
@@ -832,7 +823,7 @@ ca_set_cyclic_check(void *ap)
832
823
  /* ca.data_type == CA_OBJECT */
833
824
  if ( ca_is_object_type(ca) ) {
834
825
  if ( ca->flags & CA_FLAG_CYCLE_CHECK ) {
835
- rb_raise(rb_eRuntimeError, "cyclic reference is not allowed in CArray");
826
+ rb_raise(rb_eArgError, "cyclic reference is not allowed in CArray");
836
827
  }
837
828
  ca_set_flag(ca, CA_FLAG_CYCLE_CHECK);
838
829
  }
@@ -860,12 +851,36 @@ ca_test_cyclic_check(void *ap, void *ptr)
860
851
  CArray *cv;
861
852
  TypedData_Get_Struct(rval, CArray, &carray_data_type, cv);
862
853
  if ( ca_test_flag(cv, CA_FLAG_CYCLE_CHECK) ) {
863
- rb_raise(rb_eRuntimeError, "cyclic reference is not allowed in CArray");
854
+ rb_raise(rb_eArgError, "cyclic reference is not allowed in CArray");
864
855
  }
865
856
  }
866
857
  }
867
858
  }
868
859
 
860
+ /* Runs body(arg) -- one transfer of an object cell or cells -- with ca's
861
+ cycle check set, and clears the check however body leaves. Either
862
+ direction can call into Ruby: a GET through a CAObject slot or the
863
+ check of what it delivered, a PUT that converts the caller's value. A
864
+ raise that skipped the clear would leave every later access to ca
865
+ reporting a cyclic reference. A PUT into an entity only copies VALUEs
866
+ and cannot raise, so it runs unprotected. */
867
+ static void
868
+ ca_with_cyclic_check (CArray *ca, int dir, VALUE (*body)(VALUE), VALUE arg)
869
+ {
870
+ int state = 0;
871
+ ca_set_cyclic_check(ca);
872
+ if ( dir == CA_XFER_PUT && ca_is_entity(ca) ) {
873
+ body(arg);
874
+ }
875
+ else {
876
+ rb_protect(body, arg, &state);
877
+ }
878
+ ca_clear_cyclic_check(ca);
879
+ if ( state ) {
880
+ rb_jump_tag(state);
881
+ }
882
+ }
883
+
869
884
  /* ------------------------------------------------------------------- */
870
885
  /* CArray offers no per-cell ptr accessor (ca_ptr_at_addr /
871
886
  ca_ptr_at_index): such a slot is structurally unsafe -- CABitarray /
@@ -911,14 +926,17 @@ struct ca_xfer_index_args {
911
926
  CArray *ca;
912
927
  ca_size_t *idx;
913
928
  char *ptr;
929
+ int dir;
914
930
  };
915
931
 
916
932
  static VALUE
917
- ca_xfer_index_get_body (VALUE arg)
933
+ ca_xfer_index_body (VALUE arg)
918
934
  {
919
935
  struct ca_xfer_index_args *d = (struct ca_xfer_index_args *) arg;
920
- ca_xfer_index_dispatch(d->ca, d->idx, d->ptr, CA_XFER_GET);
921
- ca_test_cyclic_check(d->ca, d->ptr);
936
+ ca_xfer_index_dispatch(d->ca, d->idx, d->ptr, d->dir);
937
+ if ( d->dir == CA_XFER_GET ) {
938
+ ca_test_cyclic_check(d->ca, d->ptr);
939
+ }
922
940
  return Qnil;
923
941
  }
924
942
 
@@ -937,24 +955,14 @@ ca_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
937
955
  return;
938
956
  }
939
957
 
940
- /* CA_OBJECT slow path: cyclic check (+ rb_protect on GET). */
941
- if ( dir == CA_XFER_GET ) {
958
+ /* CA_OBJECT slow path: cyclic check. */
959
+ {
942
960
  struct ca_xfer_index_args args;
943
- int state = 0;
944
961
  args.ca = ca;
945
962
  args.idx = idx;
946
963
  args.ptr = (char *) data;
947
- ca_set_cyclic_check(ca);
948
- rb_protect(ca_xfer_index_get_body, (VALUE) &args, &state);
949
- ca_clear_cyclic_check(ca);
950
- if ( state ) {
951
- rb_jump_tag(state);
952
- }
953
- }
954
- else {
955
- ca_set_cyclic_check(ca);
956
- ca_xfer_index_dispatch(ca, idx, data, CA_XFER_PUT);
957
- ca_clear_cyclic_check(ca);
964
+ args.dir = dir;
965
+ ca_with_cyclic_check(ca, dir, ca_xfer_index_body, (VALUE) &args);
958
966
  }
959
967
  }
960
968
 
@@ -1043,16 +1051,19 @@ struct ca_xfer_addrs_args {
1043
1051
  ca_size_t n;
1044
1052
  ca_size_t *addrs;
1045
1053
  char *data;
1054
+ int dir;
1046
1055
  };
1047
1056
 
1048
1057
  static VALUE
1049
- ca_xfer_addrs_get_body (VALUE arg)
1058
+ ca_xfer_addrs_body (VALUE arg)
1050
1059
  {
1051
1060
  struct ca_xfer_addrs_args *d = (struct ca_xfer_addrs_args *) arg;
1052
1061
  ca_size_t i;
1053
- ca_xfer_addrs_dispatch(d->ca, d->n, d->addrs, d->data, CA_XFER_GET);
1054
- for ( i = 0; i < d->n; i++ ) {
1055
- ca_test_cyclic_check(d->ca, d->data + i * d->ca->bytes);
1062
+ ca_xfer_addrs_dispatch(d->ca, d->n, d->addrs, d->data, d->dir);
1063
+ if ( d->dir == CA_XFER_GET ) {
1064
+ for ( i = 0; i < d->n; i++ ) {
1065
+ ca_test_cyclic_check(d->ca, d->data + i * d->ca->bytes);
1066
+ }
1056
1067
  }
1057
1068
  return Qnil;
1058
1069
  }
@@ -1072,25 +1083,15 @@ ca_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs, void *data, int dir)
1072
1083
  return;
1073
1084
  }
1074
1085
 
1075
- /* CA_OBJECT slow path: cyclic check (+ rb_protect on GET). */
1076
- if ( dir == CA_XFER_GET ) {
1086
+ /* CA_OBJECT slow path: cyclic check. */
1087
+ {
1077
1088
  struct ca_xfer_addrs_args args;
1078
- int state = 0;
1079
1089
  args.ca = ca;
1080
1090
  args.n = n;
1081
1091
  args.addrs = addrs;
1082
1092
  args.data = (char *) data;
1083
- ca_set_cyclic_check(ca);
1084
- rb_protect(ca_xfer_addrs_get_body, (VALUE) &args, &state);
1085
- ca_clear_cyclic_check(ca);
1086
- if ( state ) {
1087
- rb_jump_tag(state);
1088
- }
1089
- }
1090
- else {
1091
- ca_set_cyclic_check(ca);
1092
- ca_xfer_addrs_dispatch(ca, n, addrs, data, CA_XFER_PUT);
1093
- ca_clear_cyclic_check(ca);
1093
+ args.dir = dir;
1094
+ ca_with_cyclic_check(ca, dir, ca_xfer_addrs_body, (VALUE) &args);
1094
1095
  }
1095
1096
  }
1096
1097
 
@@ -1335,6 +1336,8 @@ ca_xfer_strided_walk (char *src_base,
1335
1336
  ca_size_t doff = 0;
1336
1337
  int8_t k;
1337
1338
 
1339
+ if ( ca_region_is_empty(ndim, counts) ) return;
1340
+
1338
1341
  /* slab merge -- scan innermost contig run (src_strides[k] equals the
1339
1342
  accumulated slab byte size). This collapses per-cell memcpy(_,_,
1340
1343
  bytes) loops into per-slab memcpy when the source region is row-major
@@ -1481,16 +1484,20 @@ struct ca_xfer_stride_args {
1481
1484
  ca_size_t *counts;
1482
1485
  ca_size_t *strides;
1483
1486
  char *data;
1487
+ int dir;
1484
1488
  };
1485
1489
 
1486
1490
  static VALUE
1487
- ca_xfer_stride_get_body (VALUE arg)
1491
+ ca_xfer_stride_body (VALUE arg)
1488
1492
  {
1489
1493
  struct ca_xfer_stride_args *a = (struct ca_xfer_stride_args *) arg;
1490
1494
  ca_size_t n = 1, i;
1491
1495
  int8_t k;
1492
1496
  ca_xfer_stride_dispatch(a->ca, a->starts, a->counts, a->strides, a->data,
1493
- CA_XFER_GET);
1497
+ a->dir);
1498
+ if ( a->dir != CA_XFER_GET ) {
1499
+ return Qnil;
1500
+ }
1494
1501
  /* cyclic check over the delivered cells (CA_OBJECT only). dst is contiguous
1495
1502
  row-major over counts (semantics b), so cell i is at data + i*bytes. */
1496
1503
  for ( k = 0; k < a->ca->ndim; k++ ) n *= a->counts[k];
@@ -1510,25 +1517,18 @@ ca_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
1510
1517
  rb_raise(rb_eRuntimeError, "can not store data to read-only array");
1511
1518
  }
1512
1519
 
1520
+ if ( ca_region_is_empty(ca->ndim, counts) ) return;
1521
+
1513
1522
  if ( ca->data_type != CA_OBJECT ) {
1514
1523
  ca_xfer_stride_dispatch(ca, starts, counts, strides, data, dir);
1515
1524
  return;
1516
1525
  }
1517
1526
 
1518
- if ( dir == CA_XFER_GET ) {
1527
+ {
1519
1528
  struct ca_xfer_stride_args args;
1520
- int state = 0;
1521
1529
  args.ca = ca; args.starts = starts; args.counts = counts;
1522
- args.strides = strides; args.data = (char *) data;
1523
- ca_set_cyclic_check(ca);
1524
- rb_protect(ca_xfer_stride_get_body, (VALUE) &args, &state);
1525
- ca_clear_cyclic_check(ca);
1526
- if ( state ) rb_jump_tag(state);
1527
- }
1528
- else {
1529
- ca_set_cyclic_check(ca);
1530
- ca_xfer_stride_dispatch(ca, starts, counts, strides, data, CA_XFER_PUT);
1531
- ca_clear_cyclic_check(ca);
1530
+ args.strides = strides; args.data = (char *) data; args.dir = dir;
1531
+ ca_with_cyclic_check(ca, dir, ca_xfer_stride_body, (VALUE) &args);
1532
1532
  }
1533
1533
  }
1534
1534
 
@@ -1580,6 +1580,10 @@ ca_xfer_all (void *ap, void *data, int dir)
1580
1580
  "[BUG] xfer_all not defined for object type <%i>",
1581
1581
  ca->obj_type);
1582
1582
  }
1583
+ /* An empty view moves nothing; see ca_xfer_stride. */
1584
+ if ( ca->elements == 0 ) {
1585
+ return;
1586
+ }
1583
1587
  /* Universal arena lifetime hook. ca_xfer_all is the single universal
1584
1588
  entry for materialise (to_ca -> ca_copy -> ca_copy_data ->
1585
1589
  ca_xfer_all), so wrapping here covers every outermost view type,
@@ -1615,8 +1619,74 @@ ca_xfer_all (void *ap, void *data, int dir)
1615
1619
 
1616
1620
  /* ------------------------------------------------------------------- */
1617
1621
 
1622
+ /* What follows the slot in ca_allocate: the object cells cleared, then the
1623
+ mask. Clearing the mask can create it, which for a CAObject calls Ruby.
1624
+ mask0 is the mask the array had before; see ca_attach_view_first. */
1625
+ static void
1626
+ ca_allocate_rest (CArray *ca, CArray *mask0)
1627
+ {
1628
+ if ( ca->data_type == CA_OBJECT ) { /* protection against GC */
1629
+ volatile VALUE rzero = INT2NUM(0);
1630
+ VALUE *p = (VALUE*)ca->ptr;
1631
+ ca_size_t i;
1632
+ for (i=0; i<ca->elements; i++) {
1633
+ *p++ = rzero;
1634
+ }
1635
+ }
1636
+
1637
+ ca_clear_mask(ca); /* ca_update_mask called in ca_clear_mask */
1638
+ if ( ca->mask == mask0 ) {
1639
+ ca_allocate(ca->mask);
1640
+ }
1641
+ }
1642
+
1643
+ /* The view half of ca_allocate, after the attach level is taken. Run
1644
+ under rb_protect by ca_allocate. */
1645
+ static VALUE
1646
+ ca_allocate_view (VALUE arg)
1647
+ {
1648
+ CArray *ca = (CArray *) arg;
1649
+ CArray *mask0 = ca->mask;
1650
+ if ( ! ca->ptr ) {
1651
+ ca_func[ca->obj_type].allocate(ca);
1652
+ }
1653
+ ca_allocate_rest(ca, mask0);
1654
+ return Qnil;
1655
+ }
1656
+
1618
1657
  /* make ca->ptr to point the allocated memory block */
1619
1658
 
1659
+ /* Steps a view's attach level by d. A development build also counts the
1660
+ views whose level is above zero, and the spec compares that count
1661
+ before and after every test: a window left open shows as a view that
1662
+ stays counted. */
1663
+ #ifdef CARRAY_DEV_BUILD
1664
+ static long ca_attached_views = 0;
1665
+
1666
+ #define CA_VIEW_ATTACH_STEP(ca, d) do { \
1667
+ uint32_t attach_before_ = CAVIEW(ca)->attach; \
1668
+ CAVIEW(ca)->attach += (d); \
1669
+ ca_attached_views += ( CAVIEW(ca)->attach > 0 ) - ( attach_before_ > 0 ); \
1670
+ } while (0)
1671
+
1672
+ static VALUE
1673
+ rb_ca_s_attached_views (VALUE klass)
1674
+ {
1675
+ return LONG2NUM(ca_attached_views);
1676
+ }
1677
+
1678
+ /* Set once at load from CARRAY_GENERAL_PATH; see carray_internal.h. */
1679
+ int ca_fast_paths_on = 1;
1680
+
1681
+ static VALUE
1682
+ rb_ca_s_fast_paths_enabled (VALUE klass)
1683
+ {
1684
+ return ca_fast_paths_on ? Qtrue : Qfalse;
1685
+ }
1686
+ #else
1687
+ #define CA_VIEW_ATTACH_STEP(ca, d) ( CAVIEW(ca)->attach += (d) )
1688
+ #endif
1689
+
1620
1690
  void
1621
1691
  ca_allocate (void *ap)
1622
1692
  {
@@ -1628,31 +1698,59 @@ ca_allocate (void *ap)
1628
1698
 
1629
1699
  if ( ca_is_view(ca) ) { /* view array */
1630
1700
 
1631
- CAVIEW(ca)->attach += 1; /* increments attach level */
1632
- if ( CAVIEW(ca)->attach > CA_ATTACH_MAX ) {
1701
+ if ( CAVIEW(ca)->attach >= CA_ATTACH_MAX ) {
1633
1702
  rb_raise(rb_eRuntimeError,
1634
1703
  "too large attach count of view array");
1635
1704
  }
1636
-
1637
- if ( ! ca->ptr ) {
1638
- ca_func[ca->obj_type].allocate(ap);
1705
+ CA_VIEW_ATTACH_STEP(ca, 1); /* increments attach level */
1706
+
1707
+ /* Undo the level if the slot or the mask raises, as ca_attach does.
1708
+ Left in place, the level is invisible while ptr is NULL, and the
1709
+ next attach / detach pair then leaves the view attached for good.
1710
+ A slot that got as far as publishing ca->ptr has taken what its
1711
+ detach slot releases; one that raised earlier has published
1712
+ nothing. */
1713
+ {
1714
+ char *ptr0 = ca->ptr;
1715
+ int tag = 0;
1716
+ rb_protect(ca_allocate_view, (VALUE) ca, &tag);
1717
+ if ( tag ) {
1718
+ if ( ! ptr0 && ca->ptr ) {
1719
+ ca_func[ca->obj_type].detach(ca);
1720
+ ca->ptr = NULL;
1721
+ }
1722
+ CA_VIEW_ATTACH_STEP(ca, -1);
1723
+ rb_jump_tag(tag);
1724
+ }
1639
1725
  }
1640
1726
  }
1641
1727
  else { /* entity array */
1642
1728
  ca_func[ca->obj_type].allocate(ap);
1729
+ ca_allocate_rest(ca, ca->mask); /* an entity's new mask is not yet allocated */
1643
1730
  }
1731
+ }
1644
1732
 
1645
- if ( ca->data_type == CA_OBJECT ) { /* protection against GC */
1646
- volatile VALUE rzero = INT2NUM(0);
1647
- VALUE *p = (VALUE*)ca->ptr;
1648
- ca_size_t i;
1649
- for (i=0; i<ca->elements; i++) {
1650
- *p++ = rzero;
1651
- }
1733
+ /* The first attach of a view: materialise through the type's slot, then
1734
+ its mask. Run under rb_protect by ca_attach. */
1735
+ static VALUE
1736
+ ca_attach_view_first (VALUE arg)
1737
+ {
1738
+ CArray *ca = (CArray *) arg;
1739
+ CArray *mask0 = ca->mask;
1740
+ /* The mask moves in step with its view: attach, allocate and detach
1741
+ each take it one level with the view. A mask created here -- by the
1742
+ slot (a CAObject makes its mask while it attaches) or by
1743
+ ca_update_mask -- is already at the view's level, since
1744
+ ca_create_mask attaches a new mask to match an attached view. So
1745
+ only a mask that was there before the slot takes the step; stepping a
1746
+ new one too leaves it a level above the view, still attached after
1747
+ the view's last detach. */
1748
+ ca_func[ca->obj_type].attach(ca);
1749
+ ca_update_mask(ca);
1750
+ if ( ca->mask == mask0 ) {
1751
+ ca_attach(ca->mask);
1652
1752
  }
1653
-
1654
- ca_clear_mask(ca); /* ca_update_mask called in ca_clear_mask */
1655
- ca_allocate(ca->mask);
1753
+ return Qnil;
1656
1754
  }
1657
1755
 
1658
1756
  /* attach parent's data to ca->ptr */
@@ -1668,22 +1766,242 @@ ca_attach (void *ap)
1668
1766
 
1669
1767
  if ( ca_is_view(ca) ) { /* view array */
1670
1768
 
1671
- CAVIEW(ca)->attach += 1; /* increments attach level */
1672
- if ( CAVIEW(ca)->attach > CA_ATTACH_MAX ) {
1769
+ if ( CAVIEW(ca)->attach >= CA_ATTACH_MAX ) {
1673
1770
  rb_raise(rb_eRuntimeError,
1674
1771
  "too large attach count of view array");
1675
1772
  }
1773
+ CA_VIEW_ATTACH_STEP(ca, 1); /* increments attach level */
1676
1774
 
1677
1775
  if ( ! ca->ptr ) {
1678
- ca_func[ca->obj_type].attach(ap);
1776
+ /* A slot can raise part way (a conversion meeting a value it cannot
1777
+ hold). Undo the attach then, or the next attach finds ca->ptr
1778
+ set, skips the slot and reads a half-filled buffer. A slot that
1779
+ got as far as publishing ca->ptr has also attached what its
1780
+ detach slot releases; one that raised earlier has published
1781
+ nothing and released its own parent attach on the way out. */
1782
+ int tag = 0;
1783
+ rb_protect(ca_attach_view_first, (VALUE) ca, &tag);
1784
+ if ( tag ) {
1785
+ if ( ca->ptr ) {
1786
+ ca_func[ca->obj_type].detach(ca);
1787
+ ca->ptr = NULL;
1788
+ }
1789
+ CA_VIEW_ATTACH_STEP(ca, -1);
1790
+ rb_jump_tag(tag);
1791
+ }
1792
+ return;
1679
1793
  }
1680
1794
  }
1681
1795
  else { /* entity array */
1682
1796
  ca_func[ca->obj_type].attach(ap);
1683
1797
  }
1684
1798
 
1685
- ca_update_mask(ca);
1686
- ca_attach(ca->mask);
1799
+ {
1800
+ CArray *mask0 = ca->mask;
1801
+ ca_update_mask(ca);
1802
+ if ( ca->mask == mask0 ) { /* see ca_attach_view_first */
1803
+ ca_attach(ca->mask);
1804
+ }
1805
+ }
1806
+ }
1807
+
1808
+ /* Attach every array in list[0..n-1], or, if one of those attaches
1809
+ raises, none of them: the ones already attached are detached before the
1810
+ raise propagates. For views over several parents. */
1811
+
1812
+ typedef struct {
1813
+ CArray **list;
1814
+ int32_t n;
1815
+ int32_t done;
1816
+ } ca_attach_all_ctx_t;
1817
+
1818
+ static VALUE
1819
+ ca_attach_all_body (VALUE arg)
1820
+ {
1821
+ ca_attach_all_ctx_t *c = (ca_attach_all_ctx_t *) arg;
1822
+ for ( ; c->done < c->n; c->done++ ) {
1823
+ ca_attach(c->list[c->done]);
1824
+ }
1825
+ return Qnil;
1826
+ }
1827
+
1828
+ void
1829
+ ca_attach_all (CArray **list, int32_t n)
1830
+ {
1831
+ ca_attach_all_ctx_t c;
1832
+ int tag = 0;
1833
+ c.list = list;
1834
+ c.n = n;
1835
+ c.done = 0;
1836
+ rb_protect(ca_attach_all_body, (VALUE) &c, &tag);
1837
+ if ( tag ) {
1838
+ while ( c.done > 0 ) {
1839
+ ca_detach(list[--c.done]);
1840
+ }
1841
+ rb_jump_tag(tag);
1842
+ }
1843
+ }
1844
+
1845
+ static VALUE
1846
+ ca_sync_all_step (VALUE arg)
1847
+ {
1848
+ ca_sync((void *) arg);
1849
+ return Qnil;
1850
+ }
1851
+
1852
+ /* Syncs every one of list[0..n-1] in turn, carrying on past one that
1853
+ raises so that the others still receive what was written, then raises
1854
+ the first exception. A throw or break cannot wait, and goes at once. */
1855
+ void
1856
+ ca_sync_all (CArray **list, int32_t n)
1857
+ {
1858
+ volatile VALUE error = Qnil;
1859
+ int32_t k;
1860
+ for ( k = 0; k < n; k++ ) {
1861
+ int tag = 0;
1862
+ rb_protect(ca_sync_all_step, (VALUE) list[k], &tag);
1863
+ if ( tag ) {
1864
+ VALUE err = rb_errinfo();
1865
+ if ( ! RTEST(rb_obj_is_kind_of(err, rb_eException)) ) {
1866
+ rb_jump_tag(tag);
1867
+ }
1868
+ if ( NIL_P(error) ) {
1869
+ error = err;
1870
+ }
1871
+ rb_set_errinfo(Qnil);
1872
+ }
1873
+ }
1874
+ if ( ! NIL_P(error) ) {
1875
+ rb_exc_raise(error);
1876
+ }
1877
+ }
1878
+
1879
+ /* An attach window over list[0..n-1] that closes however body leaves.
1880
+
1881
+ modes holds one letter per array:
1882
+ 'r' attach; detach on close
1883
+ 'w' attach; sync, then detach on close
1884
+ 'a' allocate (write only); sync, then detach on close
1885
+
1886
+ Opening takes all of the arrays or none. Closing syncs the 'w' arrays
1887
+ even when body raised or broke out -- the cells body wrote reach the
1888
+ parent, as they would for an entity, which has no separate buffer. An
1889
+ 'a' array is not synced after a raise: allocate did not gather, so its
1890
+ cells body never wrote hold nothing faithful to push back.
1891
+
1892
+ Every array is detached, in reverse order, whatever happens. body's
1893
+ own exception wins over one raised by a sync; when body returned and a
1894
+ sync raised, that exception propagates once everything is detached. */
1895
+
1896
+ typedef struct {
1897
+ int32_t n;
1898
+ CArray **list;
1899
+ const char *modes;
1900
+ int32_t opened;
1901
+ int body_done; /* body returned without raising */
1902
+ int sync_tag;
1903
+ volatile VALUE sync_err;
1904
+ VALUE (*body)(VALUE);
1905
+ VALUE arg;
1906
+ } ca_window_ctx_t;
1907
+
1908
+ static VALUE
1909
+ ca_attach_window_open (VALUE varg)
1910
+ {
1911
+ ca_window_ctx_t *w = (ca_window_ctx_t *) varg;
1912
+ for ( ; w->opened < w->n; w->opened++ ) {
1913
+ if ( w->modes[w->opened] == 'a' ) {
1914
+ ca_allocate(w->list[w->opened]);
1915
+ }
1916
+ else {
1917
+ ca_attach(w->list[w->opened]);
1918
+ }
1919
+ }
1920
+ return Qnil;
1921
+ }
1922
+
1923
+ static VALUE
1924
+ ca_attach_window_body (VALUE varg)
1925
+ {
1926
+ ca_window_ctx_t *w = (ca_window_ctx_t *) varg;
1927
+ VALUE result = w->body(w->arg);
1928
+ w->body_done = 1;
1929
+ return result;
1930
+ }
1931
+
1932
+ static VALUE
1933
+ ca_attach_window_sync (VALUE varg)
1934
+ {
1935
+ ca_sync((void *) varg);
1936
+ return Qnil;
1937
+ }
1938
+
1939
+ /* The ensure half. rb_ensure keeps the exception body raised (or the
1940
+ break it took) across this call, so a sync raising in here is caught
1941
+ and set aside rather than let replace it. */
1942
+ static VALUE
1943
+ ca_attach_window_close (VALUE varg)
1944
+ {
1945
+ ca_window_ctx_t *w = (ca_window_ctx_t *) varg;
1946
+ int32_t k;
1947
+ for ( k = w->opened - 1; k >= 0; k-- ) {
1948
+ CArray *ca = w->list[k];
1949
+ char mode = w->modes[k];
1950
+ if ( ! ca ) {
1951
+ continue;
1952
+ }
1953
+ if ( ca->ptr && ( mode == 'w' || ( mode == 'a' && w->body_done ) ) ) {
1954
+ int tag = 0;
1955
+ rb_protect(ca_attach_window_sync, (VALUE) ca, &tag);
1956
+ if ( tag && ! w->sync_tag ) {
1957
+ w->sync_tag = tag;
1958
+ w->sync_err = rb_errinfo();
1959
+ }
1960
+ rb_set_errinfo(Qnil);
1961
+ }
1962
+ if ( ca->ptr ) {
1963
+ ca_detach(ca);
1964
+ }
1965
+ }
1966
+ return Qnil;
1967
+ }
1968
+
1969
+ VALUE
1970
+ ca_attach_window (int32_t n, CArray **list, const char *modes,
1971
+ VALUE (*body)(VALUE), VALUE arg)
1972
+ {
1973
+ ca_window_ctx_t w;
1974
+ VALUE result;
1975
+ int tag = 0;
1976
+
1977
+ w.n = n;
1978
+ w.list = list;
1979
+ w.modes = modes;
1980
+ w.opened = 0;
1981
+ w.body_done = 0;
1982
+ w.sync_tag = 0;
1983
+ w.sync_err = Qnil;
1984
+ w.body = body;
1985
+ w.arg = arg;
1986
+
1987
+ rb_protect(ca_attach_window_open, (VALUE) &w, &tag);
1988
+ if ( tag ) {
1989
+ while ( w.opened > 0 ) {
1990
+ w.opened--;
1991
+ if ( w.list[w.opened] ) {
1992
+ ca_detach(w.list[w.opened]);
1993
+ }
1994
+ }
1995
+ rb_jump_tag(tag);
1996
+ }
1997
+
1998
+ result = rb_ensure(ca_attach_window_body, (VALUE) &w,
1999
+ ca_attach_window_close, (VALUE) &w);
2000
+ if ( w.sync_tag && RB_TYPE_P(w.sync_err, T_OBJECT) &&
2001
+ RTEST(rb_obj_is_kind_of(w.sync_err, rb_eException)) ) {
2002
+ rb_exc_raise(w.sync_err);
2003
+ }
2004
+ return result;
1687
2005
  }
1688
2006
 
1689
2007
  /* attach parent's data to ca->ptr */
@@ -1734,8 +2052,13 @@ ca_sync (void *ap)
1734
2052
  "can not modify read-only array");
1735
2053
  }
1736
2054
 
1737
- ca_update_mask(ca);
1738
- ca_sync(ca->mask);
2055
+ /* Only a mask that exists can hold writes: one made inside the window
2056
+ is attached with the view (ca_create_mask). Making one here would
2057
+ read the parent's mask while the window closes, and a parent that
2058
+ cannot answer would leave the window open. */
2059
+ if ( ca->mask ) {
2060
+ ca_sync(ca->mask);
2061
+ }
1739
2062
 
1740
2063
  if ( ca_is_view(ca) ) { /* view array */
1741
2064
  if ( ! CAVIEW(ca)->nosync ) { /* FIXME : */
@@ -1748,6 +2071,42 @@ ca_sync (void *ap)
1748
2071
 
1749
2072
  }
1750
2073
 
2074
+ /* Sync ap and detach it -- the close of a window a frame opened with
2075
+ ca_attach or ca_allocate. The detach happens even when the sync raises
2076
+ (a read-only array, a backing whose write fails); the sync's exception
2077
+ then propagates. Written as two calls, a raising sync skips the detach
2078
+ and the window stays open.
2079
+
2080
+ Not a replacement for every ca_sync: a view's sync slot syncs its parent
2081
+ without detaching it, because that attach belongs to the view and its
2082
+ detach slot releases it. */
2083
+
2084
+ static VALUE
2085
+ ca_sync_body (VALUE arg)
2086
+ {
2087
+ ca_sync((void *) arg);
2088
+ return Qnil;
2089
+ }
2090
+
2091
+ void
2092
+ ca_sync_detach (void *ap)
2093
+ {
2094
+ CArray *ca = (CArray *) ap;
2095
+ int tag = 0;
2096
+
2097
+ if ( ! ca ) {
2098
+ return;
2099
+ }
2100
+
2101
+ rb_protect(ca_sync_body, (VALUE) ca, &tag);
2102
+ if ( ca->ptr ) {
2103
+ ca_detach(ca);
2104
+ }
2105
+ if ( tag ) {
2106
+ rb_jump_tag(tag);
2107
+ }
2108
+ }
2109
+
1751
2110
  /* make ca->ptr to be detached */
1752
2111
 
1753
2112
  void
@@ -1768,38 +2127,86 @@ ca_detach (void *ap)
1768
2127
  if ( CAVIEW(ca)->attach == 1 ) {
1769
2128
  ca_func[ca->obj_type].detach(ap);
1770
2129
  }
1771
- CAVIEW(ca)->attach -= 1;
2130
+ CA_VIEW_ATTACH_STEP(ca, -1);
1772
2131
  }
1773
2132
  else { /* entity array */
1774
2133
  ca_func[ca->obj_type].detach(ap);
1775
2134
  }
1776
2135
 
1777
- ca_update_mask(ca);
1778
- ca_detach(ca->mask);
2136
+ /* The mask, if there is one, is at the view's level (see
2137
+ ca_attach_view_first). A mask that does not exist yet is not made
2138
+ here: it would be made unattached, and making it reads the parent's
2139
+ mask, which may raise with the view half detached. */
2140
+ if ( ca->mask ) {
2141
+ ca_detach(ca->mask);
2142
+ }
1779
2143
  }
1780
2144
 
1781
2145
  /* multiple versions of ca_allocate, ca_attach, ca_sync, ca_detach */
1782
2146
 
2147
+ /* ca_allocate_n and ca_attach_n take all of their arrays or none: when
2148
+ one raises, the ones already taken are detached before the raise
2149
+ propagates. */
2150
+
2151
+ typedef struct {
2152
+ CArray **list;
2153
+ int32_t n;
2154
+ int32_t done;
2155
+ } ca_allocate_all_ctx_t;
2156
+
2157
+ static VALUE
2158
+ ca_allocate_all_body (VALUE arg)
2159
+ {
2160
+ ca_allocate_all_ctx_t *c = (ca_allocate_all_ctx_t *) arg;
2161
+ for ( ; c->done < c->n; c->done++ ) {
2162
+ ca_allocate(c->list[c->done]);
2163
+ }
2164
+ return Qnil;
2165
+ }
2166
+
2167
+ static void
2168
+ ca_allocate_all (CArray **list, int32_t n)
2169
+ {
2170
+ ca_allocate_all_ctx_t c;
2171
+ int tag = 0;
2172
+ c.list = list;
2173
+ c.n = n;
2174
+ c.done = 0;
2175
+ rb_protect(ca_allocate_all_body, (VALUE) &c, &tag);
2176
+ if ( tag ) {
2177
+ while ( c.done > 0 ) {
2178
+ ca_detach(list[--c.done]);
2179
+ }
2180
+ rb_jump_tag(tag);
2181
+ }
2182
+ }
2183
+
1783
2184
  void
1784
2185
  ca_allocate_n (int n, ...)
1785
2186
  {
2187
+ CArray **list = ALLOCA_N(CArray *, n > 0 ? n : 1);
1786
2188
  va_list args;
2189
+ int i;
1787
2190
  va_start(args, n);
1788
- while ( n-- ) {
1789
- ca_allocate(va_arg(args, CArray *));
2191
+ for ( i = 0; i < n; i++ ) {
2192
+ list[i] = va_arg(args, CArray *);
1790
2193
  }
1791
2194
  va_end(args);
2195
+ ca_allocate_all(list, n);
1792
2196
  }
1793
2197
 
1794
2198
  void
1795
2199
  ca_attach_n (int n, ...)
1796
2200
  {
2201
+ CArray **list = ALLOCA_N(CArray *, n > 0 ? n : 1);
1797
2202
  va_list args;
2203
+ int i;
1798
2204
  va_start(args, n);
1799
- while ( n-- ) {
1800
- ca_attach(va_arg(args, CArray *));
2205
+ for ( i = 0; i < n; i++ ) {
2206
+ list[i] = va_arg(args, CArray *);
1801
2207
  }
1802
2208
  va_end(args);
2209
+ ca_attach_all(list, n);
1803
2210
  }
1804
2211
 
1805
2212
  void
@@ -1924,15 +2331,12 @@ ca_fill_stride (void *ap, ca_size_t base, int8_t ndim,
1924
2331
  ca_size_t *counts, ca_size_t *steps, void *ptr)
1925
2332
  {
1926
2333
  CArray *ca = (CArray *) ap;
1927
- int8_t k;
1928
2334
 
1929
2335
  if ( ca_is_readonly(ca) ) {
1930
2336
  rb_raise(rb_eRuntimeError, "can not fill data to read-only array");
1931
2337
  }
1932
2338
 
1933
- for ( k = 0; k < ndim; k++ ) {
1934
- if ( counts[k] <= 0 ) return;
1935
- }
2339
+ if ( ca_region_is_empty(ndim, counts) ) return;
1936
2340
 
1937
2341
  if ( ca_func[ca->obj_type].fill_stride ) {
1938
2342
  ca_func[ca->obj_type].fill_stride(ap, base, ndim, counts, steps, ptr);
@@ -2079,7 +2483,7 @@ ca_fill (void *ap, void *aptr)
2079
2483
  char *ptr = (char *) aptr;
2080
2484
 
2081
2485
  if ( ca_is_readonly(ca) ) {
2082
- rb_raise(rb_eRuntimeError, "can't fill read-only carray");
2486
+ rb_raise(rb_eRuntimeError, "can not fill data to read-only array");
2083
2487
  }
2084
2488
 
2085
2489
  ca_fill_data(ap, ptr);
@@ -2087,6 +2491,17 @@ ca_fill (void *ap, void *aptr)
2087
2491
 
2088
2492
  /* ------------------------------------------------------------------- */
2089
2493
 
2494
+ #ifdef CARRAY_DEV_BUILD
2495
+
2496
+ /* The Ruby attach surface is built into development builds only. Ruby has no
2497
+ raw pointer, so the only thing a block can do inside an attach window is call
2498
+ back into the array API -- and which door that takes is invisible in the
2499
+ syntax: v[0] reads the attached buffer while v[] and v[0..1] compose past it
2500
+ to the root (R5, see the contract beside ca_attach in carray.h). Per-cell and
2501
+ whole-array writes reach the buffer and survive the sync; a write through a
2502
+ derived view is discarded. The in-tree tests drive the healthy spellings, so
2503
+ the surface stays reachable here and out of released builds. */
2504
+
2090
2505
  /* subroutines for CArray.attach, CArray.attach!
2091
2506
  CArray#attach, CArray#attach! */
2092
2507
 
@@ -2307,6 +2722,8 @@ rb_ca__detach__ (VALUE self)
2307
2722
  return self;
2308
2723
  }
2309
2724
 
2725
+ #endif /* CARRAY_DEV_BUILD */
2726
+
2310
2727
  /* ------------------------------------------------------------------- */
2311
2728
 
2312
2729
  static ID id_decode, id_encode;
@@ -2347,7 +2764,7 @@ rb_ca_members (VALUE self)
2347
2764
  {
2348
2765
  volatile VALUE data_class = rb_ca_data_class(self);
2349
2766
  if ( NIL_P(data_class) ) {
2350
- rb_raise(rb_eRuntimeError, "carray doesn't have data class");
2767
+ rb_raise(rb_eTypeError, "carray doesn't have data class");
2351
2768
  }
2352
2769
  else {
2353
2770
  return rb_obj_clone(rb_const_get(data_class, rb_intern("MEMBERS")));
@@ -2360,8 +2777,8 @@ rb_ca_members (VALUE self)
2360
2777
  (= Face strip): the @member cache lives on self (the Face) while the
2361
2778
  actual field view is the CAField on the parent. A plain FIXLEN entity
2362
2779
  keeps self == receiver. */
2363
- VALUE
2364
- rb_ca_face_field (VALUE self, VALUE sym)
2780
+ static VALUE
2781
+ ca_face_field_lookup (VALUE self, VALUE sym)
2365
2782
  {
2366
2783
  volatile VALUE data_class = rb_ca_data_class(self);
2367
2784
  volatile VALUE member;
@@ -2370,7 +2787,7 @@ rb_ca_face_field (VALUE self, VALUE sym)
2370
2787
  CArray *ca;
2371
2788
 
2372
2789
  if ( NIL_P(data_class) ) {
2373
- rb_raise(rb_eRuntimeError, "carray doesn't have data class");
2790
+ rb_raise(rb_eTypeError, "carray doesn't have data class");
2374
2791
  }
2375
2792
 
2376
2793
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
@@ -2386,7 +2803,9 @@ rb_ca_face_field (VALUE self, VALUE sym)
2386
2803
  is local to the derived view; field views are still on its parent
2387
2804
  (which itself is on the entity). */
2388
2805
  member = rb_hash_new();
2389
- rb_ivar_set(self, rb_intern("member"), member);
2806
+ if ( ! OBJ_FROZEN(self) ) {
2807
+ rb_ivar_set(self, rb_intern("member"), member);
2808
+ }
2390
2809
  }
2391
2810
 
2392
2811
  if ( rb_obj_is_kind_of(sym, rb_cInteger) ) {
@@ -2412,7 +2831,7 @@ rb_ca_face_field (VALUE self, VALUE sym)
2412
2831
  if ( TYPE(sym) != T_STRING ) {
2413
2832
  sym = rb_funcall(sym, rb_intern("to_s"), 0);
2414
2833
  }
2415
- rb_raise(rb_eRuntimeError,
2834
+ rb_raise(rb_eArgError,
2416
2835
  "can't find data_member named <%s>", StringValuePtr(sym));
2417
2836
  }
2418
2837
  Check_Type(info, T_ARRAY);
@@ -2454,12 +2873,53 @@ rb_ca_face_field (VALUE self, VALUE sym)
2454
2873
  return obj;
2455
2874
  }
2456
2875
  }
2876
+ /* `endian:` is not an option of #field; the member's byte order is a
2877
+ view over the field, as CAStruct#[] reads it. */
2878
+ {
2879
+ volatile VALUE opts = rb_ary_entry(info, 2);
2880
+ volatile VALUE endian = Qnil;
2881
+ if ( RB_TYPE_P(opts, T_HASH) ) {
2882
+ endian = rb_hash_aref(opts, ID2SYM(rb_intern("endian")));
2883
+ }
2884
+ if ( ! NIL_P(endian) ) {
2885
+ volatile VALUE field_opts = rb_hash_dup(opts);
2886
+ rb_hash_delete(field_opts, ID2SYM(rb_intern("endian")));
2887
+ if ( RHASH_SIZE(field_opts) > 0 ) {
2888
+ obj = rb_funcall(receiver, rb_intern("field"), 3,
2889
+ rb_ary_entry(info, 0), rb_ary_entry(info, 1),
2890
+ field_opts);
2891
+ }
2892
+ else {
2893
+ obj = rb_funcall(receiver, rb_intern("field"), 2,
2894
+ rb_ary_entry(info, 0), rb_ary_entry(info, 1));
2895
+ }
2896
+ obj = rb_funcall(obj, rb_intern("endian"), 1, endian);
2897
+ rb_hash_aset(member, sym, obj);
2898
+ return obj;
2899
+ }
2900
+ }
2457
2901
  obj = rb_apply(receiver, rb_intern("field"), info);
2458
2902
  rb_hash_aset(member, sym, obj);
2459
2903
  return obj;
2460
2904
  }
2461
2905
  }
2462
2906
 
2907
+ /* A field is a view on the record array's storage, below the Face, so the
2908
+ read-only state of the record array (frozen, or a read-only view) is not
2909
+ on its parent chain; the field carries it itself. */
2910
+ VALUE
2911
+ rb_ca_face_field (VALUE self, VALUE sym)
2912
+ {
2913
+ volatile VALUE obj = ca_face_field_lookup(self, sym);
2914
+ CArray *ca, *co;
2915
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
2916
+ if ( ca_is_readonly(ca) && rb_obj_is_carray(obj) ) {
2917
+ TypedData_Get_Struct(obj, CArray, &carray_data_type, co);
2918
+ ca_set_flag(co, CA_FLAG_READ_ONLY);
2919
+ }
2920
+ return obj;
2921
+ }
2922
+
2463
2923
  /* @overload fields
2464
2924
 
2465
2925
  (Reference) Returns an array of data class members (fields)
@@ -2472,7 +2932,7 @@ rb_ca_fields (VALUE self)
2472
2932
  volatile VALUE member_names, list;
2473
2933
  int i;
2474
2934
  if ( NIL_P(data_class) ) {
2475
- rb_raise(rb_eRuntimeError, "carray doesn't have data class");
2935
+ rb_raise(rb_eTypeError, "carray doesn't have data class");
2476
2936
  }
2477
2937
  member_names = rb_const_get(data_class, rb_intern("MEMBERS"));
2478
2938
  list = rb_ary_new2(RARRAY_LEN(member_names));
@@ -2495,7 +2955,7 @@ rb_ca_fields_at (int argc, VALUE *argv, VALUE self)
2495
2955
  volatile VALUE member_names, list;
2496
2956
  int i;
2497
2957
  if ( NIL_P(data_class) ) {
2498
- rb_raise(rb_eRuntimeError, "carray doesn't have data class");
2958
+ rb_raise(rb_eTypeError, "carray doesn't have data class");
2499
2959
  }
2500
2960
  member_names = rb_ary_new4(argc, argv);
2501
2961
  list = rb_ary_new2(RARRAY_LEN(member_names));
@@ -2515,6 +2975,7 @@ Init_carray_core (void)
2515
2975
 
2516
2976
  ca_init_obj_type();
2517
2977
 
2978
+ #ifdef CARRAY_DEV_BUILD
2518
2979
  rb_define_singleton_method(rb_cCArray, "attach", rb_ca_s_attach, -1);
2519
2980
  rb_define_singleton_method(rb_cCArray, "attach!", rb_ca_s_attach_bang, -1);
2520
2981
 
@@ -2525,6 +2986,17 @@ Init_carray_core (void)
2525
2986
  rb_define_method(rb_cCArray, "__sync__", rb_ca__sync__, 0);
2526
2987
  rb_define_method(rb_cCArray, "__detach__", rb_ca__detach__, 0);
2527
2988
 
2989
+ rb_define_singleton_method(rb_cCArray, "__attached_views__",
2990
+ rb_ca_s_attached_views, 0);
2991
+
2992
+ {
2993
+ const char *general = getenv("CARRAY_GENERAL_PATH");
2994
+ ca_fast_paths_on = !( general && general[0] && strcmp(general, "0") );
2995
+ }
2996
+ rb_define_singleton_method(rb_cCArray, "__fast_paths_enabled__",
2997
+ rb_ca_s_fast_paths_enabled, 0);
2998
+ #endif
2999
+
2528
3000
  rb_define_method(rb_cCArray, "members", rb_ca_members, 0);
2529
3001
 
2530
3002
  rb_define_method(rb_cCArray, "fields", rb_ca_fields, 0);