carray 2.0.0 → 3.0.0

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 (339) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +5 -25
  3. data/CHANGELOG.md +16 -0
  4. data/LICENSE +1 -1
  5. data/NEWS.md +3 -0
  6. data/README.md +128 -44
  7. data/carray.gemspec +22 -24
  8. data/ext/ca_array_pool.c +91 -0
  9. data/ext/ca_axis_descriptor.h +186 -0
  10. data/ext/ca_axis_dispatch.c +924 -0
  11. data/ext/ca_axis_group.c +1208 -0
  12. data/ext/ca_bincmp_dispatch.c +76 -0
  13. data/ext/ca_bincmp_dispatch.h +85 -0
  14. data/ext/ca_binop_dispatch.c +125 -0
  15. data/ext/ca_binop_dispatch.h +159 -0
  16. data/ext/ca_categorical_iterator.c +1375 -0
  17. data/ext/ca_compare.c +94 -0
  18. data/ext/ca_compare.h +26 -0
  19. data/ext/ca_composite_dispatch.c +414 -0
  20. data/ext/ca_composite_dispatch.h +116 -0
  21. data/ext/ca_for_buffer.h +96 -0
  22. data/ext/ca_for_each_element.h +241 -0
  23. data/ext/ca_group_iter.c +304 -0
  24. data/ext/ca_iter_substrate.h +325 -0
  25. data/ext/ca_kernel_iterator.c +4321 -0
  26. data/ext/ca_kernel_iterator.h +2603 -0
  27. data/ext/ca_moncmp_dispatch.c +37 -0
  28. data/ext/ca_moncmp_dispatch.h +62 -0
  29. data/ext/ca_monop_dispatch.c +200 -0
  30. data/ext/ca_monop_dispatch.h +235 -0
  31. data/ext/ca_obj_array.c +355 -359
  32. data/ext/ca_obj_bincmp.c +809 -0
  33. data/ext/ca_obj_binop.c +892 -0
  34. data/ext/ca_obj_bitarray.c +369 -164
  35. data/ext/ca_obj_bitfield.c +294 -234
  36. data/ext/ca_obj_block.c +189 -711
  37. data/ext/ca_obj_byte_swap.c +766 -0
  38. data/ext/ca_obj_const_string.c +965 -0
  39. data/ext/ca_obj_face.c +670 -0
  40. data/ext/ca_obj_face.h +247 -0
  41. data/ext/ca_obj_fake.c +228 -100
  42. data/ext/ca_obj_farray.c +54 -441
  43. data/ext/ca_obj_field.c +82 -529
  44. data/ext/ca_obj_fixlen_string.c +306 -0
  45. data/ext/ca_obj_grid.c +858 -440
  46. data/ext/ca_obj_meld.c +1034 -0
  47. data/ext/ca_obj_moncmp.c +569 -0
  48. data/ext/ca_obj_monop.c +1111 -0
  49. data/ext/ca_obj_object.c +774 -298
  50. data/ext/ca_obj_record.c +468 -0
  51. data/ext/ca_obj_reduce.c +97 -82
  52. data/ext/ca_obj_refer.c +569 -459
  53. data/ext/ca_obj_remap.c +475 -0
  54. data/ext/ca_obj_repeat.c +92 -477
  55. data/ext/ca_obj_roll.c +616 -0
  56. data/ext/ca_obj_select.c +344 -296
  57. data/ext/ca_obj_select_axis.c +1296 -0
  58. data/ext/ca_obj_shift.c +230 -792
  59. data/ext/ca_obj_source.c +78 -0
  60. data/ext/ca_obj_stack.c +1173 -0
  61. data/ext/ca_obj_stride.c +2501 -0
  62. data/ext/ca_obj_string.c +268 -0
  63. data/ext/ca_obj_tile.c +614 -0
  64. data/ext/ca_obj_time.c +546 -0
  65. data/ext/ca_obj_timedelta.c +435 -0
  66. data/ext/ca_obj_transpose.c +62 -516
  67. data/ext/ca_obj_triop.c +746 -0
  68. data/ext/ca_obj_unbound_repeat.c +208 -241
  69. data/ext/ca_obj_window.c +1131 -563
  70. data/ext/ca_op_byte_swap.c +175 -0
  71. data/ext/ca_op_ipower.c +319 -0
  72. data/ext/ca_op_powi.h +88 -0
  73. data/ext/ca_sort_kernels.h +132 -0
  74. data/ext/ca_sweep_engine.c +430 -0
  75. data/ext/ca_sweep_engine.h +157 -0
  76. data/ext/ca_transform_common.c +228 -0
  77. data/ext/ca_triop_dispatch.c +55 -0
  78. data/ext/ca_triop_dispatch.h +62 -0
  79. data/ext/carray.h +795 -402
  80. data/ext/carray_access.c +831 -711
  81. data/ext/carray_attribute.c +98 -330
  82. data/ext/carray_bincount.c +255 -0
  83. data/ext/carray_broadcast.c +283 -0
  84. data/ext/carray_call_cfunc.c +1360 -828
  85. data/ext/carray_call_cfunc.h +160 -0
  86. data/ext/carray_cast.c +1212 -301
  87. data/ext/carray_cast_func.rb +81 -40
  88. data/ext/carray_class.c +53 -63
  89. data/ext/carray_config.h +28 -0
  90. data/ext/carray_conversion.c +350 -346
  91. data/ext/carray_copy.c +156 -268
  92. data/ext/carray_core.c +1342 -199
  93. data/ext/carray_count.c +312 -0
  94. data/ext/carray_data_type.c +43 -19
  95. data/ext/carray_element.c +585 -213
  96. data/ext/carray_factorize.c +2542 -0
  97. data/ext/carray_generate.c +230 -559
  98. data/ext/carray_histogram.c +490 -0
  99. data/ext/carray_hold.c +228 -0
  100. data/ext/carray_index_classifier.c +1035 -0
  101. data/ext/carray_index_classifier.h +27 -0
  102. data/ext/carray_internal.h +120 -0
  103. data/ext/carray_kernels_bincmp.c +4445 -0
  104. data/ext/carray_kernels_binop.c +10979 -0
  105. data/ext/carray_kernels_init.c +36 -0
  106. data/ext/carray_kernels_map.c +3466 -0
  107. data/ext/carray_kernels_moncmp.c +2096 -0
  108. data/ext/carray_kernels_monop.c +18312 -0
  109. data/ext/carray_kernels_reduce_aggregate.c +25836 -0
  110. data/ext/carray_kernels_reduce_boolean.c +329 -0
  111. data/ext/carray_kernels_reduce_cumulative.c +14592 -0
  112. data/ext/carray_kernels_reduce_extreme.c +16947 -0
  113. data/ext/carray_kernels_reduce_variance.c +3909 -0
  114. data/ext/carray_kernels_scan.c +3692 -0
  115. data/ext/carray_kernels_search.c +32137 -0
  116. data/ext/carray_kernels_sort.c +10625 -0
  117. data/ext/carray_kernels_triop.c +1391 -0
  118. data/ext/carray_lazy.c +567 -0
  119. data/ext/carray_loop.c +88 -200
  120. data/ext/carray_mask.c +848 -154
  121. data/ext/carray_math_kernel.h +120 -0
  122. data/ext/carray_mathfunc.c +10 -241
  123. data/ext/carray_median_percentile.c +1257 -0
  124. data/ext/carray_memory_view.c +1625 -0
  125. data/ext/carray_operator.c +1526 -318
  126. data/ext/carray_order.c +664 -1394
  127. data/ext/carray_partition.c +416 -0
  128. data/ext/carray_random.c +518 -0
  129. data/ext/carray_scatter.c +357 -0
  130. data/ext/carray_slab.c +1219 -0
  131. data/ext/carray_slab.h +84 -0
  132. data/ext/carray_sort.c +829 -0
  133. data/ext/carray_sort_kernel.c +620 -0
  134. data/ext/carray_struct.c +695 -0
  135. data/ext/carray_test.c +343 -229
  136. data/ext/carray_undef.c +34 -17
  137. data/ext/carray_utils.c +175 -74
  138. data/ext/extconf.rb +216 -55
  139. data/ext/mk_call_cfunc.rb +480 -0
  140. data/ext/mkkernel.rb +8842 -0
  141. data/ext/ruby_carray.c +202 -101
  142. data/ext/version.h +4 -14
  143. data/ext/version.rb +5 -13
  144. data/lib/carray/arrow_tensor.rb +401 -0
  145. data/lib/carray/attribute.rb +166 -0
  146. data/lib/carray/autoload_carray.rb +220 -0
  147. data/lib/carray/autoload_method_extension.rb +44 -0
  148. data/lib/carray/axis_group.rb +711 -0
  149. data/lib/carray/basics.rb +481 -0
  150. data/lib/carray/bincount_nd.rb +358 -0
  151. data/lib/carray/block_iterator.rb +604 -0
  152. data/lib/carray/boolean_reduce.rb +109 -0
  153. data/lib/carray/categorical.rb +561 -0
  154. data/lib/carray/categorical_iterator.rb +1062 -0
  155. data/lib/carray/complex.rb +150 -0
  156. data/lib/carray/conditional.rb +216 -0
  157. data/lib/carray/const_string.rb +228 -0
  158. data/lib/carray/construct.rb +139 -328
  159. data/lib/carray/core_extensions.rb +240 -0
  160. data/lib/carray/data_type_extension.rb +233 -0
  161. data/lib/carray/fixlen_string.rb +95 -0
  162. data/lib/carray/frame/concat.rb +132 -0
  163. data/lib/carray/frame/convert.rb +95 -0
  164. data/lib/carray/frame/csv_parser.rb +211 -0
  165. data/lib/carray/frame/frame.rb +649 -0
  166. data/lib/carray/frame/group.rb +186 -0
  167. data/lib/carray/frame/io.rb +164 -0
  168. data/lib/carray/frame/join.rb +248 -0
  169. data/lib/carray/frame/records.rb +99 -0
  170. data/lib/carray/frame/sort.rb +113 -0
  171. data/lib/carray/frame/verbs.rb +299 -0
  172. data/lib/carray/frame.rb +16 -0
  173. data/lib/carray/histogram.rb +512 -0
  174. data/lib/carray/inspect.rb +37 -20
  175. data/lib/carray/iterator.rb +57 -349
  176. data/lib/carray/lazy.rb +889 -0
  177. data/lib/carray/mask_gap_fill.rb +200 -0
  178. data/lib/carray/math.rb +78 -342
  179. data/lib/carray/meld_reduce.rb +289 -0
  180. data/lib/carray/methods/align_addr.rb +116 -0
  181. data/lib/carray/methods/bin.rb +128 -0
  182. data/lib/carray/methods/bincount.rb +87 -0
  183. data/lib/carray/methods/bit_string.rb +92 -0
  184. data/lib/carray/methods/broadcast.rb +63 -0
  185. data/lib/carray/methods/choose.rb +39 -0
  186. data/lib/carray/methods/composition.rb +280 -0
  187. data/lib/carray/methods/gather_nd.rb +206 -0
  188. data/lib/carray/methods/index.rb +39 -0
  189. data/lib/carray/methods/insert_block.rb +99 -0
  190. data/lib/carray/methods/is_in.rb +141 -0
  191. data/lib/carray/methods/join.rb +90 -0
  192. data/lib/carray/methods/locate_addr.rb +47 -0
  193. data/lib/carray/methods/mask_duplicates.rb +41 -0
  194. data/lib/carray/methods/meshgrid.rb +91 -0
  195. data/lib/carray/methods/mode.rb +126 -0
  196. data/lib/carray/methods/nunique.rb +46 -0
  197. data/lib/carray/methods/resize.rb +56 -0
  198. data/lib/carray/methods/snap.rb +156 -0
  199. data/lib/carray/methods/string_format.rb +57 -0
  200. data/lib/carray/methods/unique.rb +47 -0
  201. data/lib/carray/methods/value_counts.rb +71 -0
  202. data/lib/carray/mkmf.rb +124 -101
  203. data/lib/carray/runtime.rb +108 -0
  204. data/lib/carray/serialize.rb +478 -167
  205. data/lib/carray/slab_iterator.rb +292 -0
  206. data/lib/carray/stack.rb +291 -0
  207. data/lib/carray/string.rb +56 -180
  208. data/lib/carray/string_operation_extension.rb +289 -0
  209. data/lib/carray/struct.rb +335 -323
  210. data/lib/carray/struct_builder.rb +697 -0
  211. data/lib/carray/table.rb +41 -2
  212. data/lib/carray/time.rb +2255 -38
  213. data/lib/carray/window_iterator.rb +655 -0
  214. data/lib/carray.rb +55 -57
  215. metadata +163 -130
  216. data/Rakefile +0 -51
  217. data/TODO.md +0 -18
  218. data/ext/ca_iter_block.c +0 -257
  219. data/ext/ca_iter_dimension.c +0 -299
  220. data/ext/ca_iter_window.c +0 -214
  221. data/ext/ca_obj_mapping.c +0 -644
  222. data/ext/carray_iterator.c +0 -641
  223. data/ext/carray_math.rb +0 -850
  224. data/ext/carray_numeric.c +0 -259
  225. data/ext/carray_sort_addr.c +0 -254
  226. data/ext/carray_stat.c +0 -2100
  227. data/ext/carray_stat_proc.rb +0 -1999
  228. data/ext/mkmath.rb +0 -741
  229. data/ext/ruby_ccomplex.c +0 -509
  230. data/ext/ruby_float_func.c +0 -86
  231. data/lib/carray/array.rb +0 -8
  232. data/lib/carray/autoload/autoload_base.rb +0 -19
  233. data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
  234. data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
  235. data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
  236. data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
  237. data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
  238. data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
  239. data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
  240. data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
  241. data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
  242. data/lib/carray/autoload/autoload_gem_random.rb +0 -8
  243. data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
  244. data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
  245. data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
  246. data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
  247. data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
  248. data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
  249. data/lib/carray/autoload/autoload_object_link.rb +0 -1
  250. data/lib/carray/autoload/autoload_object_pack.rb +0 -2
  251. data/lib/carray/autoload.rb +0 -141
  252. data/lib/carray/basic.rb +0 -191
  253. data/lib/carray/broadcast.rb +0 -101
  254. data/lib/carray/compose.rb +0 -315
  255. data/lib/carray/convert.rb +0 -115
  256. data/lib/carray/info.rb +0 -110
  257. data/lib/carray/io/imagemagick.rb +0 -235
  258. data/lib/carray/mask.rb +0 -102
  259. data/lib/carray/math/histogram.rb +0 -177
  260. data/lib/carray/math/recurrence.rb +0 -93
  261. data/lib/carray/object/ca_obj_iterator.rb +0 -50
  262. data/lib/carray/object/ca_obj_link.rb +0 -50
  263. data/lib/carray/object/ca_obj_pack.rb +0 -99
  264. data/lib/carray/obsolete.rb +0 -256
  265. data/lib/carray/ordering.rb +0 -181
  266. data/lib/carray/testing.rb +0 -51
  267. data/lib/carray/transform.rb +0 -109
  268. data/misc/Methods.ja.md +0 -182
  269. data/misc/NOTE +0 -51
  270. data/spec/Classes/CABitfield_spec.rb +0 -58
  271. data/spec/Classes/CABlockIterator_spec.rb +0 -114
  272. data/spec/Classes/CABlock_spec.rb +0 -205
  273. data/spec/Classes/CAField_spec.rb +0 -39
  274. data/spec/Classes/CAGrid_spec.rb +0 -75
  275. data/spec/Classes/CAMap_spec.rb +0 -0
  276. data/spec/Classes/CAMapping_spec.rb +0 -105
  277. data/spec/Classes/CAObject_attribute_spec.rb +0 -33
  278. data/spec/Classes/CAObject_spec.rb +0 -33
  279. data/spec/Classes/CARefer_spec.rb +0 -93
  280. data/spec/Classes/CARepeat_spec.rb +0 -65
  281. data/spec/Classes/CASelect_spec.rb +0 -22
  282. data/spec/Classes/CAShift_spec.rb +0 -16
  283. data/spec/Classes/CAStruct_spec.rb +0 -71
  284. data/spec/Classes/CATranspose_spec.rb +0 -60
  285. data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
  286. data/spec/Classes/CAWindow_spec.rb +0 -54
  287. data/spec/Classes/CAWrap_spec.rb +0 -8
  288. data/spec/Classes/CArray_spec.rb +0 -184
  289. data/spec/Classes/CScalar_spec.rb +0 -55
  290. data/spec/Classes/ex1.rb +0 -46
  291. data/spec/Features/feature_130_spec.rb +0 -19
  292. data/spec/Features/feature_attributes_spec.rb +0 -280
  293. data/spec/Features/feature_boolean_spec.rb +0 -98
  294. data/spec/Features/feature_broadcast.rb +0 -116
  295. data/spec/Features/feature_cast_function.rb +0 -19
  296. data/spec/Features/feature_cast_spec.rb +0 -33
  297. data/spec/Features/feature_class_spec.rb +0 -84
  298. data/spec/Features/feature_complex_spec.rb +0 -42
  299. data/spec/Features/feature_composite_spec.rb +0 -124
  300. data/spec/Features/feature_convert_spec.rb +0 -46
  301. data/spec/Features/feature_copy_spec.rb +0 -123
  302. data/spec/Features/feature_creation_spec.rb +0 -84
  303. data/spec/Features/feature_element_spec.rb +0 -144
  304. data/spec/Features/feature_extream_spec.rb +0 -54
  305. data/spec/Features/feature_generate_spec.rb +0 -74
  306. data/spec/Features/feature_index_spec.rb +0 -69
  307. data/spec/Features/feature_mask_spec.rb +0 -580
  308. data/spec/Features/feature_math_spec.rb +0 -97
  309. data/spec/Features/feature_order_spec.rb +0 -146
  310. data/spec/Features/feature_ref_store_spec.rb +0 -209
  311. data/spec/Features/feature_serialization_spec.rb +0 -125
  312. data/spec/Features/feature_stat_spec.rb +0 -397
  313. data/spec/Features/feature_virtual_spec.rb +0 -48
  314. data/spec/Features/method_eq_spec.rb +0 -81
  315. data/spec/Features/method_is_nan_spec.rb +0 -12
  316. data/spec/Features/method_map_spec.rb +0 -54
  317. data/spec/Features/method_max_with.rb +0 -20
  318. data/spec/Features/method_min_with.rb +0 -19
  319. data/spec/Features/method_ne_spec.rb +0 -18
  320. data/spec/Features/method_project_spec.rb +0 -188
  321. data/spec/Features/method_ref_spec.rb +0 -27
  322. data/spec/Features/method_round_spec.rb +0 -11
  323. data/spec/Features/method_s_linspace_spec.rb +0 -48
  324. data/spec/Features/method_s_span_spec.rb +0 -14
  325. data/spec/Features/method_seq_spec.rb +0 -47
  326. data/spec/Features/method_sort_with.rb +0 -43
  327. data/spec/Features/method_sorted_with.rb +0 -29
  328. data/spec/Features/method_span_spec.rb +0 -42
  329. data/spec/Features/method_wrap_readonly_spec.rb +0 -43
  330. data/spec/UnitTest/test_CAVirtual.rb +0 -214
  331. data/spec/spec_all.rb +0 -10
  332. data/utils/ca_ase.rb +0 -21
  333. data/utils/ca_methods.rb +0 -15
  334. data/utils/cast_checker.rb +0 -30
  335. data/utils/convert_test.rb +0 -73
  336. data/utils/extract_yard.rb +0 -22
  337. data/utils/guess_shape.rb +0 -76
  338. data/utils/monkey_patch_methods.rb +0 -62
  339. data/utils/remove_resource_fork.sh +0 -5
