carray 3.0.3 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -0
  3. data/README.md +3 -2
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_descriptor.h +27 -24
  6. data/ext/ca_axis_dispatch.c +1 -6
  7. data/ext/ca_axis_group.c +27 -27
  8. data/ext/ca_binop_dispatch.c +1 -29
  9. data/ext/ca_binop_dispatch.h +0 -1
  10. data/ext/ca_categorical_iterator.c +205 -134
  11. data/ext/ca_compare.c +7 -18
  12. data/ext/ca_group_iter.c +16 -17
  13. data/ext/ca_kernel_iterator.c +1234 -1994
  14. data/ext/ca_kernel_iterator.h +391 -437
  15. data/ext/ca_monop_dispatch.c +82 -59
  16. data/ext/ca_monop_dispatch.h +1 -0
  17. data/ext/ca_obj_array.c +96 -39
  18. data/ext/ca_obj_bincmp.c +24 -21
  19. data/ext/ca_obj_binop.c +15 -26
  20. data/ext/ca_obj_bitarray.c +3 -2
  21. data/ext/ca_obj_bitfield.c +5 -6
  22. data/ext/ca_obj_block.c +18 -9
  23. data/ext/ca_obj_byte_swap.c +5 -6
  24. data/ext/ca_obj_const_string.c +173 -19
  25. data/ext/ca_obj_face.c +238 -57
  26. data/ext/ca_obj_face.h +19 -0
  27. data/ext/ca_obj_fake.c +15 -29
  28. data/ext/ca_obj_farray.c +6 -3
  29. data/ext/ca_obj_field.c +17 -5
  30. data/ext/ca_obj_fixlen_string.c +2 -3
  31. data/ext/ca_obj_grid.c +406 -132
  32. data/ext/ca_obj_meld.c +172 -34
  33. data/ext/ca_obj_moncmp.c +23 -8
  34. data/ext/ca_obj_monop.c +67 -57
  35. data/ext/ca_obj_object.c +157 -51
  36. data/ext/ca_obj_record.c +12 -7
  37. data/ext/ca_obj_reduce.c +13 -8
  38. data/ext/ca_obj_refer.c +25 -27
  39. data/ext/ca_obj_remap.c +30 -9
  40. data/ext/ca_obj_repeat.c +9 -10
  41. data/ext/ca_obj_roll.c +6 -5
  42. data/ext/ca_obj_select.c +14 -9
  43. data/ext/ca_obj_select_axis.c +15 -47
  44. data/ext/ca_obj_shift.c +11 -1
  45. data/ext/ca_obj_stack.c +179 -34
  46. data/ext/ca_obj_stride.c +162 -115
  47. data/ext/ca_obj_string.c +71 -4
  48. data/ext/ca_obj_tile.c +6 -5
  49. data/ext/ca_obj_time.c +22 -6
  50. data/ext/ca_obj_timedelta.c +22 -6
  51. data/ext/ca_obj_transpose.c +5 -3
  52. data/ext/ca_obj_triop.c +23 -32
  53. data/ext/ca_obj_window.c +190 -26
  54. data/ext/ca_sweep_engine.c +2 -2
  55. data/ext/caframe_csv_reader.c +80 -1
  56. data/ext/carray.h +181 -75
  57. data/ext/carray_access.c +184 -96
  58. data/ext/carray_address_basis.c +343 -87
  59. data/ext/carray_attribute.c +89 -6
  60. data/ext/carray_bincount.c +10 -8
  61. data/ext/carray_broadcast.c +13 -8
  62. data/ext/carray_build_flags.h +3 -0
  63. data/ext/carray_call_cfunc.c +108 -0
  64. data/ext/carray_cast.c +142 -24
  65. data/ext/carray_cast_func.rb +46 -6
  66. data/ext/carray_class.c +4 -3
  67. data/ext/carray_config.h +28 -0
  68. data/ext/carray_conversion.c +16 -7
  69. data/ext/carray_copy.c +51 -14
  70. data/ext/carray_core.c +147 -55
  71. data/ext/carray_count.c +50 -20
  72. data/ext/carray_element.c +76 -33
  73. data/ext/carray_factorize.c +118 -64
  74. data/ext/carray_generate.c +191 -50
  75. data/ext/carray_histogram.c +62 -17
  76. data/ext/carray_hold.c +12 -15
  77. data/ext/carray_index_classifier.c +388 -176
  78. data/ext/carray_internal.h +336 -29
  79. data/ext/carray_kernels_bincmp.c +4453 -0
  80. data/ext/carray_kernels_binop.c +11368 -0
  81. data/ext/carray_kernels_init.c +1734 -0
  82. data/ext/carray_kernels_moncmp.c +2104 -0
  83. data/ext/carray_kernels_monop.c +18369 -0
  84. data/ext/carray_kernels_reduce_aggregate.c +23818 -0
  85. data/ext/carray_kernels_reduce_boolean.c +406 -0
  86. data/ext/carray_kernels_reduce_cumulative.c +10086 -0
  87. data/ext/carray_kernels_reduce_extreme.c +17634 -0
  88. data/ext/carray_kernels_reduce_variance.c +3849 -0
  89. data/ext/carray_kernels_scan.c +4302 -0
  90. data/ext/carray_kernels_search.c +33829 -0
  91. data/ext/carray_kernels_sort.c +10906 -0
  92. data/ext/carray_kernels_triop.c +1864 -0
  93. data/ext/carray_key_windows.c +216 -0
  94. data/ext/carray_lazy.c +170 -111
  95. data/ext/carray_mask.c +292 -72
  96. data/ext/carray_median_percentile.c +82 -28
  97. data/ext/carray_memory_view.c +669 -561
  98. data/ext/carray_operator.c +124 -47
  99. data/ext/carray_order.c +384 -76
  100. data/ext/carray_partition.c +28 -27
  101. data/ext/carray_random.c +67 -13
  102. data/ext/carray_round.c +273 -0
  103. data/ext/carray_scatter.c +63 -38
  104. data/ext/carray_segment.c +3 -3
  105. data/ext/carray_slab.c +91 -28
  106. data/ext/carray_sort.c +35 -35
  107. data/ext/carray_struct.c +19 -18
  108. data/ext/carray_test.c +26 -5
  109. data/ext/carray_undef.c +4 -0
  110. data/ext/carray_utils.c +246 -45
  111. data/ext/mk_call_cfunc.rb +19 -2
  112. data/ext/mkkernel.rb +198 -240
  113. data/ext/ruby_carray.c +10 -4
  114. data/ext/version.h +4 -4
  115. data/lib/carray/attribute.rb +126 -22
  116. data/lib/carray/autoload_carray.rb +8 -31
  117. data/lib/carray/autoload_method_extension.rb +14 -5
  118. data/lib/carray/axis_group.rb +255 -106
  119. data/lib/carray/basics.rb +78 -10
  120. data/lib/carray/bincount_nd.rb +12 -2
  121. data/lib/carray/block_iterator.rb +126 -23
  122. data/lib/carray/boolean_reduce.rb +17 -17
  123. data/lib/carray/categorical.rb +165 -43
  124. data/lib/carray/categorical_iterator.rb +130 -27
  125. data/lib/carray/complex.rb +5 -5
  126. data/lib/carray/conditional.rb +67 -18
  127. data/lib/carray/const_string.rb +14 -5
  128. data/lib/carray/construct.rb +15 -11
  129. data/lib/carray/data_type_extension.rb +1 -0
  130. data/lib/carray/distance_limit.rb +77 -0
  131. data/lib/carray/fixlen_string.rb +7 -1
  132. data/lib/carray/frame/column_verbs.rb +70 -0
  133. data/lib/carray/frame/convert.rb +69 -0
  134. data/lib/carray/frame/csv_parser.rb +111 -17
  135. data/lib/carray/frame/describe.rb +3 -2
  136. data/lib/carray/frame/frame.rb +246 -21
  137. data/lib/carray/frame/group.rb +510 -95
  138. data/lib/carray/frame/io.rb +115 -28
  139. data/lib/carray/frame/join.rb +312 -51
  140. data/lib/carray/frame/records.rb +83 -7
  141. data/lib/carray/frame/reshape.rb +42 -35
  142. data/lib/carray/frame/sort.rb +7 -19
  143. data/lib/carray/frame/verbs.rb +188 -48
  144. data/lib/carray/frame/windows.rb +117 -0
  145. data/lib/carray/frame.rb +2 -0
  146. data/lib/carray/fuse_source.rb +11 -4
  147. data/lib/carray/fusion.rb +40 -16
  148. data/lib/carray/histogram.rb +6 -1
  149. data/lib/carray/inspect.rb +31 -8
  150. data/lib/carray/iterator.rb +85 -7
  151. data/lib/carray/key_windows.rb +620 -0
  152. data/lib/carray/lazy.rb +182 -183
  153. data/lib/carray/mask_gap_fill.rb +63 -57
  154. data/lib/carray/meld_reduce.rb +12 -14
  155. data/lib/carray/methods/bin.rb +26 -18
  156. data/lib/carray/methods/bincount.rb +16 -0
  157. data/lib/carray/methods/bit_string.rb +1 -1
  158. data/lib/carray/methods/choose.rb +16 -3
  159. data/lib/carray/methods/composition.rb +3 -1
  160. data/lib/carray/methods/gather_nd.rb +14 -5
  161. data/lib/carray/methods/insert_block.rb +2 -2
  162. data/lib/carray/methods/is_between.rb +59 -0
  163. data/lib/carray/methods/is_in.rb +53 -36
  164. data/lib/carray/methods/join.rb +1 -6
  165. data/lib/carray/methods/lag.rb +49 -0
  166. data/lib/carray/methods/locate_addr.rb +47 -51
  167. data/lib/carray/methods/mode.rb +1 -1
  168. data/lib/carray/methods/nunique.rb +11 -6
  169. data/lib/carray/methods/resize.rb +3 -3
  170. data/lib/carray/methods/value_counts.rb +4 -11
  171. data/lib/carray/record_order.rb +3 -3
  172. data/lib/carray/rng.rb +3 -0
  173. data/lib/carray/runtime.rb +53 -8
  174. data/lib/carray/segment_iterator.rb +313 -82
  175. data/lib/carray/serialize.rb +24 -10
  176. data/lib/carray/slab_iterator.rb +50 -0
  177. data/lib/carray/string.rb +105 -5
  178. data/lib/carray/string_operation_extension.rb +8 -1
  179. data/lib/carray/struct.rb +9 -21
  180. data/lib/carray/struct_builder.rb +19 -8
  181. data/lib/carray/time.rb +726 -104
  182. data/lib/carray/window_iterator.rb +122 -16
  183. data/lib/carray.rb +7 -0
  184. data/yard-stubs/ca_obj_bitfield.rb +1 -1
  185. data/yard-stubs/ca_obj_field.rb +4 -0
  186. data/yard-stubs/ca_obj_grid.rb +21 -10
  187. data/yard-stubs/ca_obj_refer.rb +2 -1
  188. data/yard-stubs/ca_obj_roll.rb +2 -2
  189. data/yard-stubs/ca_obj_shift.rb +5 -1
  190. data/yard-stubs/ca_obj_tile.rb +1 -1
  191. data/yard-stubs/ca_obj_window.rb +8 -3
  192. data/yard-stubs/carray_access.rb +6 -1
  193. data/yard-stubs/carray_attribute.rb +0 -5
  194. data/yard-stubs/carray_cast.rb +10 -0
  195. data/yard-stubs/carray_conversion.rb +2 -1
  196. data/yard-stubs/carray_copy.rb +4 -2
  197. data/yard-stubs/carray_count.rb +4 -4
  198. data/yard-stubs/carray_element.rb +10 -4
  199. data/yard-stubs/carray_generate.rb +21 -1
  200. data/yard-stubs/carray_mask.rb +26 -2
  201. data/yard-stubs/carray_math.rb +27 -1
  202. data/yard-stubs/carray_median_percentile.rb +5 -2
  203. data/yard-stubs/carray_memory_view.rb +40 -23
  204. data/yard-stubs/carray_order.rb +29 -18
  205. data/yard-stubs/carray_random.rb +13 -4
  206. data/yard-stubs/carray_scatter.rb +14 -9
  207. data/yard-stubs/carray_slab.rb +9 -2
  208. data/yard-stubs/carray_sort.rb +6 -5
  209. data/yard-stubs/carray_test.rb +5 -3
  210. metadata +25 -3
  211. data/lib/carray/arrow_tensor.rb +0 -401
  212. data/lib/carray/table.rb +0 -108
