carray 3.0.2 → 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 (247) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +568 -0
  3. data/README.md +6 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_descriptor.h +27 -24
  6. data/ext/ca_axis_dispatch.c +220 -0
  7. data/ext/ca_axis_group.c +201 -123
  8. data/ext/ca_binop_dispatch.c +46 -52
  9. data/ext/ca_binop_dispatch.h +1 -1
  10. data/ext/ca_categorical_iterator.c +249 -183
  11. data/ext/ca_compare.c +7 -12
  12. data/ext/ca_for_buffer.h +21 -68
  13. data/ext/ca_group_iter.c +38 -23
  14. data/ext/ca_kernel_iterator.c +1765 -2076
  15. data/ext/ca_kernel_iterator.h +584 -566
  16. data/ext/ca_monop_dispatch.c +86 -63
  17. data/ext/ca_monop_dispatch.h +5 -0
  18. data/ext/ca_obj_array.c +223 -56
  19. data/ext/ca_obj_bincmp.c +172 -138
  20. data/ext/ca_obj_binop.c +116 -230
  21. data/ext/ca_obj_bitarray.c +40 -26
  22. data/ext/ca_obj_bitfield.c +100 -68
  23. data/ext/ca_obj_block.c +19 -9
  24. data/ext/ca_obj_byte_swap.c +45 -46
  25. data/ext/ca_obj_const_string.c +269 -44
  26. data/ext/ca_obj_face.c +612 -89
  27. data/ext/ca_obj_face.h +111 -47
  28. data/ext/ca_obj_fake.c +36 -79
  29. data/ext/ca_obj_farray.c +7 -3
  30. data/ext/ca_obj_field.c +33 -8
  31. data/ext/ca_obj_fixlen_string.c +23 -24
  32. data/ext/ca_obj_grid.c +541 -210
  33. data/ext/ca_obj_meld.c +331 -111
  34. data/ext/ca_obj_moncmp.c +80 -65
  35. data/ext/ca_obj_monop.c +177 -149
  36. data/ext/ca_obj_object.c +664 -248
  37. data/ext/ca_obj_record.c +63 -23
  38. data/ext/ca_obj_reduce.c +18 -16
  39. data/ext/ca_obj_refer.c +140 -58
  40. data/ext/ca_obj_remap.c +85 -24
  41. data/ext/ca_obj_repeat.c +24 -9
  42. data/ext/ca_obj_roll.c +121 -16
  43. data/ext/ca_obj_select.c +61 -58
  44. data/ext/ca_obj_select_axis.c +73 -107
  45. data/ext/ca_obj_shift.c +32 -102
  46. data/ext/ca_obj_stack.c +372 -122
  47. data/ext/ca_obj_stride.c +348 -140
  48. data/ext/ca_obj_string.c +88 -22
  49. data/ext/ca_obj_tile.c +38 -21
  50. data/ext/ca_obj_time.c +45 -31
  51. data/ext/ca_obj_timedelta.c +43 -29
  52. data/ext/ca_obj_transpose.c +17 -6
  53. data/ext/ca_obj_triop.c +125 -141
  54. data/ext/ca_obj_window.c +330 -44
  55. data/ext/ca_op_ipower.c +24 -11
  56. data/ext/ca_op_powi.h +57 -30
  57. data/ext/ca_pow5_table.h +656 -0
  58. data/ext/ca_sort_kernels.h +70 -0
  59. data/ext/ca_sweep_engine.c +104 -81
  60. data/ext/ca_sweep_engine.h +7 -20
  61. data/ext/ca_triop_dispatch.c +4 -1
  62. data/ext/ca_triop_dispatch.h +5 -2
  63. data/ext/caframe_csv_reader.c +542 -0
  64. data/ext/carray.h +241 -92
  65. data/ext/carray_access.c +608 -276
  66. data/ext/carray_address_basis.c +494 -97
  67. data/ext/carray_attribute.c +129 -4
  68. data/ext/carray_bincount.c +39 -7
  69. data/ext/carray_broadcast.c +56 -19
  70. data/ext/carray_build_flags.h +1 -1
  71. data/ext/carray_call_cfunc.c +108 -28
  72. data/ext/carray_cast.c +1576 -223
  73. data/ext/carray_cast_func.rb +77 -24
  74. data/ext/carray_class.c +4 -3
  75. data/ext/carray_conversion.c +208 -75
  76. data/ext/carray_copy.c +82 -24
  77. data/ext/carray_core.c +646 -162
  78. data/ext/carray_count.c +171 -88
  79. data/ext/carray_element.c +94 -60
  80. data/ext/carray_factorize.c +670 -324
  81. data/ext/carray_generate.c +260 -83
  82. data/ext/carray_histogram.c +209 -106
  83. data/ext/carray_hold.c +14 -16
  84. data/ext/carray_index_classifier.c +422 -243
  85. data/ext/carray_internal.h +692 -3
  86. data/ext/carray_kernels_bincmp.c +7 -0
  87. data/ext/carray_kernels_binop.c +487 -120
  88. data/ext/carray_kernels_init.c +653 -50
  89. data/ext/carray_kernels_moncmp.c +15 -8
  90. data/ext/carray_kernels_monop.c +99 -43
  91. data/ext/carray_kernels_reduce_aggregate.c +3925 -6112
  92. data/ext/carray_kernels_reduce_boolean.c +112 -36
  93. data/ext/carray_kernels_reduce_cumulative.c +2127 -6903
  94. data/ext/carray_kernels_reduce_extreme.c +3032 -2892
  95. data/ext/carray_kernels_reduce_variance.c +439 -500
  96. data/ext/carray_kernels_scan.c +1097 -488
  97. data/ext/carray_kernels_search.c +3787 -2176
  98. data/ext/carray_kernels_sort.c +1200 -920
  99. data/ext/carray_kernels_triop.c +479 -7
  100. data/ext/carray_key_windows.c +216 -0
  101. data/ext/carray_lazy.c +967 -153
  102. data/ext/carray_loop.c +81 -65
  103. data/ext/carray_mask.c +910 -324
  104. data/ext/carray_math_kernel.h +1 -0
  105. data/ext/carray_median_percentile.c +299 -100
  106. data/ext/carray_memory_view.c +872 -758
  107. data/ext/carray_operator.c +1171 -364
  108. data/ext/carray_order.c +505 -133
  109. data/ext/carray_partition.c +112 -65
  110. data/ext/carray_random.c +327 -239
  111. data/ext/carray_round.c +273 -0
  112. data/ext/carray_scatter.c +346 -78
  113. data/ext/carray_segment.c +257 -0
  114. data/ext/carray_slab.c +249 -86
  115. data/ext/carray_sort.c +224 -227
  116. data/ext/carray_sort_kernel.c +8 -1
  117. data/ext/carray_struct.c +118 -39
  118. data/ext/carray_test.c +193 -69
  119. data/ext/carray_time_text.c +1182 -0
  120. data/ext/carray_undef.c +4 -0
  121. data/ext/carray_utils.c +579 -113
  122. data/ext/extconf.rb +27 -2
  123. data/ext/mk_call_cfunc.rb +19 -4
  124. data/ext/mk_pow5_table.rb +35 -0
  125. data/ext/mkkernel.rb +1202 -1014
  126. data/ext/ruby_carray.c +17 -5
  127. data/ext/version.h +4 -4
  128. data/ext/version.rb +4 -2
  129. data/lib/carray/attribute.rb +160 -17
  130. data/lib/carray/autoload_carray.rb +23 -27
  131. data/lib/carray/autoload_method_extension.rb +20 -5
  132. data/lib/carray/axis_group.rb +360 -63
  133. data/lib/carray/basics.rb +162 -28
  134. data/lib/carray/bincount_nd.rb +18 -2
  135. data/lib/carray/block_iterator.rb +231 -58
  136. data/lib/carray/boolean_reduce.rb +34 -42
  137. data/lib/carray/categorical.rb +242 -52
  138. data/lib/carray/categorical_iterator.rb +437 -827
  139. data/lib/carray/complex.rb +41 -34
  140. data/lib/carray/conditional.rb +150 -14
  141. data/lib/carray/const_string.rb +59 -4
  142. data/lib/carray/construct.rb +15 -11
  143. data/lib/carray/data_type_extension.rb +1 -0
  144. data/lib/carray/distance_limit.rb +77 -0
  145. data/lib/carray/fixlen_string.rb +7 -1
  146. data/lib/carray/frame/column_verbs.rb +70 -0
  147. data/lib/carray/frame/concat.rb +157 -6
  148. data/lib/carray/frame/convert.rb +82 -7
  149. data/lib/carray/frame/csv_parser.rb +688 -87
  150. data/lib/carray/frame/describe.rb +80 -0
  151. data/lib/carray/frame/frame.rb +369 -45
  152. data/lib/carray/frame/group.rb +631 -90
  153. data/lib/carray/frame/io.rb +506 -79
  154. data/lib/carray/frame/join.rb +411 -62
  155. data/lib/carray/frame/records.rb +116 -19
  156. data/lib/carray/frame/reshape.rb +455 -0
  157. data/lib/carray/frame/sort.rb +30 -18
  158. data/lib/carray/frame/verbs.rb +868 -61
  159. data/lib/carray/frame/windows.rb +117 -0
  160. data/lib/carray/frame.rb +4 -0
  161. data/lib/carray/fuse_source.rb +118 -2
  162. data/lib/carray/fusion.rb +257 -42
  163. data/lib/carray/histogram.rb +6 -1
  164. data/lib/carray/inspect.rb +54 -13
  165. data/lib/carray/iterator.rb +99 -9
  166. data/lib/carray/key_windows.rb +620 -0
  167. data/lib/carray/lazy.rb +216 -193
  168. data/lib/carray/mask_gap_fill.rb +112 -92
  169. data/lib/carray/math.rb +32 -19
  170. data/lib/carray/meld_reduce.rb +52 -21
  171. data/lib/carray/methods/align_addr.rb +8 -4
  172. data/lib/carray/methods/bin.rb +26 -18
  173. data/lib/carray/methods/bincount.rb +18 -2
  174. data/lib/carray/methods/bit_string.rb +15 -4
  175. data/lib/carray/methods/choose.rb +16 -3
  176. data/lib/carray/methods/composition.rb +18 -10
  177. data/lib/carray/methods/factorize.rb +2 -2
  178. data/lib/carray/methods/gather_nd.rb +25 -5
  179. data/lib/carray/methods/insert_block.rb +9 -14
  180. data/lib/carray/methods/is_between.rb +59 -0
  181. data/lib/carray/methods/is_in.rb +140 -10
  182. data/lib/carray/methods/join.rb +2 -7
  183. data/lib/carray/methods/lag.rb +49 -0
  184. data/lib/carray/methods/locate_addr.rb +182 -45
  185. data/lib/carray/methods/mode.rb +18 -1
  186. data/lib/carray/methods/nunique.rb +11 -6
  187. data/lib/carray/methods/repeat.rb +1 -20
  188. data/lib/carray/methods/resize.rb +21 -23
  189. data/lib/carray/methods/snap.rb +34 -12
  190. data/lib/carray/methods/unique.rb +11 -2
  191. data/lib/carray/methods/value_counts.rb +4 -11
  192. data/lib/carray/record_order.rb +208 -0
  193. data/lib/carray/rng.rb +3 -0
  194. data/lib/carray/runtime.rb +65 -17
  195. data/lib/carray/segment_iterator.rb +1221 -0
  196. data/lib/carray/serialize.rb +214 -30
  197. data/lib/carray/slab_iterator.rb +101 -27
  198. data/lib/carray/stack.rb +10 -4
  199. data/lib/carray/string.rb +105 -5
  200. data/lib/carray/string_operation_extension.rb +12 -4
  201. data/lib/carray/struct.rb +137 -58
  202. data/lib/carray/struct_builder.rb +124 -17
  203. data/lib/carray/time.rb +1222 -186
  204. data/lib/carray/window_iterator.rb +242 -81
  205. data/lib/carray.rb +9 -2
  206. data/yard-stubs/ca_obj_array.rb +4 -4
  207. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  208. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  209. data/yard-stubs/ca_obj_fake.rb +1 -1
  210. data/yard-stubs/ca_obj_field.rb +5 -1
  211. data/yard-stubs/ca_obj_grid.rb +21 -10
  212. data/yard-stubs/ca_obj_refer.rb +5 -4
  213. data/yard-stubs/ca_obj_roll.rb +2 -3
  214. data/yard-stubs/ca_obj_shift.rb +5 -1
  215. data/yard-stubs/ca_obj_stride.rb +10 -7
  216. data/yard-stubs/ca_obj_tile.rb +1 -1
  217. data/yard-stubs/ca_obj_transpose.rb +1 -1
  218. data/yard-stubs/ca_obj_window.rb +9 -5
  219. data/yard-stubs/carray_access.rb +11 -5
  220. data/yard-stubs/carray_attribute.rb +0 -5
  221. data/yard-stubs/carray_broadcast.rb +1 -1
  222. data/yard-stubs/carray_cast.rb +88 -43
  223. data/yard-stubs/carray_conversion.rb +15 -2
  224. data/yard-stubs/carray_copy.rb +12 -5
  225. data/yard-stubs/carray_core.rb +3 -3
  226. data/yard-stubs/carray_count.rb +9 -0
  227. data/yard-stubs/carray_element.rb +10 -4
  228. data/yard-stubs/carray_generate.rb +21 -1
  229. data/yard-stubs/carray_lazy.rb +5 -1
  230. data/yard-stubs/carray_loop.rb +4 -1
  231. data/yard-stubs/carray_mask.rb +60 -12
  232. data/yard-stubs/carray_math.rb +52 -14
  233. data/yard-stubs/carray_median_percentile.rb +15 -8
  234. data/yard-stubs/carray_memory_view.rb +40 -23
  235. data/yard-stubs/carray_order.rb +61 -29
  236. data/yard-stubs/carray_random.rb +13 -4
  237. data/yard-stubs/carray_scatter.rb +21 -9
  238. data/yard-stubs/carray_segment.rb +72 -0
  239. data/yard-stubs/carray_slab.rb +10 -2
  240. data/yard-stubs/carray_sort.rb +49 -8
  241. data/yard-stubs/carray_test.rb +15 -3
  242. data/yard-stubs/carray_utils.rb +0 -26
  243. metadata +19 -5
  244. data/ext/ca_for_each_element.h +0 -239
  245. data/ext/carray_kernels_map.c +0 -3467
  246. data/lib/carray/arrow_tensor.rb +0 -401
  247. data/lib/carray/table.rb +0 -108
