carray 2.0.1 → 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 +772 -297
  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 +229 -791
  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 +207 -240
  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 -329
  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 +1341 -198
  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 +5 -15
  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/mailmap +0 -1
  269. data/misc/Methods.ja.md +0 -182
  270. data/misc/NOTE +0 -51
  271. data/spec/Classes/CABitfield_spec.rb +0 -58
  272. data/spec/Classes/CABlockIterator_spec.rb +0 -114
  273. data/spec/Classes/CABlock_spec.rb +0 -205
  274. data/spec/Classes/CAField_spec.rb +0 -39
  275. data/spec/Classes/CAGrid_spec.rb +0 -75
  276. data/spec/Classes/CAMap_spec.rb +0 -0
  277. data/spec/Classes/CAMapping_spec.rb +0 -105
  278. data/spec/Classes/CAObject_attribute_spec.rb +0 -33
  279. data/spec/Classes/CAObject_spec.rb +0 -33
  280. data/spec/Classes/CARefer_spec.rb +0 -93
  281. data/spec/Classes/CARepeat_spec.rb +0 -65
  282. data/spec/Classes/CASelect_spec.rb +0 -22
  283. data/spec/Classes/CAShift_spec.rb +0 -16
  284. data/spec/Classes/CAStruct_spec.rb +0 -71
  285. data/spec/Classes/CATranspose_spec.rb +0 -60
  286. data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
  287. data/spec/Classes/CAWindow_spec.rb +0 -54
  288. data/spec/Classes/CAWrap_spec.rb +0 -8
  289. data/spec/Classes/CArray_spec.rb +0 -184
  290. data/spec/Classes/CScalar_spec.rb +0 -55
  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