@@ -31,10 +31,14 @@
31
31
  #include "ca_obj_face.h" /* ca_strip_face for Face producers */
32
32
  #include "carray_internal.h"
33
33
  #include "ruby/memory_view.h"
34
+ #include "ruby/st.h"
35
+ #include "ruby/thread_native.h"
36
+ #include <ctype.h>
34
37
  #include <string.h>
35
38
 
36
- /* private_data is not used: ca_mv_get is alias-only and ca_mv_release
37
- needs no per-view state. Kept as a NULL field for API compatibility. */
39
+ /* private_data holds the view's own copy of a record format (see ca_mv_get)
40
+ and is NULL otherwise; ca_mv_release frees it. No other per-view state:
41
+ ca_mv_get is alias-only. */
38
42
 
39
43
  /* ---------------- forward declarations for non-public structs ----------------
40
44
  CABlock, CARefer, CARepeat are declared in carray.h. CAFarray
@@ -79,8 +83,6 @@ typedef enum {
79
83
  static ca_mv_runtime_type_t ca_mv_runtime_types[CA_MV_NUM_RUNTIME_OBJ_TYPES] = {
80
84
  { "CAFarray", "CA_OBJ_FARRAY", -1, Qnil, /* CA_MV_FARRAY */ 4 },
81
85
  { "CATranspose", "CA_OBJ_TRANSPOSE", -1, Qnil, /* CA_MV_TRANS */ 5 },
82
- /* CAMapping is retired; a[mapper] now builds a CAGrid/CAStride chain
83
- whose layers are covered above. */
84
86
  { "CAGrid", "CA_OBJ_GRID", -1, Qnil, /* CA_MV_ATTACH */ 7 },
85
87
  { "CAShift", "CA_OBJ_SHIFT", -1, Qnil, /* CA_MV_ATTACH */ 7 },
86
88
  { "CAWindow", "CA_OBJ_WINDOW", -1, Qnil, /* CA_MV_ATTACH */ 7 },
@@ -200,8 +202,9 @@ ca_mv_record_member_spec (VALUE info, char *out, size_t outlen,
200
202
  a member it has no item for (see above), or members that overlap or come
201
203
  out of offset order (a union).
202
204
 
203
- The result is frozen and cached on the data_class, so the pointer
204
- handed to a view stays valid while the class lives. */
205
+ The result is frozen and cached on the data_class. It is an ordinary
206
+ String that compaction can move, so a view takes a copy of its bytes
207
+ rather than its pointer (ca_mv_get). */
205
208
  static VALUE
206
209
  ca_mv_struct_format_for_data_class (VALUE data_class)
207
210
  {
@@ -277,33 +280,80 @@ ca_mv_ca_exportable (CArray *ca, VALUE obj)
277
280
  }
278
281
 
279
282
  /* Reverse mapping for the importer (Postel consumer; see
280
- docs/interop/MemoryViewFormat.md for the contract):
281
- - Strip a leading byte-order prefix ('<', '>', '=') if it matches host
282
- endian; reject if it disagrees.
283
- - Strip the alignment modifier '|'.
284
- - Reject endian-bearing primary specifiers ('e','g','E','G','n','v',
285
- 'N','V'); cross-endian sources are not accepted.
286
- - Dispatch on the (stripped_format, item_size) tuple.
287
- - Canonical: '?' for bool, 'Zf' / 'Zd' for complex64/128 (PEP 3118).
283
+ docs/interop/MemoryViewFormat.md for the contract). Both vocabularies
284
+ are read: Ruby's (byte order as a suffix, "i<", "s!>"; '_' for '!';
285
+ 'e' 'E' 'g' 'G' 'n' 'N' 'v' 'V' carrying their own order; 'j' 'J' for
286
+ intptr) and PEP 3118's (order as a prefix '<' '>' '!' '=' '@'; 'b' 'B'
287
+ 'h' 'H' '?'; 'Zf' 'Zd'). The alignment modifier '|' is skipped.
288
+ - CArray stores host order, so an order that disagrees with the host
289
+ is refused. A one-byte type has no order, so any is accepted.
290
+ - Dispatch on the (type, item_size) tuple.
288
291
  - Synonyms accepted but not emitted: 'C'/1 maps to UINT8 (so other
289
292
  producers' bool buffers are received as UINT8); 'ff' / 'dd' map to
290
293
  complex64/128 (producers that split complex into two floats). */
294
+
295
+ /* Byte order named in a format: 0 none, 1 little, 2 big. */
296
+ #define CA_MV_ORDER_NONE 0
297
+ #define CA_MV_ORDER_LITTLE 1
298
+ #define CA_MV_ORDER_BIG 2
299
+
300
+ static int
301
+ ca_mv_order_merge (int a, int b)
302
+ {
303
+ if ( a == CA_MV_ORDER_NONE ) return b;
304
+ if ( b == CA_MV_ORDER_NONE ) return a;
305
+ return ( a == b ) ? a : -1;
306
+ }
307
+
291
308
  static int8_t
292
309
  ca_mv_data_type_from_format (const char *format, ssize_t item_size)
293
310
  {
311
+ const int host = (ca_endian == CA_LITTLE_ENDIAN) ? CA_MV_ORDER_LITTLE
312
+ : CA_MV_ORDER_BIG;
294
313
  const char *fmt = format ? format : "";
295
- /* Strip alignment and host-matching byte-order prefix. */
314
+ char buf[16];
315
+ size_t len;
316
+ int order = CA_MV_ORDER_NONE;
317
+ int one_byte;
318
+
296
319
  if (*fmt == '|') fmt++;
297
- if (*fmt == '<' || *fmt == '>' || *fmt == '=') {
298
- /* The carray runtime stores data in host-endian; cross-endian
299
- sources are rejected. */
300
- int host_is_le = (ca_endian == CA_LITTLE_ENDIAN);
301
- int wants_le = (*fmt == '<') || (*fmt == '=' && host_is_le);
302
- int wants_be = (*fmt == '>') || (*fmt == '=' && !host_is_le);
303
- if ((wants_le && !host_is_le) || (wants_be && host_is_le)) return -1;
304
- fmt++;
320
+ switch (*fmt) {
321
+ case '<': order = CA_MV_ORDER_LITTLE; fmt++; break;
322
+ case '>': case '!': order = CA_MV_ORDER_BIG; fmt++; break;
323
+ case '=': case '@': order = host; fmt++; break;
324
+ }
325
+ len = strlen(fmt);
326
+ if ( len == 0 || len >= sizeof(buf) ) return -1;
327
+ memcpy(buf, fmt, len + 1);
328
+
329
+ /* Ruby's suffix form on a single type: "i<", "s!>", "q_<". */
330
+ if ( len >= 2 && len <= 3 && isalpha((unsigned char) buf[0]) &&
331
+ ( buf[len - 1] == '<' || buf[len - 1] == '>' ) ) {
332
+ order = ca_mv_order_merge(order, buf[len - 1] == '<' ? CA_MV_ORDER_LITTLE
333
+ : CA_MV_ORDER_BIG);
334
+ if ( order < 0 ) return -1;
335
+ buf[--len] = '\0';
336
+ }
337
+ /* '_' is Ruby's spelling of the native-size modifier '!'. */
338
+ if ( len == 2 && buf[1] == '_' ) {
339
+ buf[1] = '!';
340
+ }
341
+ /* Types that carry their own byte order. */
342
+ if ( len == 1 ) {
343
+ int own = CA_MV_ORDER_NONE;
344
+ switch (buf[0]) {
345
+ case 'e': case 'E': case 'v': case 'V': own = CA_MV_ORDER_LITTLE; break;
346
+ case 'g': case 'G': case 'n': case 'N': own = CA_MV_ORDER_BIG; break;
347
+ }
348
+ order = ca_mv_order_merge(order, own);
349
+ if ( order < 0 ) return -1;
305
350
  }
306
- if (*fmt == '\0') return -1;
351
+
352
+ one_byte = ( len == 1 && strchr("cCbB?", buf[0]) != NULL );
353
+ if ( order != CA_MV_ORDER_NONE && order != host && ! one_byte ) {
354
+ return -1;
355
+ }
356
+ fmt = buf;
307
357
 
308
358
  /* single-byte formats */
309
359
  if (strcmp(fmt, "c") == 0 && item_size == 1) return CA_INT8;
@@ -316,24 +366,32 @@ ca_mv_data_type_from_format (const char *format, ssize_t item_size)
316
366
  if ((strcmp(fmt, "s") == 0 || strcmp(fmt, "s!") == 0 ||
317
367
  strcmp(fmt, "h") == 0) && item_size == 2) return CA_INT16;
318
368
  if ((strcmp(fmt, "S") == 0 || strcmp(fmt, "S!") == 0 ||
319
- strcmp(fmt, "H") == 0) && item_size == 2) return CA_UINT16;
369
+ strcmp(fmt, "H") == 0 || strcmp(fmt, "v") == 0 ||
370
+ strcmp(fmt, "n") == 0) && item_size == 2) return CA_UINT16;
320
371
 
321
372
  /* 32-bit */
322
373
  if ((strcmp(fmt, "l") == 0 || strcmp(fmt, "i") == 0 ||
323
- strcmp(fmt, "i!") == 0 || strcmp(fmt, "l!") == 0) && item_size == 4) return CA_INT32;
374
+ strcmp(fmt, "i!") == 0 || strcmp(fmt, "l!") == 0 ||
375
+ strcmp(fmt, "j") == 0) && item_size == 4) return CA_INT32;
324
376
  if ((strcmp(fmt, "L") == 0 || strcmp(fmt, "I") == 0 ||
325
- strcmp(fmt, "I!") == 0 || strcmp(fmt, "L!") == 0) && item_size == 4) return CA_UINT32;
377
+ strcmp(fmt, "I!") == 0 || strcmp(fmt, "L!") == 0 ||
378
+ strcmp(fmt, "J") == 0 || strcmp(fmt, "V") == 0 ||
379
+ strcmp(fmt, "N") == 0) && item_size == 4) return CA_UINT32;
326
380
 
327
381
  /* 64-bit. `l`/`L` at item_size 8 accepted as LP64 native long
328
382
  (numpy default for `np.array([1,2,3]).dtype == int64`). */
329
383
  if ((strcmp(fmt, "q") == 0 || strcmp(fmt, "q!") == 0 ||
330
- strcmp(fmt, "l!") == 0 || strcmp(fmt, "l") == 0) && item_size == 8) return CA_INT64;
384
+ strcmp(fmt, "l!") == 0 || strcmp(fmt, "l") == 0 ||
385
+ strcmp(fmt, "j") == 0) && item_size == 8) return CA_INT64;
331
386
  if ((strcmp(fmt, "Q") == 0 || strcmp(fmt, "Q!") == 0 ||
332
- strcmp(fmt, "L!") == 0 || strcmp(fmt, "L") == 0) && item_size == 8) return CA_UINT64;
387
+ strcmp(fmt, "L!") == 0 || strcmp(fmt, "L") == 0 ||
388
+ strcmp(fmt, "J") == 0) && item_size == 8) return CA_UINT64;
333
389
 