data/ext/ca_obj_object.c CHANGED
@@ -34,6 +34,7 @@
34
34
  */
35
35
 
36
36
  #include "carray.h"
37
+ #include "carray_internal.h"
37
38
  #include "ca_obj_face.h" /* ca_face_* helpers for Face mode shortcuts */
38
39
 
39
40
  static size_t
@@ -59,6 +60,15 @@ ca_object_pool_init (void *ap, int8_t ndim)
59
60
  ca->dim = (ca_size_t *) ca->_pool;
60
61
  }
61
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
+
62
72
  const rb_data_type_t caobject_data_type = {
63
73
  .parent = &caview_data_type,
64
74
  .wrap_struct_name = "CAObject",
@@ -66,7 +76,7 @@ const rb_data_type_t caobject_data_type = {
66
76
  .dmark = ca_mark,
67
77
  .dfree = ca_free,
68
78
  .dsize = ca_object_dsize,
69
- .dcompact = NULL
79
+ .dcompact = ca_object_compact
70
80
  },
71
81
  .flags = RUBY_TYPED_FREE_IMMEDIATELY
72
82
  };
@@ -82,14 +92,32 @@ ca_objectmask_dsize (const void *ap)
82
92
  return sizeof(CAObjectMask) + ca->ndim * sizeof(ca_size_t);
83
93
  }
84
94
 
95
+ /* The mask reads and writes through its CAObject, and its cache is the
96
+ CAObject's; it keeps the CAObject alive, and follows it when compaction
97
+ moves it. */
98
+ static void
99
+ ca_objmask_mark (void *ap)
100
+ {
101
+ CAObjectMask *ca = (CAObjectMask *) ap;
102
+ rb_gc_mark_movable(ca->array);
103
+ ca_mark(ap);
104
+ }
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
+
85
113
  const rb_data_type_t caobjectmask_data_type = {
86
114
  .parent = &carray_data_type,
87
115
  .wrap_struct_name = "CAObjectMask",
88
116
  .function = {
89
- .dmark = ca_mark,
117
+ .dmark = ca_objmask_mark,
90
118
  .dfree = ca_free,
91
119
  .dsize = ca_objectmask_dsize,
92
- .dcompact = NULL
120
+ .dcompact = ca_objmask_compact
93
121
  },
94
122
  .flags = RUBY_TYPED_FREE_IMMEDIATELY
95
123
  };
@@ -100,6 +128,7 @@ VALUE rb_cCAObjectMask;
100
128
  static CAObjectMask *
101
129
  ca_objmask_new (VALUE array, int8_t ndim, ca_size_t *dim)