@@ -0,0 +1,892 @@
1
+ /* ---------------------------------------------------------------------------
2
+
3
+ Lazy binadic element-wise arithmetic view: holds (left, right,
4
+ op_id) and materialises the promoted result on attach. Carries
5
+ CA_FLAG_READ_ONLY; there is no bang path and `[]=` raises.
6
+
7
+ Sibling of ca_obj_monop.c (unary arithmetic) and ca_obj_bincmp.c
8
+ (comparison → boolean); dispatched by ca_binop_kernel_lookup_vv
9
+ from ca_binop_dispatch.h. Casts to the common data_type are
10
+ inserted as CAMonOp nodes by the public builder before the
11
+ CABinOp is constructed.
12
+
13
+ xfer_stride model:
14
+ 1. Pull left into `data` (the output buffer). Left and output
15
+ share data_type (cast-before invariant), so this is safe as
16
+ an in-place gather.
17
+ 2. Pull right into an arena scratch (per-node).
18
+ 3. Call the eager 1-D kernel with src1 == dst == data (in-place
19
+ left) and src2 == scratch.
20
+
21
+ Mask handling: non-trapping ops always pass m=NULL (SIMD fast
22
+ path). Trapping ops (integer DIV / MOD / QUO) build a per-slab
23
+ mask from `left->mask | right->mask` when either operand has a
24
+ mask, using the operand-side masks directly to avoid a
25
+ create_mask ordering dependency on the output view.
26
+
27
+ Cross-ndim promotion: intentional design rejection, not a
28
+ deferred feature. Implicit trailing-align (`(M, N) + (N,)` etc.)
29
+ is permanently excluded; callers must reshape.
30
+
31
+ ---------------------------------------------------------------------------- */
32
+
33
+ #include "carray.h"
34
+ #include "carray_internal.h" /* ca_lazy_arena_* */
35
+ #include "ca_binop_dispatch.h"
36
+ #include "ca_monop_dispatch.h" /* CA_MONOP_CAST_BASE */
37
+
38
+ /* carray_broadcast.c */
39
+ extern VALUE ca_broadcast_view (VALUE src, int8_t ndim,
40
+ ca_size_t *target_dim);
41
+
42
+ int8_t CA_OBJ_BINOP;
43
+ VALUE rb_cCABinOp;
44
+
45
+ /* CALazyMarker obj_type — collapse-on-consume marker checked by
46
+ the public builder below. */
47
+ extern int8_t CA_OBJ_LAZY_MARKER;
48
+
49
+ /* Defined in carray_lazy.c: wrap a non-CArray Ruby value as a
50
+ CScalar carrying the other operand's natural data_type. */
51
+ extern VALUE ca_lazy_wrap_scalar (VALUE other, CArray *self_ca);
52
+
53
+ /* ------------------------------------------------------------------- */
54
+ /* CABinOp struct */
55
+ /* ------------------------------------------------------------------- */
56
+
57
+ typedef struct CABinOp {
58
+ int16_t obj_type;
59
+ int8_t data_type;
60
+ int8_t ndim;
61
+ int32_t flags;
62
+ ca_size_t bytes;
63
+ ca_size_t elements;
64
+ ca_size_t *dim;
65
+ char *ptr;
66
+ CArray *mask;
67
+ char *_pool; /* framework-managed pool buffer (NULL = legacy ALLOC_N path). */
68
+ CArray *parent; /* = left (so generic walk machinery treats
69
+ left as the primary parent for marker
70
+ collapse and attach lifecycle traversal) */
71
+ uint32_t attach;
72
+ uint8_t nosync;
73
+ /* CABinOp-specific tail */
74
+ CArray *right;
75
+ uint16_t op_id;
76
+ uint8_t right_is_scalar; /* 1 ⇒ right has elements == 1, walk
77
+ with element-stride 0 (= broadcast) */
78
+ } CABinOp;
79
+
80
+ static size_t
81
+ ca_binop_dsize (const void *ap)
82
+ {
83
+ const CABinOp *ca = (const CABinOp *) ap;
84
+ return sizeof(CABinOp) + ca->ndim * sizeof(ca_size_t);
85
+ }
86
+
87
+ /* Pool framework hooks: single ndim-sized tail (dim) in the _pool
88
+ buffer. See ca_array_pool.c for the shared alloc/free discipline. */
89
+ static size_t
90
+ ca_binop_pool_bytes (int8_t ndim)
91
+ {
92
+ ca_size_t n = (ndim > 0) ? ndim : 1;
93
+ return (size_t) n * sizeof(ca_size_t);
94
+ }
95
+
96
+ static void
97
+ ca_binop_pool_init (void *ap, int8_t ndim)
98
+ {
99
+ CABinOp *ca = (CABinOp *) ap;
100
+ ca->dim = (ca_size_t *) ca->_pool;
101
+ }
102
+
103
+ const rb_data_type_t cabinop_data_type = {
104
+ .parent = &caview_data_type,
105
+ .wrap_struct_name = "CABinOp",
106
+ .function = {
107
+ .dmark = ca_mark,
108
+ .dfree = ca_free,
109
+ .dsize = ca_binop_dsize,
110
+ .dcompact = NULL
111
+ },
112
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
113
+ };
114
+
115
+ /* ------------------------------------------------------------------- */
116
+ /* setup / new / free */
117
+ /* ------------------------------------------------------------------- */
118
+
119
+ static int
120
+ ca_binop_setup (CABinOp *ca, CArray *left, CArray *right, uint16_t op_id)
121
+ {
122
+ int8_t out_dt = ca_lazy_promote_binop(op_id, left->data_type,
123
+ right->data_type);
124
+ ca_size_t out_bytes = ca_sizeof[out_dt];
125
+
126
+ ca->obj_type = CA_OBJ_BINOP;
127
+ ca->data_type = out_dt;
128
+ ca->flags = CA_FLAG_READ_ONLY;
129
+ ca->ndim = left->ndim;
130
+ ca->bytes = out_bytes;
131
+ ca->elements = left->elements;
132
+ ca->ptr = NULL;
133
+ ca->mask = NULL;
134
+ if ( ! ca->_pool ) {
135
+ ca->dim = ALLOC_N(ca_size_t, left->ndim);
136
+ }
137
+ ca->parent = left;
138
+ ca->attach = 0;
139
+ ca->nosync = 0;
140
+ ca->right = right;
141
+ ca->op_id = op_id;
142
+ /* right_is_scalar fast path: when right holds a single element,
143
+ xfer_stride pulls only that cell into scratch and walks the
144
+ kernel with element-stride 0 (broadcast). The symmetric
145
+ left_is_scalar case is currently handled by wrapping left in
146
+ a broadcast view of right's shape (correct but slower). */
147
+ ca->right_is_scalar = ( right->elements == 1 && left->elements > 1 ) ? 1 : 0;
148
+
149
+ memcpy(ca->dim, left->dim, left->ndim * sizeof(ca_size_t));
150
+
151
+ if ( ca_has_mask(left) || ca_has_mask(right) ) {
152
+ ca_create_mask(ca);
153
+ }
154
+
155
+ if ( ca_is_scalar(left) && ca_is_scalar(right) ) {
156
+ ca_set_flag(ca, CA_FLAG_SCALAR);
157
+ }
158
+
159
+ return 0;
160
+ }
161
+
162
+ CABinOp *
163
+ ca_binop_new (CArray *left, CArray *right, uint16_t op_id)
164
+ {
165
+ CABinOp *ca = (CABinOp *) ca_array_alloc(CA_OBJ_BINOP, left->ndim);
166
+ ca_binop_setup(ca, left, right, op_id);
167
+ return ca;
168
+ }
169
+
170
+ static void
171
+ free_ca_binop (void *ap)
172
+ {
173
+ CABinOp *ca = (CABinOp *) ap;
174
+ if ( ca != NULL ) {
175
+ ca_free(ca->mask);
176
+ if ( ca->_pool ) {
177
+ ca_array_free(ca); /* dim lives in _pool */
178
+ }
179
+ else {
180
+ xfree(ca->dim);
181
+ xfree(ca);
182
+ }
183
+ }
184
+ }
185
+
186
+ /* ------------------------------------------------------------------- */
187
+ /* operation function table */
188
+ /* ------------------------------------------------------------------- */
189
+
190
+ static void ca_binop_func_xfer_stride (void *ap, ca_size_t *starts,
191
+ ca_size_t *counts, ca_size_t *strides,
192
+ void *data, int dir);
193
+
194
+ static void *
195
+ ca_binop_func_clone (void *ap)
196
+ {
197
+ CABinOp *ca = (CABinOp *) ap;
198
+ return ca_binop_new(ca->parent, ca->right, ca->op_id);
199
+ }
200
+
201
+ /* Per-cell get: dispatched through the same xfer_stride machinery
202
+ with counts == 1 on every axis. Not the hot path — a hot loop
203
+ should call `.to_ca` and read the entity directly. */
204
+ static void
205
+ ca_binop_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
206
+ {
207
+ CABinOp *ca = (CABinOp *) ap;
208
+ ca_size_t starts[CA_RANK_MAX];
209
+ ca_size_t counts[CA_RANK_MAX];
210
+ ca_size_t strides[CA_RANK_MAX];
211
+ int8_t k;
212
+ ca_size_t s;
213
+
214
+ if ( dir != CA_XFER_GET ) {
215
+ rb_raise(rb_eRuntimeError, "CABinOp is read-only (xfer_index PUT)");
216
+ }
217
+
218
+ s = ca->bytes;
219
+ for ( k = ca->ndim - 1; k >= 0; k-- ) {
220
+ starts[k] = idx[k];
221
+ counts[k] = 1;
222
+ strides[k] = s;
223
+ }
224
+ ca_binop_func_xfer_stride(ca, starts, counts, strides, data, CA_XFER_GET);
225
+ }
226
+
227
+ static void
228
+ ca_binop_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
229
+ void *data, int dir)
230
+ {
231
+ CABinOp *ca = (CABinOp *) ap;
232
+ ca_size_t i;
233
+ char *out = (char *) data;
234
+
235
+ if ( dir != CA_XFER_GET ) {
236
+ rb_raise(rb_eRuntimeError, "CABinOp is read-only (xfer_addrs PUT)");
237
+ }
238
+
239
+ for ( i = 0; i < n; i++ ) {
240
+ ca_size_t idx[CA_RANK_MAX];
241
+ ca_addr2index((CArray *)ca, addrs[i], idx);
242
+ ca_binop_func_xfer_index(ca, idx, out + i * ca->bytes, CA_XFER_GET);
243
+ }
244
+ }
245
+
246
+ /* Test / observability counters bumped from the xfer_stride hot
247
+ path so specs can assert scratch acquisition (one per node,
248
+ independent of left-chain depth) and materialise vs
249
+ non-materialise behaviour without attaching the view. */
250
+ ca_size_t ca_binop_scratch_acquire_count = 0;
251
+ ca_size_t ca_binop_materialise_call_count = 0;
252
+
253
+ static void
254
+ ca_binop_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
255
+ ca_size_t *strides, void *data, int dir)
256
+ {
257
+ CABinOp *bo = (CABinOp *) ap;
258
+ ca_size_t slab_n;
259
+ int8_t k;
260
+ void *scratch;
261
+ volatile VALUE holder;
262
+ ca_size_t right_bytes;
263
+ ca_size_t right_step; /* element-stride for the kernel walk */
264
+
265
+ if ( dir != CA_XFER_GET ) {
266
+ rb_raise(rb_eRuntimeError, "CABinOp is read-only (xfer_stride PUT)");
267
+ }
268
+ ca_binop_materialise_call_count++;
269
+
270
+ slab_n = 1;
271
+ for ( k = 0; k < bo->ndim; k++ ) slab_n *= counts[k];
272
+
273
+ /* === 1. Pull left into the output buffer ===
274
+ CAREFUL: left.data_type == output.data_type by the cast-before
275
+ invariant, so the caller's `strides` (output byte layout) is
276
+ also valid for left. A mismatch would silently write past the
277
+ output row. */
278
+ ca_xfer_stride(bo->parent, starts, counts, strides, data, CA_XFER_GET);
279
+
280
+ /* === 2. Pull right into an arena scratch === */
281
+ right_bytes = bo->right->bytes;
282
+ (void) holder; /* Kept for stack-shape compatibility with the
283
+ surrounding code; arena replaces the ALLOCV. */
284
+ if ( bo->right_is_scalar ) {
285
+ /* CScalar-shaped right: pull exactly 1 element and walk it in
286
+ the kernel with element-stride 0. */
287
+ ca_size_t one_starts[CA_RANK_MAX] = {0};
288
+ ca_size_t one_counts[CA_RANK_MAX];
289
+ ca_size_t one_strides[CA_RANK_MAX];
290
+ for ( k = 0; k < bo->right->ndim; k++ ) {
291
+ one_counts[k] = 1;
292
+ one_strides[k] = right_bytes;
293
+ }
294
+ scratch = ca_lazy_arena_acquire(right_bytes);
295
+ ca_binop_scratch_acquire_count++;
296
+ ca_xfer_stride(bo->right, one_starts, one_counts, one_strides,
297
+ scratch, CA_XFER_GET);
298
+ right_step = 0;
299
+ }
300
+ else {
301
+ /* Same-shape right. Pull with the same byte layout the caller
302
+ requested for the output; right.bytes matches output.bytes
303
+ because right.data_type == out.data_type by cast-before. */
304
+ ca_size_t right_strides[CA_RANK_MAX];
305
+ ca_size_t s = right_bytes;
306
+ for ( k = bo->ndim - 1; k >= 0; k-- ) {
307
+ right_strides[k] = s;
308
+ s *= counts[k];
309
+ }
310
+ scratch = ca_lazy_arena_acquire(slab_n * right_bytes);
311
+ ca_binop_scratch_acquire_count++;
312
+ ca_xfer_stride(bo->right, starts, counts, right_strides, scratch,
313
+ CA_XFER_GET);
314
+ right_step = 1;
315
+ }
316
+
317
+ /* === 3. Apply the kernel: data (src1 == dst) op scratch (src2) → data ===
318
+ *
319
+ * Kernel signature (ext/carray.h):
320
+ * fn(n, m, ptr1=src1, i1, ptr2=src2, i2, ptr3=dst, i3)
321
+ *
322
+ * Mask handling:
323
+ * - non-trapping op → m = NULL (SIMD fast path; garbage compute
324
+ * on masked cells is allowed).
325
+ * - trapping op with any operand mask → build a per-slab OR
326
+ * mask from the operand-side masks.
327
+ *
328
+ * CAREFUL: the trapping-op mask is built from left.mask and
329
+ * right.mask directly, NOT from out->mask. The output's mask
330
+ * comes from create_mask (called separately on view
331
+ * construction), and reading it here would introduce an
332
+ * ordering dependency that reversed the invariant "the mask is
333
+ * always available before kernels run".
334
+ *
335
+ * CAREFUL: ptr1 == ptr3 == data (in-place left) is safe because
336
+ * each cell reads src1, reads src2, and writes dst atomically
337
+ * before the loop advances. */
338
+ {
339
+ boolean8_t *slab_mask = NULL;
340
+ void *mscratch = NULL;
341
+ volatile VALUE mholder = Qnil;
342
+ int is_trapping = ca_binop_is_trapping(bo->op_id, bo->data_type);
343
+
344
+ if ( is_trapping && ( ca_has_mask(bo->parent) ||
345
+ ca_has_mask(bo->right) ) ) {
346
+ /* Build a slab-shaped mask = left.slab_mask | right.slab_mask.
347
+ Pull each operand's mask (or fill with 0 if absent) into the
348
+ output-shaped layout, then OR them in place. */
349
+ ca_size_t k_;
350
+ ca_size_t right_mask_strides[CA_RANK_MAX];
351
+ (void) mholder; /* Kept for stack-shape compatibility; arena
352
+ replaces the ALLOCV path here. */
353
+
354
+ mscratch = ca_lazy_arena_acquire(slab_n);
355
+ slab_mask = (boolean8_t *) mscratch;
356
+
357
+ if ( ca_has_mask(bo->parent) ) {
358
+ ca_size_t mstrides[CA_RANK_MAX];
359
+ ca_size_t s = 1;
360
+ for ( k_ = bo->ndim - 1; k_ >= 0; k_-- ) {
361
+ mstrides[k_] = s; s *= counts[k_];
362
+ }
363
+ ca_xfer_stride(bo->parent->mask, starts, counts, mstrides,
364
+ slab_mask, CA_XFER_GET);
365
+ }
366
+ else {
367
+ memset(slab_mask, 0, slab_n);
368
+ }
369
+
370
+ if ( ca_has_mask(bo->right) ) {
371
+ boolean8_t *rm = NULL;
372
+ ca_size_t i_;
373
+ if ( bo->right_is_scalar ) {
374
+ /* CScalar right: pull the single mask bit and broadcast OR. */
375
+ ca_size_t one_starts[CA_RANK_MAX] = {0};
376
+ ca_size_t one_counts[CA_RANK_MAX];
377
+ ca_size_t one_strides[CA_RANK_MAX];
378
+ boolean8_t one_bit = 0;
379
+ for ( k_ = 0; k_ < bo->right->ndim; k_++ ) {
380
+ one_counts[k_] = 1;
381
+ one_strides[k_] = 1;
382
+ }
383
+ ca_xfer_stride(bo->right->mask, one_starts, one_counts,
384
+ one_strides, &one_bit, CA_XFER_GET);
385
+ if ( one_bit ) {
386
+ for ( i_ = 0; i_ < slab_n; i_++ ) slab_mask[i_] = 1;
387
+ }
388
+ }
389
+ else {
390
+ ca_size_t s = 1;
391
+ for ( k_ = bo->ndim - 1; k_ >= 0; k_-- ) {
392
+ right_mask_strides[k_] = s; s *= counts[k_];
393
+ }
394
+ /* Small mask-scratch for right; OR into slab_mask in
395
+ place to avoid a persistent second buffer. */
396
+ rm = (boolean8_t *) ALLOCA_N(boolean8_t, slab_n);
397
+ ca_xfer_stride(bo->right->mask, starts, counts,
398
+ right_mask_strides, rm, CA_XFER_GET);
399
+ for ( i_ = 0; i_ < slab_n; i_++ ) slab_mask[i_] |= rm[i_];
400
+ }
401
+ }
402
+ }
403
+
404
+ ca_binop_func_t fn = ca_binop_kernel_lookup_vv(bo->op_id, bo->data_type);
405
+ if ( fn == NULL ) {
406
+ if ( mscratch ) ca_lazy_arena_release(mscratch);
407
+ ca_lazy_arena_release(scratch);
408
+ rb_raise(rb_eNotImpError,
409
+ "CABinOp: kernel not implemented (op_id=%u data_type=%d)",
410
+ (unsigned) bo->op_id, (int) bo->data_type);
411
+ }
412
+ fn(slab_n, slab_mask,
413
+ (char *)data, 1, /* src1 == dst (left, in-place) */
414
+ (char *)scratch, right_step, /* src2 (right) */
415
+ (char *)data, 1); /* dst */
416
+
417
+ if ( mscratch ) ca_lazy_arena_release(mscratch);
418
+ }
419
+
420
+ ca_lazy_arena_release(scratch);
421
+ }
422
+
423
+ static void
424
+ ca_binop_func_xfer_all (void *ap, void *data, int dir)
425
+ {
426
+ CABinOp *ca = (CABinOp *) ap;
427
+ ca_size_t starts[CA_RANK_MAX];
428
+ ca_size_t native[CA_RANK_MAX];
429
+ int8_t k;
430
+ ca_size_t s = ca->bytes;
431
+ for ( k = ca->ndim - 1; k >= 0; k-- ) { native[k] = s; s *= ca->dim[k]; }
432
+ for ( k = 0; k < ca->ndim; k++ ) starts[k] = 0;
433
+ ca_xfer_stride(ca, starts, ca->dim, native, data, dir);
434
+ }
435
+
436
+ static void
437
+ ca_binop_func_allocate (void *ap)
438
+ {
439
+ CABinOp *ca = (CABinOp *) ap;
440
+ ca->ptr = xmalloc(ca_length(ca));
441
+ }
442
+
443
+ static void
444
+ ca_binop_func_attach (void *ap)
445
+ {
446
+ /* CAREFUL: call the view-specific xfer_stride directly here,
447
+ not the public ca_xfer_stride dispatcher. The dispatcher's
448
+ self-memcpy fast path detects data == ca->ptr and returns
449
+ without materialising, so the freshly-allocated buffer would
450
+ still hold garbage. */
451
+ CABinOp *ca = (CABinOp *) ap;
452
+ ca_size_t starts[CA_RANK_MAX];
453
+ ca_size_t native[CA_RANK_MAX];
454
+ int8_t k;
455
+ ca_size_t s;
456
+
457
+ ca->ptr = xmalloc(ca_length(ca));
458
+
459
+ s = ca->bytes;
460
+ for ( k = ca->ndim - 1; k >= 0; k-- ) { native[k] = s; s *= ca->dim[k]; }
461
+ for ( k = 0; k < ca->ndim; k++ ) starts[k] = 0;
462
+ ca_binop_func_xfer_stride(ca, starts, ca->dim, native, ca->ptr, CA_XFER_GET);
463
+ }
464
+
465
+ static void
466
+ ca_binop_func_sync (void *ap)
467
+ {
468
+ /* read-only */
469
+ }
470
+
471
+ static void
472
+ ca_binop_func_detach (void *ap)
473
+ {
474
+ CABinOp *ca = (CABinOp *) ap;
475
+ xfree(ca->ptr);
476
+ ca->ptr = NULL;
477
+ }
478
+
479
+ NORETURN(static void ca_binop_func_fill_data (void *ap, void *ptr));
480
+ static void
481
+ ca_binop_func_fill_data (void *ap, void *ptr)
482
+ {
483
+ rb_raise(rb_eRuntimeError, "CABinOp is read-only (fill_data)");
484
+ }
485
+
486
+ /* Build out.mask. When either parent has a mask, materialise a
487
+ freshly-allocated boolean8_t buffer. For most ops this is the blind
488
+ OR `left.mask | right.mask`.
489
+
490
+ Boolean AND / OR are three-valued (Kleene): a masked cell is resolved by
491
+ the *known* side (`unknown | true = true`, `unknown & false = false`), so
492
+ it is NOT masked in the result. This needs operand VALUES, not just
493
+ masks, so both operands are attached; the blind value the value kernel
494
+ later writes is already correct on a resolved cell because the absorbing
495
+ element (true for OR, false for AND) dominates whatever garbage sits on
496
+ the masked side. Nested trees compose: an inner boolean `&`/`|` node's
497
+ mask is already Kleene-resolved by the time an outer node reads it.
498
+
499
+ Not memory-optimal (a CARepeat-like view would share pages), but correct
500
+ and decoupled from xfer_stride ordering. */
501
+ static void
502
+ ca_binop_func_create_mask (void *ap)
503
+ {
504
+ CABinOp *bo = (CABinOp *) ap;
505
+ CArray *l = bo->parent;
506
+ CArray *r = bo->right;
507
+ boolean8_t *dst;
508
+ ca_size_t i, n;
509
+ int has_l, has_r, kleene, need_l, need_r, is_or;
510
+ boolean8_t *lm, *rm, *lv, *rv;
511
+
512
+ has_l = ca_has_mask(l);
513
+ has_r = ca_has_mask(r);
514
+ if ( ! has_l && ! has_r ) return;
515
+
516
+ kleene = ( bo->data_type == CA_BOOLEAN &&
517
+ ( bo->op_id == CA_BINOP_BIT_AND || bo->op_id == CA_BINOP_BIT_OR ) );
518
+ is_or = ( bo->op_id == CA_BINOP_BIT_OR );
519
+
520
+ bo->mask = (CArray *) carray_new(CA_BOOLEAN, bo->ndim, bo->dim, 0, NULL);
521
+ dst = (boolean8_t *) bo->mask->ptr;
522
+ n = bo->elements;
523
+
524
+ /* Kleene needs operand values even where an operand is unmasked. */
525
+ need_l = has_l || kleene;
526
+ need_r = has_r || kleene;
527
+ if ( need_l ) ca_attach(l);
528
+ if ( need_r ) ca_attach(r);
529
+
530
+ lm = has_l ? (boolean8_t *) l->mask->ptr : NULL;
531
+ rm = has_r ? (boolean8_t *) r->mask->ptr : NULL;
532
+ lv = kleene ? (boolean8_t *) l->ptr : NULL;
533
+ rv = kleene ? (boolean8_t *) r->ptr : NULL;
534
+
535
+ for ( i = 0; i < n; i++ ) {
536
+ /* right broadcast: repeat element 0 when right was a scalar. */
537
+ ca_size_t ri = bo->right_is_scalar ? 0 : i;
538
+ boolean8_t am = lm ? lm[i] : 0;
539
+ boolean8_t bm = rm ? rm[ri] : 0;
540
+ boolean8_t m = (boolean8_t)( am | bm );
541
+ if ( kleene && m ) {
542
+ boolean8_t av = lv[i], bv = rv[ri];
543
+ if ( is_or ) {
544
+ if ( ( ! am && av ) || ( ! bm && bv ) ) m = 0; /* known TRUE */
545
+ }
546
+ else {
547
+ if ( ( ! am && ! av ) || ( ! bm && ! bv ) ) m = 0; /* known FALSE */
548
+ }
549
+ }
550
+ dst[i] = m;
551
+ }
552
+
553
+ if ( need_l ) ca_detach(l);
554
+ if ( need_r ) ca_detach(r);
555
+ }
556
+
557
+ ca_operation_function_t ca_binop_func = {
558
+ -1, /* CA_OBJ_BINOP, set at install time */
559
+ CA_VIEW_ARRAY,
560
+ free_ca_binop,
561
+ ca_binop_func_clone,
562
+ ca_binop_func_allocate,
563
+ ca_binop_func_attach,
564
+ ca_binop_func_sync,
565
+ ca_binop_func_detach,
566
+ ca_binop_func_fill_data,
567
+ ca_binop_func_create_mask,
568
+ ca_binop_func_xfer_index,
569
+ ca_binop_func_xfer_addrs,
570
+ NULL, /* fold_stride: never fold — op boundary */
571
+ ca_binop_func_xfer_stride,
572
+ ca_binop_func_xfer_all,
573
+ };
574
+
575
+ /* ------------------------------------------------------------------- */
576
+ /* Public builder (= lib/carray/lazy.rb dispatch entry) */
577
+ /* ------------------------------------------------------------------- */
578
+
579
+ /* Marker collapse helper: if cary wraps a CALazyMarker, replace with
580
+ the marker's stored Ruby parent so the marker does not appear in
581
+ the constructed CABinOp tree. */
582
+ static VALUE
583
+ collapse_marker (VALUE cary)
584
+ {
585
+ CArray *ca;
586
+ if ( ! rb_obj_is_carray(cary) ) return cary;
587
+ TypedData_Get_Struct(cary, CArray, &carray_data_type, ca);
588
+ if ( ca->obj_type == CA_OBJ_LAZY_MARKER ) {
589
+ return rb_ca_parent(cary);
590
+ }
591
+ return cary;
592
+ }
593
+
594
+ /* Low-level constructor: wraps l_cary (left) and r_cary (right)
595
+ into a CABinOp. Does NOT insert cast nodes; the caller must
596
+ have promoted both operands to the common data_type. */
597
+ static VALUE
598
+ rb_ca_binop_new (VALUE l_cary, VALUE r_cary, uint16_t op_id)
599
+ {
600
+ volatile VALUE obj;
601
+ CArray *l, *r;
602
+ CABinOp *ca;
603
+ rb_check_carray_object(l_cary);
604
+ rb_check_carray_object(r_cary);
605
+ TypedData_Get_Struct(l_cary, CArray, &carray_data_type, l);
606
+ TypedData_Get_Struct(r_cary, CArray, &carray_data_type, r);
607
+ ca = ca_binop_new(l, r, op_id);
608
+ obj = ca_wrap_struct(ca);
609
+ rb_ca_set_parent(obj, l_cary);
610
+ /* Pin right via an ivar so GC keeps it alive. parent slot already
611
+ pins left. */
612
+ rb_ivar_set(obj, rb_intern("__binop_right__"), r_cary);
613
+ return obj;
614
+ }
615
+
616
+ /* Public builder: build a CABinOp tree node for op_id over
617
+ (l_cary, r_cary). Inserts cast nodes when either operand's
618
+ data_type differs from the common data_type, then resolves
619
+ same-ndim size-1 broadcast via ca_broadcast_pair. The cast
620
+ nodes are inserted BEFORE broadcast so the cast acts on the
621
+ smallest possible element count.
622
+
623
+ Broadcast scope (bound-dimension only):
624
+ same shape → no-op
625
+ same ndim + size-1 → ca_broadcast_pair wraps the size-1 axis
626
+ operand in a stride-0 CAStride
627
+ CScalar vs array → xfer_stride walks with element_step = 0
628
+ different ndim → ArgumentError
629
+
630
+ CAREFUL: the different-ndim raise is an intentional design
631
+ rejection, not a deferred feature. Implicit cross-ndim
632
+ promotion (`(M, N) + (N,)` etc.) is permanently excluded from
633
+ CArray; callers must reshape explicitly. The eager CArray path
634
+ enforces the same rule, and re-introducing a silent
635
+ trailing-align here would diverge the lazy path from it. */
636
+ VALUE
637
+ rb_ca_binop_build (VALUE l_cary, VALUE r_cary, uint16_t op_id)
638
+ {
639
+ CArray *l, *r;
640
+ int8_t l_in_dt, r_in_dt;
641
+ volatile VALUE l_resolved, r_resolved;
642
+
643
+ /* Collapse markers on both sides. */
644
+ l_resolved = collapse_marker(l_cary);
645
+ r_resolved = collapse_marker(r_cary);
646
+
647
+ /* Promote a non-CArray Ruby value to a CScalar carrying the
648
+ other side's natural data_type. */
649
+ if ( ! rb_obj_is_carray(l_resolved) ) {
650
+ rb_check_carray_object(r_resolved);
651
+ TypedData_Get_Struct(r_resolved, CArray, &carray_data_type, r);
652
+ l_resolved = ca_lazy_wrap_scalar(l_resolved, r);
653
+ }
654
+ if ( ! rb_obj_is_carray(r_resolved) ) {
655
+ rb_check_carray_object(l_resolved);
656
+ TypedData_Get_Struct(l_resolved, CArray, &carray_data_type, l);
657
+ r_resolved = ca_lazy_wrap_scalar(r_resolved, l);
658
+ }
659
+
660
+ TypedData_Get_Struct(l_resolved, CArray, &carray_data_type, l);
661
+ TypedData_Get_Struct(r_resolved, CArray, &carray_data_type, r);
662
+
663
+ /* === Step 1: insert cast nodes. Applied to each operand before
664
+ broadcast so the cast acts on the smallest element count. */
665
+ ca_binop_kernel_input_data_types(op_id, l->data_type, r->data_type,
666
+ &l_in_dt, &r_in_dt);
667
+
668
+ if ( l_in_dt != l->data_type ) {
669
+ VALUE cast_op = INT2NUM(CA_MONOP_CAST_BASE + l_in_dt);
670
+ l_resolved = rb_funcall(rb_const_get(rb_cObject, rb_intern("CAMonOp")),
671
+ rb_intern("__build__"), 2,
672
+ l_resolved, cast_op);
673
+ TypedData_Get_Struct(l_resolved, CArray, &carray_data_type, l);
674
+ }
675
+ if ( r_in_dt != r->data_type ) {
676
+ VALUE cast_op = INT2NUM(CA_MONOP_CAST_BASE + r_in_dt);
677
+ r_resolved = rb_funcall(rb_const_get(rb_cObject, rb_intern("CAMonOp")),
678
+ rb_intern("__build__"), 2,
679
+ r_resolved, cast_op);
680
+ TypedData_Get_Struct(r_resolved, CArray, &carray_data_type, r);
681
+ }
682
+
683
+ /* === Step 2: broadcast via ca_broadcast_pair (same-ndim size-1
684
+ expansion). No-op when shapes already match or either operand
685
+ is scalar; also a no-op for incompatible shapes, which the
686
+ final elements-mismatch check below catches. */
687
+ ca_broadcast_pair(&l_resolved, &r_resolved);
688
+ TypedData_Get_Struct(l_resolved, CArray, &carray_data_type, l);
689
+ TypedData_Get_Struct(r_resolved, CArray, &carray_data_type, r);
690
+
691
+ /* === Step 3: shape sanity. After broadcast_pair, accept:
692
+ (a) equal elements (matched shape, common case),
693
+ (b) right is a 1-element CScalar (xfer_stride uses
694
+ element_step = 0),
695
+ (c) left is a 1-element CScalar — swap operands when the
696
+ op commutes (ADD / MUL / BIT_*). Non-commutative +
697
+ left scalar wraps left in a broadcast view of right's
698
+ shape so the kernel sees matched shapes; cost is N
699
+ reads of the scalar cell with stride 0. */
700
+ if ( l->elements != r->elements ) {
701
+ if ( r->elements == 1 && l->elements > 1 ) {
702
+ /* CScalar-vs-array, right. xfer_stride handles this with
703
+ right_step = 0. */
704
+ }
705
+ else if ( l->elements == 1 && r->elements > 1 ) {
706
+ int commutes = ( op_id == CA_BINOP_ADD ||
707
+ op_id == CA_BINOP_MUL ||
708
+ op_id == CA_BINOP_BIT_AND ||
709
+ op_id == CA_BINOP_BIT_OR ||
710
+ op_id == CA_BINOP_BIT_XOR );
711
+ if ( commutes ) {
712
+ VALUE tmp = l_resolved; l_resolved = r_resolved; r_resolved = tmp;
713
+ }
714
+ else {
715
+ /* Non-commutative + left scalar: broadcast the left operand
716
+ (CScalar with ndim=1 dim=[1]) up to right's shape.
717
+ ca_repeat_setup signature:
718
+ count[i] > 0 → repeat (stride-0) axis of size count[i]
719
+ count[i] == 0 → data axis (inherits from parent)
720
+ Total data axes must equal parent->ndim.
721
+ For lifting a CScalar (parent.ndim=1, dim=[1]) to right's
722
+ ndim N: count = {1, 1, ..., 1, 0} (N-1 leading dummy 1s,
723
+ 1 trailing data axis), then broadcast that (1,1,...,1)
724
+ shape to right's dim via ca_broadcast_view. */
725
+ ca_size_t count[CA_RANK_MAX];
726
+ int8_t k;
727
+ if ( r->ndim > l->ndim ) {
728
+ for ( k = 0; k < r->ndim - 1; k++ ) count[k] = 1; /* dummy */
729
+ count[r->ndim - 1] = 0; /* data */
730
+ l_resolved = rb_ca_repeat_new(l_resolved, r->ndim, count);
731
+ TypedData_Get_Struct(l_resolved, CArray, &carray_data_type, l);
732
+ }
733
+ l_resolved = ca_broadcast_view(l_resolved, r->ndim, r->dim);
734
+ TypedData_Get_Struct(l_resolved, CArray, &carray_data_type, l);
735
+ }
736
+ }
737
+ else {
738
+ /* Cross-ndim or otherwise incompatible shapes; see the
739
+ builder header comment for why CArray rejects implicit
740
+ cross-ndim promotion. */
741
+ rb_raise(rb_eArgError,
742
+ "CABinOp: shape mismatch (%lld vs %lld) — only same-"
743
+ "ndim size-1 broadcast is supported; cross-ndim "
744
+ "promotion is not adopted in CArray "
745
+ "(reshape explicitly)",
746
+ (long long) l->elements, (long long) r->elements);
747
+ }
748
+ }
749
+
750
+ return rb_ca_binop_new(l_resolved, r_resolved, op_id);
751
+ }
752
+
753
+ static VALUE
754
+ rb_ca_binop_s_build (VALUE klass, VALUE l_cary, VALUE r_cary, VALUE op_id_val)
755
+ {
756
+ uint16_t op_id = (uint16_t) NUM2UINT(op_id_val);
757
+ (void) klass;
758
+ return rb_ca_binop_build(l_cary, r_cary, op_id);
759
+ }
760
+
761
+ /* op_id accessor for inspect / dump_tree consumers in Ruby. */
762
+ static VALUE
763
+ rb_ca_binop_op_id (VALUE self)
764
+ {
765
+ CABinOp *bo;
766
+ TypedData_Get_Struct(self, CABinOp, &cabinop_data_type, bo);
767
+ return UINT2NUM(bo->op_id);
768
+ }
769
+
770
+ /* Right-operand accessor — needed by inspect / dump_tree because
771
+ the right reference lives off the prefix and is not reachable
772
+ via .parent. */
773
+ static VALUE
774
+ rb_ca_binop_right (VALUE self)
775
+ {
776
+ return rb_ivar_get(self, rb_intern("__binop_right__"));
777
+ }
778
+
779
+ /* Test instrumentation (mirrors CAMonOp counters). */
780
+ static VALUE
781
+ rb_ca_binop_s_reset_scratch_counter (VALUE klass)
782
+ {
783
+ ca_binop_scratch_acquire_count = 0;
784
+ return Qnil;
785
+ }
786
+
787
+ static VALUE
788
+ rb_ca_binop_s_scratch_count (VALUE klass)
789
+ {
790
+ return SIZE2NUM(ca_binop_scratch_acquire_count);
791
+ }
792
+
793
+ static VALUE
794
+ rb_ca_binop_s_reset_materialise_counter (VALUE klass)
795
+ {
796
+ ca_binop_materialise_call_count = 0;
797
+ return Qnil;
798
+ }
799
+
800
+ static VALUE
801
+ rb_ca_binop_s_materialise_count (VALUE klass)
802
+ {
803
+ return SIZE2NUM(ca_binop_materialise_call_count);
804
+ }
805
+
806
+ static VALUE
807
+ rb_ca_binop_s_allocate (VALUE klass)
808
+ {
809
+ CABinOp *ca;
810
+ return TypedData_Make_Struct(klass, CABinOp, &cabinop_data_type, ca);
811
+ }
812
+
813
+ static VALUE
814
+ rb_ca_binop_initialize_copy (VALUE self, VALUE other)
815
+ {
816
+ CABinOp *ca, *cs;
817
+ TypedData_Get_Struct(self, CABinOp, &cabinop_data_type, ca);
818
+ TypedData_Get_Struct(other, CABinOp, &cabinop_data_type, cs);
819
+ if ( ca_func[CA_OBJ_BINOP].pool_init ) {
820
+ ca_array_pool_alloc(ca, CA_OBJ_BINOP, cs->parent->ndim);
821
+ }
822
+ ca_binop_setup(ca, cs->parent, cs->right, cs->op_id);
823
+ return self;
824
+ }
825
+
826
+ void
827
+ Init_ca_obj_binop (void)
828
+ {
829
+ rb_cCABinOp = rb_define_class("CABinOp", rb_cCAView);
830
+
831
+ ca_binop_func.struct_size = sizeof(CABinOp);
832
+ ca_binop_func.pool_bytes = ca_binop_pool_bytes;
833
+ ca_binop_func.pool_init = ca_binop_pool_init;
834
+
835
+ CA_OBJ_BINOP = ca_install_obj_type(rb_cCABinOp,
836
+ &cabinop_data_type,
837
+ rb_cCArrayMask,
838
+ &carray_mask_data_type, &ca_binop_func, sizeof(ca_binop_func));
839
+ rb_define_const(rb_cObject, "CA_OBJ_BINOP", INT2NUM(CA_OBJ_BINOP));
840
+
841
+ /* op_id constants shared with lib/carray/lazy.rb. */
842
+ rb_define_const(rb_cCABinOp, "OP_ADD", INT2NUM(CA_BINOP_ADD));
843
+ rb_define_const(rb_cCABinOp, "OP_SUB", INT2NUM(CA_BINOP_SUB));
844
+ rb_define_const(rb_cCABinOp, "OP_MUL", INT2NUM(CA_BINOP_MUL));
845
+ rb_define_const(rb_cCABinOp, "OP_DIV", INT2NUM(CA_BINOP_DIV));
846
+ rb_define_const(rb_cCABinOp, "OP_POW", INT2NUM(CA_BINOP_POW));
847
+ rb_define_const(rb_cCABinOp, "OP_BIT_AND", INT2NUM(CA_BINOP_BIT_AND));
848
+ rb_define_const(rb_cCABinOp, "OP_BIT_OR", INT2NUM(CA_BINOP_BIT_OR));
849
+ rb_define_const(rb_cCABinOp, "OP_BIT_XOR", INT2NUM(CA_BINOP_BIT_XOR));
850
+ rb_define_const(rb_cCABinOp, "OP_BIT_LSHIFT", INT2NUM(CA_BINOP_BIT_LSHIFT));
851
+ rb_define_const(rb_cCABinOp, "OP_BIT_RSHIFT", INT2NUM(CA_BINOP_BIT_RSHIFT));
852
+ rb_define_const(rb_cCABinOp, "OP_MOD", INT2NUM(CA_BINOP_MOD));
853
+ rb_define_const(rb_cCABinOp, "OP_QUO", INT2NUM(CA_BINOP_QUO));
854
+ rb_define_const(rb_cCABinOp, "OP_RCP_MUL", INT2NUM(CA_BINOP_RCP_MUL));
855
+ rb_define_const(rb_cCABinOp, "OP_IPOWER", INT2NUM(CA_BINOP_IPOWER));
856
+ rb_define_const(rb_cCABinOp, "OP_COPYSIGN", INT2NUM(CA_BINOP_COPYSIGN));
857
+ rb_define_const(rb_cCABinOp, "OP_LOGADDEXP", INT2NUM(CA_BINOP_LOGADDEXP));
858
+ rb_define_const(rb_cCABinOp, "OP_NEXTAFTER", INT2NUM(CA_BINOP_NEXTAFTER));
859
+ rb_define_const(rb_cCABinOp, "OP_FMOD", INT2NUM(CA_BINOP_FMOD));
860
+ rb_define_const(rb_cCABinOp, "OP_ATAN2", INT2NUM(CA_BINOP_ATAN2));
861
+ rb_define_const(rb_cCABinOp, "OP_HYPOT", INT2NUM(CA_BINOP_HYPOT));
862
+ rb_define_const(rb_cCABinOp, "OP_PMAX", INT2NUM(CA_BINOP_PMAX));
863
+ rb_define_const(rb_cCABinOp, "OP_PMIN", INT2NUM(CA_BINOP_PMIN));
864
+ rb_define_const(rb_cCABinOp, "OP_MAXIMUM", INT2NUM(CA_BINOP_MAXIMUM));
865
+ rb_define_const(rb_cCABinOp, "OP_MINIMUM", INT2NUM(CA_BINOP_MINIMUM));
866
+ rb_define_const(rb_cCABinOp, "OP_AND", INT2NUM(CA_BINOP_AND));
867
+ rb_define_const(rb_cCABinOp, "OP_OR", INT2NUM(CA_BINOP_OR));
868
+ rb_define_const(rb_cCABinOp, "OP_XOR", INT2NUM(CA_BINOP_XOR));
869
+ rb_define_const(rb_cCABinOp, "OP_REMINDER", INT2NUM(CA_BINOP_REMINDER));
870
+
871
+ rb_define_alloc_func(rb_cCABinOp, rb_ca_binop_s_allocate);
872
+ rb_define_method(rb_cCABinOp, "initialize_copy",
873
+ rb_ca_binop_initialize_copy, 1);
874
+
875
+ rb_define_singleton_method(rb_cCABinOp, "__build__",
876
+ rb_ca_binop_s_build, 3);
877
+
878
+ rb_define_method(rb_cCABinOp, "__op_id__",
879
+ rb_ca_binop_op_id, 0);
880
+ rb_define_method(rb_cCABinOp, "__binop_right__",
881
+ rb_ca_binop_right, 0);
882
+
883
+ /* Test instrumentation, not user-facing. */
884
+ rb_define_singleton_method(rb_cCABinOp, "__reset_scratch_counter__",
885
+ rb_ca_binop_s_reset_scratch_counter, 0);
886
+ rb_define_singleton_method(rb_cCABinOp, "__scratch_count__",
887
+ rb_ca_binop_s_scratch_count, 0);
888
+ rb_define_singleton_method(rb_cCABinOp, "__reset_materialise_counter__",
889
+ rb_ca_binop_s_reset_materialise_counter, 0);
890
+ rb_define_singleton_method(rb_cCABinOp, "__materialise_count__",
891
+ rb_ca_binop_s_materialise_count, 0);
892
+ }