334
- /* floats: no endian-bearing variants accepted */
335
- if (strcmp(fmt, "f") == 0 && item_size == 4) return CA_FLOAT32;
336
- if (strcmp(fmt, "d") == 0 && item_size == 8) return CA_FLOAT64;
390
+ /* floats */
391
+ if ((strcmp(fmt, "f") == 0 || strcmp(fmt, "e") == 0 ||
392
+ strcmp(fmt, "g") == 0) && item_size == 4) return CA_FLOAT32;
393
+ if ((strcmp(fmt, "d") == 0 || strcmp(fmt, "E") == 0 ||
394
+ strcmp(fmt, "G") == 0) && item_size == 8) return CA_FLOAT64;
337
395
 
338
396
  /* complex: PEP 3118 'Zf' / 'Zd' canonical. */
339
397
  if (strcmp(fmt, "Zf") == 0 && item_size == 8) return CA_CMPLX64;
@@ -631,26 +689,36 @@ ca_mv_available_p (VALUE obj)
631
689
  return ca_mv_check_alias_chain(ca, &rej);
632
690
  }
633
691
 
634
- /* "Ns" format for a plain CA_FIXLEN of N bytes. The Strings are kept in
635
- a table keyed by N for the life of the process, so the pointer stays
692
+ /* "Ns" format for a plain CA_FIXLEN of N bytes. The C strings are kept
693
+ in a table keyed by N for the life of the process, so the pointer stays
636
694
  valid for any view, and a frozen array (which cannot take an ivar) is
637
- exported like any other. */
638
- static VALUE ca_mv_fixlen_formats = Qnil;
695
+ exported like any other. The table is shared by every Ractor, hence
696
+ the lock. */
697
+ static st_table *ca_mv_fixlen_formats = NULL;
698
+ static rb_nativethread_lock_t ca_mv_fixlen_formats_lock;
639
699
 
640
- static VALUE
700
+ static const char *
641
701
  ca_mv_fixlen_format (ca_size_t bytes)