102
130
  {
131
+ /* check: the mask takes the CAObject's own shape, already checked */
103
132
  CAObjectMask *ca = ALLOC(CAObjectMask);
104
133
  /* CA_OBJ_OBJECT_MASK registers no pool_init, so it lives on the legacy
105
134
  ALLOC_N path (dim allocated separately, freed via free_ca_wrap's else
@@ -110,6 +139,8 @@ ca_objmask_new (VALUE array, int8_t ndim, ca_size_t *dim)
110
139
  ca_wrap_setup_null((CArray *)ca, CA_BOOLEAN, ndim, dim, 0, NULL);
111
140
  ca->obj_type = CA_OBJ_OBJECT_MASK;
112
141
  ca->array = array;
142
+ ca->cache = NULL;
143
+ ca->holds = 0;
113
144
 
114
145
  return ca;
115
146
  }
@@ -122,125 +153,376 @@ ca_objmask_mask_data (void *ap)
122
153
  rb_intern("mask"), 0);
123
154
  }
124
155
 
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. */
180
+
181
+ typedef struct {
182
+ CAObject *ca;
183
+ char *saved;
184
+ ID mid;
185
+ VALUE wrap;
186
+ } ca_object_lend_t;
187
+
188
+ static VALUE
189
+ ca_object_lend_body (VALUE varg)
190
+ {
191
+ ca_object_lend_t *l = (ca_object_lend_t *) varg;
192
+ return rb_funcall(l->ca->self, l->mid, 1, l->wrap);
193
+ }
194
+
195
+ static VALUE
196
+ ca_object_lend_restore (VALUE varg)
197
+ {
198
+ ca_object_lend_t *l = (ca_object_lend_t *) varg;
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;
204
+ return Qnil;
205
+ }
206
+
207
+ static void
208
+ ca_object_call_lending (CAObject *ca, void *ptr, const char *mid)
209
+ {
210
+ ca_object_lend_t l;
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;
219
+ l.mid = rb_intern(mid);
220
+ data->ptr = (char *) ptr;
221
+ rb_ensure(ca_object_lend_body, (VALUE) &l,
222
+ ca_object_lend_restore, (VALUE) &l);
223
+ RB_GC_GUARD(l.wrap);
224
+ }
225
+
226
+ /* Calls recv.mid(*argv), where some of argv are transient wraps of a
227
+ caller's buffer (ca_object_wrap_transient), and takes the buffer back
228
+ from each of them however the call leaves. The caller frees its buffer
229
+ when it returns, and a wrap outlives the call: the collector scans the
230
+ machine stack conservatively and still finds it there, and an object
231
+ wrap is marked cell by cell -- through a buffer that is gone. A wrap
232
+ taken back has no cells (ca_object_empty_wrap). */
233
+
234
+ typedef struct {
235
+ VALUE recv;
236
+ ID mid;
237
+ int argc;
238
+ const VALUE *argv;
239
+ int ntrans;
240
+ const VALUE *trans;
241
+ } ca_object_hook_t;
242
+
243
+ static VALUE
244
+ ca_object_hook_body (VALUE varg)
245
+ {
246
+ ca_object_hook_t *h = (ca_object_hook_t *) varg;
247
+ return rb_funcallv(h->recv, h->mid, h->argc, h->argv);
248
+ }
249
+
250
+ static VALUE
251
+ ca_object_hook_release (VALUE varg)
252
+ {
253
+ ca_object_hook_t *h = (ca_object_hook_t *) varg;
254
+ int i;
255
+ for ( i = 0; i < h->ntrans; i++ ) {
256
+ ca_object_empty_wrap((CArray *) DATA_PTR(h->trans[i]));
257
+ }
258
+ return Qnil;
259
+ }
260
+
261
+ static void
262
+ ca_object_call_transient (VALUE recv, const char *mid,
263
+ int argc, const VALUE *argv,
264
+ int ntrans, const VALUE *trans)
265
+ {
266
+ ca_object_hook_t h;
267
+ h.recv = recv;
268
+ h.mid = rb_intern(mid);
269
+ h.argc = argc;
270
+ h.argv = argv;
271
+ h.ntrans = ntrans;
272
+ h.trans = trans;
273
+ rb_ensure(ca_object_hook_body, (VALUE) &h,
274
+ ca_object_hook_release, (VALUE) &h);
275
+ }
276
+
277
+
278
+
279
+ /* Forward declaration: ca_object_wrap_transient (defined further down,
280
+ near the data-side xfer functions). */
281
+ static VALUE
282
+ ca_object_wrap_transient (int8_t data_type, ca_size_t bytes,
283
+ int8_t ndim, ca_size_t *dim, void *data, int dir);
284
+
125
285
  static void *
126
286
  ca_objmask_func_clone (void *ap)
127
287
  {
128
288
  CAObjectMask *ca = (CAObjectMask *) ap;
129
- return ca_objmask_new(ca->array, ca->ndim, ca->dim);
289
+ CAObjectMask *co = ca_objmask_new(ca->array, ca->ndim, ca->dim);
290
+ co->cache = ca->cache;
291
+ return co;
130
292
  }
131
293
 
132
- /* per-cell cores: objmask reads/writes its own CA_BOOLEAN bit storage
133
- (a contiguous wrap over the shared mask bytes) AND mirrors through the
134
- user's Ruby mask_fetch_* / mask_store_* callbacks. Entity bit access
135
- goes through ca_array_func_xfer_index / xfer_addrs. GET/PUT
136
- direction-unified; the slots below forward. */
137
- static void
138
- ca_objmask_xfer_addr_one (CAObjectMask *ca, ca_size_t addr, void *ptr, int dir)
294
+ /* Whether the CAObject answers one of the mask hooks. A copy made by dup
295
+ holds its own bits and answers none (array is nil). */
296
+ static int
297
+ ca_objmask_answers (CAObjectMask *ca, const char *mid)
139
298
  {
140
- volatile VALUE ridx, raddr, rval;
299
+ return ! NIL_P(ca->array) && rb_obj_respond_to(ca->array, rb_intern(mid), Qtrue);
300
+ }
301
+
302
+ static VALUE
303
+ ca_objmask_index_of (CAObjectMask *ca, ca_size_t addr)
304
+ {
305
+ ca_size_t idx[CA_RANK_MAX];
306
+ VALUE ridx;
141
307
  int i;
142
- if ( dir == CA_XFER_GET ) {
143
- if ( rb_obj_respond_to(ca->array, rb_intern("mask_fetch_addr"), Qtrue) ) {
144
- raddr = SIZE2NUM(addr);
145
- rval = rb_funcall(ca->array, rb_intern("mask_fetch_addr"), 1, raddr);
146
- *(uint8_t*) ptr = NUM2INT(rval) == 0 ? 0 : 1;
147
- ca_array_func_xfer_addrs(ca, 1, &addr, ptr, CA_XFER_PUT);
148
- }
149
- else if ( rb_obj_respond_to(ca->array, rb_intern("mask_fetch_index"), Qtrue) ) {
150
- ca_size_t idx[CA_RANK_MAX];
151
- ca_addr2index((CArray *)ca, addr, idx);
152
- ridx = rb_ary_new2(ca->ndim);
153
- for (i=0; i<ca->ndim; i++) {
154
- rb_ary_store(ridx, i, SIZE2NUM(idx[i]));
155
- }
156
- rval = rb_funcall(ca->array, rb_intern("mask_fetch_index"), 1, ridx);
157
- *(uint8_t*) ptr = NUM2INT(rval) == 0 ? 0 : 1;
158
- ca_array_func_xfer_index(ca, idx, ptr, CA_XFER_PUT);
159
- }
160
- else {
161
- ca_array_func_xfer_addrs(ca, 1, &addr, ptr, CA_XFER_GET);
162
- }
308
+ ca_addr2index((CArray *) ca, addr, idx);
309
+ ridx = rb_ary_new2(ca->ndim);
310
+ for (i=0; i<ca->ndim; i++) {
311
+ rb_ary_store(ridx, i, SIZE2NUM(idx[i]));
312
+ }
313
+ return ridx;
314
+ }
315
+
316
+ /* The bit the per-cell hooks give for addr (1 / 0 / true / false), or -1
317
+ when the object has neither mask_fetch_addr nor mask_fetch_index. */
318
+ static int
319
+ ca_objmask_fetch_one (CAObjectMask *ca, ca_size_t addr)
320
+ {
321
+ volatile VALUE rval;
322
+ if ( ca_objmask_answers(ca, "mask_fetch_addr") ) {
323
+ rval = rb_funcall(ca->array, rb_intern("mask_fetch_addr"), 1, SIZE2NUM(addr));
324
+ }
325
+ else if ( ca_objmask_answers(ca, "mask_fetch_index") ) {
326
+ rval = rb_funcall(ca->array, rb_intern("mask_fetch_index"), 1,
327
+ ca_objmask_index_of(ca, addr));
163
328
  }
164
329
  else {
165
- ca_array_func_xfer_addrs(ca, 1, &addr, ptr, CA_XFER_PUT);
166
- rval = INT2NUM( *(uint8_t*)ptr );
167
- if ( rb_obj_respond_to(ca->array, rb_intern("mask_store_addr"), Qtrue) ) {
168
- raddr = SIZE2NUM(addr);
169
- rb_funcall(ca->array, rb_intern("mask_store_addr"), 2, raddr, rval);
330
+ return -1;
331
+ }
332
+ if ( rval == Qtrue || rval == Qfalse ) {
333
+ return RTEST(rval);
334
+ }
335
+ return NUM2INT(rval) != 0;
336
+ }
337
+
338
+ /* Hands one bit to the per-cell hooks; 0 when the object has neither
339
+ mask_store_addr nor mask_store_index. */
340
+ static int
341
+ ca_objmask_store_one (CAObjectMask *ca, ca_size_t addr, boolean8_t bit)
342
+ {
343
+ if ( ca_objmask_answers(ca, "mask_store_addr") ) {
344
+ rb_funcall(ca->array, rb_intern("mask_store_addr"), 2,
345
+ SIZE2NUM(addr), INT2NUM(bit ? 1 : 0));
346
+ return 1;
347
+ }
348
+ if ( ca_objmask_answers(ca, "mask_store_index") ) {
349
+ rb_funcall(ca->array, rb_intern("mask_store_index"), 2,
350
+ ca_objmask_index_of(ca, addr), INT2NUM(bit ? 1 : 0));
351
+ return 1;
352
+ }
353
+ return 0;
354
+ }
355
+
356
+ static int
357
+ ca_objmask_stores_cells (CAObjectMask *ca)
358
+ {
359
+ return ca_objmask_answers(ca, "mask_store_addr") ||
360
+ ca_objmask_answers(ca, "mask_store_index");
361
+ }
362
+
363
+ /* Brings the cache up to date from the hooks that give the mask: the bulk
364
+ one, else the per-cell one. With neither, the cache is the mask. */
365
+ static void
366
+ ca_objmask_refresh (CAObjectMask *ca)
367
+ {
368
+ boolean8_t *c = (boolean8_t *) ca->cache;
369
+ ca_size_t addr;
370
+ int bit;
371
+ if ( ca_objmask_answers(ca, "mask_copy_data") ) {
372
+ rb_funcall(ca->array, rb_intern("mask_copy_data"), 1,
373
+ ca_objmask_mask_data(ca));
374
+ return;
375
+ }
376
+ for ( addr = 0; addr < ca->elements; addr++ ) {
377
+ if ( ( bit = ca_objmask_fetch_one(ca, addr) ) < 0 ) {
378
+ return;
170
379
  }
171
- else if ( rb_obj_respond_to(ca->array, rb_intern("mask_store_index"), Qtrue) ) {
172
- ca_size_t idx[CA_RANK_MAX];
173
- ca_addr2index((CArray *)ca, addr, idx);
174
- ridx = rb_ary_new2(ca->ndim);
175
- for (i=0; i<ca->ndim; i++) {
176
- rb_ary_store(ridx, i, SIZE2NUM(idx[i]));
177
- }
178
- rb_funcall(ca->array, rb_intern("mask_store_index"), 2, ridx, rval);
380
+ c[addr] = (boolean8_t) bit;
381
+ }
382
+ }
383
+
384
+ /* Hands the whole cache to the hooks that take the mask: the bulk one,
385
+ else the per-cell one. */
386
+ static void
387
+ ca_objmask_push (CAObjectMask *ca)
388
+ {
389
+ const boolean8_t *c = (const boolean8_t *) ca->cache;
390
+ ca_size_t addr;
391
+ if ( ca_objmask_answers(ca, "mask_sync_data") ) {
392
+ rb_funcall(ca->array, rb_intern("mask_sync_data"), 1,
393
+ ca_objmask_mask_data(ca));
394
+ return;
395
+ }
396
+ for ( addr = 0; addr < ca->elements; addr++ ) {
397
+ if ( ! ca_objmask_store_one(ca, addr, c[addr]) ) {
398
+ return;
179
399
  }
180
400
  }
181
401
  }
182
402
 
403
+ /* Reads n bits at addrs while the mask is closed: mask_copy_addrs, else
404
+ the per-cell hooks, else the whole mask brought up to date. */
183
405
  static void
184
- ca_objmask_xfer_index_one (CAObjectMask *ca, ca_size_t *idx, void *ptr, int dir)
406
+ ca_objmask_get_cells (CAObjectMask *ca, ca_size_t n, ca_size_t *addrs,
407
+ boolean8_t *bits)
185
408
  {
186
- volatile VALUE ridx, raddr, rval;
187
- int i;
188
- if ( dir == CA_XFER_GET ) {
189
- if ( rb_obj_respond_to(ca->array, rb_intern("mask_fetch_index"), Qtrue) ) {
190
- ridx = rb_ary_new2(ca->ndim);
191
- for (i=0; i<ca->ndim; i++) {
192
- rb_ary_store(ridx, i, SIZE2NUM(idx[i]));
193
- }
194
- rval = rb_funcall(ca->array, rb_intern("mask_fetch_index"), 1, ridx);
195
- *(uint8_t*) ptr = NUM2INT(rval) == 0 ? 0 : 1;
196
- ca_array_func_xfer_index(ca, idx, ptr, CA_XFER_PUT);
409
+ boolean8_t *c = (boolean8_t *) ca->cache;
410
+ ca_size_t i;
411
+ int bit;
412
+ if ( n > 0 && ca_objmask_answers(ca, "mask_copy_addrs") ) {
413
+ volatile VALUE raddrs, rdata;
414
+ ca_size_t dim1[1] = { n };
415
+ raddrs = ca_object_wrap_transient(CA_SIZE, sizeof(ca_size_t),
416
+ 1, dim1, addrs, CA_XFER_PUT);
417
+ rdata = ca_object_wrap_transient(CA_BOOLEAN, 1, 1, dim1, bits,
418
+ CA_XFER_GET);
419
+ {
420
+ VALUE args[2];
421
+ args[0] = raddrs;
422
+ args[1] = rdata;
423
+ ca_object_call_transient(ca->array, "mask_copy_addrs", 2, args, 2, args);
197
424
  }
198
- else if ( rb_obj_respond_to(ca->array, rb_intern("mask_fetch_addr"), Qtrue) ) {
199
- ca_size_t addr = ca_index2addr((CArray *)ca, idx);
200
- raddr = SIZE2NUM(addr);
201
- rval = rb_funcall(ca->array, rb_intern("mask_fetch_addr"), 1, raddr);
202
- *(uint8_t*) ptr = NUM2INT(rval) == 0 ? 0 : 1;
203
- ca_array_func_xfer_addrs(ca, 1, &addr, ptr, CA_XFER_PUT);
425
+ return;
426
+ }
427
+ if ( n > 0 && ( bit = ca_objmask_fetch_one(ca, addrs[0]) ) >= 0 ) {
428
+ c[addrs[0]] = bits[0] = (boolean8_t) bit;
429
+ for ( i = 1; i < n; i++ ) {
430
+ c[addrs[i]] = bits[i] = (boolean8_t) ca_objmask_fetch_one(ca, addrs[i]);
204
431
  }
205
- else {
206
- ca_array_func_xfer_index(ca, idx, ptr, CA_XFER_GET);
432
+ return;
433
+ }
434
+ ca_objmask_refresh(ca);
435
+ for ( i = 0; i < n; i++ ) {
436
+ bits[i] = c[addrs[i]];
437
+ }
438
+ }
439
+
440
+ /* Writes n bits at addrs while the mask is closed, and hands them over:
441
+ one by one to the per-cell hooks, as a list to mask_sync_addrs, or else
442
+ the whole mask to mask_sync_data -- brought up to date first, so that
443
+ the cells not written go back as they were. */
444
+ static void
445
+ ca_objmask_put_cells (CAObjectMask *ca, ca_size_t n, ca_size_t *addrs,
446
+ const boolean8_t *bits)
447
+ {
448
+ boolean8_t *c = (boolean8_t *) ca->cache;
449
+ ca_size_t i;
450
+ if ( ca_objmask_stores_cells(ca) ) {
451
+ for ( i = 0; i < n; i++ ) {
452
+ c[addrs[i]] = bits[i] ? 1 : 0;
453
+ ca_objmask_store_one(ca, addrs[i], c[addrs[i]]);
207
454
  }
455
+ return;
208
456
  }
209
- else {
210
- ca_array_func_xfer_index(ca, idx, ptr, CA_XFER_PUT);
211
- rval = INT2NUM( *(uint8_t*)ptr );
212
- if ( rb_obj_respond_to(ca->array, rb_intern("mask_store_index"), Qtrue) ) {
213
- ridx = rb_ary_new2(ca->ndim);
214
- for (i=0; i<ca->ndim; i++) {
215
- rb_ary_store(ridx, i, SIZE2NUM(idx[i]));
216
- }
217
- rb_funcall(ca->array, rb_intern("mask_store_index"), 2, ridx, rval);
457
+ if ( n > 0 && ca_objmask_answers(ca, "mask_sync_addrs") ) {
458
+ volatile VALUE raddrs, rdata;
459
+ ca_size_t dim1[1] = { n };
460
+ for ( i = 0; i < n; i++ ) {
461
+ c[addrs[i]] = bits[i] ? 1 : 0;
218
462
  }
219
- else if ( rb_obj_respond_to(ca->array, rb_intern("mask_store_addr"), Qtrue) ) {
220
- ca_size_t addr = ca_index2addr((CArray *)ca, idx);
221
- raddr = SIZE2NUM(addr);
222
- rb_funcall(ca->array, rb_intern("mask_store_addr"), 2, raddr, rval);
463
+ raddrs = ca_object_wrap_transient(CA_SIZE, sizeof(ca_size_t),
464
+ 1, dim1, addrs, CA_XFER_PUT);
465
+ rdata = ca_object_wrap_transient(CA_BOOLEAN, 1, 1, dim1, (void *) bits,
466
+ CA_XFER_PUT);
467
+ {
468
+ VALUE args[2];
469
+ args[0] = raddrs;
470
+ args[1] = rdata;
471
+ ca_object_call_transient(ca->array, "mask_sync_addrs", 2, args, 2, args);
472
+ }
473
+ return;
474
+ }
475
+ if ( ca_objmask_answers(ca, "mask_sync_data") ) {
476
+ ca_objmask_refresh(ca);
477
+ }
478
+ for ( i = 0; i < n; i++ ) {
479
+ c[addrs[i]] = bits[i] ? 1 : 0;
480
+ }
481
+ ca_objmask_push(ca);
482
+ }
483
+
484
+ /* Open: the cache is published in ptr, and it is the mask until the sync
485
+ hands it over. The first attach reads the mask in; an allocate does
486
+ not, since whoever allocates writes every cell. */
487
+ static void
488
+ ca_objmask_open (CAObjectMask *ca, int refresh)
489
+ {
490
+ if ( ca->holds == 0 ) {
491
+ if ( refresh ) {
492
+ ca_objmask_refresh(ca);
223
493
  }
494
+ ca->ptr = ca->cache;
224
495
  }
496
+ ca->holds++;
497
+ }
498
+
499
+ static void
500
+ ca_objmask_func_allocate (void *ap)
501
+ {
502
+ ca_objmask_open((CAObjectMask *) ap, 0);
225
503
  }
226
504
 
227
505
  static void
228
506
  ca_objmask_func_attach (void *ap)
507
+ {
508
+ ca_objmask_open((CAObjectMask *) ap, 1);
509
+ }
510
+
511
+ static void
512
+ ca_objmask_func_sync (void *ap)
229
513
  {
230
514
  CAObjectMask *ca = (CAObjectMask *) ap;
231
- if ( rb_obj_respond_to(ca->array, rb_intern("mask_copy_data"), Qtrue) ) {
232
- rb_funcall(ca->array, rb_intern("mask_copy_data"),
233
- 1, ca_objmask_mask_data(ca));
515
+ if ( ca->ptr ) {
516
+ ca_objmask_push(ca);
234
517
  }
235
518
  }
236
519
 
237
- void
238
- ca_objmask_func_sync (void *ap)
520
+ static void
521
+ ca_objmask_func_detach (void *ap)
239
522
  {
240
523
  CAObjectMask *ca = (CAObjectMask *) ap;
241
- if ( rb_obj_respond_to(ca->array, rb_intern("mask_sync_data"), Qtrue) ) {
242
- rb_funcall(ca->array, rb_intern("mask_sync_data"),
243
- 1, ca_objmask_mask_data(ca));
524
+ if ( ca->holds > 0 && --ca->holds == 0 ) {
525
+ ca->ptr = NULL;
244
526
  }
245
527
  }
246
528
 
@@ -248,83 +530,76 @@ static void
248
530
  ca_objmask_func_xfer_all (void *ap, void *data, int dir)
249
531
  {
250
532
  CAObjectMask *ca = (CAObjectMask *) ap;
533
+ size_t len = (size_t) ca->elements * (size_t) ca->bytes;
251
534
  if ( dir == CA_XFER_GET ) {
252
- if ( rb_obj_respond_to(ca->array, rb_intern("mask_copy_data"), Qtrue) ) {
253
- char *ptr0 = ca->ptr;
254
- ca->ptr = (char *) data;
255
- rb_funcall(ca->array, rb_intern("mask_copy_data"),
256
- 1, ca_objmask_mask_data(ca));
257
- ca->ptr = ptr0;
258
- }
259
- else {
260
- ca_array_func_xfer_all(ca, data, CA_XFER_GET);
535
+ if ( ! ca->ptr ) {
536
+ ca_objmask_refresh(ca);
261
537
  }
538
+ memcpy(data, ca->cache, len);
262
539
  }
263
540
  else {
264
- if ( rb_obj_respond_to(ca->array, rb_intern("mask_copy_data"), Qtrue) ) {
265
- char *ptr0 = ca->ptr;
266
- ca->ptr = (char *) data;
267
- rb_funcall(ca->array, rb_intern("mask_sync_data"),
268
- 1, ca_objmask_mask_data(ca));
269
- ca->ptr = ptr0;
270
- }
271
- else {
272
- ca_array_func_xfer_all(ca, data, CA_XFER_PUT);
541
+ memcpy(ca->cache, data, len);
542
+ if ( ! ca->ptr ) {
543
+ ca_objmask_push(ca);
273
544
  }
274
545
  }
275
546
  }
276
547
 
277
- void
278
- ca_objmask_func_fill_data (void *ap, void *val)
548
+ static void
549
+ ca_objmask_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
550
+ void *data, int dir)
279
551
  {
280
552
  CAObjectMask *ca = (CAObjectMask *) ap;
281
- ca_array_func_fill_data(ca, val);
282
- if ( rb_obj_respond_to(ca->array, rb_intern("mask_fill_data"), Qtrue) ) {
283
- rb_funcall(ca->array, rb_intern("mask_fill_data"),
284
- 1, INT2NUM(*(uint8_t*)val));
553
+ boolean8_t *c = (boolean8_t *) ca->cache;
554
+ boolean8_t *d = (boolean8_t *) data;
555
+ ca_size_t i;
556
+ if ( ca->ptr ) {
557
+ for ( i = 0; i < n; i++ ) {
558
+ if ( dir == CA_XFER_GET ) { d[i] = c[addrs[i]]; }
559
+ else { c[addrs[i]] = d[i] ? 1 : 0; }
560
+ }
561
+ return;
562
+ }
563
+ if ( dir == CA_XFER_GET ) {
564
+ ca_objmask_get_cells(ca, n, addrs, d);
565
+ }
566
+ else {
567
+ ca_objmask_put_cells(ca, n, addrs, d);
285
568
  }
286
569
  }
287
570
 
288
571
  static void
289
572
  ca_objmask_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
290
573
  {
291
- ca_objmask_xfer_index_one((CAObjectMask *) ap, idx, data, dir);
574
+ ca_size_t addr = ca_index2addr((CArray *) ap, idx);
575
+ ca_objmask_func_xfer_addrs(ap, 1, &addr, data, dir);
292
576
  }
293
577
 
294
- /* Forward declaration: ca_object_wrap_transient (defined further down,
295
- near the data-side xfer functions). */
296
- static VALUE
297
- ca_object_wrap_transient (int8_t data_type, ca_size_t bytes,
298
- int8_t ndim, ca_size_t *dim, void *data, int dir);
299
-
300
- /* Mask parallel of the bulk-addrs path. When `mask_copy_addrs` /
301
- `mask_sync_addrs` is defined, 1-call bulk dispatch; else per-cell loop. */
302
578
  static void
303
- ca_objmask_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
304
- void *data, int dir)
579
+ ca_objmask_func_fill_data (void *ap, void *val)
305
580
  {
306
581
  CAObjectMask *ca = (CAObjectMask *) ap;
307
- ID mid = (dir == CA_XFER_GET) ? rb_intern("mask_copy_addrs")
308
- : rb_intern("mask_sync_addrs");
309
-
310
- if ( n > 0 && rb_obj_respond_to(ca->array, mid, Qtrue) ) {
311
- volatile VALUE raddrs, rdata;
312
- ca_size_t dim1[1] = { n };
313
- raddrs = ca_object_wrap_transient(CA_SIZE, sizeof(ca_size_t),
314
- 1, dim1, addrs, CA_XFER_PUT);
315
- rdata = ca_object_wrap_transient(ca->data_type, ca->bytes,
316
- 1, dim1, data, dir);
317
- rb_funcall(ca->array, mid, 2, raddrs, rdata);
582
+ boolean8_t bit = *(boolean8_t *) val ? 1 : 0;
583
+ memset(ca->cache, bit, (size_t) ca->elements);
584
+ if ( ca->ptr ) {
318
585
  return;
319
586
  }
320
-
321
- {
322
- char *d = (char *) data;
323
- ca_size_t i;
324
- for ( i = 0; i < n; i++ ) {
325
- ca_objmask_xfer_addr_one(ca, addrs[i], d + i * ca->bytes, dir);
326
- }
587
+ if ( ca_objmask_answers(ca, "mask_fill_data") ) {
588
+ rb_funcall(ca->array, rb_intern("mask_fill_data"), 1, INT2NUM(bit));
589
+ return;
327
590
  }
591
+ ca_objmask_push(ca);
592
+ }
593
+
594
+ static void
595
+ ca_objmask_func_fill_addrs (void *ap, ca_size_t n, ca_size_t *addrs, void *val)
596
+ {
597
+ CAObjectMask *ca = (CAObjectMask *) ap;
598
+ volatile VALUE hold = 0;
599
+ boolean8_t *bits = ALLOCV_N(boolean8_t, hold, n > 0 ? n : 1);
600
+ memset(bits, *(boolean8_t *) val ? 1 : 0, (size_t) n);
601
+ ca_objmask_func_xfer_addrs(ca, n, addrs, bits, CA_XFER_PUT);
602
+ ALLOCV_END(hold);
328
603
  }
329
604
 
330
605
  /* Mask parallel of the partial-region path. Same per-axis index-step
@@ -335,6 +610,7 @@ static void
335
610
  ca_objmask_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
336
611
  ca_size_t *strides, void *data, int dir)
337
612
  {
613
+ if ( ca_region_is_empty(((CArray *) ap)->ndim, counts) ) return;
338
614
  CAObjectMask *ca = (CAObjectMask *) ap;
339
615
  int8_t ndim = ca->ndim;
340
616
  ca_size_t native[CA_RANK_MAX], steps[CA_RANK_MAX], idx[CA_RANK_MAX];
@@ -351,7 +627,8 @@ ca_objmask_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
351
627
 
352
628
  if ( n == 0 ) return;
353
629
 
354
- if ( rb_obj_respond_to(ca->array, mid_block, Qtrue) ) {
630
+ if ( ! ca->ptr && ca_objmask_answers(ca, dir == CA_XFER_GET ? "mask_copy_block"
631
+ : "mask_sync_block") ) {
355
632
  /* The request is over the view's addresses, so a transposed / flat
356
633
  request is legal; the per-axis copy_block dispatch below would
357
634
  misread it. See ca_xfer_stride_request_is_axis_box (carray.h). */
@@ -372,7 +649,15 @@ ca_objmask_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
372
649
  }
373
650
  rdata = ca_object_wrap_transient(ca->data_type, ca->bytes,
374
651
  ndim, counts, data, dir);
375
- rb_funcall(ca->array, mid_block, 4, rstarts, rcounts, rsteps, rdata);
652
+ {
653
+ VALUE args[4];
654
+ args[0] = rstarts;
655
+ args[1] = rcounts;
656
+ args[2] = rsteps;
657
+ args[3] = rdata;
658
+ ca_object_call_transient(ca->array, rb_id2name(mid_block),
659
+ 4, args, 1, args + 3);
660
+ }
376
661
  return;
377
662
  }
378
663
  }
@@ -390,15 +675,27 @@ ca_objmask_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
390
675
  ALLOCV_END(holder);
391
676
  }
392
677
 
678
+ /* A mask copied by dup holds its own bits, and frees them. */
679
+ static void
680
+ ca_objmask_free (void *ap)
681
+ {
682
+ CAObjectMask *ca = (CAObjectMask *) ap;
683
+ if ( ca != NULL && NIL_P(ca->array) && ca->cache ) {
684
+ xfree(ca->cache);
685
+ ca->cache = NULL;
686
+ }
687
+ free_ca_wrap(ap);
688
+ }
689
+
393
690
  static ca_operation_function_t ca_objmask_func = {
394
691
  -1, /* CA_OBJ_OBJECT_MASK */
395
692
  CA_REAL_ARRAY,
396
- free_ca_wrap,
693
+ ca_objmask_free,
397
694
  ca_objmask_func_clone,
398
- ca_array_func_allocate,
695
+ ca_objmask_func_allocate,
399
696
  ca_objmask_func_attach,
400
697
  ca_objmask_func_sync,
401
- ca_array_func_detach,
698
+ ca_objmask_func_detach,
402
699
  ca_objmask_func_fill_data,
403
700
  ca_array_func_create_mask,
404
701
  ca_objmask_func_xfer_index,
@@ -406,6 +703,7 @@ static ca_operation_function_t ca_objmask_func = {
406
703
  NULL, /* fold_stride: never-fold (callback boundary) */
407
704
  ca_objmask_func_xfer_stride,
408
705
  ca_objmask_func_xfer_all,
706
+ .fill_addrs = ca_objmask_func_fill_addrs,
409
707
  };
410
708
 
411
709
  static VALUE
@@ -421,11 +719,18 @@ rb_ca_objmask_initialize_copy (VALUE self, VALUE other)
421
719
  CAObjectMask *ca, *cs;
422
720
 
423
721
  TypedData_Get_Struct(self, CAObjectMask, &caobjectmask_data_type, ca);
722
+ ca_check_uninitialized(ca);
424
723
  TypedData_Get_Struct(other, CAObjectMask, &caobjectmask_data_type, cs);
425
724
 
725
+ /* A copy of the mask as it reads now, with bits of its own: writing to
726
+ it does not reach the CAObject's hooks. */
426
727
  carray_setup((CArray *)ca, CA_BOOLEAN, cs->ndim, cs->dim, 0, NULL);
427
728
  ca->obj_type = CA_OBJ_OBJECT_MASK;
428
- ca->array = cs->array;
729
+ ca->array = Qnil;
730
+ ca->cache = ca->ptr;
731
+ ca->holds = 0;
732
+ ca_copy_data((CArray *) cs, ca->cache);
733
+ ca->ptr = NULL;
429
734
 
430
735
  return self;
431
736
  }
@@ -437,28 +742,24 @@ ca_object_setup (CAObject *ca,
437
742
  int8_t data_type, int8_t ndim, ca_size_t *dim, ca_size_t bytes)
438
743
  {
439
744
  ca_size_t elements;
440
- double length;
441
745
  int8_t i;
442
746
 
443
747
  /* check arguments */
444
748
 
445
- CA_CHECK_DATA_TYPE(data_type);
446
- CA_CHECK_RANK(ndim);
447
- CA_CHECK_DIM(ndim, dim);
448
- 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
+ }
449
759
 
450
760
  /* calculate total number of elements */
451
761
 
452
- elements = 1;
453
- length = bytes;
454
- for (i=0; i<ndim; i++) {
455
- elements *= dim[i];
456
- length *= dim[i];
457
- }
458
-
459
- if ( length > CA_LENGTH_MAX ) {
460
- rb_raise(rb_eRuntimeError, "too large byte length");
461
- }
762
+ elements = ca_shape_elements(ndim, dim, bytes);
462
763
 
463
764
  ca->obj_type = CA_OBJ_OBJECT;
464
765
  ca->data_type = data_type;
@@ -486,7 +787,9 @@ ca_object_setup (CAObject *ca,
486
787
  static CAObject *
487
788
  ca_object_new (int8_t data_type, int8_t ndim, ca_size_t *dim, ca_size_t bytes)
488
789
  {
489
- 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);
490
793
  ca_object_setup(ca, data_type, ndim, dim, bytes);
491
794
  return ca;
492
795
  }
@@ -496,7 +799,12 @@ free_ca_object (void *ap)
496
799
  {
497
800
  CAObject *ca = (CAObject *) ap;
498
801
  if ( ca != NULL ) {
499
- /* 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
+ }
500
808
  if ( ca->_pool ) {
501
809
  ca_array_free(ca); /* dim lives in _pool */
502
810
  }
@@ -519,6 +827,30 @@ ca_object_func_clone (void *ap)
519
827
  #define ca_object_func_ptr_at_addr ca_array_func_ptr_at_addr
520
828
  #define ca_object_func_ptr_at_index ca_array_func_ptr_at_index
521
829
 
830
+ /* The bytes of this array's mask. A fetcher that answers UNDEF marks the
831
+ cell there, whether or not the mask is open. */
832
+ static boolean8_t *
833
+ ca_object_mask_bytes (CAObject *ca)
834
+ {
835
+ if ( ca->mask->obj_type == CA_OBJ_OBJECT_MASK ) {
836
+ return (boolean8_t *) ((CAObjectMask *) ca->mask)->cache;
837
+ }
838
+ return (boolean8_t *) ca->mask->ptr;
839
+ }
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
+
522
854
  /* per-cell cores: the addr- and index-primary Ruby-callback bridge logic,
523
855
  direction-unified. The fetch_* / store_* slots below are thin
524
856
  forwarders onto these. */
@@ -532,18 +864,15 @@ ca_object_xfer_addr_one (CAObject *ca, ca_size_t addr, void *ptr, int dir)
532
864
  raddr = SIZE2NUM(addr);
533
865
  rval = rb_funcall(ca->self, rb_intern("fetch_addr"), 1, raddr);
534
866
  if ( rval == CA_UNDEF ) {
535
- ca_update_mask(ca);
536
- if ( ! ca->mask ) {
537
- ca_create_mask(ca);
538
- }
539
- *((boolean8_t*)ca->mask->ptr + addr) = 1;
867
+ ca_create_mask(ca);
868
+ ca_object_mask_bytes(ca)[addr] = 1;
540
869
  if ( ca->data_type == CA_OBJECT ) {
541
870
  rb_ca_obj2ptr(ca->self, INT2NUM(0), ptr);
542
871
  }
543
872
  }
544
873
  else {
545
- if ( ca_has_mask(ca) ) {
546
- *((boolean8_t*)ca->mask->ptr + addr) = 0;
874
+ if ( ! ca_object_mask_from_hooks(ca) && ca_has_mask(ca) ) {
875
+ ca_object_mask_bytes(ca)[addr] = 0;
547
876
  }
548
877
  rb_ca_obj2ptr(ca->self, rval, ptr);
549
878
  }
@@ -557,18 +886,15 @@ ca_object_xfer_addr_one (CAObject *ca, ca_size_t addr, void *ptr, int dir)
557
886
  }
558
887
  rval = rb_funcall(ca->self, rb_intern("fetch_index"), 1, ridx);
559
888
  if ( rval == CA_UNDEF ) {
560
- ca_update_mask(ca);
561
- if ( ! ca->mask ) {
562
- ca_create_mask(ca);
563
- }
564
- *((boolean8_t*)ca->mask->ptr + ca_index2addr(ca->mask, idx)) = 1;
889
+ ca_create_mask(ca);
890
+ ca_object_mask_bytes(ca)[ca_index2addr(ca->mask, idx)] = 1;
565
891
  if ( ca->data_type == CA_OBJECT ) {
566
892
  rb_ca_obj2ptr(ca->self, INT2NUM(0), ptr);
567
893
  }
568
894
  }
569
895
  else {
570
- if ( ca_has_mask(ca) ) {
571
- *((boolean8_t*)ca->mask->ptr + ca_index2addr(ca->mask, idx)) = 0;
896
+ if ( ! ca_object_mask_from_hooks(ca) && ca_has_mask(ca) ) {
897
+ ca_object_mask_bytes(ca)[ca_index2addr(ca->mask, idx)] = 0;
572
898
  }
573
899
  rb_ca_obj2ptr(ca->self, rval, ptr);
574
900
  }
@@ -606,18 +932,15 @@ ca_object_xfer_index_one (CAObject *ca, ca_size_t *idx, void *ptr, int dir)
606
932
  }
607
933
  rval = rb_funcall(ca->self, rb_intern("fetch_index"), 1, ridx);
608
934
  if ( rval == CA_UNDEF ) {
609
- ca_update_mask(ca);
610
- if ( ! ca->mask ) {
611
- ca_create_mask(ca);
612
- }
613
- *((boolean8_t*)ca->mask->ptr + ca_index2addr(ca->mask, idx)) = 1;
935
+ ca_create_mask(ca);
936
+ ca_object_mask_bytes(ca)[ca_index2addr(ca->mask, idx)] = 1;
614
937
  if ( ca->data_type == CA_OBJECT ) {
615
938
  rb_ca_obj2ptr(ca->self, INT2NUM(0), ptr);
616
939
  }
617
940
  }
618
941
  else {
619
- if ( ca_has_mask(ca) ) {
620
- *((boolean8_t*)ca->mask->ptr + ca_index2addr(ca->mask, idx)) = 0;
942
+ if ( ! ca_object_mask_from_hooks(ca) && ca_has_mask(ca) ) {
943
+ ca_object_mask_bytes(ca)[ca_index2addr(ca->mask, idx)] = 0;
621
944
  }
622
945
  rb_ca_obj2ptr(ca->self, rval, ptr);
623
946
  }
@@ -627,18 +950,15 @@ ca_object_xfer_index_one (CAObject *ca, ca_size_t *idx, void *ptr, int dir)
627
950
  raddr = SIZE2NUM(addr);
628
951
  rval = rb_funcall(ca->self, rb_intern("fetch_addr"), 1, raddr);
629
952
  if ( rval == CA_UNDEF ) {
630
- ca_update_mask(ca);
631
- if ( ! ca->mask ) {
632
- ca_create_mask(ca);
633
- }
634
- *((boolean8_t*)ca->mask->ptr + addr) = 1;
953
+ ca_create_mask(ca);
954
+ ca_object_mask_bytes(ca)[addr] = 1;
635
955
  if ( ca->data_type == CA_OBJECT ) {
636
956
  rb_ca_obj2ptr(ca->self, INT2NUM(0), ptr);
637
957
  }
638
958
  }
639
959
  else {
640
- if ( ca_has_mask(ca) ) {
641
- *((boolean8_t*)ca->mask->ptr + addr) = 0;
960
+ if ( ! ca_object_mask_from_hooks(ca) && ca_has_mask(ca) ) {
961
+ ca_object_mask_bytes(ca)[addr] = 0;
642
962
  }
643
963
  rb_ca_obj2ptr(ca->self, rval, ptr);
644
964
  }
@@ -666,8 +986,8 @@ static void
666
986
  ca_object_func_allocate (void *ap)
667
987
  {
668
988
  CAObject *ca = (CAObject *) ap;
669
- /* ca->data->ptr = ALLOC_N(char, ca_length(ca)); */
670
- 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));
671
991
  if ( ca_is_object_type(ca->data) ) { /* GC safe */
672
992
  VALUE *p = (VALUE *) ca->data->ptr;
673
993
  VALUE zero = INT2NUM(0);
@@ -693,15 +1013,51 @@ ca_object_func_allocate (void *ap)
693
1013
  arithmetic all work via the slow per-element fallback. The fast bulk
694
1014
  path is opt-in by defining copy_data / sync_data / fill_data. */
695
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
+
696
1046
  static void
697
1047
  ca_object_dispatch_copy (CAObject *ca, void *ptr)
698
1048
  {
699
1049
  if ( rb_obj_respond_to(ca->self, rb_intern("copy_data"), Qtrue) ) {
700
- char *ptr0 = ca->data->ptr;
701
- ca->data->ptr = ptr;
702
- rb_funcall(ca->self, rb_intern("copy_data"),
703
- 1, rb_ivar_get(ca->self, rb_intern("__data__")));
704
- ca->data->ptr = ptr0;
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");
705
1061
  }
706
1062
  else {
707
1063
  ca_size_t addr;
@@ -716,11 +1072,7 @@ static void
716
1072
  ca_object_dispatch_sync (CAObject *ca, void *ptr)
717
1073
  {
718
1074
  if ( rb_obj_respond_to(ca->self, rb_intern("sync_data"), Qtrue) ) {
719
- char *ptr0 = ca->data->ptr;
720
- ca->data->ptr = ptr;
721
- rb_funcall(ca->self, rb_intern("sync_data"),
722
- 1, rb_ivar_get(ca->self, rb_intern("__data__")));
723
- ca->data->ptr = ptr0;
1075
+ ca_object_call_lending(ca, ptr, "sync_data");
724
1076
  }
725
1077
  else {
726
1078
  ca_size_t addr;
@@ -765,7 +1117,7 @@ ca_object_func_attach (void *ap)
765
1117
  ca_face_attach(ap);
766
1118
  return;
767
1119
  }
768
- /* ca->data->ptr = ALLOC_N(char, ca_length(ca)); */
1120
+ /* cells: seeded with 0, which a copy_data that skips cells leaves visible */
769
1121
  ca->data->ptr = xmalloc(ca_length(ca));
770
1122
  if ( ca_is_object_type(ca->data) ) { /* GC safe */
771
1123
  VALUE *p = (VALUE *) ca->data->ptr;
@@ -778,10 +1130,7 @@ ca_object_func_attach (void *ap)
778
1130
  ca->ptr = ca->data->ptr;
779
1131
  ca_object_dispatch_copy(ca, ca->ptr);
780
1132
  if ( ca_has_mask(ca->data) ) {
781
- ca_update_mask(ca);
782
- if ( ! ca->mask ) {
783
- ca_create_mask(ca);
784
- }
1133
+ ca_create_mask(ca);
785
1134
  }
786
1135
  }
787
1136
 
@@ -822,7 +1171,8 @@ ca_object_func_detach (void *ap)
822
1171
  [n] for addrs) before invoking copy_block / sync_block / copy_addrs /
823
1172
  sync_addrs. Lifecycle: buffer is owned by the slot caller; the wrapper
824
1173
  installs free_ca_wrap as dfree, so the underlying ptr is NOT freed when
825
- the wrapper is GC'd.
1174
+ the wrapper is GC'd. Call the hook through ca_object_call_transient,
1175
+ which takes the buffer back from the wrapper when the hook returns.
826
1176
 
827
1177
  Object data_type GC safety: for GET (dir == CA_XFER_GET) the buffer may be
828
1178
  uninitialized garbage on entry, so we INT2NUM(0) zero-fill before
@@ -880,7 +1230,12 @@ ca_object_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
880
1230
  1, dim1, addrs, CA_XFER_PUT);
881
1231
  rdata = ca_object_wrap_transient(ca->data_type, ca->bytes,
882
1232
  1, dim1, data, dir);
883
- rb_funcall(ca->self, mid, 2, raddrs, rdata);
1233
+ {
1234
+ VALUE args[2];
1235
+ args[0] = raddrs;
1236
+ args[1] = rdata;
1237
+ ca_object_call_transient(ca->self, rb_id2name(mid), 2, args, 2, args);
1238
+ }
884
1239
  return;
885
1240
  }
886
1241
 
@@ -917,6 +1272,7 @@ static void
917
1272
  ca_object_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
918
1273
  ca_size_t *strides, void *data, int dir)
919
1274
  {
1275
+ if ( ca_region_is_empty(((CArray *) ap)->ndim, counts) ) return;
920
1276
  CAObject *ca = (CAObject *) ap;
921
1277
  int8_t ndim;
922
1278
  ca_size_t native[CA_RANK_MAX], steps[CA_RANK_MAX], idx[CA_RANK_MAX];
@@ -969,7 +1325,15 @@ ca_object_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
969
1325
  }
970
1326
  rdata = ca_object_wrap_transient(ca->data_type, ca->bytes,
971
1327
  ndim, counts, data, dir);
972
- rb_funcall(ca->self, mid_block, 4, rstarts, rcounts, rsteps, rdata);
1328
+ {
1329
+ VALUE args[4];
1330
+ args[0] = rstarts;
1331
+ args[1] = rcounts;
1332
+ args[2] = rsteps;
1333
+ args[3] = rdata;
1334
+ ca_object_call_transient(ca->self, rb_id2name(mid_block),
1335
+ 4, args, 1, args + 3);
1336
+ }
973
1337
  return;
974
1338
  }
975
1339
  }
@@ -1040,7 +1404,12 @@ ca_object_func_fill_addrs (void *ap, ca_size_t n, ca_size_t *addrs, void *ptr)
1040
1404
  raddrs = ca_object_wrap_transient(CA_SIZE, sizeof(ca_size_t),
1041
1405
  1, dim1, addrs, CA_XFER_PUT);
1042
1406
  rval = rb_ca_ptr2obj(ca->self, ptr);
1043
- rb_funcall(ca->self, mid, 2, raddrs, rval);
1407
+ {
1408
+ VALUE args[2];
1409
+ args[0] = raddrs;
1410
+ args[1] = rval;
1411
+ ca_object_call_transient(ca->self, rb_id2name(mid), 2, args, 1, args);
1412
+ }
1044
1413
  return;
1045
1414
  }
1046
1415
 
@@ -1108,31 +1477,32 @@ ca_object_func_create_mask (void *ap)
1108
1477
  CAObject *ca = (CAObject *) ap;
1109
1478
  volatile VALUE rmask;
1110
1479
  if ( ca_is_face(ca) ) {
1111
- /* Face: the mask refers to the parent's mask (same as the C-level Face
1112
- create_mask, e.g. ca_time_func_create_mask). Storage is the
1113
- parent, so the mask must be the parent's, not the internal __data__. */
1114
- ca_update_mask(ca->parent);
1115
- if ( ! ca->parent->mask ) {
1116
- ca_create_mask(ca->parent);
1117
- }
1118
- ca->mask = (CArray *) ca_refer_new(ca->parent->mask,
1119
- CA_BOOLEAN, ca->ndim, ca->dim, 0, 0);
1480
+ /* Face: storage is the parent, so the mask is the parent's (as for the
1481
+ C-level Faces), not the internal __data__'s. */
1482
+ ca_view_func_create_mask(ca);
1120
1483
  return;
1121
1484
  }
1122
1485
  if ( rb_obj_respond_to(ca->self, rb_intern("create_mask"), Qtrue) ) {
1123
1486
  rb_funcall(ca->self, rb_intern("create_mask"), 0);
1124
1487
  }
1125
1488
  else {
1126
- rb_raise(rb_eRuntimeError, "can't create mask for CAObject");
1489
+ rb_raise(rb_eTypeError, "can't create mask for CAObject");
1127
1490
  }
1128
- ca_update_mask(ca->data);
1129
- if ( ! ca->data->mask ) {
1130
- ca_create_mask(ca->data);
1491
+ ca_create_mask(ca->data);
1492
+ {
1493
+ CAObjectMask *cm = ca_objmask_new(ca->self, ca->ndim, ca->dim);
1494
+ cm->cache = ca->data->mask->ptr;
1495
+ ca->mask = (CArray *) cm;
1496
+ rmask = ca_wrap_struct(ca->mask);
1497
+ rb_ivar_set(ca->self, rb_intern("mask"), rmask);
1498
+ /* Made while this array is attached, the mask joins it at its level:
1499
+ ca_create_mask attaches it once more when this returns. */
1500
+ if ( ca->attach > 1 ) {
1501
+ ca_objmask_refresh(cm);
1502
+ cm->ptr = cm->cache;
1503
+ cm->holds = ca->attach - 1;
1504
+ }
1131
1505
  }
1132
- ca->mask = (CArray*) ca_objmask_new(ca->self, ca->ndim, ca->dim);
1133
- ca->mask->ptr = ca->data->mask->ptr;
1134
- rmask = ca_wrap_struct(ca->mask);
1135
- rb_ivar_set(ca->self, rb_intern("mask"), rmask);
1136
1506
  }
1137
1507
 
1138
1508
  ca_operation_function_t ca_object_func = {
@@ -1171,6 +1541,7 @@ rb_ca_object_initialize_copy (VALUE self, VALUE other)
1171
1541
  CAObject *ca, *cs;
1172
1542
 
1173
1543
  TypedData_Get_Struct(self, CAObject, &caobject_data_type, ca);
1544
+ ca_check_uninitialized(ca);
1174
1545
  TypedData_Get_Struct(other, CAObject, &caobject_data_type, cs);
1175
1546
 
1176
1547
  if ( ca_func[CA_OBJ_OBJECT].pool_init ) {
@@ -1197,6 +1568,12 @@ rb_ca_object_initialize_copy (VALUE self, VALUE other)
1197
1568
  data = ca_wrap_struct(ca->data);
1198
1569
  rb_ivar_set(self, rb_intern("__data__"), data);
1199
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
+
1200
1577
  ca_update_mask(cs);
1201
1578
  if ( cs->mask ) {
1202
1579
  ca->mask = cs->mask;
@@ -1212,20 +1589,21 @@ rb_ca_object_initialize (int argc, VALUE *argv, VALUE self)
1212
1589
  {
1213
1590
  volatile VALUE rtype, rdim, ropt, rbytes = Qnil, rrdonly = Qnil,
1214
1591
  rparent = Qnil, rface = Qnil, rstorage = Qnil, rdata,
1215
- rorderable = Qnil, rcomparable = Qnil;
1592
+ rorderable = Qnil, rcomparable = Qnil, rnumeric = Qnil;
1216
1593
  CAObject *ca;
1217
1594
  int8_t data_type, ndim;
1218
1595
  ca_size_t dim[CA_RANK_MAX];
1219
1596
  ca_size_t bytes;
1220
1597
  int i;
1598
+ int storage_named = 0; /* :storage given by the caller */
1221
1599
 
1222
1600
  rb_scan_args(argc, argv, "21", (VALUE *) &rtype, (VALUE *) &rdim, (VALUE *) &ropt);
1223
- rb_scan_options(ropt, "bytes,read_only,parent,face,storage,orderable_storage,comparable_storage",
1601
+ rb_scan_options(ropt, "bytes,read_only,parent,face,storage,orderable_storage,comparable_storage,numeric",
1224
1602
  &rbytes, &rrdonly, &rparent, &rface, &rstorage,
1225
- &rorderable, &rcomparable);
1603
+ &rorderable, &rcomparable, &rnumeric);
1226
1604
 
1227
1605
  if ( ( ! NIL_P(rparent) ) && ( ! rb_obj_is_carray(rparent) ) ) {
1228
- rb_raise(rb_eRuntimeError, "option :parent should be a carray");
1606
+ rb_raise(rb_eTypeError, "option :parent should be a carray");
1229
1607
  }
1230
1608
 
1231
1609
  /* :storage is meaningful only under face: true (= it documents the
@@ -1246,6 +1624,16 @@ rb_ca_object_initialize (int argc, VALUE *argv, VALUE self)
1246
1624
  "meaningful with face: true");
1247
1625
  }
1248
1626
 
1627
+ if ( ! NIL_P(rnumeric) && ! RTEST(rface) ) {
1628
+ rb_raise(rb_eArgError,
1629
+ "CAObject: :numeric is only meaningful with face: true");
1630
+ }
1631
+ if ( ! NIL_P(rnumeric) && ! NIL_P(rstorage) ) {
1632
+ rb_raise(rb_eArgError,
1633
+ "CAObject: give either :numeric or :storage, not both "
1634
+ "(:storage declares a surface of its own)");
1635
+ }
1636
+
1249
1637
  /* face: true requires :parent. Under Face semantics the parent is the
1250
1638
  true storage owner and CAObject itself carries the semantic identity.
1251
1639
  The declared data_type is the SURFACE (= what dispatch sees);
@@ -1262,7 +1650,30 @@ rb_ca_object_initialize (int argc, VALUE *argv, VALUE self)
1262
1650
 
1263
1651
  rb_ca_guess_type_and_bytes(rtype, rbytes, &data_type, &bytes);
1264
1652
 
1653
+ /* Without :storage the data_type argument names the storage of a Face.
1654
+ A numeric storage is gated by default: the surface becomes CA_FIXLEN,
1655
+ so arithmetic, math and reductions raise instead of computing on the
1656
+ stored numbers, which the Face exists to give another meaning.
1657
+ numeric: true lets them through. An object or fixlen storage keeps
1658
+ its own data_type: fixlen kernels already raise, and an object cell
1659
+ may not be read as bytes. */
1660
+ storage_named = ! NIL_P(rstorage);
1661
+ if ( RTEST(rface) && NIL_P(rstorage) ) {
1662
+ if ( data_type == CA_OBJECT || data_type == CA_FIXLEN ) {
1663
+ if ( ! NIL_P(rnumeric) ) {
1664
+ rb_raise(rb_eArgError,
1665
+ "CAObject Face: :numeric applies to a numeric storage only "
1666
+ "(the storage is %s)", ca_type_name[data_type]);
1667
+ }
1668
+ }
1669
+ else if ( ! RTEST(rnumeric) ) {
1670
+ rstorage = INT2NUM(data_type);
1671
+ data_type = CA_FIXLEN;
1672
+ }
1673
+ }
1674
+
1265
1675
  Check_Type(rdim, T_ARRAY);
1676
+ CA_CHECK_RANK_GIVEN(RARRAY_LEN(rdim)); /* before dim[] is filled */
1266
1677
 
1267
1678
  ndim = RARRAY_LEN(rdim);
1268
1679
  for (i=0; i<ndim; i++) {
@@ -1270,6 +1681,7 @@ rb_ca_object_initialize (int argc, VALUE *argv, VALUE self)
1270
1681
  }
1271
1682
 
1272
1683
  TypedData_Get_Struct(self, CAObject, &caobject_data_type, ca);
1684
+ ca_check_uninitialized(ca);
1273
1685
  ca_object_setup(ca, data_type, ndim, dim, bytes);
1274
1686
  ca->self = self;
1275
1687
 
@@ -1287,17 +1699,11 @@ rb_ca_object_initialize (int argc, VALUE *argv, VALUE self)
1287
1699
  ca->parent = cp;
1288
1700
  rb_ca_set_parent(self, rparent);
1289
1701
 
1290
- /* Face mode invariants. The check the author opts into depends on
1291
- whether :storage is explicit:
1292
- - :storage omitted → "surface == storage" contract:
1293
- surface (= declared data_type) must equal
1294
- parent.data_type. This catches typos for
1295
- Numeric Face authors who didn't intend
1296
- surface/storage to differ.
1297
- - :storage given → "surface != storage" opted-in:
1298
- surface is free (= e.g. CA_FIXLEN for
1299
- NonNumeric Face), :storage must agree with
1300
- parent.data_type, and bytes must match. */
1702
+ /* Face mode invariants: bytes match the parent's, and the storage
1703
+ (:storage, or the data_type argument when :storage was omitted)
1704
+ is the parent's data_type. The surface is the data_type argument
1705
+ under :storage, CA_FIXLEN for a gated numeric storage (decided
1706
+ above), and the storage itself otherwise. */
1301
1707
  if ( RTEST(rface) ) {
1302
1708
  if ( bytes != cp->bytes ) {
1303
1709
  rb_raise(rb_eTypeError,
@@ -1306,12 +1712,12 @@ rb_ca_object_initialize (int argc, VALUE *argv, VALUE self)
1306
1712
  (long long) bytes, (long long) cp->bytes);
1307
1713
  }
1308
1714
  if ( NIL_P(rstorage) ) {
1309
- /* surface == storage contract */
1715
+ /* The data_type argument names the storage. */
1310
1716
  if ( cp->data_type != data_type ) {
1311
1717
  rb_raise(rb_eTypeError,
1312
1718
  "CAObject Face: parent data_type mismatch "
1313
1719
  "(declared=%d, parent=%d). Use :storage option to "
1314
- "opt into surface != storage (NonNumeric Face).",
1720
+ "declare a surface that differs from the storage.",
1315
1721
  (int) data_type, (int) cp->data_type);
1316
1722
  }
1317
1723
  }
@@ -1323,10 +1729,20 @@ rb_ca_object_initialize (int argc, VALUE *argv, VALUE self)
1323
1729
  &declared_storage, &declared_storage_bytes);
1324
1730
  if ( cp->data_type != declared_storage ) {
1325
1731
  rb_raise(rb_eTypeError,
1326
- "CAObject Face: :storage mismatch "
1327
- "(declared=%d, parent.data_type=%d)",
1732
+ storage_named
1733
+ ? "CAObject Face: :storage mismatch "
1734
+ "(declared=%d, parent.data_type=%d)"
1735
+ : "CAObject Face: parent data_type mismatch "
1736
+ "(declared=%d, parent=%d)",
1328
1737
  (int) declared_storage, (int) cp->data_type);
1329
1738
  }
1739
+ /* An object cell is a VALUE: reading it under another surface hands
1740
+ out its address, and writing one forges it. */
1741
+ if ( ca_is_object_type(cp) && data_type != CA_OBJECT ) {
1742
+ rb_raise(rb_eTypeError,
1743
+ "CAObject Face: an object storage can't be given "
1744
+ "another surface data_type");
1745
+ }
1330
1746
  }
1331
1747
  ca_set_flag(ca, CA_FLAG_IS_FACE);
1332
1748
  if ( RTEST(rorderable) ) {