data/ext/carray_mask.c CHANGED
@@ -1,16 +1,253 @@
1
1
  /* ---------------------------------------------------------------------------
2
2
 
3
- carray_mask.c
4
-
5
- This file is part of Ruby/CArray extension library.
6
-
7
- Copyright (C) 2005-2020 Hiroki Motoyoshi
3
+ Mask machinery shared by every CArray subtype: has_mask / any-masked /
4
+ all-masked scans, mask creation and propagation across the view chain,
5
+ the mask SET family (mask_eq / mask_invalid / mask_where / strip_mask),
6
+ count_masked, and the word-bulk boolean bit-op fast paths.
8
7
 
9
8
  ---------------------------------------------------------------------------- */
10
9
 
11
10
  #include <stdarg.h>
11
+ #include <stdint.h>
12
12
  #include "ruby.h"
13
13
  #include "carray.h"
14
+ #include "carray_internal.h" /* ca_lazy_arena_* */
15
+ #include "ca_obj_face.h" /* CA_FACE_LIFT_IF_FACE for the mask-lift path */
16
+
17
+ /* ============================================================================
18
+ * Word-bulk mask substrate helpers.
19
+ *
20
+ * CAREFUL: every helper here relies on the invariant that a mask / bool
21
+ * byte is in {0, 1} throughout all production code paths (OBJ2BOOL strict,
22
+ * integer->bool cast raises on out-of-range, comparison output is
23
+ * C-semantic 0/1, the mask combiner forces *ma=1, and an external mask via
24
+ * ca_wrap_readonly normalises). Breaking it corrupts the word-level ops.
25
+ *
26
+ * Word loop: uint64_t (8 byte/op) + tail byte fallback (n % 8).
27
+ * NOT uses an LSB XOR pattern (w ^ 0x0101...01ULL) to preserve the
28
+ * invariant (byte ~ would produce 0xFE, violating {0,1}).
29
+ *
30
+ * Helpers are static inline (file-local), inlinable into callers within
31
+ * this translation unit (overlay_n, is_any_masked, is_all_masked, etc.).
32
+ * extern + header extraction can wait until a cross-file need arises.
33
+ * Placed at the top of the TU so the inline body is visible at every call
34
+ * site.
35
+ * ============================================================================ */
36
+
37
+ #define CA_MASK_WORD_BYTES 8
38
+ #define CA_MASK_WORD_LSB 0x0101010101010101ULL
39
+
40
+ /* ma[i] |= ms[i] for i in [0, n) -- used by ca_copy_mask_overlay_n */
41
+ static inline void
42
+ ca_mask_word_or (boolean8_t *ma, const boolean8_t *ms, ca_size_t n)
43
+ {
44
+ ca_size_t nw = n / CA_MASK_WORD_BYTES;
45
+ ca_size_t i;
46
+ uint64_t *wa = (uint64_t *) ma;
47
+ const uint64_t *ws = (const uint64_t *) ms;
48
+ for (i = 0; i < nw; i++) {
49
+ wa[i] |= ws[i];
50
+ }
51
+ for (i = nw * CA_MASK_WORD_BYTES; i < n; i++) {
52
+ ma[i] |= ms[i];
53
+ }
54
+ }
55
+
56
+ /* md[i] = ma[i] & mb[i] for i in [0, n) */
57
+ static inline void
58
+ ca_mask_word_and (boolean8_t *md,
59
+ const boolean8_t *ma,
60
+ const boolean8_t *mb,
61
+ ca_size_t n)
62
+ {
63
+ ca_size_t nw = n / CA_MASK_WORD_BYTES;
64
+ ca_size_t i;
65
+ uint64_t *wd = (uint64_t *) md;
66
+ const uint64_t *wa = (const uint64_t *) ma;
67
+ const uint64_t *wb = (const uint64_t *) mb;
68
+ for (i = 0; i < nw; i++) {
69
+ wd[i] = wa[i] & wb[i];
70
+ }
71
+ for (i = nw * CA_MASK_WORD_BYTES; i < n; i++) {
72
+ md[i] = ma[i] & mb[i];
73
+ }
74
+ }
75
+
76
+ /* md[i] = ma[i] ^ mb[i] for i in [0, n) */
77
+ static inline void
78
+ ca_mask_word_xor (boolean8_t *md,
79
+ const boolean8_t *ma,
80
+ const boolean8_t *mb,
81
+ ca_size_t n)
82
+ {
83
+ ca_size_t nw = n / CA_MASK_WORD_BYTES;
84
+ ca_size_t i;
85
+ uint64_t *wd = (uint64_t *) md;
86
+ const uint64_t *wa = (const uint64_t *) ma;
87
+ const uint64_t *wb = (const uint64_t *) mb;
88
+ for (i = 0; i < nw; i++) {
89
+ wd[i] = wa[i] ^ wb[i];
90
+ }
91
+ for (i = nw * CA_MASK_WORD_BYTES; i < n; i++) {
92
+ md[i] = ma[i] ^ mb[i];
93
+ }
94
+ }
95
+
96
+ /* md[i] = 1 - ms[i] (LSB XOR pattern; preserves byte ∈ {0,1} invariant) */
97
+ static inline void
98
+ ca_mask_word_not (boolean8_t *md, const boolean8_t *ms, ca_size_t n)
99
+ {
100
+ ca_size_t nw = n / CA_MASK_WORD_BYTES;
101
+ ca_size_t i;
102
+ uint64_t *wd = (uint64_t *) md;
103
+ const uint64_t *ws = (const uint64_t *) ms;
104
+ for (i = 0; i < nw; i++) {
105
+ wd[i] = ws[i] ^ CA_MASK_WORD_LSB;
106
+ }
107
+ for (i = nw * CA_MASK_WORD_BYTES; i < n; i++) {
108
+ md[i] = ms[i] ^ (boolean8_t) 1;
109
+ }
110
+ }
111
+
112
+ /* 1 if any byte in [0, n) is non-zero, 0 otherwise (with early exit).
113
+ Consumer: ca_is_any_masked / any_masked? scan. */
114
+ static inline int
115
+ ca_mask_word_any (const boolean8_t *m, ca_size_t n)
116
+ {
117
+ ca_size_t nw = n / CA_MASK_WORD_BYTES;
118
+ ca_size_t i;
119
+ const uint64_t *w = (const uint64_t *) m;
120
+ for (i = 0; i < nw; i++) {
121
+ if ( w[i] != 0 ) return 1;
122
+ }
123
+ for (i = nw * CA_MASK_WORD_BYTES; i < n; i++) {
124
+ if ( m[i] ) return 1;
125
+ }
126
+ return 0;
127
+ }
128
+
129
+ /* 1 if every byte in [0, n) is non-zero, 0 otherwise (with early exit).
130
+ The {0,1} invariant makes a word check (w == LSB pattern) sufficient.
131
+ Consumer: ca_is_all_masked / all_masked? scan. */
132
+ static inline int
133
+ ca_mask_word_all_set (const boolean8_t *m, ca_size_t n)
134
+ {
135
+ ca_size_t nw = n / CA_MASK_WORD_BYTES;
136
+ ca_size_t i;
137
+ const uint64_t *w = (const uint64_t *) m;
138
+ for (i = 0; i < nw; i++) {
139
+ if ( w[i] != CA_MASK_WORD_LSB ) return 0;
140
+ }
141
+ for (i = nw * CA_MASK_WORD_BYTES; i < n; i++) {
142
+ if ( ! m[i] ) return 0;
143
+ }
144
+ return 1;
145
+ }
146
+
147
+ /* Count of set bytes in [0, n). Under the {0,1} invariant, a plain accumulate of
148
+ uint8 lanes (no popcount needed); compilers lower this to a horizontal
149
+ byte-sum (psadbw / uaddlv). Consumer: ca_count_masked. */
150
+ static inline ca_size_t
151
+ ca_mask_word_count (const boolean8_t *m, ca_size_t n)
152
+ {
153
+ ca_size_t i, count = 0;
154
+ for (i = 0; i < n; i++) {
155
+ count += m[i];
156
+ }
157
+ return count;
158
+ }
159
+
160
+ /* ============================================================================
161
+ * (End of the word-bulk helpers. Smoke surface for these helpers is at
162
+ * the bottom of this file, just before Init_carray_mask.)
163
+ * ============================================================================ */
164
+
165
+ /* ----------------------------------------------------------------------------
166
+ * Virtual-mask block scan (non-materialise reduction)
167
+ *
168
+ * When a mask is a virtual view (e.g. CAStackMask composed by
169
+ * ca_stack_func_create_mask), ca_attach
170
+ * materialises the WHOLE composed mask before the word scan — which defeats
171
+ * the early-exit of any/all and forces peak O(n) memory. Instead, drive
172
+ * ca_xfer_stride over the mask in row-slab blocks pulled into one arena
173
+ * scratch (the same mechanism CAMonOp uses to consume a virtual source
174
+ * without materialising it). Peak memory = one block; ANY/ALL short-circuit
175
+ * on the first decisive block (a masked cell in block 0 of a K-deep CAStack
176
+ * returns after pulling 1/K of the data, never touching the rest).
177
+ *
178
+ * Only used for NON-alias masks (cheap/entity masks keep the O(1)-alias
179
+ * attach + direct word scan above). Blocking is along axis 0; for arrays
180
+ * with a very wide inner product (> CA_MASK_BLOCK_BYTES) the block degrades
181
+ * to a single row, which is still O(row) peak rather than O(n).
182
+ * ---------------------------------------------------------------------------- */
183
+
184
+ #define CA_MASK_BLOCK_BYTES (32 * 1024) /* L1-resident scratch budget */
185
+
186
+ enum { CA_MASK_SCAN_ANY, CA_MASK_SCAN_ALL, CA_MASK_SCAN_COUNT };
187
+
188
+ static ca_size_t
189
+ ca_mask_scan_virtual (CArray *mask, int op)
190
+ {
191
+ int8_t ndim = mask->ndim;
192
+ ca_size_t outer = mask->dim[0];
193
+ ca_size_t inner = 1;
194
+ ca_size_t native[CA_RANK_MAX];
195
+ ca_size_t rows, off, result = 0;
196
+ boolean8_t *scratch;
197
+ int8_t k;
198
+
199
+ for (k = 1; k < ndim; k++) {
200
+ inner *= mask->dim[k];
201
+ }
202
+
203
+ /* compact row-major byte strides (boolean = 1 byte/cell); native[0] == inner
204
+ so passing these as the destination layout writes each block contiguously
205
+ into scratch as [nrow * inner] cells. */
206
+ {
207
+ ca_size_t s = 1;
208
+ for (k = ndim - 1; k >= 0; k--) { native[k] = s; s *= mask->dim[k]; }
209
+ }
210
+
211
+ rows = (inner > 0) ? (CA_MASK_BLOCK_BYTES / inner) : CA_MASK_BLOCK_BYTES;
212
+ if ( rows < 1 ) rows = 1;
213
+ if ( rows > outer ) rows = outer;
214
+
215
+ if ( op == CA_MASK_SCAN_ALL ) {
216
+ result = 1; /* vacuously all-set until a non-set cell is found */
217
+ }
218
+
219
+ ca_lazy_arena_enter();
220
+ scratch = (boolean8_t *) ca_lazy_arena_acquire(rows * inner);
221
+
222
+ for (off = 0; off < outer; off += rows) {
223
+ ca_size_t nrow = (outer - off < rows) ? (outer - off) : rows;
224
+ ca_size_t n = nrow * inner;
225
+ ca_size_t starts[CA_RANK_MAX] = {0};
226
+ ca_size_t counts[CA_RANK_MAX];
227
+
228
+ starts[0] = off;
229
+ counts[0] = nrow;
230
+ for (k = 1; k < ndim; k++) {
231
+ counts[k] = mask->dim[k];
232
+ }
233
+
234
+ ca_xfer_stride(mask, starts, counts, native, scratch, CA_XFER_GET);
235
+
236
+ if ( op == CA_MASK_SCAN_ANY ) {
237
+ if ( ca_mask_word_any(scratch, n) ) { result = 1; break; }
238
+ }
239
+ else if ( op == CA_MASK_SCAN_ALL ) {
240
+ if ( ! ca_mask_word_all_set(scratch, n) ) { result = 0; break; }
241
+ }
242
+ else { /* CA_MASK_SCAN_COUNT — no early exit */
243
+ result += ca_mask_word_count(scratch, n);
244
+ }
245
+ }
246
+
247
+ ca_lazy_arena_release(scratch);
248
+ ca_lazy_arena_exit();
249
+ return result;
250
+ }
14
251
 