642
702
  {
643
- VALUE key = SIZE2NUM(bytes);
644
- VALUE fmt;
645
- if ( NIL_P(ca_mv_fixlen_formats) ) {
646
- ca_mv_fixlen_formats = rb_hash_new();
647
- rb_gc_register_mark_object(ca_mv_fixlen_formats);
703
+ st_data_t found;
704
+ char *fmt;
705
+ rb_nativethread_lock_lock(&ca_mv_fixlen_formats_lock);
706
+ if ( st_lookup(ca_mv_fixlen_formats, (st_data_t) bytes, &found) ) {
707
+ rb_nativethread_lock_unlock(&ca_mv_fixlen_formats_lock);
708
+ return (const char *) found;
709
+ }
710
+ rb_nativethread_lock_unlock(&ca_mv_fixlen_formats_lock);
711
+ fmt = ALLOC_N(char, 24);
712
+ snprintf(fmt, 24, "%llds", (long long) bytes);
713
+ rb_nativethread_lock_lock(&ca_mv_fixlen_formats_lock);
714
+ if ( st_lookup(ca_mv_fixlen_formats, (st_data_t) bytes, &found) ) {
715
+ xfree(fmt);
716
+ fmt = (char *) found;
648
717
  }
649
- fmt = rb_hash_lookup(ca_mv_fixlen_formats, key);
650
- if ( NIL_P(fmt) ) {
651
- fmt = rb_obj_freeze(rb_sprintf("%lds", (long) bytes));
652
- rb_hash_aset(ca_mv_fixlen_formats, key, fmt);
718
+ else {
719
+ st_insert(ca_mv_fixlen_formats, (st_data_t) bytes, (st_data_t) fmt);
653
720
  }
721
+ rb_nativethread_lock_unlock(&ca_mv_fixlen_formats_lock);
654
722
  return fmt;
655
723
  }
656
724
 
@@ -663,6 +731,7 @@ ca_mv_get (VALUE obj, rb_memory_view_t *view, int flags)
663
731
  ssize_t *strides = NULL;
664
732
  void *data_ptr = NULL;
665
733
  const char *format;
734
+ char *record_format = NULL;
666
735
  int writable_request;
667
736
 
668
737
  if (! ca_mv_extract(obj, &ca)) return false;
@@ -705,16 +774,22 @@ ca_mv_get (VALUE obj, rb_memory_view_t *view, int flags)
705
774
 
706
775
  /* Format string, resolved before shape / strides are allocated since
707
776
  building it can raise. Primitive types use the top-level table;
708
- CA_FIXLEN + data_class emits the record's format, cached on the class,
709
- so the pointer is stable until the class is GC'd. */
777
+ CA_FIXLEN + data_class emits the record's format, cached on the class
778
+ as a String. The String can move, so the view carries its own copy in
779
+ private_data, freed by ca_mv_release. */
710
780
  if (ca->data_type == CA_FIXLEN && RTEST(rb_ca_has_data_class(obj))) {
711
781
  VALUE klass = rb_ca_data_class(obj);
712
782
  VALUE fmt = ca_mv_struct_format_for_data_class(klass);
713
- format = RSTRING_PTR(fmt);
783
+ long len = RSTRING_LEN(fmt);
784
+ record_format = ALLOC_N(char, len + 1);
785
+ memcpy(record_format, RSTRING_PTR(fmt), len);
786
+ record_format[len] = '\0';
787
+ RB_GC_GUARD(fmt);
788
+ format = record_format;
714
789
  }
715
790
  else if (ca->data_type == CA_FIXLEN) {
716
791
  /* Plain CA_FIXLEN (no data_class): PEP 3118 "Ns" fixed-bytes. */
717
- format = RSTRING_PTR(ca_mv_fixlen_format(ca->bytes));
792
+ format = ca_mv_fixlen_format(ca->bytes);
718
793
  }
719
794
  else {
720
795
  format = ca_mv_format_for(ca->data_type);
@@ -768,7 +843,7 @@ ca_mv_get (VALUE obj, rb_memory_view_t *view, int flags)
768
843
  view->shape = (view->ndim == 0) ? NULL : shape;
769
844
  view->strides = (view->ndim == 0) ? NULL : strides;
770
845
  view->sub_offsets = NULL;
771
- view->private_data = NULL;
846
+ view->private_data = record_format;
772
847
 
773
848
  /* Enforce caller's layout request. */
774
849
  {
@@ -791,6 +866,11 @@ reject:
791
866
  return false; there is no attach state to unwind. */
792
867
  if (shape) { xfree(shape); view->shape = NULL; }
793
868
  if (strides) { xfree(strides); view->strides = NULL; }
869
+ if (record_format) {
870
+ xfree(record_format);
871
+ view->format = NULL;
872
+ view->private_data = NULL;
873
+ }
794
874
  return false;
795
875
  }
796
876
 
@@ -812,7 +892,10 @@ ca_mv_release (VALUE obj, rb_memory_view_t *view)
812
892
  ca_sync(ca->mask) recursion and raise; raising from a release
813
893
  callback is fatal during GC sweep. See ca_mv_get's CAStride-family
814
894
  case comment for the full rationale. */
815
- view->private_data = NULL;
895
+ if (view->private_data) {
896
+ xfree(view->private_data);
897
+ view->private_data = NULL;
898
+ }
816
899
  return true;
817
900
  }
818
901
 
@@ -831,9 +914,25 @@ ca_mv_release (VALUE obj, rb_memory_view_t *view)
831
914
  a TypedData holder (also stored as an ivar) releases the view in its
832
915
  dfree callback when the CAWrap is collected. */
833
916
 
917
+ /* One borrowed view and the geometry read from it. The geometry is
918
+ copied out and normalised once (a 0-d view as one element, omitted
919
+ shape or strides filled in as row-major), so nothing below reads the
920
+ producer's shape or strides arrays again.
921
+
922
+ The holder is a Ruby object from before the view is taken, so a raise
923
+ at any later point loses nothing: the importers release it at once
924
+ (rb_protect / rb_ensure), and the holder's dfree releases it in any
925
+ case. `lo` / `hi` bound the bytes the elements reach, relative to
926
+ view.data. */
834
927
  typedef struct {
835
928
  rb_memory_view_t view;
836
929
  bool valid;
930
+ int8_t ndim;
931
+ ca_size_t dim[CA_RANK_MAX];
932
+ ssize_t strides[CA_RANK_MAX];
933
+ ca_size_t elements;
934
+ bool contiguous;
935
+ ssize_t lo, hi;
837
936
  } ca_mv_imported_holder_t;
838
937
 
839
938
  static void
@@ -841,8 +940,21 @@ ca_mv_imported_holder_free (void *p)
841
940
  {
842
941
  ca_mv_imported_holder_t *h = (ca_mv_imported_holder_t *) p;
843
942
  if (h->valid) {
844
- rb_memory_view_release(&h->view);
845
943
  h->valid = false;
944
+ /* While a view is exported Ruby keeps the producer alive, so in an
945
+ ordinary sweep it is still here. At exit, though, the objects
946
+ left are freed in heap order and the producer may have gone first
947
+ (its slot is T_NONE with the class still set). Its release
948
+ callback would then get a dead object, and one that checks the
949
+ type raises, which is fatal during GC. Skip the release there:
950
+ the process is ending, so only the components are freed. */
951
+ if (! RB_SPECIAL_CONST_P(h->view.obj) &&
952
+ RB_BUILTIN_TYPE(h->view.obj) == RUBY_T_NONE) {
953
+ xfree((void *) h->view.item_desc.components);
954
+ }
955
+ else {
956
+ rb_memory_view_release(&h->view);
957
+ }
846
958
  }
847
959
  xfree(h);
848
960
  }
@@ -864,149 +976,311 @@ static const rb_data_type_t ca_mv_imported_holder_data_type = {
864
976
  .flags = RUBY_TYPED_FREE_IMMEDIATELY
865
977
  };
866
978
 
867
- /* Acquire a MemoryView from src and validate/derive data_type, ndim
868
- and shape. Tries STRIDES first; falls back to SIMPLE so that
869
- producers that advertise only contiguous still work. On any
870
- failure, releases the view (if acquired) and raises ArgumentError;
871
- on success, the view is left active and the caller owns the
872
- responsibility to release it.
873
-
874
- `explicit_data_type` is the caller-supplied target data_type (>= 0) or -1
875
- if the caller has no preference. Behaviour:
876
- - explicit_data_type < 0, format != NULL: derive data_type from format.
877
- - explicit_data_type < 0, format == NULL: reject (typeless source
878
- without consumer-supplied type cannot be interpreted).
879
- - explicit_data_type >= 0, format != NULL: must match the format's
880
- data_type; mismatch -> reject.
881
- - explicit_data_type >= 0, format == NULL: use explicit_data_type; the
882
- byte buffer is reinterpreted as that data_type. ndim is forced
883
- to 1 (multi-dim from a typeless source is ambiguous; user
884
- chains .reshape).
885
- */
886
- /* Row-major contiguity test that tolerates a NULL shape array. ndim-0
887
- scalars and SIMPLE-form producers (which omit the shape array for a
888
- contiguous 1-D buffer, e.g. red-arrow's primitive arrays) are
889
- contiguous by construction; rb_memory_view_is_row_major_contiguous
890
- dereferences view->shape, so it must not be reached with shape NULL. */
891
- static bool
892
- ca_mv_view_is_contiguous (const rb_memory_view_t *view)
979
+ static VALUE
980
+ ca_mv_holder_new (ca_mv_imported_holder_t **out)
893
981
  {
894
- if (view->ndim == 0 || view->shape == NULL) {
895
- return true;
982
+ /* Zero-filled: valid is false and the view's item_desc.components is
983
+ NULL, which release frees unconditionally (red-arrow 24.0.0 leaves
984
+ that field unwritten, apache/arrow#45187). */
985
+ return TypedData_Make_Struct(rb_cObject, ca_mv_imported_holder_t,
986
+ &ca_mv_imported_holder_data_type, *out);
987
+ }
988
+
989
+ static void
990
+ ca_mv_holder_release (ca_mv_imported_holder_t *h)
991
+ {
992
+ if (h != NULL && h->valid) {
993
+ h->valid = false;
994
+ rb_memory_view_release(&h->view);
896
995
  }
897
- return rb_memory_view_is_row_major_contiguous(view);
898
996
  }
899
997
 
998
+ /* Take a view from src: STRIDES first, SIMPLE for producers that offer
999
+ only a contiguous buffer. `writable` asks the producer for a writable
1000
+ view; a producer that refuses that but gives a read-only one is a
1001
+ read-only source. */
900
1002
  static void
901
- ca_mv_acquire_and_validate (VALUE src, rb_memory_view_t *view,
902
- int8_t explicit_data_type,
903
- int8_t *out_data_type, int8_t *out_ndim,
904
- ca_size_t *out_dim)
1003
+ ca_mv_holder_acquire (ca_mv_imported_holder_t *h, VALUE src, int writable,
1004
+ const char *who)
905
1005
  {
906
- int8_t data_type;
907
- int i;
1006
+ int extra = writable ? RUBY_MEMORY_VIEW_WRITABLE : 0;
1007
+ memset(&h->view, 0, sizeof(h->view));
1008
+ if ( ! rb_memory_view_get(src, &h->view, RUBY_MEMORY_VIEW_STRIDES | extra) ) {
1009
+ memset(&h->view, 0, sizeof(h->view));
1010
+ if ( ! rb_memory_view_get(src, &h->view, RUBY_MEMORY_VIEW_SIMPLE | extra) ) {
1011
+ if ( writable ) {
1012
+ memset(&h->view, 0, sizeof(h->view));
1013
+ if ( rb_memory_view_get(src, &h->view, RUBY_MEMORY_VIEW_STRIDES) ||
1014
+ rb_memory_view_get(src, &h->view, RUBY_MEMORY_VIEW_SIMPLE) ) {
1015
+ h->valid = true;
1016
+ rb_raise(rb_eRuntimeError,
1017
+ "MemoryView source is read-only; can't wrap as writable");
1018
+ }
1019
+ }
1020
+ rb_raise(rb_eArgError, "%sobject does not support MemoryView", who);
1021
+ }
1022
+ }
1023
+ h->valid = true;
1024
+ if ( writable && h->view.readonly ) {
1025
+ rb_raise(rb_eRuntimeError,
1026
+ "MemoryView source is read-only; can't wrap as writable");
1027
+ }
1028
+ }
908
1029
 
909
- /* Defensive zero-init: rb_memory_view_release() unconditionally
910
- xfree()s view->item_desc.components, but not every producer writes
911
- that field. red-arrow 24.0.0 leaves it uninitialized, so a view
912
- acquired from it frees stack garbage on release (abort). Zeroing
913
- here makes the field NULL. Consumer side of apache/arrow#45187
914
- (fixed in Arrow 25.0.0); harmless once producers initialize it. */
915
- memset(view, 0, sizeof(*view));
1030
+ /* Read the view's geometry for elements of `item_size` bytes, and check
1031
+ it against the buffer. The buffer is [data, data + byte_size). For a
1032
+ strided view byte_size is the element count times item_size (the
1033
+ convention CArray's own producer, NumPy and Arrow follow), which does
1034
+ not bound where the strides reach, so only that count is checked; for
1035
+ a contiguous view the shape is checked against the bytes. */
1036
+ static void
1037
+ ca_mv_holder_read_geometry (ca_mv_imported_holder_t *h, ssize_t item_size,
1038
+ const char *who)
1039
+ {
1040
+ const rb_memory_view_t *v = &h->view;
1041
+ ssize_t expect, need;
1042
+ int8_t k;
916
1043
 
917
- if (! rb_memory_view_get(src, view, RUBY_MEMORY_VIEW_STRIDES)) {
918
- if (! rb_memory_view_get(src, view, RUBY_MEMORY_VIEW_SIMPLE)) {
1044
+ /* rank-check: a producer's rank, named by which MemoryView it is */
1045
+ if ( v->ndim < 0 || v->ndim > CA_RANK_MAX ) {
1046
+ rb_raise(rb_eArgError, "%sMemoryView ndim (%ld) exceeds CArray CA_RANK_MAX (%d)",
1047
+ who, (long) v->ndim, CA_RANK_MAX);
1048
+ }
1049
+ if ( v->ndim == 0 ) {
1050
+ h->ndim = 1;
1051
+ h->dim[0] = 1;
1052
+ }
1053
+ else if ( v->shape == NULL ) {
1054
+ /* Only 1-D is readable without a shape: length = byte_size / item_size. */
1055
+ if ( v->ndim > 1 ) {
919
1056
  rb_raise(rb_eArgError,
920
- "object does not support MemoryView");
1057
+ "%sMemoryView reports ndim %ld but provides no shape array",
1058
+ who, (long) v->ndim);
1059
+ }
1060
+ h->ndim = 1;
1061
+ h->dim[0] = (ca_size_t) (item_size > 0 ? v->byte_size / item_size : 0);
1062
+ }
1063
+ else {
1064
+ h->ndim = (int8_t) v->ndim;
1065
+ for (k = 0; k < h->ndim; k++) {
1066
+ if ( v->shape[k] < 0 ) {
1067
+ rb_raise(rb_eArgError, "%sMemoryView shape[%d] is negative (%ld)",
1068
+ who, (int) k, (long) v->shape[k]);
1069
+ }
1070
+ h->dim[k] = (ca_size_t) v->shape[k];
1071
+ }
1072
+ }
1073
+ h->elements = ca_shape_elements(h->ndim, h->dim, (ca_size_t) item_size);
1074
+
1075
+ if ( v->ndim >= 1 && v->shape != NULL && v->strides != NULL ) {
1076
+ for (k = 0; k < h->ndim; k++) {
1077
+ h->strides[k] = v->strides[k];
1078
+ }
1079
+ }
1080
+ else {
1081
+ expect = item_size;
1082
+ for (k = h->ndim - 1; k >= 0; k--) {
1083
+ h->strides[k] = expect;
1084
+ expect *= (ssize_t) h->dim[k];
1085
+ }
1086
+ }
1087
+
1088
+ h->contiguous = true;
1089
+ expect = item_size;
1090
+ for (k = h->ndim - 1; k >= 0; k--) {
1091
+ if ( h->dim[k] > 1 && h->strides[k] != expect ) {
1092
+ h->contiguous = false;
1093
+ }
1094
+ expect *= (ssize_t) h->dim[k];
1095
+ }
1096
+
1097
+ h->lo = h->hi = 0;
1098
+ if ( h->elements > 0 ) {
1099
+ h->hi = item_size;
1100
+ for (k = 0; k < h->ndim; k++) {
1101
+ ssize_t reach;
1102
+ if ( __builtin_mul_overflow((ssize_t) h->dim[k] - 1, h->strides[k], &reach) ||
1103
+ ( reach < 0 ? __builtin_add_overflow(h->lo, reach, &h->lo)
1104
+ : __builtin_add_overflow(h->hi, reach, &h->hi) ) ) {
1105
+ rb_raise(rb_eArgError, "%sMemoryView strides reach too far", who);
1106
+ }
921
1107
  }
922
1108
  }
923
1109
 
924
- if (view->format == NULL) {
925
- /* Typeless producer (e.g. mmap-view, IO#read-style byte blob).
926
- Consumer must supply the target data_type. */
927
- if (explicit_data_type < 0) {
928
- ssize_t got = view->byte_size;
929
- rb_memory_view_release(view);
1110
+ need = (ssize_t) h->elements * item_size;
1111
+ if ( v->byte_size < need ) {
1112
+ rb_raise(rb_eArgError,
1113
+ "%sMemoryView reports %ld bytes, but its shape needs %ld",
1114
+ who, (long) v->byte_size, (long) need);
1115
+ }
1116
+ }
1117
+
1118
+ /* Take the data side's view and resolve its data_type.
1119
+ `explicit_data_type` (the data_type: keyword) must agree with a typed
1120
+ producer's format and names the type of a typeless one.
1121
+ `typeless_data_type` names the type only when the producer is typeless
1122
+ (wrap_readonly / wrap_writable, which adapt a typed producer
1123
+ afterwards). A typeless producer's bytes are read as one 1-D run, so
1124
+ one whose own geometry is strided is refused. */
1125
+ static int8_t
1126
+ ca_mv_holder_take_data (ca_mv_imported_holder_t *h, VALUE src,
1127
+ int8_t explicit_data_type, int8_t typeless_data_type,
1128
+ int writable)
1129
+ {
1130
+ rb_memory_view_t *v = &h->view;
1131
+ int8_t data_type;
1132
+
1133
+ ca_mv_holder_acquire(h, src, writable, "");
1134
+
1135
+ if ( v->format == NULL ) {
1136
+ ssize_t target;
1137
+ data_type = ( explicit_data_type >= 0 ) ? explicit_data_type : typeless_data_type;
1138
+ if ( data_type < 0 ) {
930
1139
  rb_raise(rb_eArgError,
931
1140
  "typeless MemoryView (format=NULL, %ld bytes); "
932
1141
  "specify the target data_type via data_type: kwarg or use a "
933
1142
  "concrete CArray::<Dtype> factory class",
934
- (long) got);
1143
+ (long) v->byte_size);
935
1144
  }
936
- if (ca_mv_format_for(explicit_data_type) == NULL) {
937
- rb_memory_view_release(view);
1145
+ if ( ca_mv_format_for(data_type) == NULL ) {
938
1146
  rb_raise(rb_eArgError,
939
1147
  "target data_type is not representable via MemoryView");
940
1148
  }
941
- ssize_t target_item_size = (ssize_t) ca_sizeof[explicit_data_type];
942
- if (view->byte_size % target_item_size != 0) {
943
- ssize_t bs = view->byte_size;
944
- rb_memory_view_release(view);
1149
+ ca_mv_holder_read_geometry(h, v->item_size > 0 ? v->item_size : 1, "");
1150
+ if ( ! h->contiguous ) {
1151
+ rb_raise(rb_eArgError,
1152
+ "typeless MemoryView is strided; its bytes cannot be read as "
1153
+ "one run of %s", ca_type_name[data_type]);
1154
+ }
1155
+ target = (ssize_t) ca_sizeof[data_type];
1156
+ if ( v->byte_size % target != 0 ) {
945
1157
  rb_raise(rb_eArgError,
946
1158
  "byte_size %ld is not a multiple of target item_size %ld",
947
- (long) bs, (long) target_item_size);
1159
+ (long) v->byte_size, (long) target);
948
1160
  }
949
- data_type = explicit_data_type;
950
- *out_ndim = 1;
951
- out_dim[0] = (ca_size_t) (view->byte_size / target_item_size);
952
- *out_data_type = data_type;
953
- return;
954
- }
955
-
956
- /* Typed producer: derive data_type from format. */
957
- data_type = ca_mv_data_type_from_format(view->format, view->item_size);
958
- if (data_type < 0) {
959
- char fmt_buf[64];
960
- snprintf(fmt_buf, sizeof(fmt_buf), "%s", view->format);
961
- rb_memory_view_release(view);
962
- rb_raise(rb_eArgError,
963
- "unsupported MemoryView format: %s (item_size=%ld)",
964
- fmt_buf, (long) view->item_size);
965
- }
966
-
967
- /* If the caller specified a data_type too, it must match. */
968
- if (explicit_data_type >= 0 && explicit_data_type != data_type) {
969
- rb_memory_view_release(view);
1161
+ h->ndim = 1;
1162
+ h->dim[0] = (ca_size_t) (v->byte_size / target);
1163
+ h->strides[0] = target;
1164
+ h->elements = h->dim[0];
1165
+ h->contiguous = true;
1166
+ h->lo = 0;
1167
+ h->hi = v->byte_size;
1168
+ return data_type;
1169
+ }
1170
+
1171
+ data_type = ca_mv_data_type_from_format(v->format, v->item_size);
1172
+ if ( data_type < 0 ) {
1173
+ rb_raise(rb_eArgError, "unsupported MemoryView format: %s (item_size=%ld)",
1174
+ v->format, (long) v->item_size);
1175
+ }
1176
+ if ( explicit_data_type >= 0 && explicit_data_type != data_type ) {
970
1177
  rb_raise(rb_eArgError,
971
1178
  "explicit data_type does not match producer format "
972
- "(producer: %s, requested data_type %d)",
973
- view->format, (int) explicit_data_type);
1179
+ "(producer: %s, requested data_type %s)",
1180
+ v->format, ca_type_name[explicit_data_type]);
974
1181
  }
1182
+ ca_mv_holder_read_geometry(h, v->item_size, "");
1183
+ return data_type;
1184
+ }
975
1185
 
976
- if (view->ndim == 0) {
977
- *out_ndim = 1;
978
- out_dim[0] = 1;
1186
+ /* Take the mask's view: one byte per element (bool / uint8 / int8),
1187
+ contiguous, of the data side's shape. */
1188
+ static void
1189
+ ca_mv_holder_take_mask (ca_mv_imported_holder_t *m, VALUE src,
1190
+ const ca_mv_imported_holder_t *d)
1191
+ {
1192
+ rb_memory_view_t *v = &m->view;
1193
+ int8_t k;
1194
+
1195
+ ca_mv_holder_acquire(m, src, 0, "mask: ");
1196
+ if ( v->format != NULL ) {
1197
+ int8_t dt = ca_mv_data_type_from_format(v->format, v->item_size);
1198
+ if ( dt != CA_BOOLEAN && dt != CA_UINT8 && dt != CA_INT8 ) {
1199
+ rb_raise(rb_eArgError, "mask: format must be bool/uint8/int8 (got %s)",
1200
+ v->format);
1201
+ }
979
1202
  }
980
- else if (view->ndim > CA_RANK_MAX) {
981
- long got = (long) view->ndim;
982
- rb_memory_view_release(view);
983
- rb_raise(rb_eArgError,
984
- "MemoryView ndim (%ld) exceeds CArray CA_RANK_MAX (%d)",
985
- got, CA_RANK_MAX);
986
- }
987
- else if (view->shape == NULL) {
988
- /* SIMPLE form: a typed producer may advertise a format but omit the
989
- shape array for a contiguous 1-D buffer (red-arrow's primitive
990
- arrays do this). Only 1-D is interpretable without a shape --
991
- length = byte_size / item_size. A missing shape at ndim > 1 is
992
- unrecoverable. */
993
- if (view->ndim > 1) {
994
- long got = (long) view->ndim;
995
- rb_memory_view_release(view);
996
- rb_raise(rb_eArgError,
997
- "MemoryView reports ndim %ld but provides no shape array",
998
- got);
1203
+ if ( v->item_size != 1 ) {
1204
+ rb_raise(rb_eArgError, "mask: item_size must be 1 (got %ld)",
1205
+ (long) v->item_size);
1206
+ }
1207
+ ca_mv_holder_read_geometry(m, 1, "mask: ");
1208
+ if ( m->ndim != d->ndim ) {
1209
+ rb_raise(rb_eArgError, "mask: ndim (%d) does not match data ndim (%d)",
1210
+ (int) m->ndim, (int) d->ndim);
1211
+ }
1212
+ for (k = 0; k < d->ndim; k++) {
1213
+ if ( m->dim[k] != d->dim[k] ) {
1214
+ rb_raise(rb_eArgError, "mask: shape[%d] (%ld) does not match data shape (%ld)",
1215
+ (int) k, (long) m->dim[k], (long) d->dim[k]);
999
1216
  }
1000
- *out_ndim = 1;
1001
- out_dim[0] = (ca_size_t) (view->item_size ? view->byte_size / view->item_size : 0);
1002
1217
  }
1003
- else {
1004
- *out_ndim = (int8_t) view->ndim;
1005
- for (i = 0; i < *out_ndim; i++) {
1006
- out_dim[i] = (ca_size_t) view->shape[i];
1218
+ if ( ! m->contiguous ) {
1219
+ rb_raise(rb_eArgError,
1220
+ "mask: strided masks are not supported in this release; "
1221
+ "pass a row-major contiguous mask buffer");
1222
+ }
1223
+ }
1224
+
1225
+ /* Walk the elements of a view in row-major order, calling `fn` with each
1226
+ element's address. Returns false as soon as fn does. */
1227
+ static bool
1228
+ ca_mv_holder_each (const ca_mv_imported_holder_t *h,
1229
+ bool (*fn)(const char *elem, void *arg), void *arg)
1230
+ {
1231
+ ca_size_t idx[CA_RANK_MAX];
1232
+ ca_size_t n;
1233
+ int8_t k;
1234
+ const char *base = (const char *) h->view.data;
1235
+
1236
+ for (k = 0; k < h->ndim; k++) idx[k] = 0;
1237
+ for (n = 0; n < h->elements; n++) {
1238
+ ssize_t off = 0;
1239
+ for (k = 0; k < h->ndim; k++) {
1240
+ off += (ssize_t) idx[k] * h->strides[k];
1007
1241
  }
1242
+ if ( ! fn(base + off, arg) ) {
1243
+ return false;
1244
+ }
1245
+ for (k = h->ndim - 1; k >= 0; k--) {
1246
+ if ( ++idx[k] < h->dim[k] ) break;
1247
+ idx[k] = 0;
1248
+ }
1249
+ }
1250
+ return true;
1251
+ }
1252
+
1253
+ static bool
1254
+ ca_mv_byte_is_bit (const char *elem, void *arg)
1255
+ {
1256
+ (void) arg;
1257
+ return *(const uint8_t *) elem <= 1;
1258
+ }
1259
+
1260
+ typedef struct {
1261
+ char *dst;
1262
+ size_t item_size;
1263
+ } ca_mv_gather_t;
1264
+
1265
+ static bool
1266
+ ca_mv_gather_one (const char *elem, void *arg)
1267
+ {
1268
+ ca_mv_gather_t *g = (ca_mv_gather_t *) arg;
1269
+ memcpy(g->dst, elem, g->item_size);
1270
+ g->dst += g->item_size;
1271
+ return true;
1272
+ }
1273
+
1274
+ /* A boolean CArray holds 0 or 1 in each byte. A copy can normalise other
1275
+ bytes; a wrap shares them, so it refuses a buffer that holds any. */
1276
+ static void
1277
+ ca_mv_check_bits (const ca_mv_imported_holder_t *h, const char *who)
1278
+ {
1279
+ if ( ! ca_mv_holder_each(h, ca_mv_byte_is_bit, NULL) ) {
1280
+ rb_raise(rb_eArgError,
1281
+ "%sboolean MemoryView holds bytes other than 0 and 1; "
1282
+ "a wrap cannot normalise them (CArray.from_memory_view can)", who);
1008
1283
  }
1009
- *out_data_type = data_type;
1010
1284
  }
1011
1285
 
1012
1286
  /* Extract the optional data_type: and mask: keywords from argv.
@@ -1039,453 +1313,284 @@ ca_mv_extract_type_and_mask_kwargs (int argc, VALUE *argv,
1039
1313
  return data_type;
1040
1314
  }
1041
1315
 
1042
- /* ca_mv_acquire_and_validate as an rb_protect body. */
1043
1316
  typedef struct {
1044
- VALUE src;
1045
- rb_memory_view_t *view;
1046
- int8_t explicit_data_type;
1047
- int8_t *data_type;
1048
- int8_t *ndim;
1049
- ca_size_t *dim;
1050
- } ca_mv_acquire_args_t;
1051
-
1317
+ VALUE obj, mask_obj, target_class;
1318
+ int8_t explicit_data_type, typeless_data_type;
1319
+ int writable;
1320
+ ca_mv_imported_holder_t *data, *mask;
1321
+ VALUE data_holder_obj, mask_holder_obj;
1322
+ } ca_mv_import_args_t;
1323
+
1324
+ /* The body of wrap_memory_view. Runs under rb_protect; the caller
1325
+ releases both views if it raises. */
1052
1326
  static VALUE
1053
- ca_mv_acquire_body (VALUE arg)
1327
+ ca_mv_wrap_body (VALUE arg)
1054
1328
  {
1055
- ca_mv_acquire_args_t *a = (ca_mv_acquire_args_t *) arg;
1056
- ca_mv_acquire_and_validate(a->src, a->view, a->explicit_data_type,
1057
- a->data_type, a->ndim, a->dim);
1058
- return Qnil;
1059
- }
1329
+ ca_mv_import_args_t *a = (ca_mv_import_args_t *) arg;
1330
+ ca_mv_imported_holder_t *d = a->data, *m = a->mask;
1331
+ rb_memory_view_t *v = &d->view;
1332
+ ca_size_t bytes;
1333
+ int8_t data_type;
1334
+ volatile VALUE wrap;
1335
+ CArray *ca;
1060
1336
 
1061
- /* Acquire and validate a mask MemoryView against the data side's shape.
1062
- Returns an allocated holder (caller owns; release via the imported
1063
- holder TypedData dfree path) with view active on success.
1064
-
1065
- On validation failure: releases the mask view, frees the mask holder,
1066
- AND ALSO releases the supplied `data_view_to_release_on_fail` + frees
1067
- the supplied `data_holder_to_free_on_fail` before raising. This
1068
- avoids leaking the caller's already-acquired data side when mask
1069
- validation aborts. Pass NULL for the data cleanup args if not
1070
- applicable (e.g. mask validated for a stack-allocated data view in
1071
- the copy path). */
1072
- static ca_mv_imported_holder_t *
1073
- ca_mv_acquire_mask_view (VALUE mask_src, int8_t data_ndim,
1074
- const ca_size_t *data_dim,
1075
- ca_mv_imported_holder_t *data_holder_to_free_on_fail,
1076
- rb_memory_view_t *data_view_to_release_on_fail)
1077
- {
1078
- ca_mv_imported_holder_t *holder;
1079
- rb_memory_view_t *mv;
1080
- int i;
1337
+ data_type = ca_mv_holder_take_data(d, a->obj, a->explicit_data_type,
1338
+ a->typeless_data_type, a->writable);
1339
+ bytes = ca_mv_carray_bytes_for(data_type, v->item_size);
1081
1340
 
1082
- # define MV_MASK_CLEANUP_AND_RAISE(msg, ...) do { \
1083
- if (holder) { \
1084
- if (holder->valid) { rb_memory_view_release(&holder->view); } \
1085
- xfree(holder); \
1086
- } \
1087
- if (data_view_to_release_on_fail) { \
1088
- rb_memory_view_release(data_view_to_release_on_fail); \
1089
- } \
1090
- if (data_holder_to_free_on_fail) { \
1091
- data_holder_to_free_on_fail->valid = false; \
1092
- xfree(data_holder_to_free_on_fail); \
1093
- } \
1094
- rb_raise(rb_eArgError, msg, ##__VA_ARGS__); \
1095
- } while (0)
1096
-
1097
- holder = ALLOC(ca_mv_imported_holder_t);
1098
- holder->valid = false;
1099
-
1100
- /* Zero-init before get: same apache/arrow#45187 defense as the data
1101
- path (release frees view->item_desc.components unconditionally). */
1102
- memset(&holder->view, 0, sizeof(holder->view));
1103
-
1104
- if (! rb_memory_view_get(mask_src, &holder->view, RUBY_MEMORY_VIEW_STRIDES)) {
1105
- if (! rb_memory_view_get(mask_src, &holder->view, RUBY_MEMORY_VIEW_SIMPLE)) {
1106
- MV_MASK_CLEANUP_AND_RAISE("mask: object does not support MemoryView");
1107
- }
1341
+ /* A strided producer comes back as a CAStride, so a caller who asked
1342
+ for a class would get something that is not it. */
1343
+ if ( a->target_class != rb_cCAWrap && ! d->contiguous ) {
1344
+ rb_raise(rb_eArgError,
1345
+ "%s was requested, but this producer is strided, so the result "
1346
+ "would be a CAStride; pass a row-major contiguous producer, or "
1347
+ "use CArray.from_memory_view for a copy",
1348
+ rb_class2name(a->target_class));
1108
1349
  }
1109
- holder->valid = true;
1110
- mv = &holder->view;
1111
1350
 
1112
- if (mv->format != NULL) {
1113
- char c = mv->format[0];
1114
- /* skip a leading byte-order/native prefix '<>=!@' */
1115
- if (c == '<' || c == '>' || c == '=' || c == '!' || c == '@') {
1116
- c = mv->format[1];
1351
+ if ( m != NULL ) {
1352
+ ca_mv_holder_take_mask(m, a->mask_obj, d);
1353
+ if ( ! d->contiguous ) {
1354
+ rb_raise(rb_eArgError,
1355
+ "mask: combining mask: with a strided data source is not "
1356
+ "supported in this release; pass a row-major contiguous "
1357
+ "data source");
1117
1358
  }
1118
- /* One byte per element, under either vocabulary: PEP 3118 spells the
1119
- integer widths 'b' / 'B', Ruby's MemoryView spells them 'c' / 'C'.
1120
- '?' is PEP's boolean, which Ruby has no spelling for. */
1121
- if (c != '?' && c != 'B' && c != 'b' && c != 'C' && c != 'c') {
1122
- char fmt_buf[64];
1123
- snprintf(fmt_buf, sizeof(fmt_buf), "%s", mv->format);
1124
- MV_MASK_CLEANUP_AND_RAISE("mask: format must be bool/uint8/int8 (got %s)",
1125
- fmt_buf);
1359
+ /* A mask that shares bytes with the values would take every write
1360
+ to a value as a write to the mask. */
1361
+ if ( d->elements > 0 ) {
1362
+ const char *dlo = (const char *) v->data + d->lo;
1363
+ const char *dhi = (const char *) v->data + d->hi;
1364
+ const char *mlo = (const char *) m->view.data;
1365
+ const char *mhi = mlo + m->elements;
1366
+ if ( mlo < dhi && dlo < mhi ) {
1367
+ rb_raise(rb_eArgError, "mask: the mask buffer overlaps the data buffer");
1368
+ }
1126
1369
  }
1370
+ ca_mv_check_bits(m, "mask: ");
1127
1371
  }
1128
- if (mv->item_size != 1) {
1129
- ssize_t got = mv->item_size;
1130
- MV_MASK_CLEANUP_AND_RAISE("mask: item_size must be 1 (got %ld)",
1131
- (long) got);
1132
- }
1133
- if (mv->ndim != data_ndim) {
1134
- int got = (int) mv->ndim;
1135
- MV_MASK_CLEANUP_AND_RAISE("mask: ndim (%d) does not match data ndim (%d)",
1136
- got, (int) data_ndim);
1137
- }
1138
- if (mv->shape == NULL) {
1139
- /* SIMPLE-form mask (contiguous 1-D, shape omitted). item_size is 1
1140
- (checked above), so byte_size is the element count. */
1141
- ca_size_t mlen = (ca_size_t) mv->byte_size;
1142
- if (data_ndim != 1 || mlen != data_dim[0]) {
1143
- long ms = (long) mlen, ds = (long) (data_ndim == 1 ? data_dim[0] : -1);
1144
- MV_MASK_CLEANUP_AND_RAISE(
1145
- "mask: length (%ld) does not match data shape (%ld)", ms, ds);
1146
- }
1372
+ if ( data_type == CA_BOOLEAN ) {
1373
+ ca_mv_check_bits(d, "");
1147
1374
  }
1148
- else {
1149
- for (i = 0; i < data_ndim; i++) {
1150
- if ((ca_size_t) mv->shape[i] != data_dim[i]) {
1151
- long ms = (long) mv->shape[i], ds = (long) data_dim[i];
1152
- MV_MASK_CLEANUP_AND_RAISE(
1153
- "mask: shape[%d] (%ld) does not match data shape (%ld)",
1154
- i, ms, ds);
1155
- }
1375
+
1376
+ if ( ! d->contiguous ) {
1377
+ /* The foreign bytes the elements reach, [data + lo, data + hi), as a
1378
+ byte array, with the user's CAStride over it. The parent covers
1379
+ every byte a stride can land on, so a mask made for the view (one
1380
+ mask byte per parent byte) has a place for every element whatever
1381
+ the strides are. */
1382
+ ca_size_t span = (ca_size_t) (d->hi - d->lo);
1383
+ ca_size_t strides_arr[CA_RANK_MAX];
1384
+ volatile VALUE inner_wrap;
1385
+ CArray *iw;
1386
+ int8_t k;
1387
+
1388
+ inner_wrap = rb_ca_wrap_new(CA_UINT8, 1, &span, 1, NULL,
1389
+ (char *) v->data + d->lo);
1390
+ rb_ivar_set(inner_wrap, rb_intern("memory_view_source"), a->obj);
1391
+ rb_ivar_set(inner_wrap, rb_intern("memory_view_holder"), a->data_holder_obj);
1392
+ if ( v->readonly && ca_mv_extract(inner_wrap, &iw) ) {
1393
+ ca_set_flag(iw, CA_FLAG_READ_ONLY);
1156
1394
  }
1157
- }
1158
- if (! ca_mv_view_is_contiguous(mv)) {
1159
- MV_MASK_CLEANUP_AND_RAISE(
1160
- "mask: strided masks are not supported in this release; "
1161
- "pass a row-major contiguous mask buffer");
1395
+ for (k = 0; k < d->ndim; k++) {
1396
+ strides_arr[k] = (ca_size_t) d->strides[k];
1397
+ }
1398
+ wrap = rb_ca_stride_new(inner_wrap, data_type, bytes, d->ndim, d->dim,
1399
+ strides_arr, (ca_size_t) -d->lo);
1400
+ if ( v->readonly && ca_mv_extract(wrap, &ca) ) {
1401
+ ca_set_flag(ca, CA_FLAG_READ_ONLY);
1402
+ }
1403
+ return wrap;
1162
1404
  }
1163
1405
 
1164
- # undef MV_MASK_CLEANUP_AND_RAISE
1165
- return holder;
1406
+ /* Same struct rb_ca_wrap_new would build, wrapped in the class the
1407
+ receiver asked for. */
1408
+ wrap = ca_wrap_struct_as(ca_wrap_new(data_type, d->ndim, d->dim, bytes,
1409
+ NULL, (char *) v->data),
1410
+ a->target_class);
1411
+ rb_ivar_set(wrap, rb_intern("memory_view_source"), a->obj);
1412
+ rb_ivar_set(wrap, rb_intern("memory_view_holder"), a->data_holder_obj);
1413
+ ca_mv_extract(wrap, &ca);
1414
+ if ( v->readonly ) {
1415
+ ca_set_flag(ca, CA_FLAG_READ_ONLY);
1416
+ }
1417
+
1418
+ /* Borrowed mask: a CAWrap over the mask bytes, anchored on the same
1419
+ wrap so its lifetime is the data side's. A store writes the mask
1420
+ cell of every element it stores, so a read-only mask makes the whole
1421
+ array read-only. */
1422
+ if ( m != NULL ) {
1423
+ CArray *mask_ca = (CArray *) ca_wrap_new(CA_BOOLEAN, d->ndim, d->dim, 1,
1424
+ NULL, (char *) m->view.data);
1425
+ ca_set_flag(mask_ca, CA_FLAG_MASK_ARRAY);
1426
+ if ( m->view.readonly ) {
1427
+ ca_set_flag(mask_ca, CA_FLAG_READ_ONLY);
1428
+ ca_set_flag(ca, CA_FLAG_READ_ONLY);
1429
+ }
1430
+ ca->mask = mask_ca;
1431
+ rb_ivar_set(wrap, rb_intern("mask_memory_view_source"), a->mask_obj);
1432
+ rb_ivar_set(wrap, rb_intern("mask_memory_view_holder"), a->mask_holder_obj);
1433
+ }
1434
+ return wrap;
1166
1435
  }
1167
1436
 
1168
- /* Copy a strided MemoryView into a contiguous row-major destination buffer.
1169
- Walks indices in row-major order and reads from view->data using
1170
- view->strides at each step. Assumes view->ndim >= 1, view->strides
1171
- non-NULL, dst sized to hold elements * item_size. */
1172
1437
  static void
1173
- ca_mv_copy_strided_to_contiguous (char *dst, const rb_memory_view_t *view,
1174
- ca_size_t elements)
1438
+ ca_mv_import_args_init (ca_mv_import_args_t *a, VALUE obj, VALUE mask_obj)
1175
1439
  {
1176
- int8_t ndim = (int8_t) view->ndim;
1177
- ssize_t item_size = view->item_size;
1178
- ca_size_t idx[CA_RANK_MAX];
1179
- ca_size_t n;
1180
- int8_t k;
1181
-
1182
- for (k = 0; k < ndim; k++) idx[k] = 0;
1183
-
1184
- for (n = 0; n < elements; n++) {
1185
- ssize_t src_off = 0;
1186
- for (k = 0; k < ndim; k++) {
1187
- src_off += (ssize_t) idx[k] * view->strides[k];
1188
- }
1189
- memcpy(dst, (const char *) view->data + src_off, (size_t) item_size);
1190
- dst += item_size;
1191
- /* increment row-major: last dim varies fastest */
1192
- for (k = ndim - 1; k >= 0; k--) {
1193
- idx[k]++;
1194
- if (idx[k] < (ca_size_t) view->shape[k]) break;
1195
- idx[k] = 0;
1196
- }
1440
+ a->obj = obj;
1441
+ a->mask_obj = mask_obj;
1442
+ a->data_holder_obj = ca_mv_holder_new(&a->data);
1443
+ a->mask = NULL;
1444
+ a->mask_holder_obj = Qnil;
1445
+ if ( ! NIL_P(mask_obj) ) {
1446
+ a->mask_holder_obj = ca_mv_holder_new(&a->mask);
1197
1447
  }
1198
1448
  }
1199
1449
 
1200
- /* CArray.wrap_memory_view(obj, data_type: nil) -- zero-copy CAWrap.
1201
- Rejects non-row-major-contiguous sources with a helpful pointer to
1202
- from_memory_view; the contiguous-only contract keeps the boundary
1203
- between Numo/CArray internal invariants clean (see the design note
1204
- on strided wrap rejection).
1450
+ /* Wrap obj's MemoryView without copying. `explicit_data_type` and
1451
+ `typeless_data_type` are as for ca_mv_holder_take_data; `writable`
1452
+ asks the producer for a writable view (wrap_writable). Every view
1453
+ taken is released before a raise leaves. */
1454
+ VALUE
1455
+ ca_mv_wrap (VALUE obj, VALUE mask_obj, VALUE target_class,
1456
+ int8_t explicit_data_type, int8_t typeless_data_type, int writable)
1457
+ {
1458
+ ca_mv_import_args_t a;
1459
+ volatile VALUE result;
1460
+ int tag = 0;
1461
+
1462
+ ca_mv_import_args_init(&a, obj, mask_obj);
1463
+ a.target_class = target_class;
1464
+ a.explicit_data_type = explicit_data_type;
1465
+ a.typeless_data_type = typeless_data_type;
1466
+ a.writable = writable;
1467
+ /* protect: the views are released at once rather than when the holders are collected */
1468
+ result = rb_protect(ca_mv_wrap_body, (VALUE) &a, &tag);
1469
+ if ( tag ) {
1470
+ ca_mv_holder_release(a.mask);
1471
+ ca_mv_holder_release(a.data);
1472
+ rb_jump_tag(tag);
1473
+ }
1474
+ RB_GC_GUARD(a.data_holder_obj);
1475
+ RB_GC_GUARD(a.mask_holder_obj);
1476
+ return result;
1477
+ }
1478
+
1479
+ /* CArray.wrap_memory_view(obj, data_type: nil, mask: nil) -- zero-copy.
1480
+ A row-major contiguous producer comes back as a CAWrap; a strided one
1481
+ as a CAStride over the foreign bytes, its strides (negative ones
1482
+ included) kept.
1205
1483
 
1206
1484
  When the producer is typeless (format=NULL), `data_type:` must be
1207
1485
  supplied; the byte buffer is reinterpreted as that data_type, ndim=1.
1208
1486
 
1209
1487
  The receiver picks the class of the result. Called on CArray itself
1210
- it builds a CAWrap, as it always has; called on a subclass of CAWrap
1211
- it builds that subclass, so a gem bridging a foreign buffer can name
1212
- where the array came from without writing a C extension of its own.
1213
- The class marks the provenance of this object only -- a view derived
1214
- from it is a CABlock or a CAStride like any other. */
1488
+ it builds a CAWrap; called on a subclass of CAWrap it builds that
1489
+ subclass, so a gem bridging a foreign buffer can name where the array
1490
+ came from without writing a C extension of its own. The class marks
1491
+ the provenance of this object only -- a view derived from it is a
1492
+ CABlock or a CAStride like any other. */
1215
1493
  static VALUE
1216
1494
  rb_ca_s_wrap_memory_view (int argc, VALUE *argv, VALUE klass)
1217
1495
  {
1218
- ca_mv_imported_holder_t *holder;
1219
- ca_mv_imported_holder_t *mask_holder = NULL;
1220
- rb_memory_view_t *v;
1221
- int8_t data_type, ndim;
1222
- ca_size_t dim[CA_RANK_MAX];
1223
- VALUE wrap, holder_obj, obj, mask_obj, target_class;
1496
+ VALUE obj, mask_obj, target_class;
1224
1497
  int8_t explicit_data_type =
1225
1498
  ca_mv_extract_type_and_mask_kwargs(argc, argv,
1226
1499
  (VALUE *) &obj, (VALUE *) &mask_obj);
1227
1500
 
1228
- /* Resolve the class before anything is acquired, so this rejection
1229
- needs no cleanup. CArray means CAWrap; any other receiver has to
1230
- be a CAWrap the caller can actually be handed. */
1231
1501
  target_class = ( klass == rb_cCArray ) ? rb_cCAWrap : klass;
1232
1502
  if ( ! RTEST(rb_class_inherited_p(target_class, rb_cCAWrap)) ) {
1233
1503
  rb_raise(rb_eTypeError,
1234
1504
  "wrap_memory_view builds a CAWrap; %s is not a subclass of it",
1235
1505
  rb_class2name(target_class));
1236
1506
  }
1507
+ return ca_mv_wrap(obj, mask_obj, target_class, explicit_data_type, -1, 0);
1508
+ }
1237
1509
 
1238
- holder = ALLOC(ca_mv_imported_holder_t);
1239
- holder->valid = false;
1510
+ /* The body of from_memory_view. Runs under rb_ensure, which releases
1511
+ both views however it leaves. */
1512
+ static VALUE
1513
+ ca_mv_from_body (VALUE arg)
1514
+ {
1515
+ ca_mv_import_args_t *a = (ca_mv_import_args_t *) arg;
1516
+ ca_mv_imported_holder_t *d = a->data, *m = a->mask;
1517
+ rb_memory_view_t *v = &d->view;
1518
+ int8_t data_type;
1519
+ volatile VALUE result;
1520
+ CArray *ca;
1521
+ ca_size_t i;
1240
1522
 
1241
- /* Acquisition raises on a producer it cannot take (and releases the
1242
- view itself first); the holder is not a Ruby object yet, so it is
1243
- freed here. */
1244
- {
1245
- ca_mv_acquire_args_t a;
1246
- int tag = 0;
1247
- a.src = obj; a.view = &holder->view;
1248
- a.explicit_data_type = explicit_data_type;
1249
- a.data_type = &data_type; a.ndim = &ndim; a.dim = dim;
1250
- rb_protect(ca_mv_acquire_body, (VALUE) &a, &tag);
1251
- if ( tag ) {
1252
- xfree(holder);
1253
- rb_jump_tag(tag);
1254
- }
1255
- }
1256
- holder->valid = true;
1257
- v = &holder->view;
1258
-
1259
- /* Whether the producer is strided is only knowable once the view is
1260
- acquired, and a strided one is returned as a CAStride wrapping an
1261
- inner CAWrap -- so a caller who asked for a class would get back
1262
- something that is not it. Refuse instead, and release first: this
1263
- runs before the mask is acquired, so the holder is all there is to
1264
- free. */
1265
- if ( target_class != rb_cCAWrap &&
1266
- v->ndim >= 1 && v->format != NULL && ! ca_mv_view_is_contiguous(v) ) {
1267
- rb_memory_view_release(v);
1268
- holder->valid = false;
1269
- xfree(holder);
1270
- rb_raise(rb_eArgError,
1271
- "%s was requested, but this producer is strided, so the result "
1272
- "would be a CAStride; pass a row-major contiguous producer, or "
1273
- "use CArray.from_memory_view for a copy",
1274
- rb_class2name(target_class));
1523
+ data_type = ca_mv_holder_take_data(d, a->obj, a->explicit_data_type, -1, 0);
1524
+ if ( m != NULL ) {
1525
+ ca_mv_holder_take_mask(m, a->mask_obj, d);
1275
1526
  }
1276
1527
 
1277
- /* mask: paired buffer. Acquire + validate now (before building the
1278
- wrap). On failure the helper releases both the mask view AND the
1279
- already-acquired data view + holder, then raises. */
1280
- if (! NIL_P(mask_obj)) {
1281
- mask_holder = ca_mv_acquire_mask_view(mask_obj, ndim, dim,
1282
- holder, &holder->view);
1283
- /* Strided data side combined with mask: is not supported in this
1284
- release. Catch this AFTER mask acquisition so the cleanup path can release
1285
- both views uniformly. */
1286
- if (v->ndim >= 1 && v->format != NULL &&
1287
- ! ca_mv_view_is_contiguous(v)) {
1288
- rb_memory_view_release(&mask_holder->view);
1289
- mask_holder->valid = false;
1290
- xfree(mask_holder);
1291
- rb_memory_view_release(v);
1292
- holder->valid = false;
1293
- xfree(holder);
1294
- rb_raise(rb_eArgError,
1295
- "mask: combining mask: with a strided data source is not "
1296
- "supported in this release; pass a row-major contiguous "
1297
- "data source");
1298
- }
1299
- }
1528
+ result = rb_carray_new(data_type, d->ndim, d->dim,
1529
+ ca_mv_carray_bytes_for(data_type, v->item_size), NULL);
1530
+ ca_mv_extract(result, &ca);
1300
1531
 
1301
- /* Strided producer: build a CAStride on top of an inner CAWrap that
1302
- anchors the foreign memory. The inner CAWrap holds the MemoryView
1303
- lifecycle (memory_view_source + memory_view_holder ivars); the
1304
- outer CAStride is what the user sees, with full strides
1305
- (including negative) preserved. */
1306
- if (v->ndim >= 1 && v->format != NULL &&
1307
- ! ca_mv_view_is_contiguous(v)) {
1308
- ca_size_t inner_dim[1] = { 1 };
1309
- volatile VALUE inner_wrap;
1310
- ca_size_t strides_arr[CA_RANK_MAX];
1311
- int i;
1312
-
1313
- inner_wrap = rb_ca_wrap_new(data_type, 1, inner_dim,
1314
- ca_mv_carray_bytes_for(data_type, v->item_size),
1315
- NULL, (char *) v->data);
1316
- rb_ivar_set(inner_wrap, rb_intern("memory_view_source"), obj);
1317
- holder_obj = TypedData_Wrap_Struct(rb_cObject,
1318
- &ca_mv_imported_holder_data_type,
1319
- holder);
1320
- rb_ivar_set(inner_wrap, rb_intern("memory_view_holder"), holder_obj);
1321
- if (v->readonly) {
1322
- CArray *iw;
1323
- if (ca_mv_extract(inner_wrap, &iw)) {
1324
- ca_set_flag(iw, CA_FLAG_READ_ONLY);
1325
- }
1326
- }
1327
- for (i = 0; i < ndim; i++) {
1328
- strides_arr[i] = (ca_size_t) v->strides[i];
1329
- }
1330
- {
1331
- VALUE result =
1332
- rb_ca_stride_new(inner_wrap, data_type,
1333
- ca_mv_carray_bytes_for(data_type, v->item_size),
1334
- ndim, dim, strides_arr, /* base_offset */ 0);
1335
- if (v->readonly) {
1336
- CArray *rs;
1337
- if (ca_mv_extract(result, &rs)) {
1338
- ca_set_flag(rs, CA_FLAG_READ_ONLY);
1339
- }
1340
- }
1341
- return result;
1342
- }
1532
+ if ( d->contiguous ) {
1533
+ memcpy(ca->ptr, v->data, (size_t) ca_length(ca));
1343
1534
  }
1344
-
1345
- /* Same struct rb_ca_wrap_new would build, wrapped in the class the
1346
- receiver asked for. Everything downstream -- the holder and source
1347
- ivars, the read-only flag, the borrowed mask -- is untouched, which
1348
- is the point: those are what a gem building its own wrap by hand
1349
- has to give up. */
1350
- wrap = ca_wrap_struct_as(
1351
- ca_wrap_new(data_type, ndim, dim,
1352
- ca_mv_carray_bytes_for(data_type, v->item_size),
1353
- NULL, (char *) v->data),
1354
- target_class);
1355
-
1356
- rb_ivar_set(wrap, rb_intern("memory_view_source"), obj);
1357
- holder_obj = TypedData_Wrap_Struct(rb_cObject,
1358
- &ca_mv_imported_holder_data_type,
1359
- holder);
1360
- rb_ivar_set(wrap, rb_intern("memory_view_holder"), holder_obj);
1361
-
1362
- if (v->readonly) {
1363
- CArray *ca;
1364
- if (ca_mv_extract(wrap, &ca)) {
1365
- ca_set_flag(ca, CA_FLAG_READ_ONLY);
1366
- }
1535
+ else {
1536
+ ca_mv_gather_t g;
1537
+ g.dst = ca->ptr;
1538
+ g.item_size = (size_t) v->item_size;
1539
+ ca_mv_holder_each(d, ca_mv_gather_one, &g);
1540
+ }
1541
+ if ( data_type == CA_BOOLEAN ) {
1542
+ ca_boolean_normalize_bytes(ca->ptr, ca->elements);
1367
1543
  }
1368
1544
 
1369
- /* Attach borrowed mask: build a CAWrap over the mask buffer and assign
1370
- it to wrap->mask. Anchor the mask MV's source + holder on the same
1371
- wrap object so the lifecycle is co-terminal with the data side.
1372
- When `wrap` is collected: free_ca_wrap recurses into ca->mask (frees
1373
- the mask wrap struct, leaves its borrowed ptr alone); the holder
1374
- ivars become unreachable and are GC'd separately, each holder.dfree
1375
- calling rb_memory_view_release. */
1376
- if (mask_holder != NULL) {
1377
- CArray *data_ca, *mask_ca;
1378
- rb_memory_view_t *mv = &mask_holder->view;
1379
- VALUE mask_holder_obj;
1380
- if (! ca_mv_extract(wrap, &data_ca)) {
1381
- rb_memory_view_release(mv);
1382
- mask_holder->valid = false;
1383
- xfree(mask_holder);
1384
- rb_raise(rb_eRuntimeError,
1385
- "internal: rb_ca_wrap_new did not return a CArray");
1386
- }
1387
- mask_ca = (CArray *) ca_wrap_new(CA_BOOLEAN, ndim, dim, 1, NULL,
1388
- (char *) mv->data);
1389
- ca_set_flag(mask_ca, CA_FLAG_MASK_ARRAY);
1390
- if (mv->readonly) {
1391
- ca_set_flag(mask_ca, CA_FLAG_READ_ONLY);
1545
+ /* Mask bytes: any non-zero byte means masked. */
1546
+ if ( m != NULL ) {
1547
+ const uint8_t *src = (const uint8_t *) m->view.data;
1548
+ boolean8_t *dst;
1549
+ ca_create_mask(ca);
1550
+ dst = (boolean8_t *) ca->mask->ptr;
1551
+ for (i = 0; i < ca->elements; i++) {
1552
+ dst[i] = ( src[i] != 0 ) ? 1 : 0;
1392
1553
  }
1393
- data_ca->mask = mask_ca;
1394
-
1395
- rb_ivar_set(wrap, rb_intern("mask_memory_view_source"), mask_obj);
1396
- mask_holder_obj = TypedData_Wrap_Struct(rb_cObject,
1397
- &ca_mv_imported_holder_data_type,
1398
- mask_holder);
1399
- rb_ivar_set(wrap, rb_intern("mask_memory_view_holder"), mask_holder_obj);
1400
1554
  }
1555
+ return result;
1556
+ }
1401
1557
 
1402
- return wrap;
1558
+ static VALUE
1559
+ ca_mv_from_release (VALUE arg)
1560
+ {
1561
+ ca_mv_import_args_t *a = (ca_mv_import_args_t *) arg;
1562
+ ca_mv_holder_release(a->mask);
1563
+ ca_mv_holder_release(a->data);
1564
+ return Qnil;
1403
1565
  }
1404
1566
 
1405
- /* CArray.from_memory_view(obj, data_type: nil) -- copy into an independent CArray.
1406
- Accepts strided typed sources (gather copy) and typeless sources
1407
- (must be 1D contiguous; bytes reinterpreted as `data_type:`). */
1567
+ /* CArray.from_memory_view(obj, data_type: nil, mask: nil) -- copy into an
1568
+ independent CArray. Accepts strided typed sources (gather copy) and
1569
+ typeless sources (bytes reinterpreted as `data_type:`). The result is
1570
+ a plain CArray whatever the receiver: a copy no longer borrows anything
1571
+ for a class to name. A receiver that is not CArray or a CAWrap could
1572
+ not be returned, so it is refused. */
1408
1573
  static VALUE
1409
1574
  rb_ca_s_from_memory_view (int argc, VALUE *argv, VALUE klass)
1410
1575
  {
1411
- rb_memory_view_t view;
1412
- ca_mv_imported_holder_t *mask_holder = NULL;
1413
- int8_t data_type, ndim;
1414
- ca_size_t dim[CA_RANK_MAX];
1415
- VALUE result, obj, mask_obj;
1416
- CArray *ca;
1417
- bool contig;
1576
+ ca_mv_import_args_t a;
1577
+ VALUE obj, mask_obj;
1578
+ volatile VALUE result;
1418
1579
  int8_t explicit_data_type =
1419
1580
  ca_mv_extract_type_and_mask_kwargs(argc, argv,
1420
1581
  (VALUE *) &obj, (VALUE *) &mask_obj);
1421
1582
 
1422
- ca_mv_acquire_and_validate(obj, &view, explicit_data_type,
1423
- &data_type, &ndim, dim);
1424
-
1425
- /* mask: acquire + validate now so failures clean up the data view too. */
1426
- if (! NIL_P(mask_obj)) {
1427
- mask_holder = ca_mv_acquire_mask_view(mask_obj, ndim, dim,
1428
- NULL, &view);
1429
- }
1430
-
1431
- result = rb_carray_new(data_type, ndim, dim,
1432
- ca_mv_carray_bytes_for(data_type, view.item_size),
1433
- NULL);
1434
- if (! ca_mv_extract(result, &ca)) {
1435
- rb_memory_view_release(&view);
1436
- rb_raise(rb_eRuntimeError, "internal: rb_carray_new did not return a CArray");
1437
- }
1438
-
1439
- /* Typeless producers (format == NULL) and SIMPLE-form producers (shape
1440
- omitted) are 1-D contiguous by construction; ca_mv_view_is_contiguous
1441
- handles the NULL shape that would crash the raw contiguity helper. */
1442
- contig = (view.format == NULL) || ca_mv_view_is_contiguous(&view);
1443
-
1444
- if (contig) {
1445
- size_t total_bytes = (size_t) ca_length(ca);
1446
- /* For typeless producers byte_size and total_bytes are equal by
1447
- construction; for typed producers we already validated alignment. */
1448
- if (view.byte_size < (ssize_t) total_bytes) {
1449
- rb_memory_view_release(&view);
1450
- if (mask_holder) {
1451
- rb_memory_view_release(&mask_holder->view);
1452
- mask_holder->valid = false;
1453
- xfree(mask_holder);
1454
- }
1455
- rb_raise(rb_eRuntimeError,
1456
- "internal: byte_size mismatch (carray=%lu, view=%ld)",
1457
- (unsigned long) total_bytes, (long) view.byte_size);
1458
- }
1459
- memcpy(ca->ptr, view.data, total_bytes);
1460
- }
1461
- else {
1462
- /* Strided typed source: walk indices in row-major order and gather. */
1463
- ca_mv_copy_strided_to_contiguous(ca->ptr, &view, ca->elements);
1464
- }
1465
-
1466
- rb_memory_view_release(&view);
1467
-
1468
- /* mask: materialise the canonical CA_BOOLEAN mask slot via ca_create_mask,
1469
- then memcpy bytes from the (validated row-major contig 1-byte/elem) mask
1470
- buffer. Source format ? / B / b all use byte values where any non-zero
1471
- means masked; that semantic matches CArray's mask convention directly
1472
- when we coerce non-zero to 1. */
1473
- if (mask_holder) {
1474
- rb_memory_view_t *mv = &mask_holder->view;
1475
- size_t n = (size_t) ca->elements;
1476
- size_t i;
1477
- const uint8_t *src = (const uint8_t *) mv->data;
1478
- boolean8_t *dst;
1479
- ca_create_mask(ca);
1480
- dst = (boolean8_t *) ca->mask->ptr;
1481
- for (i = 0; i < n; i++) {
1482
- dst[i] = (src[i] != 0) ? 1 : 0;
1483
- }
1484
- rb_memory_view_release(mv);
1485
- mask_holder->valid = false;
1486
- xfree(mask_holder);
1487
- }
1488
-
1583
+ if ( klass != rb_cCArray &&
1584
+ ! RTEST(rb_class_inherited_p(klass, rb_cCAWrap)) ) {
1585
+ rb_raise(rb_eTypeError,
1586
+ "from_memory_view builds a CArray; %s cannot be returned from it",
1587
+ rb_class2name(klass));
1588
+ }
1589
+ ca_mv_import_args_init(&a, obj, mask_obj);
1590
+ a.explicit_data_type = explicit_data_type;
1591
+ result = rb_ensure(ca_mv_from_body, (VALUE) &a, ca_mv_from_release, (VALUE) &a);
1592
+ RB_GC_GUARD(a.data_holder_obj);
1593
+ RB_GC_GUARD(a.mask_holder_obj);
1489
1594
  return result;
1490
1595
  }
1491
1596
 
@@ -1508,7 +1613,7 @@ rb_ca_s_memory_view_reject_reason (VALUE klass, VALUE obj)
1508
1613
  CArray *ca;
1509
1614
  ca_mv_reject_t rej;
1510
1615
  if (! ca_mv_extract(obj, &ca)) {
1511
- return rb_str_new_cstr("not a CArray");
1616
+ return Qnil;
1512
1617
  }
1513
1618
  if (! ca_mv_ca_exportable(ca, obj)) {
1514
1619
  if (ca->data_type == CA_FIXLEN && RTEST(rb_ca_has_data_class(obj))) {
@@ -1577,7 +1682,7 @@ rb_ca_s_memory_view_format (VALUE klass, VALUE obj)
1577
1682
  if (ca->data_type == CA_FIXLEN) {
1578
1683
  /* Plain CA_FIXLEN: the same "Ns" String the producer (ca_mv_get)
1579
1684
  hands out via view->format. */
1580
- return ca_mv_fixlen_format(ca->bytes);
1685
+ return rb_obj_freeze(rb_str_new_cstr(ca_mv_fixlen_format(ca->bytes)));
1581
1686
  }
1582
1687
  const char *fmt = ca_mv_format_for(ca->data_type);
1583
1688
  return fmt ? rb_str_new_cstr(fmt) : Qnil;
@@ -1610,6 +1715,8 @@ static const rb_memory_view_entry_t ca_memory_view_entry = {
1610
1715
  void
1611
1716
  Init_carray_memory_view (void)
1612
1717
  {
1718
+ ca_mv_fixlen_formats = st_init_numtable();
1719
+ rb_nativethread_lock_initialize(&ca_mv_fixlen_formats_lock);
1613
1720
  /* Statically known classes. */
1614
1721
  rb_memory_view_register(rb_cCArray, &ca_memory_view_entry);
1615
1722
  rb_memory_view_register(rb_cCAWrap, &ca_memory_view_entry);
@@ -1643,13 +1750,14 @@ Init_carray_memory_view (void)
1643
1750
  rb_ca_s_from_memory_view, -1);
1644
1751
  rb_define_singleton_method(rb_cCArray, "wrap_memory_view",
1645
1752
  rb_ca_s_wrap_memory_view, -1);
1646
- /* Test hooks (used by spec_ai/test_format_parser.rb and
1647
- spec_ai/test_mv_struct_format.rb). Private API; names start
1648
- with __ to discourage external use. */
1649
- rb_define_singleton_method(rb_cCArray, "__memory_view_parse_format__",
1650
- rb_ca_s_parse_memory_view_format, 2);
1651
- rb_define_singleton_method(rb_cCArray, "__memory_view_format__",
1652
- rb_ca_s_memory_view_format, 1);
1753
+ /* The format parser and the format an array exports, for the specs
1754
+ (test_format_parser.rb, test_mv_struct_format.rb). Private. */
1755
+ rb_define_private_method(rb_singleton_class(rb_cCArray),
1756
+ "parse_memory_view_format",
1757
+ rb_ca_s_parse_memory_view_format, 2);
1758
+ rb_define_private_method(rb_singleton_class(rb_cCArray),
1759
+ "memory_view_format_of",
1760
+ rb_ca_s_memory_view_format, 1);
1653
1761
  rb_define_method(rb_const_get(rb_cObject, rb_intern("CARecord")),
1654
1762
  "memory_view_field_names",
1655
1763
  rb_ca_record_memory_view_field_names, 0);