15
252
  boolean8_t *
16
253
  ca_mask_ptr (void *ap)
@@ -36,13 +273,32 @@ ca_has_mask (void *ap)
36
273
  }
37
274
  else if ( ca_is_caobject(ca) ) { /* CAObject */
38
275
  CAObject *co = (CAObject *) ca;
276
+ if ( ca_is_face(ca) ) { /* Face: the mask follows the parent */
277
+ if ( co->parent && ca_has_mask(co->parent) ) {
278
+ ca_create_mask(ca);
279
+ return 1;
280
+ }
281
+ return 0;
282
+ }
39
283
  if ( rb_obj_respond_to(co->self, rb_intern("mask_created?"), Qtrue) ) {
40
284
  return RTEST(rb_funcall(co->self, rb_intern("mask_created?"), 0));
41
285
  }
42
286
  return ( ca->mask != NULL ) ? 1 : 0;
43
287
  }
288
+ else if ( ca_test_flag(ca, CA_FLAG_MULTI_PARENTS) ) {
289
+ /* multi-parent view: masked iff ANY parent is (union of masked cells). */
290
+ CAMultiParent *mp = (CAMultiParent *) ca;
291
+ int32_t k;
292
+ for ( k = 0; k < mp->n_parents; k++ ) {
293
+ if ( ca_has_mask(mp->parents[k]) ) {
294
+ ca_create_mask(ca);
295
+ return 1;
296
+ }
297
+ }
298
+ return 0;
299
+ }
44
300
  else {
45
- CAVirtual *cr = (CAVirtual *) ca; /* virtual array, check parent array */
301
+ CAView *cr = (CAView *) ca; /* view array, check parent array */
46
302
  if ( ca_has_mask(cr->parent) ) {
47
303
  ca_create_mask(ca);
48
304
  return 1;
@@ -57,21 +313,26 @@ int
57
313
  ca_is_any_masked (void *ap)
58
314
  {
59
315
  CArray *ca = (CArray *) ap;
60
- boolean8_t *m;
61
- ca_size_t i;
62
316
  int flag = 0;
63
317
 
64
318
  ca_update_mask(ca);
65
319
  if ( ca->mask ) {
66
- ca_attach(ca->mask);
67
- m = (boolean8_t *) ca->mask->ptr;
68
- for (i=0; i<ca->elements; i++) {
69
- if ( *m++ ) {
70
- flag = 1;
71
- break;
72
- }
320
+ if ( ca_attach_is_alias(ca->mask) ) {
321
+ /* cheap (entity / contiguous-alias) mask: O(1) alias attach +
322
+ word-level any with early exit. A sparse mask returns on the first
323
+ non-zero word (the typical "all zero, no mask actually set" hot
324
+ pattern). An O(n) scan cache is not viable here: the mask is
325
+ dynamic and a view chain can introduce a mask mid-chain. */
326
+ ca_attach(ca->mask);
327
+ flag = ca_mask_word_any((boolean8_t *) ca->mask->ptr, ca->elements);
328
+ ca_detach(ca->mask);
329
+ }
330
+ else {
331
+ /* virtual mask (e.g. CAStackMask): block-loop the composed mask so it
332
+ is never materialised whole, and short-circuit on the first masked
333
+ block. See ca_mask_scan_virtual. */
334
+ flag = (int) ca_mask_scan_virtual(ca->mask, CA_MASK_SCAN_ANY);
73
335
  }
74
- ca_detach(ca->mask);
75
336
  }
76
337
 
77
338
  return flag;
@@ -81,26 +342,25 @@ int
81
342
  ca_is_all_masked (void *ap)
82
343
  {
83
344
  CArray *ca = (CArray *) ap;
84
- boolean8_t *m;
85
- ca_size_t i;
86
- int flag;
345
+ int flag = 0;
87
346
 
88
347
  ca_update_mask(ca);
89
348
  if ( ca->mask ) {
90
- flag = 1;
91
- ca_attach(ca->mask);
92
- m = (boolean8_t *) ca->mask->ptr;
93
- for (i=0; i<ca->elements; i++) {
94
- if ( ! *m++ ) {
95
- flag = 0;
96
- break;
97
- }
349
+ if ( ca_attach_is_alias(ca->mask) ) {
350
+ /* cheap mask: O(1) alias attach + word-level all_set with early exit.
351
+ The word check uses (w == 0x0101...01ULL) under the {0,1}
352
+ invariant; returns on the first non-fully-set word. */
353
+ ca_attach(ca->mask);
354
+ flag = ca_mask_word_all_set((boolean8_t *) ca->mask->ptr, ca->elements);
355
+ ca_detach(ca->mask);
356
+ }
357
+ else {
358
+ /* virtual mask: block-loop + short-circuit on first non-fully-set block. */
359
+ flag = (int) ca_mask_scan_virtual(ca->mask, CA_MASK_SCAN_ALL);
98
360
  }
99
- ca_detach(ca->mask);
100
- return flag;
101
- } else {
102
- return 0;
103
361
  }
362
+
363
+ return flag;
104
364
  }
105
365
 
106
366
  /* create mask array if array has mask but has not mask array */
@@ -132,11 +392,11 @@ ca_create_mask (void *ap)
132
392
  if ( ! ca->mask ) {
133
393
  ca_func[ca->obj_type].create_mask(ca);
134
394
  ca_set_flag(ca->mask, CA_FLAG_MASK_ARRAY); /* set array as mask array */
135
- if ( ca_is_virtual(ca) ) {
136
- if ( CAVIRTUAL(ca)->attach ) {
395
+ if ( ca_is_view(ca) ) {
396
+ if ( CAVIEW(ca)->attach ) {
137
397
  ca_attach(ca->mask);
138
- if ( ca_is_virtual(ca->mask) ) {
139
- CAVIRTUAL(ca->mask)->attach = CAVIRTUAL(ca)->attach;
398
+ if ( ca_is_view(ca->mask) ) {
399
+ CAVIEW(ca->mask)->attach = CAVIEW(ca)->attach;
140
400
  }
141
401
  }
142
402
  }
@@ -190,7 +450,6 @@ ca_copy_mask_overlay_n (void *ap, ca_size_t elements, int n, CArray **slist)
190
450
  CArray *cs;
191
451
  boolean8_t *ma, *ms;
192
452
  int i, some_has_mask = 0;
193
- ca_size_t j;
194
453
 
195
454
  for (i=0; i<n; i++) {
196
455
  if ( slist[i] && ca_has_mask(slist[i]) ) {
@@ -223,21 +482,17 @@ ca_copy_mask_overlay_n (void *ap, ca_size_t elements, int n, CArray **slist)
223
482
  ca_attach(cs->mask);
224
483
  ma = (boolean8_t *) ca->mask->ptr;
225
484
  ms = (boolean8_t *) cs->mask->ptr;
485
+ /* scalar masked source -> broadcast to all (memset); array source
486
+ -> pure word OR (ca_mask_word_or). A defensive `if (*ms)` is
487
+ unnecessary: under the {0,1} invariant, `*ma |= *ms` equals
488
+ `if (*ms) *ma = 1`. Post-condition: the mask byte stays in {0,1}. */
226
489
  if ( ca_is_scalar(cs) ) {
227
490
  if ( *ms ) {
228
- for (j=0; j<elements; j++) {
229
- *ma = 1;
230
- ma++;
231
- }
491
+ memset(ma, 1, elements);
232
492
  }
233
493
  }
234
494
  else {
235
- for (j=0; j<elements; j++) {
236
- if ( *ms ) {
237
- *ma = 1;
238
- }
239
- ma++; ms++;
240
- }
495
+ ca_mask_word_or(ma, ms, elements);
241
496
  }
242
497
  ca_detach(cs->mask);
243
498
  }
@@ -254,7 +509,7 @@ ca_copy_mask_overlay (void *ap, ca_size_t elements, int n, ...)
254
509
  va_list args;
255
510
  int i;
256
511
 
257
- slist = malloc_with_check(sizeof(CArray *)*n);
512
+ slist = xmalloc(sizeof(CArray *)*n);
258
513
  va_start(args, n);
259
514
  for (i=0; i<n; i++) {
260
515
  slist[i] = va_arg(args, CArray *);
@@ -263,7 +518,7 @@ ca_copy_mask_overlay (void *ap, ca_size_t elements, int n, ...)
263
518
 
264
519
  ca_copy_mask_overlay_n(ca, elements, n, slist);
265
520
 
266
- free(slist);
521
+ xfree(slist);
267
522
  }
268
523
 
269
524
  void
@@ -288,7 +543,7 @@ ca_copy_mask_overwrite (void *ap, ca_size_t elements, int n, ...)
288
543
  va_list args;
289
544
  int i;
290
545
 
291
- slist = malloc_with_check(sizeof(CArray*)*n);
546
+ slist = xmalloc(sizeof(CArray*)*n);
292
547
  va_start(args, n);
293
548
  for (i=0; i<n; i++) {
294
549
  slist[i] = va_arg(args, CArray*);
@@ -303,7 +558,7 @@ ca_copy_mask_overwrite (void *ap, ca_size_t elements, int n, ...)
303
558
 
304
559
  ca_copy_mask_overlay_n(ca, elements, n, slist);
305
560
 
306
- free(slist);
561
+ xfree(slist);
307
562
  }
308
563
 
309
564
  void
@@ -320,19 +575,23 @@ ca_count_masked (void *ap)
320
575
  {
321
576
  CArray *ca = (CArray *) ap;
322
577
  boolean8_t *m;
323
- ca_size_t i, count = 0;
578
+ ca_size_t count = 0;
324
579
 
325
580
  ca_update_mask(ca);
326
581
 
327
582
  if ( ca->mask ) {
328
- ca_attach(ca->mask);
329
- m = (boolean8_t *) ca->mask->ptr;
330
- for (i=0; i<ca->elements; i++) {
331
- if ( *m++ ) {
332
- count++;
333
- }
583
+ if ( ca_attach_is_alias(ca->mask) ) {
584
+ /* cheap mask: O(1) alias attach + word-bulk count. */
585
+ ca_attach(ca->mask);
586
+ m = (boolean8_t *) ca->mask->ptr;
587
+ count = ca_mask_word_count(m, ca->elements);
588
+ ca_detach(ca->mask);
589
+ }
590
+ else {
591
+ /* virtual mask: block-loop the composed mask (peak O(block), no full
592
+ materialise). COUNT has no early exit but still avoids O(n) peak. */
593
+ count = ca_mask_scan_virtual(ca->mask, CA_MASK_SCAN_COUNT);
334
594
  }
335
- ca_detach(ca->mask);
336
595
  }
337
596
 
338
597
  return count;
@@ -345,31 +604,30 @@ ca_count_not_masked (void *ap)
345
604
  return ca->elements - ca_count_masked(ca);
346
605
  }
347
606
 
348
- #define proc_fill_bang(type) \
607
+ /* Scalar-fill every masked cell with fill_value, then clear its mask bit.
608
+ This depends only on the cell BYTE WIDTH, not the data type (the work is
609
+ a width-sized copy), so a single width switch covers every numeric /
610
+ complex / object dtype; the default arm handles fixlen and any other
611
+ width via memcpy. The typed arms let the compiler emit a plain store. */
612
+ #define proc_fill_bang_width(T) \
349
613
  { \
350
- ca_size_t i; \
351
- type *p = (type *)ca->ptr; \
352
- type v = *(type *)fill_value; \
614
+ ca_size_t i; \
615
+ T *p = (T *)ca->ptr; \
616
+ T v = *(T *)fill_value; \
353
617
  boolean8_t *m = (boolean8_t *) ca->mask->ptr; \
354
618
  for (i=0; i<ca->elements; i++) { \
355
- if ( *m ) { \
356
- *p = v; \
357
- *m = 0; \
358
- } \
619
+ if ( *m ) { *p = v; *m = 0; } \
359
620
  m++; p++; \
360
621
  } \
361
622
  }
362
623
 
363
- #define proc_fill_bang_fixlen() \
624
+ #define proc_fill_bang_bytes() \
364
625
  { \
365
- ca_size_t i; \
626
+ ca_size_t i; \
366
627
  char *p = ca->ptr; \
367
628
  boolean8_t *m = (boolean8_t *) ca->mask->ptr; \
368
629
  for (i=0; i<ca->elements; i++) { \
369
- if ( *m ) { \
370
- memcpy(p, fill_value, ca->bytes); \
371
- *m = 0; \
372
- } \
630
+ if ( *m ) { memcpy(p, fill_value, ca->bytes); *m = 0; } \
373
631
  m++; p+=ca->bytes; \
374
632
  } \
375
633
  }
@@ -388,27 +646,12 @@ ca_unmask (void *ap, char *fill_value)
388
646
  else {
389
647
  ca_attach(ca);
390
648
 
391
- switch ( ca->data_type ) {
392
- case CA_FIXLEN: proc_fill_bang_fixlen(); break;
393
- case CA_BOOLEAN:
394
- case CA_INT8:
395
- case CA_UINT8: proc_fill_bang(int8_t); break;
396
- case CA_INT16:
397
- case CA_UINT16: proc_fill_bang(int16_t); break;
398
- case CA_INT32:
399
- case CA_UINT32:
400
- case CA_FLOAT32: proc_fill_bang(int32_t); break;
401
- case CA_INT64:
402
- case CA_UINT64:
403
- case CA_FLOAT64: proc_fill_bang(float64_t); break;
404
- case CA_FLOAT128: proc_fill_bang(float128_t); break;
405
- #ifdef HAVE_COMPLEX_H
406
- case CA_CMPLX64: proc_fill_bang(cmplx64_t); break;
407
- case CA_CMPLX128: proc_fill_bang(cmplx128_t); break;
408
- case CA_CMPLX256: proc_fill_bang(cmplx256_t); break;
409
- #endif
410
- case CA_OBJECT: proc_fill_bang(VALUE); break;
411
- default: rb_bug("array has an unknown data type");
649
+ switch ( ca->bytes ) {
650
+ case 1: proc_fill_bang_width(uint8_t); break;
651
+ case 2: proc_fill_bang_width(uint16_t); break;
652
+ case 4: proc_fill_bang_width(uint32_t); break;
653
+ case 8: proc_fill_bang_width(uint64_t); break;
654
+ default: proc_fill_bang_bytes(); break; /* cmplx128 (16) + fixlen */
412
655
  }
413
656
 
414
657
  ca_sync(ca);
@@ -450,7 +693,6 @@ ca_invert_mask (void *ap)
450
693
  {
451
694
  CArray *ca = (CArray *) ap;
452
695
  boolean8_t *m;
453
- ca_size_t i;
454
696
 
455
697
  ca_update_mask(ca);
456
698
 
@@ -460,10 +702,7 @@ ca_invert_mask (void *ap)
460
702
 
461
703
  ca_attach(ca->mask);
462
704
  m = (boolean8_t *) ca->mask->ptr;
463
- for (i=0; i<ca->elements; i++) {
464
- *m ^= 1;
465
- m++;
466
- }
705
+ ca_mask_word_not(m, m, ca->elements); /* in-place 1 - m, LSB-XOR word loop */
467
706
  ca_detach(ca->mask);
468
707
 
469
708
  return;
@@ -500,7 +739,11 @@ ca_allocate_mask_iterator_n (int n, CArray **slist)
500
739
  }
501
740
  }
502
741
 
503
- m = malloc(sizeof(boolean8_t)*elements);
742
+ /* elements stays at its -1 sentinel only when no non-NULL array was given
743
+ (degenerate call); guard so the alloc/memset never see a negative size
744
+ (which would wrap to a huge bound). Also silences GCC's VRP warnings. */
745
+ if ( elements < 0 ) elements = 0;
746
+ m = xmalloc(sizeof(boolean8_t)*elements);
504
747
  memset(m, 0, elements);
505
748
 
506
749
  if ( ! some_has_mask ) {
@@ -528,10 +771,7 @@ ca_allocate_mask_iterator_n (int n, CArray **slist)
528
771
  }
529
772
  }
530
773
  else {
531
- for (j=0; j<elements; j++) {
532
- *mp = ( *mp || *ms );
533
- mp++; ms++;
534
- }
774
+ ca_mask_word_or(mp, ms, elements); /* m[j] |= ms[j], word-bulk OR */
535
775
  }
536
776
  ca_detach(cs->mask);
537
777
  }
@@ -546,7 +786,7 @@ ca_allocate_mask_iterator (int n, ...)
546
786
  va_list args;
547
787
  int i;
548
788
 
549
- slist = malloc_with_check(sizeof(CArray *)*n);
789
+ slist = xmalloc(sizeof(CArray *)*n);
550
790
  va_start(args, n);
551
791
  for (i=0; i<n; i++) {
552
792
  slist[i] = va_arg(args, CArray *);
@@ -555,7 +795,7 @@ ca_allocate_mask_iterator (int n, ...)
555
795
 
556
796
  m = ca_allocate_mask_iterator_n(n, slist);
557
797
 
558
- free(slist);
798
+ xfree(slist);
559
799
 
560
800
  return m;
561
801
  }
@@ -656,8 +896,38 @@ rb_ca_value_array (VALUE self)
656
896
  obj = rb_ca_refer_new(self, ca->data_type, ca->ndim, ca->dim, ca->bytes, 0);
657
897
  TypedData_Get_Struct(obj, CArray, &carray_data_type, co);
658
898
 
899
+ /* Value arrays ignore the mask by definition. ca_stride_setup
900
+ auto-propagates the parent's mask into newly-built CAStride
901
+ subclasses; strip it here so ca_has_mask(value_array) stays
902
+ false. */
903
+ if (co->mask) {
904
+ ca_free(co->mask);
905
+ co->mask = NULL;
906
+ }
659
907
  ca_set_flag(co, CA_FLAG_VALUE_ARRAY);
660
908
 
909
+ /* CAREFUL: when self is a Face, rb_ca_refer_new has already lifted its
910
+ result, so `co` above is the Face wrapper and the refer that actually
911
+ carries the data sits underneath it. Marking only the wrapper is not
912
+ enough: kernels strip Faces at entry (ca_strip_face) and would go on
913
+ to ask that refer for a mask, which -- having none of its own -- it
914
+ answers from its parent. The values would read back raw while a
915
+ reduction over them still skipped the masked cells.
916
+
917
+ Marking the storage level is enough for anything stacked in between,
918
+ since ca_is_value_array inherits from parent to child. */
919
+ if ( ca_is_face(co) ) {
920
+ CArray *storage = ca_strip_face(co);
921
+ if ( storage ) {
922
+ if ( storage->mask ) {
923
+ ca_free(storage->mask);
924
+ storage->mask = NULL;
925
+ }
926
+ ca_set_flag(storage, CA_FLAG_VALUE_ARRAY);
927
+ }
928
+ }
929
+
930
+ CA_FACE_LIFT_IF_FACE(obj, self, ca);
661
931
  return obj;
662
932
  }
663
933
 
@@ -671,7 +941,7 @@ The mask array can't be set mask.
671
941
  VALUE
672
942
  rb_ca_mask_array (VALUE self)
673
943
  {
674
- VALUE obj;
944
+ volatile VALUE obj;
675
945
  CArray *ca;
676
946
 
677
947
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
@@ -830,36 +1100,6 @@ rb_ca_is_not_masked (VALUE self)
830
1100
  return ca_wrap_struct(co);
831
1101
  }
832
1102
 
833
- /* @overload count_masked
834
-
835
- (Masking, Statistics)
836
- Returns the number of masked elements.
837
- */
838
-
839
- VALUE
840
- rb_ca_count_masked (VALUE self)
841
- {
842
- CArray *ca;
843
- printf("hello\n");
844
- TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
845
- printf("world\n");
846
- return SIZE2NUM(ca_count_masked(ca));
847
- }
848
-
849
- /* @overload count_not_masked
850
-
851
- (Masking, Statistics)
852
- Returns the number of not-masked elements.
853
- */
854
-
855
- VALUE
856
- rb_ca_count_not_masked (VALUE self)
857
- {
858
- CArray *ca;
859
- TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
860
- return SIZE2NUM(ca_count_not_masked(ca));
861
- }
862
-
863
1103
  /* @overload unmask (fill_value = nil)
864
1104
 
865
1105
  (Masking, Destructive)
@@ -886,7 +1126,14 @@ rb_ca_unmask_method (int argc, VALUE *argv, VALUE self)
886
1126
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
887
1127
 
888
1128
  if ( rfval != CA_NIL ) {
889
- rcs = rb_cscalar_new_with_value(ca->data_type, ca->bytes, rfval);
1129
+ /* Face has surface != storage; the fill value is cast in the storage data_type. */
1130
+ int8_t conv_type = ca->data_type;
1131
+ if ( ca_is_face(ca) ) {
1132
+ CArray *root = ca;
1133
+ while (root && ca_is_face(root)) root = ((CAView *) root)->parent;
1134
+ if (root) conv_type = root->data_type;
1135
+ }
1136
+ rcs = rb_cscalar_new_with_value(conv_type, ca->bytes, rfval);
890
1137
  TypedData_Get_Struct(rcs, CScalar, &cscalar_data_type, cv);
891
1138
  fval = cv->ptr;
892
1139
  }
@@ -936,14 +1183,21 @@ rb_ca_unmask_copy_method (int argc, VALUE *argv, VALUE self)
936
1183
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
937
1184
 
938
1185
  if ( rfval != CA_NIL ) {
939
- rcs = rb_cscalar_new_with_value(ca->data_type, ca->bytes, rfval);
1186
+ /* Face has surface != storage; the fill value is cast in the storage data_type. */
1187
+ int8_t conv_type = ca->data_type;
1188
+ if ( ca_is_face(ca) ) {
1189
+ CArray *root = ca;
1190
+ while (root && ca_is_face(root)) root = ((CAView *) root)->parent;
1191
+ if (root) conv_type = root->data_type;
1192
+ }
1193
+ rcs = rb_cscalar_new_with_value(conv_type, ca->bytes, rfval);
940
1194
  TypedData_Get_Struct(rcs, CScalar, &cscalar_data_type, cv);
941
1195
  fval = cv->ptr;
942
1196
  }
943
1197
 
944
1198
  co = ca_unmask_copy(ca, fval);
945
1199
  obj = ca_wrap_struct(co);
946
- rb_ca_data_type_inherit(obj, self);
1200
+ CA_FACE_LIFT_IF_FACE(obj, self, ca);
947
1201
  return obj;
948
1202
  }
949
1203
 
@@ -978,6 +1232,71 @@ rb_ca_invert_mask (VALUE self)
978
1232
  return self;
979
1233
  }
980
1234
 
1235
+ /* guard_undef(*values, fill_value: UNDEF, &block) — class method.
1236
+ Returns fill_value if any of values is UNDEF, otherwise yields values
1237
+ to the block and returns its result.
1238
+ See lib/carray/mask.rb (removed in CIFY). */
1239
+ static VALUE
1240
+ rb_ca_s_guard_undef(int argc, VALUE *argv, VALUE klass)
1241
+ {
1242
+ VALUE values, opts;
1243
+ VALUE fill_value = CA_UNDEF;
1244
+ long i, n;
1245
+
1246
+ rb_scan_args(argc, argv, "*:", &values, &opts);
1247
+ rb_scan_options(opts, "fill_value", &fill_value);
1248
+
1249
+ n = RARRAY_LEN(values);
1250
+ for (i = 0; i < n; i++) {
1251
+ if (RARRAY_AREF(values, i) == CA_UNDEF) {
1252
+ return fill_value;
1253
+ }
1254
+ }
1255
+
1256
+ return rb_yield_splat(values);
1257
+ }
1258
+
1259
+ /* mask_eq(v) — return form of "mask cells equal to v".
1260
+ Equivalent to: obj = copy; obj[:eq, v] = UNDEF; obj */
1261
+ static VALUE
1262
+ rb_ca_mask_eq (VALUE self, VALUE v)
1263
+ {
1264
+ volatile VALUE obj = rb_ca_copy(self);
1265
+ rb_funcall(obj, rb_intern("[]="), 3,
1266
+ ID2SYM(rb_intern("eq")), v, CA_UNDEF);
1267
+ return obj;
1268
+ }
1269
+
1270
+ /* mask_invalid — return form of "mask NaN/Inf cells".
1271
+ Equivalent to: obj = copy; obj[:is_invalid] = UNDEF; obj */
1272
+ static VALUE
1273
+ rb_ca_mask_invalid (VALUE self)
1274
+ {
1275
+ volatile VALUE obj = rb_ca_copy(self);
1276
+ rb_funcall(obj, rb_intern("[]="), 2,
1277
+ ID2SYM(rb_intern("is_invalid")), CA_UNDEF);
1278
+ return obj;
1279
+ }
1280
+
1281
+ /* mask_where(*args) — generic return form predicate masking.
1282
+ Equivalent to: obj = copy; obj[*args] = UNDEF; obj
1283
+ Requires at least 1 argument. */
1284
+ static VALUE
1285
+ rb_ca_mask_where (int argc, VALUE *argv, VALUE self)
1286
+ {
1287
+ volatile VALUE obj;
1288
+ VALUE *all_args;
1289
+ if (argc == 0) {
1290
+ rb_raise(rb_eArgError, "mask_where requires at least 1 argument");
1291
+ }
1292
+ obj = rb_ca_copy(self);
1293
+ all_args = ALLOCA_N(VALUE, argc + 1);
1294
+ memcpy(all_args, argv, argc * sizeof(VALUE));
1295
+ all_args[argc] = CA_UNDEF;
1296
+ rb_funcallv(obj, rb_intern("[]="), argc + 1, all_args);
1297
+ return obj;
1298
+ }
1299
+
981
1300
  /* @overload inherit_mask (*others):
982
1301
 
983
1302
  (Masking, Destructive)
@@ -996,7 +1315,7 @@ rb_ca_inherit_mask_method (int argc, VALUE *argv, VALUE self)
996
1315
 
997
1316
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
998
1317
 
999
- slist = malloc_with_check(sizeof(CArray *)*argc);
1318
+ slist = xmalloc(sizeof(CArray *)*argc);
1000
1319
  for (i=0; i<argc; i++) {
1001
1320
  if ( rb_obj_is_carray(argv[i]) ) {
1002
1321
  TypedData_Get_Struct(argv[i], CArray, &carray_data_type, cs);
@@ -1008,7 +1327,7 @@ rb_ca_inherit_mask_method (int argc, VALUE *argv, VALUE self)
1008
1327
  }
1009
1328
  ca_copy_mask_overlay_n(ca, ca->elements, argc, slist);
1010
1329
 
1011
- free(slist);
1330
+ xfree(slist);
1012
1331
 
1013
1332
  return self;
1014
1333
  }
@@ -1037,7 +1356,7 @@ rb_ca_inherit_mask (VALUE self, int n, ...)
1037
1356
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
1038
1357
 
1039
1358
  va_start(rothers, n);
1040
- slist = malloc_with_check(sizeof(CArray *)*n);
1359
+ slist = xmalloc(sizeof(CArray *)*n);
1041
1360
  for (i=0; i<n; i++) {
1042
1361
  other = va_arg(rothers, VALUE);
1043
1362
  if ( rb_obj_is_carray(other) ) {
@@ -1052,7 +1371,7 @@ rb_ca_inherit_mask (VALUE self, int n, ...)
1052
1371
 
1053
1372
  ca_copy_mask_overlay_n(ca, ca->elements, n, slist);
1054
1373
 
1055
- free(slist);
1374
+ xfree(slist);
1056
1375
 
1057
1376
  return self;
1058
1377
  }
@@ -1075,7 +1394,7 @@ rb_ca_inherit_mask_replace_method (int argc, VALUE *argv, VALUE self)
1075
1394
 
1076
1395
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
1077
1396
 
1078
- slist = malloc_with_check(sizeof(CArray *)*argc);
1397
+ slist = xmalloc(sizeof(CArray *)*argc);
1079
1398
  for (i=0; i<argc; i++) {
1080
1399
  if ( rb_obj_is_carray(argv[i]) ) {
1081
1400
  TypedData_Get_Struct(argv[i], CArray, &carray_data_type, cs);
@@ -1087,7 +1406,7 @@ rb_ca_inherit_mask_replace_method (int argc, VALUE *argv, VALUE self)
1087
1406
  }
1088
1407
  ca_copy_mask_overwrite_n(ca, ca->elements, argc, slist);
1089
1408
 
1090
- free(slist);
1409
+ xfree(slist);
1091
1410
 
1092
1411
  return self;
1093
1412
  }
@@ -1116,7 +1435,7 @@ rb_ca_inherit_mask_replace (VALUE self, int n, ...)
1116
1435
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
1117
1436
 
1118
1437
  va_start(rothers, n);
1119
- slist = malloc_with_check(sizeof(CArray *)*n);
1438
+ slist = xmalloc(sizeof(CArray *)*n);
1120
1439
  for (i=0; i<n; i++) {
1121
1440
  other = va_arg(rothers, VALUE);
1122
1441
  if ( rb_obj_is_carray(other) ) {
@@ -1131,13 +1450,375 @@ rb_ca_inherit_mask_replace (VALUE self, int n, ...)
1131
1450
 
1132
1451
  ca_copy_mask_overwrite_n(ca, ca->elements, n, slist);
1133
1452
 
1134
- free(slist);
1453
+ xfree(slist);
1135
1454
 
1136
1455
  return self;
1137
1456
  }
1138
1457
 
1458
+ /* Helper: build a fill-valued int64 CArray whose shape is ca's shape
1459
+ with the listed axes removed. Used by count_masked/count_not_masked
1460
+ when there is no mask. */
1461
+ static VALUE
1462
+ ca_make_reduced_int64 (CArray *ca, int argc, VALUE *argv, ca_size_t fill_val)
1463
+ {
1464
+ ca_size_t dim[CA_RANK_MAX];
1465
+ int8_t ndim = 0;
1466
+ int i, j;
1467
+ int64_t v = (int64_t) fill_val;
1468
+ VALUE result;
1469
+ CArray *co;
1470
+
1471
+ for (i = 0; i < ca->ndim; i++) {
1472
+ int is_axis = 0;
1473
+ for (j = 0; j < argc; j++) {
1474
+ int ax = NUM2INT(argv[j]);
1475
+ if (ax < 0) ax += ca->ndim;
1476
+ if (ax == i) { is_axis = 1; break; }
1477
+ }
1478
+ if (!is_axis) dim[ndim++] = ca->dim[i];
1479
+ }
1480
+
1481
+ result = rb_carray_new(CA_INT64, ndim, dim, 0, NULL);
1482
+ TypedData_Get_Struct(result, CArray, &carray_data_type, co);
1483
+ ca_fill(co, &v);
1484
+ return result;
1485
+ }
1486
+
1487
+ /* count_true_ki / count_false_ki -- mkkernel-generated count kernels
1488
+ (carray_kernels.c, bind_ruby: false; output CA_INT64). Used to reduce
1489
+ the boolean mask along axes in C for count_masked / count_not_masked,
1490
+ so the axis path needs no Ruby accumulate trampoline. */
1491
+ extern VALUE rb_ca_count_true_ki (int argc, VALUE *argv, VALUE self);
1492
+ extern VALUE rb_ca_count_false_ki (int argc, VALUE *argv, VALUE self);
1493
+
1494
+ /* count_masked(*axis) -- number of masked elements, optionally reduced
1495
+ along axes (no args -> Integer; axis args -> int64 CArray with those
1496
+ axes collapsed).
1497
+
1498
+ Public (non-static) linkage so rb_ca_count in carray_count.c can forward
1499
+ count(UNDEF) here directly without going through Ruby method dispatch. */
1500
+ VALUE
1501
+ rb_ca_count_masked (int argc, VALUE *argv, VALUE self)
1502
+ {
1503
+ CArray *ca;
1504
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
1505
+
1506
+ /* Accepts the axis: kwarg only (positional axis is not accepted),
1507
+ matching the aggregate _ki family. */
1508
+ VALUE kw_hash, axis_val = Qnil;
1509
+ rb_scan_args(argc, argv, "0:", &kw_hash);
1510
+ rb_scan_options(kw_hash, "axis", &axis_val);
1511
+
1512
+ /* No mask -> nothing is masked. Short-circuit without materialising a
1513
+ zero mask (axis form returns a zero-filled reduced int64 array). */
1514
+ if (!ca_has_mask(ca)) {
1515
+ if (NIL_P(axis_val)) {
1516
+ return SIZE2NUM(0);
1517
+ }
1518
+ VALUE axis_ary = (TYPE(axis_val) == T_ARRAY) ? axis_val : rb_ary_new3(1, axis_val);
1519
+ return ca_make_reduced_int64(ca, (int) RARRAY_LEN(axis_ary),
1520
+ (VALUE *) RARRAY_CONST_PTR(axis_ary), 0);
1521
+ }
1522
+
1523
+ /* Masked count = count of `true` in the mask, routed through the
1524
+ count_true_ki kernel for both the flat (no axis -> scalar Integer) and
1525
+ per-axis (-> reduced CArray) forms. The scalar C primitive
1526
+ ca_count_masked stays separately for internal C callers (grid /
1527
+ median / order) that need a raw ca_size_t without Ruby allocation. */
1528
+ VALUE mask_obj = rb_ca_mask_array(self);
1529
+ if (NIL_P(axis_val)) {
1530
+ return rb_ca_count_true_ki(0, NULL, mask_obj);
1531
+ }
1532
+ VALUE kw = rb_hash_new();
1533
+ rb_hash_aset(kw, ID2SYM(rb_intern("axis")), axis_val);
1534
+ VALUE kargv[1] = { kw };
1535
+ return rb_ca_count_true_ki(1, kargv, mask_obj);
1536
+ }
1537
+
1538
+ /* @overload count_not_masked(*axis)
1539
+
1540
+ (Masking, Statistics)
1541
+ Returns the number of not-masked elements, optionally reduced along axes.
1542
+ With no arguments returns an Integer. With axis arguments returns a
1543
+ CArray of int64 with the specified axes collapsed.
1544
+ */
1545
+ /* C-callable entry: skip rb_scan_args (call-frame state dependency).
1546
+ * axis_val = Qnil -> scalar Integer count over whole array
1547
+ * axis_val = Integer/Array -> reduced CArray int64
1548
+ * For ext authors who need to call count_not_masked from C. */
1549
+ VALUE
1550
+ rb_ca_count_not_masked_c (VALUE self, VALUE axis_val)
1551
+ {
1552
+ CArray *ca;
1553
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
1554
+
1555
+ /* No mask -> every element is not-masked. Short-circuit without
1556
+ materialising a mask (no axis -> elements; axis form -> reduced int64
1557
+ filled with the product of the reduced-axis extents). */
1558
+ if (!ca_has_mask(ca)) {
1559
+ if (NIL_P(axis_val)) {
1560
+ return SIZE2NUM(ca->elements);
1561
+ }
1562
+ VALUE axis_ary = (TYPE(axis_val) == T_ARRAY) ? axis_val : rb_ary_new3(1, axis_val);
1563
+ int n_axes = (int) RARRAY_LEN(axis_ary);
1564
+ VALUE *axes_argv = (VALUE *) RARRAY_CONST_PTR(axis_ary);
1565
+ ca_size_t axis_vol = 1;
1566
+ int j;
1567
+ for (j = 0; j < n_axes; j++) {
1568
+ int ax = NUM2INT(axes_argv[j]);
1569
+ if (ax < 0) ax += ca->ndim;
1570
+ axis_vol *= ca->dim[ax];
1571
+ }
1572
+ return ca_make_reduced_int64(ca, n_axes, axes_argv, axis_vol);
1573
+ }
1574
+
1575
+ /* Not-masked count = count of `false` in the mask, routed through the
1576
+ count_false_ki kernel for both the flat (no axis -> scalar Integer) and
1577
+ per-axis (-> reduced CArray) forms. The scalar C primitive
1578
+ ca_count_not_masked (= elements - ca_count_masked) stays separately for
1579
+ internal C callers (median / order) that need a raw ca_size_t. */
1580
+ VALUE mask_obj = rb_ca_mask_array(self);
1581
+ if (NIL_P(axis_val)) {
1582
+ return rb_ca_count_false_ki(0, NULL, mask_obj);
1583
+ }
1584
+ VALUE kw = rb_hash_new();
1585
+ rb_hash_aset(kw, ID2SYM(rb_intern("axis")), axis_val);
1586
+ VALUE kargv[1] = { kw };
1587
+ return rb_ca_count_false_ki(1, kargv, mask_obj);
1588
+ }
1589
+
1590
+ /* Ruby binding entry: parses (axis: kwarg) -> forwards to _c. */
1591
+ VALUE
1592
+ rb_ca_count_not_masked (int argc, VALUE *argv, VALUE self)
1593
+ {
1594
+ /* Accepts the axis: kwarg only. */
1595
+ VALUE kw_hash, axis_val = Qnil;
1596
+ rb_scan_args(argc, argv, "0:", &kw_hash);
1597
+ rb_scan_options(kw_hash, "axis", &axis_val);
1598
+ return rb_ca_count_not_masked_c(self, axis_val);
1599
+ }
1600
+
1601
+ /* ============================================================================
1602
+ * boolean8_t bit-op fast paths (table slot patching)
1603
+ *
1604
+ * Strategy: ext/carray_math.c is GENERATED (= .gitignored, regen'd from
1605
+ * ext/carray_math.rb). Hand-editing the generated functions would be
1606
+ * overwritten by `ruby carray_math.rb`. Instead we:
1607
+ *
1608
+ * 1. Define standalone boolean8_t-only fast functions here in
1609
+ * carray_mask.c (= hand-written, regen-safe).
1610
+ * 2. Patch the generated dispatch tables at Init time:
1611
+ * ca_binop_bit_and_i[CA_BOOLEAN] = ca_binop_bit_and_i_boolean8_fast;
1612
+ * (and similarly for _or_i, _xor_i, and ca_monop_bit_neg)
1613
+ * 3. Init_carray_mask_fast_paths() is called AFTER Init_carray_math()
1614
+ * from ruby_carray.c so the patch sticks.
1615
+ *
1616
+ * Each fast function handles BOTH paths internally:
1617
+ * fast: m == NULL && i1 == i2 == i3 == 1 (contig + no mask)
1618
+ * → word loop on uint64_t + tail byte fallback
1619
+ * slow: anything else (mask present, or non-contig view, etc.)
1620
+ * → duplicate of the original generated byte loop
1621
+ *
1622
+ * The slow path is bit-identical to the generated function's body so
1623
+ * hooking is transparent — no semantic change, only fast-path addition.
1624
+ * ============================================================================ */
1625
+
1626
+ /* Generated tables to be patched (defined non-static in carray_math.c). */
1627
+ extern ca_binop_func_t ca_binop_bit_and_i[];
1628
+ extern ca_binop_func_t ca_binop_bit_or_i[];
1629
+ extern ca_binop_func_t ca_binop_bit_xor_i[];
1630
+ extern ca_monop_func_t ca_monop_bit_neg[];
1631
+ /* Note: Ruby `~` is registered as an alias of `bit_neg` (carray_math.c:
1632
+ rb_define_alias(rb_cCArray, "~", "bit_neg")); the table is
1633
+ ca_monop_bit_neg. */
1634
+
1635
+ /* ----- bit_and_i (boolean8_t fast) ----- */
1636
+ static void
1637
+ ca_binop_bit_and_i_boolean8_fast (ca_size_t n, boolean8_t *m,
1638
+ char *ptr1, ca_size_t i1,
1639
+ char *ptr2, ca_size_t i2,
1640
+ char *ptr3, ca_size_t i3)
1641
+ {
1642
+ boolean8_t *q1 = (boolean8_t *) ptr1;
1643
+ boolean8_t *q2 = (boolean8_t *) ptr2;
1644
+ boolean8_t *q3 = (boolean8_t *) ptr3;
1645
+ boolean8_t *p1, *p2, *p3;
1646
+ ca_size_t k;
1647
+
1648
+ if ( m ) {
1649
+ /* mask path: existing byte loop (skip masked positions) */
1650
+ boolean8_t *pm;
1651
+ for (k = 0; k < n; k++) {
1652
+ pm = m + k;
1653
+ if ( ! *pm ) {
1654
+ p1 = q1 + k*i1;
1655
+ p2 = q2 + k*i2;
1656
+ p3 = q3 + k*i3;
1657
+ (*p3) = (*p1) & (*p2);
1658
+ }
1659
+ }
1660
+ }
1661
+ else if ( i1 == 1 && i2 == 1 && i3 == 1 ) {
1662
+ /* fast path: word-bulk AND */
1663
+ ca_mask_word_and(q3, q1, q2, n);
1664
+ }
1665
+ else {
1666
+ /* non-contig view: existing byte loop */
1667
+ for (k = 0; k < n; k++) {
1668
+ p1 = q1 + k*i1;
1669
+ p2 = q2 + k*i2;
1670
+ p3 = q3 + k*i3;
1671
+ (*p3) = (*p1) & (*p2);
1672
+ }
1673
+ }
1674
+ }
1675
+
1676
+ /* ----- bit_or_i (boolean8_t fast) ----- */
1677
+ static void
1678
+ ca_binop_bit_or_i_boolean8_fast (ca_size_t n, boolean8_t *m,
1679
+ char *ptr1, ca_size_t i1,
1680
+ char *ptr2, ca_size_t i2,
1681
+ char *ptr3, ca_size_t i3)
1682
+ {
1683
+ boolean8_t *q1 = (boolean8_t *) ptr1;
1684
+ boolean8_t *q2 = (boolean8_t *) ptr2;
1685
+ boolean8_t *q3 = (boolean8_t *) ptr3;
1686
+ boolean8_t *p1, *p2, *p3;
1687
+ ca_size_t k;
1688
+
1689
+ if ( m ) {
1690
+ boolean8_t *pm;
1691
+ for (k = 0; k < n; k++) {
1692
+ pm = m + k;
1693
+ if ( ! *pm ) {
1694
+ p1 = q1 + k*i1;
1695
+ p2 = q2 + k*i2;
1696
+ p3 = q3 + k*i3;
1697
+ (*p3) = (*p1) | (*p2);
1698
+ }
1699
+ }
1700
+ }
1701
+ else if ( i1 == 1 && i2 == 1 && i3 == 1 ) {
1702
+ /* fast path: inline 3-way word OR (ca_mask_word_or is 2-arg
1703
+ accumulating; here we need 3-arg). */
1704
+ ca_size_t nw = n / CA_MASK_WORD_BYTES;
1705
+ ca_size_t j;
1706
+ uint64_t *wd = (uint64_t *) q3;
1707
+ const uint64_t *wa = (const uint64_t *) q1;
1708
+ const uint64_t *wb = (const uint64_t *) q2;
1709
+ for (j = 0; j < nw; j++) {
1710
+ wd[j] = wa[j] | wb[j];
1711
+ }
1712
+ for (j = nw * CA_MASK_WORD_BYTES; j < n; j++) {
1713
+ q3[j] = q1[j] | q2[j];
1714
+ }
1715
+ }
1716
+ else {
1717
+ for (k = 0; k < n; k++) {
1718
+ p1 = q1 + k*i1;
1719
+ p2 = q2 + k*i2;
1720
+ p3 = q3 + k*i3;
1721
+ (*p3) = (*p1) | (*p2);
1722
+ }
1723
+ }
1724
+ }
1725
+
1726
+ /* ----- bit_xor_i (boolean8_t fast) -----
1727
+ Original generated form for boolean8_t uses `(*p3) = ((*p1) != (*p2)) ? 1 : 0`
1728
+ (logical XOR for {0,1}). Word XOR is equivalent under invariant {0,1}:
1729
+ bytewise XOR of {0,1} stays in {0,1}. */
1730
+ static void
1731
+ ca_binop_bit_xor_i_boolean8_fast (ca_size_t n, boolean8_t *m,
1732
+ char *ptr1, ca_size_t i1,
1733
+ char *ptr2, ca_size_t i2,
1734
+ char *ptr3, ca_size_t i3)
1735
+ {
1736
+ boolean8_t *q1 = (boolean8_t *) ptr1;
1737
+ boolean8_t *q2 = (boolean8_t *) ptr2;
1738
+ boolean8_t *q3 = (boolean8_t *) ptr3;
1739
+ boolean8_t *p1, *p2, *p3;
1740
+ ca_size_t k;
1741
+
1742
+ if ( m ) {
1743
+ boolean8_t *pm;
1744
+ for (k = 0; k < n; k++) {
1745
+ pm = m + k;
1746
+ if ( ! *pm ) {
1747
+ p1 = q1 + k*i1;
1748
+ p2 = q2 + k*i2;
1749
+ p3 = q3 + k*i3;
1750
+ (*p3) = ((*p1) != (*p2)) ? 1 : 0;
1751
+ }
1752
+ }
1753
+ }
1754
+ else if ( i1 == 1 && i2 == 1 && i3 == 1 ) {
1755
+ /* fast path: word XOR */
1756
+ ca_mask_word_xor(q3, q1, q2, n);
1757
+ }
1758
+ else {
1759
+ for (k = 0; k < n; k++) {
1760
+ p1 = q1 + k*i1;
1761
+ p2 = q2 + k*i2;
1762
+ p3 = q3 + k*i3;
1763
+ (*p3) = ((*p1) != (*p2)) ? 1 : 0;
1764
+ }
1765
+ }
1766
+ }
1767
+
1768
+ /* ----- bit_neg (= Ruby `~`, monop) (boolean8_t fast) -----
1769
+ The generated boolean8_t variant uses `(*p2) = (*p1) ? 0 : 1` (logical
1770
+ inversion via ternary), NOT bitwise `~`. No caller expects byte `~`
1771
+ semantics for a boolean array, and the LSB XOR pattern
1772
+ (= ca_mask_word_not) is fully equivalent while preserving {0,1}. */
1773
+ static void
1774
+ ca_monop_bit_neg_boolean8_fast (ca_size_t n, boolean8_t *m,
1775
+ char *ptr1, ca_size_t i1,
1776
+ char *ptr2, ca_size_t i2)
1777
+ {
1778
+ boolean8_t *q1 = (boolean8_t *) ptr1;
1779
+ boolean8_t *q2 = (boolean8_t *) ptr2;
1780
+ boolean8_t *p1, *p2;
1781
+ ca_size_t k;
1782
+
1783
+ if ( m ) {
1784
+ boolean8_t *pm;
1785
+ for (k = 0; k < n; k++) {
1786
+ pm = m + k;
1787
+ if ( ! *pm ) {
1788
+ p1 = q1 + k*i1;
1789
+ p2 = q2 + k*i2;
1790
+ (*p2) = (*p1) ? 0 : 1;
1791
+ }
1792
+ }
1793
+ }
1794
+ else if ( i1 == 1 && i2 == 1 ) {
1795
+ /* fast path: word NOT (LSB XOR) */
1796
+ ca_mask_word_not(q2, q1, n);
1797
+ }
1798
+ else {
1799
+ for (k = 0; k < n; k++) {
1800
+ p1 = q1 + k*i1;
1801
+ p2 = q2 + k*i2;
1802
+ (*p2) = (*p1) ? 0 : 1;
1803
+ }
1804
+ }
1805
+ }
1806
+
1807
+ /* Init_carray_mask_fast_paths — patch generated dispatch tables.
1808
+ Must be called AFTER Init_carray_math (table is set up there).
1809
+ Wired in ruby_carray.c. */
1139
1810
  void
1140
- Init_carray_mask ()
1811
+ Init_carray_mask_fast_paths (void)
1812
+ {
1813
+ ca_binop_bit_and_i[CA_BOOLEAN] = ca_binop_bit_and_i_boolean8_fast;
1814
+ ca_binop_bit_or_i [CA_BOOLEAN] = ca_binop_bit_or_i_boolean8_fast;
1815
+ ca_binop_bit_xor_i[CA_BOOLEAN] = ca_binop_bit_xor_i_boolean8_fast;
1816
+ ca_monop_bit_neg [CA_BOOLEAN] = ca_monop_bit_neg_boolean8_fast;
1817
+ }
1818
+
1819
+
1820
+ void
1821
+ Init_carray_mask (void)
1141
1822
  {
1142
1823
  rb_define_private_method(rb_cCArray, "__create_mask__", rb_ca_create_mask, 0);
1143
1824
  /*
@@ -1154,16 +1835,29 @@ Init_carray_mask ()
1154
1835
  rb_define_method(rb_cCArray, "is_masked", rb_ca_is_masked, 0);
1155
1836
  rb_define_method(rb_cCArray, "is_not_masked", rb_ca_is_not_masked, 0);
1156
1837
  rb_define_method(rb_cCArray, "unmask", rb_ca_unmask_method, -1);
1157
- rb_define_method(rb_cCArray, "unmask_copy", rb_ca_unmask_copy_method, -1);
1838
+
1839
+ /* unmask_copy is not offered (removed in 3.0), no alias. Migration:
1840
+ ca.unmask_copy() -> ca.value.to_ca (mask structure dissociated)
1841
+ ca.unmask_copy(fill) -> ca.strip_mask(fill)
1842
+ The internal C function ca_unmask_copy / rb_ca_mask_fill_copy stays
1843
+ since strip_mask wraps it. */
1844
+
1845
+ /* strip_mask(fill) is the return form of "produce a mask-free copy
1846
+ filled with fill at previously-masked positions". Fill is mandatory
1847
+ (arity 1, no default), matching the "all arguments mandatory"
1848
+ regularity of the mask SET family. */
1849
+ rb_define_method(rb_cCArray, "strip_mask", rb_ca_mask_fill_copy, 1);
1158
1850
  rb_define_method(rb_cCArray, "invert_mask", rb_ca_invert_mask, 0);
1851
+ rb_define_singleton_method(rb_cCArray, "guard_undef", rb_ca_s_guard_undef, -1);
1852
+ rb_define_method(rb_cCArray, "mask_eq", rb_ca_mask_eq, 1);
1853
+ rb_define_method(rb_cCArray, "mask_invalid", rb_ca_mask_invalid, 0);
1854
+ rb_define_method(rb_cCArray, "mask_where", rb_ca_mask_where, -1);
1159
1855
 
1160
1856
  rb_define_method(rb_cCArray, "inherit_mask", rb_ca_inherit_mask_method, -1);
1161
- rb_define_method(rb_cCArray, "inherit_mask_replace",
1857
+ rb_define_method(rb_cCArray, "inherit_mask_replace",
1162
1858
  rb_ca_inherit_mask_replace_method, -1);
1163
1859
 
1164
- /* These methods go to lib/carray/mask.rb. */
1165
- /* def count_masked (*axis); end */
1166
- /* def count_not_masked (*axis); end */
1167
-
1860
+ rb_define_method(rb_cCArray, "count_masked", rb_ca_count_masked, -1);
1861
+ rb_define_method(rb_cCArray, "count_not_masked", rb_ca_count_not_masked, -1);
1168
1862
  }
1169
1863