carray 2.0.1 → 3.0.1

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 (386) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -25
  3. data/CHANGELOG.md +338 -0
  4. data/{NEWS.md → CHANGELOG.v1.md} +3 -0
  5. data/LICENSE +1 -1
  6. data/README.md +120 -36
  7. data/carray.gemspec +32 -30
  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 +1244 -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 +124 -0
  15. data/ext/ca_binop_dispatch.h +152 -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 +239 -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 +4367 -0
  26. data/ext/ca_kernel_iterator.h +2596 -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 +839 -0
  33. data/ext/ca_obj_binop.c +948 -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 +967 -0
  39. data/ext/ca_obj_face.c +750 -0
  40. data/ext/ca_obj_face.h +279 -0
  41. data/ext/ca_obj_fake.c +239 -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 +308 -0
  45. data/ext/ca_obj_grid.c +866 -440
  46. data/ext/ca_obj_meld.c +1039 -0
  47. data/ext/ca_obj_moncmp.c +588 -0
  48. data/ext/ca_obj_monop.c +1123 -0
  49. data/ext/ca_obj_object.c +866 -296
  50. data/ext/ca_obj_record.c +470 -0
  51. data/ext/ca_obj_reduce.c +97 -82
  52. data/ext/ca_obj_refer.c +593 -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 +624 -0
  56. data/ext/ca_obj_select.c +344 -296
  57. data/ext/ca_obj_select_axis.c +1306 -0
  58. data/ext/ca_obj_shift.c +231 -793
  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 +2584 -0
  62. data/ext/ca_obj_string.c +270 -0
  63. data/ext/ca_obj_tile.c +622 -0
  64. data/ext/ca_obj_time.c +548 -0
  65. data/ext/ca_obj_timedelta.c +437 -0
  66. data/ext/ca_obj_transpose.c +62 -516
  67. data/ext/ca_obj_triop.c +785 -0
  68. data/ext/ca_obj_window.c +1202 -565
  69. data/ext/ca_op_byte_swap.c +175 -0
  70. data/ext/ca_op_cmplx64.h +123 -0
  71. data/ext/ca_op_ipower.c +316 -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 +473 -0
  75. data/ext/ca_sweep_engine.h +166 -0
  76. data/ext/ca_transform_common.c +235 -0
  77. data/ext/ca_triop_dispatch.c +55 -0
  78. data/ext/ca_triop_dispatch.h +62 -0
  79. data/ext/carray.h +810 -420
  80. data/ext/carray_access.c +873 -731
  81. data/ext/carray_attribute.c +98 -329
  82. data/ext/carray_bincount.c +255 -0
  83. data/ext/carray_broadcast.c +376 -0
  84. data/ext/carray_build_flags.h +3 -0
  85. data/ext/carray_call_cfunc.c +2897 -874
  86. data/ext/carray_call_cfunc.h +313 -0
  87. data/ext/carray_cast.c +1264 -315
  88. data/ext/carray_cast_func.rb +81 -40
  89. data/ext/carray_class.c +53 -63
  90. data/ext/carray_config.h +28 -0
  91. data/ext/carray_conversion.c +350 -346
  92. data/ext/carray_copy.c +168 -270
  93. data/ext/carray_core.c +1396 -206
  94. data/ext/carray_count.c +312 -0
  95. data/ext/carray_data_type.c +43 -19
  96. data/ext/carray_element.c +585 -213
  97. data/ext/carray_factorize.c +2542 -0
  98. data/ext/carray_generate.c +230 -559
  99. data/ext/carray_histogram.c +490 -0
  100. data/ext/carray_hold.c +228 -0
  101. data/ext/carray_index_classifier.c +1021 -0
  102. data/ext/carray_index_classifier.h +27 -0
  103. data/ext/carray_internal.h +136 -0
  104. data/ext/carray_kernels_bincmp.c +4446 -0
  105. data/ext/carray_kernels_binop.c +11001 -0
  106. data/ext/carray_kernels_init.c +1131 -0
  107. data/ext/carray_kernels_map.c +3467 -0
  108. data/ext/carray_kernels_moncmp.c +2097 -0
  109. data/ext/carray_kernels_monop.c +18313 -0
  110. data/ext/carray_kernels_reduce_aggregate.c +25837 -0
  111. data/ext/carray_kernels_reduce_boolean.c +330 -0
  112. data/ext/carray_kernels_reduce_cumulative.c +14593 -0
  113. data/ext/carray_kernels_reduce_extreme.c +16948 -0
  114. data/ext/carray_kernels_reduce_variance.c +3910 -0
  115. data/ext/carray_kernels_scan.c +3693 -0
  116. data/ext/carray_kernels_search.c +32138 -0
  117. data/ext/carray_kernels_sort.c +10626 -0
  118. data/ext/carray_kernels_triop.c +1392 -0
  119. data/ext/carray_lazy.c +737 -0
  120. data/ext/carray_loop.c +88 -200
  121. data/ext/carray_mask.c +853 -158
  122. data/ext/carray_math_kernel.h +120 -0
  123. data/ext/carray_mathfunc.c +10 -241
  124. data/ext/carray_median_percentile.c +1257 -0
  125. data/ext/carray_memory_view.c +1650 -0
  126. data/ext/carray_operator.c +1525 -320
  127. data/ext/carray_order.c +664 -1394
  128. data/ext/carray_partition.c +416 -0
  129. data/ext/carray_random.c +518 -0
  130. data/ext/carray_scatter.c +357 -0
  131. data/ext/carray_slab.c +1219 -0
  132. data/ext/carray_slab.h +84 -0
  133. data/ext/carray_sort.c +829 -0
  134. data/ext/carray_sort_kernel.c +620 -0
  135. data/ext/carray_struct.c +695 -0
  136. data/ext/carray_test.c +343 -229
  137. data/ext/carray_undef.c +34 -17
  138. data/ext/carray_utils.c +175 -74
  139. data/ext/extconf.rb +234 -55
  140. data/ext/mk_call_cfunc.rb +671 -0
  141. data/ext/mkkernel.rb +9096 -0
  142. data/ext/ruby_carray.c +211 -108
  143. data/ext/version.h +4 -14
  144. data/ext/version.rb +5 -13
  145. data/lib/carray/arrow_tensor.rb +401 -0
  146. data/lib/carray/attribute.rb +166 -0
  147. data/lib/carray/autoload_carray.rb +239 -0
  148. data/lib/carray/autoload_method_extension.rb +45 -0
  149. data/lib/carray/axis_group.rb +711 -0
  150. data/lib/carray/basics.rb +481 -0
  151. data/lib/carray/bincount_nd.rb +358 -0
  152. data/lib/carray/block_iterator.rb +614 -0
  153. data/lib/carray/boolean_reduce.rb +109 -0
  154. data/lib/carray/categorical.rb +561 -0
  155. data/lib/carray/categorical_iterator.rb +1084 -0
  156. data/lib/carray/complex.rb +150 -0
  157. data/lib/carray/conditional.rb +216 -0
  158. data/lib/carray/const_string.rb +228 -0
  159. data/lib/carray/construct.rb +160 -328
  160. data/lib/carray/core_extensions.rb +297 -0
  161. data/lib/carray/data_type_extension.rb +250 -0
  162. data/lib/carray/fixlen_string.rb +95 -0
  163. data/lib/carray/frame/concat.rb +132 -0
  164. data/lib/carray/frame/convert.rb +95 -0
  165. data/lib/carray/frame/csv_parser.rb +211 -0
  166. data/lib/carray/frame/frame.rb +642 -0
  167. data/lib/carray/frame/group.rb +186 -0
  168. data/lib/carray/frame/io.rb +321 -0
  169. data/lib/carray/frame/join.rb +248 -0
  170. data/lib/carray/frame/records.rb +99 -0
  171. data/lib/carray/frame/sort.rb +113 -0
  172. data/lib/carray/frame/verbs.rb +316 -0
  173. data/lib/carray/frame.rb +16 -0
  174. data/lib/carray/fuse_source.rb +123 -0
  175. data/lib/carray/fusion.rb +218 -0
  176. data/lib/carray/histogram.rb +512 -0
  177. data/lib/carray/inspect.rb +37 -26
  178. data/lib/carray/iterator.rb +58 -349
  179. data/lib/carray/lazy.rb +941 -0
  180. data/lib/carray/mask_gap_fill.rb +200 -0
  181. data/lib/carray/math.rb +78 -342
  182. data/lib/carray/meld_reduce.rb +289 -0
  183. data/lib/carray/methods/align_addr.rb +116 -0
  184. data/lib/carray/methods/bin.rb +128 -0
  185. data/lib/carray/methods/bincount.rb +87 -0
  186. data/lib/carray/methods/bit_string.rb +92 -0
  187. data/lib/carray/methods/broadcast.rb +63 -0
  188. data/lib/carray/methods/choose.rb +39 -0
  189. data/lib/carray/methods/composition.rb +280 -0
  190. data/lib/carray/methods/gather_nd.rb +206 -0
  191. data/lib/carray/methods/index.rb +39 -0
  192. data/lib/carray/methods/insert_block.rb +99 -0
  193. data/lib/carray/methods/is_in.rb +141 -0
  194. data/lib/carray/methods/join.rb +90 -0
  195. data/lib/carray/methods/locate_addr.rb +52 -0
  196. data/lib/carray/methods/mask_duplicates.rb +41 -0
  197. data/lib/carray/methods/meshgrid.rb +90 -0
  198. data/lib/carray/methods/mode.rb +126 -0
  199. data/lib/carray/methods/nunique.rb +46 -0
  200. data/lib/carray/methods/resize.rb +56 -0
  201. data/lib/carray/methods/snap.rb +161 -0
  202. data/lib/carray/methods/string_format.rb +57 -0
  203. data/lib/carray/methods/unique.rb +47 -0
  204. data/lib/carray/methods/value_counts.rb +71 -0
  205. data/lib/carray/mkmf.rb +124 -101
  206. data/lib/carray/runtime.rb +89 -0
  207. data/lib/carray/serialize.rb +478 -167
  208. data/lib/carray/slab_iterator.rb +305 -0
  209. data/lib/carray/stack.rb +291 -0
  210. data/lib/carray/string.rb +56 -180
  211. data/lib/carray/string_operation_extension.rb +289 -0
  212. data/lib/carray/struct.rb +335 -323
  213. data/lib/carray/struct_builder.rb +697 -0
  214. data/lib/carray/table.rb +41 -2
  215. data/lib/carray/time.rb +2654 -38
  216. data/lib/carray/window_iterator.rb +927 -0
  217. data/lib/carray.rb +55 -57
  218. data/yard-stubs/ca_obj_array.rb +385 -0
  219. data/yard-stubs/ca_obj_bitarray.rb +38 -0
  220. data/yard-stubs/ca_obj_bitfield.rb +43 -0
  221. data/yard-stubs/ca_obj_block.rb +73 -0
  222. data/yard-stubs/ca_obj_byte_swap.rb +56 -0
  223. data/yard-stubs/ca_obj_fake.rb +31 -0
  224. data/yard-stubs/ca_obj_farray.rb +32 -0
  225. data/yard-stubs/ca_obj_field.rb +45 -0
  226. data/yard-stubs/ca_obj_grid.rb +35 -0
  227. data/yard-stubs/ca_obj_refer.rb +72 -0
  228. data/yard-stubs/ca_obj_roll.rb +45 -0
  229. data/yard-stubs/ca_obj_shift.rb +43 -0
  230. data/yard-stubs/ca_obj_stride.rb +181 -0
  231. data/yard-stubs/ca_obj_tile.rb +29 -0
  232. data/yard-stubs/ca_obj_transpose.rb +40 -0
  233. data/yard-stubs/ca_obj_window.rb +49 -0
  234. data/yard-stubs/carray_access.rb +131 -0
  235. data/yard-stubs/carray_attribute.rb +246 -0
  236. data/yard-stubs/carray_broadcast.rb +37 -0
  237. data/yard-stubs/carray_cast.rb +489 -0
  238. data/yard-stubs/carray_class.rb +65 -0
  239. data/yard-stubs/carray_conversion.rb +76 -0
  240. data/yard-stubs/carray_copy.rb +79 -0
  241. data/yard-stubs/carray_core.rb +114 -0
  242. data/yard-stubs/carray_count.rb +79 -0
  243. data/yard-stubs/carray_element.rb +108 -0
  244. data/yard-stubs/carray_generate.rb +66 -0
  245. data/yard-stubs/carray_lazy.rb +23 -0
  246. data/yard-stubs/carray_loop.rb +140 -0
  247. data/yard-stubs/carray_mask.rb +259 -0
  248. data/yard-stubs/carray_math.rb +132 -0
  249. data/yard-stubs/carray_mathfunc.rb +45 -0
  250. data/yard-stubs/carray_median_percentile.rb +89 -0
  251. data/yard-stubs/carray_memory_view.rb +163 -0
  252. data/yard-stubs/carray_order.rb +312 -0
  253. data/yard-stubs/carray_random.rb +89 -0
  254. data/yard-stubs/carray_scatter.rb +106 -0
  255. data/yard-stubs/carray_slab.rb +57 -0
  256. data/yard-stubs/carray_sort.rb +163 -0
  257. data/yard-stubs/carray_test.rb +85 -0
  258. data/yard-stubs/carray_undef.rb +64 -0
  259. data/yard-stubs/carray_utils.rb +97 -0
  260. data/yard-stubs/ruby_carray.rb +193 -0
  261. metadata +220 -138
  262. data/Rakefile +0 -51
  263. data/TODO.md +0 -18
  264. data/ext/ca_iter_block.c +0 -257
  265. data/ext/ca_iter_dimension.c +0 -299
  266. data/ext/ca_iter_window.c +0 -214
  267. data/ext/ca_obj_mapping.c +0 -644
  268. data/ext/ca_obj_unbound_repeat.c +0 -529
  269. data/ext/carray_iterator.c +0 -641
  270. data/ext/carray_math.rb +0 -850
  271. data/ext/carray_numeric.c +0 -259
  272. data/ext/carray_sort_addr.c +0 -254
  273. data/ext/carray_stat.c +0 -2100
  274. data/ext/carray_stat_proc.rb +0 -1999
  275. data/ext/mkmath.rb +0 -741
  276. data/ext/ruby_ccomplex.c +0 -509
  277. data/ext/ruby_float_func.c +0 -86
  278. data/lib/carray/array.rb +0 -8
  279. data/lib/carray/autoload/autoload_base.rb +0 -19
  280. data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
  281. data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
  282. data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
  283. data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
  284. data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
  285. data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
  286. data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
  287. data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
  288. data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
  289. data/lib/carray/autoload/autoload_gem_random.rb +0 -8
  290. data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
  291. data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
  292. data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
  293. data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
  294. data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
  295. data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
  296. data/lib/carray/autoload/autoload_object_link.rb +0 -1
  297. data/lib/carray/autoload/autoload_object_pack.rb +0 -2
  298. data/lib/carray/autoload.rb +0 -141
  299. data/lib/carray/basic.rb +0 -191
  300. data/lib/carray/broadcast.rb +0 -101
  301. data/lib/carray/compose.rb +0 -315
  302. data/lib/carray/convert.rb +0 -115
  303. data/lib/carray/info.rb +0 -110
  304. data/lib/carray/io/imagemagick.rb +0 -235
  305. data/lib/carray/mask.rb +0 -102
  306. data/lib/carray/math/histogram.rb +0 -177
  307. data/lib/carray/math/recurrence.rb +0 -93
  308. data/lib/carray/object/ca_obj_iterator.rb +0 -50
  309. data/lib/carray/object/ca_obj_link.rb +0 -50
  310. data/lib/carray/object/ca_obj_pack.rb +0 -99
  311. data/lib/carray/obsolete.rb +0 -256
  312. data/lib/carray/ordering.rb +0 -181
  313. data/lib/carray/testing.rb +0 -51
  314. data/lib/carray/transform.rb +0 -109
  315. data/mailmap +0 -1
  316. data/misc/Methods.ja.md +0 -182
  317. data/misc/NOTE +0 -51
  318. data/spec/Classes/CABitfield_spec.rb +0 -58
  319. data/spec/Classes/CABlockIterator_spec.rb +0 -114
  320. data/spec/Classes/CABlock_spec.rb +0 -205
  321. data/spec/Classes/CAField_spec.rb +0 -39
  322. data/spec/Classes/CAGrid_spec.rb +0 -75
  323. data/spec/Classes/CAMap_spec.rb +0 -0
  324. data/spec/Classes/CAMapping_spec.rb +0 -105
  325. data/spec/Classes/CAObject_attribute_spec.rb +0 -33
  326. data/spec/Classes/CAObject_spec.rb +0 -33
  327. data/spec/Classes/CARefer_spec.rb +0 -93
  328. data/spec/Classes/CARepeat_spec.rb +0 -65
  329. data/spec/Classes/CASelect_spec.rb +0 -22
  330. data/spec/Classes/CAShift_spec.rb +0 -16
  331. data/spec/Classes/CAStruct_spec.rb +0 -71
  332. data/spec/Classes/CATranspose_spec.rb +0 -60
  333. data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
  334. data/spec/Classes/CAWindow_spec.rb +0 -54
  335. data/spec/Classes/CAWrap_spec.rb +0 -8
  336. data/spec/Classes/CArray_spec.rb +0 -184
  337. data/spec/Classes/CScalar_spec.rb +0 -55
  338. data/spec/Features/feature_130_spec.rb +0 -19
  339. data/spec/Features/feature_attributes_spec.rb +0 -280
  340. data/spec/Features/feature_boolean_spec.rb +0 -98
  341. data/spec/Features/feature_broadcast.rb +0 -116
  342. data/spec/Features/feature_cast_function.rb +0 -19
  343. data/spec/Features/feature_cast_spec.rb +0 -33
  344. data/spec/Features/feature_class_spec.rb +0 -84
  345. data/spec/Features/feature_complex_spec.rb +0 -42
  346. data/spec/Features/feature_composite_spec.rb +0 -124
  347. data/spec/Features/feature_convert_spec.rb +0 -46
  348. data/spec/Features/feature_copy_spec.rb +0 -123
  349. data/spec/Features/feature_creation_spec.rb +0 -84
  350. data/spec/Features/feature_element_spec.rb +0 -144
  351. data/spec/Features/feature_extream_spec.rb +0 -54
  352. data/spec/Features/feature_generate_spec.rb +0 -74
  353. data/spec/Features/feature_index_spec.rb +0 -69
  354. data/spec/Features/feature_mask_spec.rb +0 -580
  355. data/spec/Features/feature_math_spec.rb +0 -97
  356. data/spec/Features/feature_order_spec.rb +0 -146
  357. data/spec/Features/feature_ref_store_spec.rb +0 -209
  358. data/spec/Features/feature_serialization_spec.rb +0 -125
  359. data/spec/Features/feature_stat_spec.rb +0 -397
  360. data/spec/Features/feature_virtual_spec.rb +0 -48
  361. data/spec/Features/method_eq_spec.rb +0 -81
  362. data/spec/Features/method_is_nan_spec.rb +0 -12
  363. data/spec/Features/method_map_spec.rb +0 -54
  364. data/spec/Features/method_max_with.rb +0 -20
  365. data/spec/Features/method_min_with.rb +0 -19
  366. data/spec/Features/method_ne_spec.rb +0 -18
  367. data/spec/Features/method_project_spec.rb +0 -188
  368. data/spec/Features/method_ref_spec.rb +0 -27
  369. data/spec/Features/method_round_spec.rb +0 -11
  370. data/spec/Features/method_s_linspace_spec.rb +0 -48
  371. data/spec/Features/method_s_span_spec.rb +0 -14
  372. data/spec/Features/method_seq_spec.rb +0 -47
  373. data/spec/Features/method_sort_with.rb +0 -43
  374. data/spec/Features/method_sorted_with.rb +0 -29
  375. data/spec/Features/method_span_spec.rb +0 -42
  376. data/spec/Features/method_wrap_readonly_spec.rb +0 -43
  377. data/spec/UnitTest/test_CAVirtual.rb +0 -214
  378. data/spec/spec_all.rb +0 -10
  379. data/utils/ca_ase.rb +0 -21
  380. data/utils/ca_methods.rb +0 -15
  381. data/utils/cast_checker.rb +0 -30
  382. data/utils/convert_test.rb +0 -73
  383. data/utils/extract_yard.rb +0 -22
  384. data/utils/guess_shape.rb +0 -76
  385. data/utils/monkey_patch_methods.rb +0 -62
  386. data/utils/remove_resource_fork.sh +0 -5
@@ -0,0 +1,948 @@
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
+ /* ---- CAMultiParent conformance (CA_FLAG_MULTI_PARENTS): n_parents and
74
+ parents[] sit immediately after the CAView header, as the layout
75
+ convention in carray.h requires, so generic code that would walk a
76
+ single ->parent folds over both operands instead. What this buys
77
+ here is ca_has_mask: it answers for a two-parent view by asking both
78
+ and creating the mask on demand, which lets the mask stay unbuilt
79
+ until something reads it. ---- */
80
+ int32_t n_parents; /* always 2 */
81
+ CArray **parents; /* = &operands[0]; no separate allocation */
82
+ /* CABinOp-specific tail */
83
+ CArray *right;
84
+ uint16_t op_id;
85
+ uint8_t right_is_scalar; /* 1 ⇒ right has elements == 1, walk
86
+ with element-stride 0 (= broadcast) */
87
+ CArray *operands[2]; /* {left, right}; what parents points at */
88
+ } CABinOp;
89
+
90
+ static size_t
91
+ ca_binop_dsize (const void *ap)
92
+ {
93
+ const CABinOp *ca = (const CABinOp *) ap;
94
+ return sizeof(CABinOp) + ca->ndim * sizeof(ca_size_t);
95
+ }
96
+
97
+ /* Pool framework hooks: single ndim-sized tail (dim) in the _pool
98
+ buffer. See ca_array_pool.c for the shared alloc/free discipline. */
99
+ static size_t
100
+ ca_binop_pool_bytes (int8_t ndim)
101
+ {
102
+ ca_size_t n = (ndim > 0) ? ndim : 1;
103
+ return (size_t) n * sizeof(ca_size_t);
104
+ }
105
+
106
+ static void
107
+ ca_binop_pool_init (void *ap, int8_t ndim)
108
+ {
109
+ CABinOp *ca = (CABinOp *) ap;
110
+ ca->dim = (ca_size_t *) ca->_pool;
111
+ }
112
+
113
+ const rb_data_type_t cabinop_data_type = {
114
+ .parent = &caview_data_type,
115
+ .wrap_struct_name = "CABinOp",
116
+ .function = {
117
+ .dmark = ca_mark,
118
+ .dfree = ca_free,
119
+ .dsize = ca_binop_dsize,
120
+ .dcompact = NULL
121
+ },
122
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
123
+ };
124
+
125
+ /* ------------------------------------------------------------------- */
126
+ /* setup / new / free */
127
+ /* ------------------------------------------------------------------- */
128
+
129
+ static int
130
+ ca_binop_setup (CABinOp *ca, CArray *left, CArray *right, uint16_t op_id)
131
+ {
132
+ int8_t out_dt = ca_lazy_promote_binop(op_id, left->data_type,
133
+ right->data_type);
134
+ ca_size_t out_bytes = ca_sizeof[out_dt];
135
+
136
+ ca->obj_type = CA_OBJ_BINOP;
137
+ ca->data_type = out_dt;
138
+ ca->flags = CA_FLAG_READ_ONLY | CA_FLAG_MULTI_PARENTS;
139
+ ca->ndim = left->ndim;
140
+ ca->bytes = out_bytes;
141
+ ca->elements = left->elements;
142
+ ca->ptr = NULL;
143
+ ca->mask = NULL;
144
+ if ( ! ca->_pool ) {
145
+ ca->dim = ALLOC_N(ca_size_t, left->ndim);
146
+ }
147
+ ca->parent = left;
148
+ ca->attach = 0;
149
+ ca->nosync = 0;
150
+ ca->right = right;
151
+ ca->op_id = op_id;
152
+ ca->operands[0] = left;
153
+ ca->operands[1] = right;
154
+ ca->parents = ca->operands;
155
+ ca->n_parents = 2;
156
+ /* right_is_scalar fast path: when right holds a single element,
157
+ xfer_stride pulls only that cell into scratch and walks the
158
+ kernel with element-stride 0 (broadcast). The symmetric
159
+ left_is_scalar case is currently handled by wrapping left in
160
+ a broadcast view of right's shape (correct but slower). */
161
+ ca->right_is_scalar = ( right->elements == 1 && left->elements > 1 ) ? 1 : 0;
162
+
163
+ memcpy(ca->dim, left->dim, left->ndim * sizeof(ca_size_t));
164
+
165
+ /* The mask is NOT built here. ca_has_mask folds over parents[] for a
166
+ multi-parent view and creates it on demand, so an expression whose mask
167
+ nobody reads never allocates one -- and a chain of masked nodes builds
168
+ one array at the end rather than one per node. */
169
+
170
+ if ( ca_is_scalar(left) && ca_is_scalar(right) ) {
171
+ ca_set_flag(ca, CA_FLAG_SCALAR);
172
+ }
173
+
174
+ return 0;
175
+ }
176
+
177
+ CABinOp *
178
+ ca_binop_new (CArray *left, CArray *right, uint16_t op_id)
179
+ {
180
+ CABinOp *ca = (CABinOp *) ca_array_alloc(CA_OBJ_BINOP, left->ndim);
181
+ ca_binop_setup(ca, left, right, op_id);
182
+ return ca;
183
+ }
184
+
185
+ static void
186
+ free_ca_binop (void *ap)
187
+ {
188
+ CABinOp *ca = (CABinOp *) ap;
189
+ if ( ca != NULL ) {
190
+ ca_free(ca->mask);
191
+ if ( ca->_pool ) {
192
+ ca_array_free(ca); /* dim lives in _pool */
193
+ }
194
+ else {
195
+ xfree(ca->dim);
196
+ xfree(ca);
197
+ }
198
+ }
199
+ }
200
+
201
+ /* ------------------------------------------------------------------- */
202
+ /* operation function table */
203
+ /* ------------------------------------------------------------------- */
204
+
205
+ static void ca_binop_func_xfer_stride (void *ap, ca_size_t *starts,
206
+ ca_size_t *counts, ca_size_t *strides,
207
+ void *data, int dir);
208
+
209
+ static void *
210
+ ca_binop_func_clone (void *ap)
211
+ {
212
+ CABinOp *ca = (CABinOp *) ap;
213
+ return ca_binop_new(ca->parent, ca->right, ca->op_id);
214
+ }
215
+
216
+ /* Per-cell get: dispatched through the same xfer_stride machinery
217
+ with counts == 1 on every axis. Not the hot path — a hot loop
218
+ should call `.to_ca` and read the entity directly. */
219
+ static void
220
+ ca_binop_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
221
+ {
222
+ CABinOp *ca = (CABinOp *) ap;
223
+ ca_size_t starts[CA_RANK_MAX];
224
+ ca_size_t counts[CA_RANK_MAX];
225
+ ca_size_t strides[CA_RANK_MAX];
226
+ int8_t k;
227
+ ca_size_t s;
228
+
229
+ if ( dir != CA_XFER_GET ) {
230
+ rb_raise(rb_eRuntimeError, "CABinOp is read-only (xfer_index PUT)");
231
+ }
232
+
233
+ s = ca->bytes;
234
+ for ( k = ca->ndim - 1; k >= 0; k-- ) {
235
+ starts[k] = idx[k];
236
+ counts[k] = 1;
237
+ strides[k] = s;
238
+ }
239
+ ca_binop_func_xfer_stride(ca, starts, counts, strides, data, CA_XFER_GET);
240
+ }
241
+
242
+ static void
243
+ ca_binop_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
244
+ void *data, int dir)
245
+ {
246
+ CABinOp *ca = (CABinOp *) ap;
247
+ ca_size_t i;
248
+ char *out = (char *) data;
249
+
250
+ if ( dir != CA_XFER_GET ) {
251
+ rb_raise(rb_eRuntimeError, "CABinOp is read-only (xfer_addrs PUT)");
252
+ }
253
+
254
+ for ( i = 0; i < n; i++ ) {
255
+ ca_size_t idx[CA_RANK_MAX];
256
+ ca_addr2index((CArray *)ca, addrs[i], idx);
257
+ ca_binop_func_xfer_index(ca, idx, out + i * ca->bytes, CA_XFER_GET);
258
+ }
259
+ }
260
+
261
+ /* Test / observability counters bumped from the xfer_stride hot
262
+ path so specs can assert scratch acquisition (one per node,
263
+ independent of left-chain depth) and materialise vs
264
+ non-materialise behaviour without attaching the view. */
265
+ ca_size_t ca_binop_scratch_acquire_count = 0;
266
+ ca_size_t ca_binop_materialise_call_count = 0;
267
+
268
+ static void
269
+ ca_binop_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
270
+ ca_size_t *strides, void *data, int dir)
271
+ {
272
+ CABinOp *bo = (CABinOp *) ap;
273
+ ca_size_t slab_n;
274
+ int8_t k;
275
+ void *scratch;
276
+ volatile VALUE holder;
277
+ ca_size_t right_bytes;
278
+ ca_size_t right_step; /* element-stride for the kernel walk */
279
+
280
+ if ( dir != CA_XFER_GET ) {
281
+ rb_raise(rb_eRuntimeError, "CABinOp is read-only (xfer_stride PUT)");
282
+ }
283
+ ca_binop_materialise_call_count++;
284
+
285
+ slab_n = 1;
286
+ for ( k = 0; k < bo->ndim; k++ ) slab_n *= counts[k];
287
+
288
+ /* === 1. Pull left into the output buffer ===
289
+ CAREFUL: left.data_type == output.data_type by the cast-before
290
+ invariant, so the caller's `strides` (output byte layout) is
291
+ also valid for left. A mismatch would silently write past the
292
+ output row. */
293
+ ca_xfer_stride(bo->parent, starts, counts, strides, data, CA_XFER_GET);
294
+
295
+ /* === 2. Pull right into an arena scratch === */
296
+ right_bytes = bo->right->bytes;
297
+ (void) holder; /* Kept for stack-shape compatibility with the
298
+ surrounding code; arena replaces the ALLOCV. */
299
+ if ( bo->right_is_scalar ) {
300
+ /* CScalar-shaped right: pull exactly 1 element and walk it in
301
+ the kernel with element-stride 0. */
302
+ ca_size_t one_starts[CA_RANK_MAX] = {0};
303
+ ca_size_t one_counts[CA_RANK_MAX];
304
+ ca_size_t one_strides[CA_RANK_MAX];
305
+ for ( k = 0; k < bo->right->ndim; k++ ) {
306
+ one_counts[k] = 1;
307
+ one_strides[k] = right_bytes;
308
+ }
309
+ scratch = ( bo->data_type == CA_OBJECT )
310
+ ? ca_lazy_arena_acquire_object(1)
311
+ : ca_lazy_arena_acquire(right_bytes);
312
+ ca_binop_scratch_acquire_count++;
313
+ ca_xfer_stride(bo->right, one_starts, one_counts, one_strides,
314
+ scratch, CA_XFER_GET);
315
+ right_step = 0;
316
+ }
317
+ else {
318
+ /* Same-shape right. `strides` is the step through the operand, the
319
+ same one the left was pulled with -- recomputing a packed one here
320
+ reads the right operand's cells 0, 1, 2 against the left's 0, 2, 4.
321
+ right.bytes matches output.bytes because right.data_type ==
322
+ out.data_type by cast-before, so the same steps serve both. */
323
+ scratch = ( bo->data_type == CA_OBJECT )
324
+ ? ca_lazy_arena_acquire_object(slab_n)
325
+ : ca_lazy_arena_acquire(slab_n * right_bytes);
326
+ ca_binop_scratch_acquire_count++;
327
+ ca_xfer_stride(bo->right, starts, counts, strides, scratch,
328
+ CA_XFER_GET);
329
+ right_step = 1;
330
+ }
331
+
332
+ /* === 3. Apply the kernel: data (src1 == dst) op scratch (src2) → data ===
333
+ *
334
+ * Kernel signature (ext/carray.h):
335
+ * fn(n, m, ptr1=src1, i1, ptr2=src2, i2, ptr3=dst, i3)
336
+ *
337
+ * Mask handling:
338
+ * - non-trapping op → m = NULL (SIMD fast path; garbage compute
339
+ * on masked cells is allowed).
340
+ * - trapping op with any operand mask → build a per-slab OR
341
+ * mask from the operand-side masks.
342
+ *
343
+ * CAREFUL: the trapping-op mask is built from left.mask and
344
+ * right.mask directly, NOT from out->mask. The output's mask
345
+ * comes from create_mask (called separately on view
346
+ * construction), and reading it here would introduce an
347
+ * ordering dependency that reversed the invariant "the mask is
348
+ * always available before kernels run".
349
+ *
350
+ * CAREFUL: ptr1 == ptr3 == data (in-place left) is safe because
351
+ * each cell reads src1, reads src2, and writes dst atomically
352
+ * before the loop advances. */
353
+ {
354
+ boolean8_t *slab_mask = NULL;
355
+ void *mscratch = NULL;
356
+ volatile VALUE mholder = Qnil;
357
+ int is_trapping = ca_binop_is_trapping(bo->op_id, bo->data_type);
358
+
359
+ if ( is_trapping && ( ca_has_mask(bo->parent) ||
360
+ ca_has_mask(bo->right) ) ) {
361
+ /* Build a slab-shaped mask = left.slab_mask | right.slab_mask.
362
+ Pull each operand's mask (or fill with 0 if absent) into the
363
+ output-shaped layout, then OR them in place. */
364
+ ca_size_t k_;
365
+ ca_size_t right_mask_strides[CA_RANK_MAX];
366
+ (void) mholder; /* Kept for stack-shape compatibility; arena
367
+ replaces the ALLOCV path here. */
368
+
369
+ mscratch = ca_lazy_arena_acquire(slab_n);
370
+ slab_mask = (boolean8_t *) mscratch;
371
+
372
+ if ( ca_has_mask(bo->parent) ) {
373
+ ca_size_t mstrides[CA_RANK_MAX];
374
+ ca_size_t s = 1;
375
+ for ( k_ = bo->ndim - 1; k_ >= 0; k_-- ) {
376
+ mstrides[k_] = s; s *= counts[k_];
377
+ }
378
+ ca_xfer_stride(bo->parent->mask, starts, counts, mstrides,
379
+ slab_mask, CA_XFER_GET);
380
+ }
381
+ else {
382
+ memset(slab_mask, 0, slab_n);
383
+ }
384
+
385
+ if ( ca_has_mask(bo->right) ) {
386
+ boolean8_t *rm = NULL;
387
+ ca_size_t i_;
388
+ if ( bo->right_is_scalar ) {
389
+ /* CScalar right: pull the single mask bit and broadcast OR. */
390
+ ca_size_t one_starts[CA_RANK_MAX] = {0};
391
+ ca_size_t one_counts[CA_RANK_MAX];
392
+ ca_size_t one_strides[CA_RANK_MAX];
393
+ boolean8_t one_bit = 0;
394
+ for ( k_ = 0; k_ < bo->right->ndim; k_++ ) {
395
+ one_counts[k_] = 1;
396
+ one_strides[k_] = 1;
397
+ }
398
+ ca_xfer_stride(bo->right->mask, one_starts, one_counts,
399
+ one_strides, &one_bit, CA_XFER_GET);
400
+ if ( one_bit ) {
401
+ for ( i_ = 0; i_ < slab_n; i_++ ) slab_mask[i_] = 1;
402
+ }
403
+ }
404
+ else {
405
+ ca_size_t s = 1;
406
+ for ( k_ = bo->ndim - 1; k_ >= 0; k_-- ) {
407
+ right_mask_strides[k_] = s; s *= counts[k_];
408
+ }
409
+ /* Small mask-scratch for right; OR into slab_mask in
410
+ place to avoid a persistent second buffer. */
411
+ rm = (boolean8_t *) ALLOCA_N(boolean8_t, slab_n);
412
+ ca_xfer_stride(bo->right->mask, starts, counts,
413
+ right_mask_strides, rm, CA_XFER_GET);
414
+ for ( i_ = 0; i_ < slab_n; i_++ ) slab_mask[i_] |= rm[i_];
415
+ }
416
+ }
417
+ }
418
+
419
+ ca_binop_func_t fn = ca_binop_kernel_lookup_vv(bo->op_id, bo->data_type);
420
+ if ( fn == NULL ) {
421
+ if ( mscratch ) ca_lazy_arena_release(mscratch);
422
+ ca_lazy_arena_release(scratch);
423
+ rb_raise(rb_eNotImpError,
424
+ "CABinOp: kernel not implemented (op_id=%u data_type=%d)",
425
+ (unsigned) bo->op_id, (int) bo->data_type);
426
+ }
427
+ fn(slab_n, slab_mask,
428
+ (char *)data, 1, /* src1 == dst (left, in-place) */
429
+ (char *)scratch, right_step, /* src2 (right) */
430
+ (char *)data, 1); /* dst */
431
+
432
+ if ( mscratch ) ca_lazy_arena_release(mscratch);
433
+ }
434
+
435
+ ca_lazy_arena_release(scratch);
436
+ }
437
+
438
+ static void
439
+ ca_binop_func_xfer_all (void *ap, void *data, int dir)
440
+ {
441
+ CABinOp *ca = (CABinOp *) ap;
442
+ ca_size_t starts[CA_RANK_MAX];
443
+ ca_size_t native[CA_RANK_MAX];
444
+ int8_t k;
445
+ ca_size_t s = ca->bytes;
446
+ for ( k = ca->ndim - 1; k >= 0; k-- ) { native[k] = s; s *= ca->dim[k]; }
447
+ for ( k = 0; k < ca->ndim; k++ ) starts[k] = 0;
448
+ ca_xfer_stride(ca, starts, ca->dim, native, data, dir);
449
+ }
450
+
451
+ static void
452
+ ca_binop_func_allocate (void *ap)
453
+ {
454
+ CABinOp *ca = (CABinOp *) ap;
455
+ ca->ptr = xmalloc(ca_length(ca));
456
+ /* CA_OBJECT cells are VALUEs and this buffer is about to be marked as
457
+ soon as the view is, so it must not be handed to the GC as raw
458
+ xmalloc garbage. */
459
+ if ( ca->data_type == CA_OBJECT ) {
460
+ VALUE *p = (VALUE *) ca->ptr;
461
+ ca_size_t i;
462
+ for ( i = 0; i < ca->elements; i++ ) *p++ = Qnil;
463
+ }
464
+ }
465
+
466
+ static void
467
+ ca_binop_func_attach (void *ap)
468
+ {
469
+ /* CAREFUL: call the view-specific xfer_stride directly here,
470
+ not the public ca_xfer_stride dispatcher. The dispatcher's
471
+ self-memcpy fast path detects data == ca->ptr and returns
472
+ without materialising, so the freshly-allocated buffer would
473
+ still hold garbage. */
474
+ CABinOp *ca = (CABinOp *) ap;
475
+ ca_size_t starts[CA_RANK_MAX];
476
+ ca_size_t native[CA_RANK_MAX];
477
+ int8_t k;
478
+ ca_size_t s;
479
+
480
+ ca->ptr = xmalloc(ca_length(ca));
481
+
482
+ s = ca->bytes;
483
+ for ( k = ca->ndim - 1; k >= 0; k-- ) { native[k] = s; s *= ca->dim[k]; }
484
+ for ( k = 0; k < ca->ndim; k++ ) starts[k] = 0;
485
+ /* CA_OBJECT cells are VALUEs and this buffer is about to be marked as
486
+ soon as the view is, so it must not be handed to the GC as raw
487
+ xmalloc garbage. */
488
+ if ( ca->data_type == CA_OBJECT ) {
489
+ VALUE *p = (VALUE *) ca->ptr;
490
+ ca_size_t i;
491
+ for ( i = 0; i < ca->elements; i++ ) *p++ = Qnil;
492
+ }
493
+ ca_binop_func_xfer_stride(ca, starts, ca->dim, native, ca->ptr, CA_XFER_GET);
494
+ }
495
+
496
+ static void
497
+ ca_binop_func_sync (void *ap)
498
+ {
499
+ /* read-only */
500
+ }
501
+
502
+ static void
503
+ ca_binop_func_detach (void *ap)
504
+ {
505
+ CABinOp *ca = (CABinOp *) ap;
506
+ xfree(ca->ptr);
507
+ ca->ptr = NULL;
508
+ }
509
+
510
+ NORETURN(static void ca_binop_func_fill_data (void *ap, void *ptr));
511
+ static void
512
+ ca_binop_func_fill_data (void *ap, void *ptr)
513
+ {
514
+ rb_raise(rb_eRuntimeError, "CABinOp is read-only (fill_data)");
515
+ }
516
+
517
+ /* Build out.mask. When either parent has a mask, materialise a
518
+ freshly-allocated boolean8_t buffer. For most ops this is the blind
519
+ OR `left.mask | right.mask`.
520
+
521
+ Boolean AND / OR are three-valued (Kleene): a masked cell is resolved by
522
+ the *known* side (`unknown | true = true`, `unknown & false = false`), so
523
+ it is NOT masked in the result. This needs operand VALUES, not just
524
+ masks, so both operands are attached; the blind value the value kernel
525
+ later writes is already correct on a resolved cell because the absorbing
526
+ element (true for OR, false for AND) dominates whatever garbage sits on
527
+ the masked side. Nested trees compose: an inner boolean `&`/`|` node's
528
+ mask is already Kleene-resolved by the time an outer node reads it.
529
+
530
+ Not memory-optimal (a CARepeat-like view would share pages), but correct
531
+ and decoupled from xfer_stride ordering. */
532
+ static void
533
+ ca_binop_func_create_mask (void *ap)
534
+ {
535
+ CABinOp *bo = (CABinOp *) ap;
536
+ CArray *l = bo->parent;
537
+ CArray *r = bo->right;
538
+ boolean8_t *dst;
539
+ ca_size_t i, n;
540
+ int has_l, has_r, kleene, need_l, need_r, is_or;
541
+ boolean8_t *lm, *rm, *lv, *rv;
542
+
543
+ has_l = ca_has_mask(l);
544
+ has_r = ca_has_mask(r);
545
+ if ( ! has_l && ! has_r ) return;
546
+
547
+ kleene = ( bo->data_type == CA_BOOLEAN &&
548
+ ( bo->op_id == CA_BINOP_BIT_AND || bo->op_id == CA_BINOP_BIT_OR ) );
549
+ is_or = ( bo->op_id == CA_BINOP_BIT_OR );
550
+
551
+ /* One masked operand and no Kleene resolution: the answer is that
552
+ operand's mask, cell for cell. Share it the way CAMonOp shares its
553
+ parent's, rather than allocating a copy per node -- a chain of k
554
+ binops over one masked array then holds one boolean array, not k. */
555
+ if ( ! kleene && ( has_l != has_r ) ) {
556
+ CArray *src = has_l ? l : r;
557
+ if ( src->elements == bo->elements ) {
558
+ bo->mask = (CArray *) ca_refer_new(src->mask, CA_BOOLEAN,
559
+ bo->ndim, bo->dim, 0, 0);
560
+ return;
561
+ }
562
+ }
563
+
564
+ bo->mask = (CArray *) carray_new(CA_BOOLEAN, bo->ndim, bo->dim, 0, NULL);
565
+ dst = (boolean8_t *) bo->mask->ptr;
566
+ n = bo->elements;
567
+
568
+ /* Kleene needs operand values even where an operand is unmasked. */
569
+ need_l = has_l || kleene;
570
+ need_r = has_r || kleene;
571
+ /* Only Kleene reads operand values. Every other op reads the operand
572
+ masks alone, and attaching the operand to reach its mask materialises
573
+ the whole subexpression under it -- so a chain of k masked binops
574
+ evaluates its own subtree k times over. Attach what is read. */
575
+ if ( kleene ) {
576
+ if ( need_l ) ca_attach(l);
577
+ if ( need_r ) ca_attach(r);
578
+ }
579
+ else {
580
+ if ( has_l ) ca_attach(l->mask);
581
+ if ( has_r ) ca_attach(r->mask);
582
+ }
583
+
584
+ lm = has_l ? (boolean8_t *) l->mask->ptr : NULL;
585
+ rm = has_r ? (boolean8_t *) r->mask->ptr : NULL;
586
+ lv = kleene ? (boolean8_t *) l->ptr : NULL;
587
+ rv = kleene ? (boolean8_t *) r->ptr : NULL;
588
+
589
+ for ( i = 0; i < n; i++ ) {
590
+ /* right broadcast: repeat element 0 when right was a scalar. */
591
+ ca_size_t ri = bo->right_is_scalar ? 0 : i;
592
+ boolean8_t am = lm ? lm[i] : 0;
593
+ boolean8_t bm = rm ? rm[ri] : 0;
594
+ boolean8_t m = (boolean8_t)( am | bm );
595
+ if ( kleene && m ) {
596
+ boolean8_t av = lv[i], bv = rv[ri];
597
+ if ( is_or ) {
598
+ if ( ( ! am && av ) || ( ! bm && bv ) ) m = 0; /* known TRUE */
599
+ }
600
+ else {
601
+ if ( ( ! am && ! av ) || ( ! bm && ! bv ) ) m = 0; /* known FALSE */
602
+ }
603
+ }
604
+ dst[i] = m;
605
+ }
606
+
607
+ if ( kleene ) {
608
+ if ( need_r ) ca_detach(r);
609
+ if ( need_l ) ca_detach(l);
610
+ }
611
+ else {
612
+ if ( has_r ) ca_detach(r->mask);
613
+ if ( has_l ) ca_detach(l->mask);
614
+ }
615
+ }
616
+
617
+ ca_operation_function_t ca_binop_func = {
618
+ -1, /* CA_OBJ_BINOP, set at install time */
619
+ CA_VIEW_ARRAY,
620
+ free_ca_binop,
621
+ ca_binop_func_clone,
622
+ ca_binop_func_allocate,
623
+ ca_binop_func_attach,
624
+ ca_binop_func_sync,
625
+ ca_binop_func_detach,
626
+ ca_binop_func_fill_data,
627
+ ca_binop_func_create_mask,
628
+ ca_binop_func_xfer_index,
629
+ ca_binop_func_xfer_addrs,
630
+ NULL, /* fold_stride: never fold — op boundary */
631
+ ca_binop_func_xfer_stride,
632
+ ca_binop_func_xfer_all,
633
+ };
634
+
635
+ /* ------------------------------------------------------------------- */
636
+ /* Public builder (= lib/carray/lazy.rb dispatch entry) */
637
+ /* ------------------------------------------------------------------- */
638
+
639
+ /* Marker collapse helper: if cary wraps a CALazyMarker, replace with
640
+ the marker's stored Ruby parent so the marker does not appear in
641
+ the constructed CABinOp tree. */
642
+ static VALUE
643
+ collapse_marker (VALUE cary)
644
+ {
645
+ CArray *ca;
646
+ if ( ! rb_obj_is_carray(cary) ) return cary;
647
+ TypedData_Get_Struct(cary, CArray, &carray_data_type, ca);
648
+ if ( ca->obj_type == CA_OBJ_LAZY_MARKER ) {
649
+ return rb_ca_parent(cary);
650
+ }
651
+ return cary;
652
+ }
653
+
654
+ /* Low-level constructor: wraps l_cary (left) and r_cary (right)
655
+ into a CABinOp. Does NOT insert cast nodes; the caller must
656
+ have promoted both operands to the common data_type. */
657
+ static VALUE
658
+ rb_ca_binop_new (VALUE l_cary, VALUE r_cary, uint16_t op_id)
659
+ {
660
+ volatile VALUE obj;
661
+ CArray *l, *r;
662
+ CABinOp *ca;
663
+ rb_check_carray_object(l_cary);
664
+ rb_check_carray_object(r_cary);
665
+ TypedData_Get_Struct(l_cary, CArray, &carray_data_type, l);
666
+ TypedData_Get_Struct(r_cary, CArray, &carray_data_type, r);
667
+ ca = ca_binop_new(l, r, op_id);
668
+ obj = ca_wrap_struct(ca);
669
+ rb_ca_set_parent(obj, l_cary);
670
+ /* Pin right via an ivar so GC keeps it alive. parent slot already
671
+ pins left. */
672
+ rb_ivar_set(obj, rb_intern("__binop_right__"), r_cary);
673
+ return obj;
674
+ }
675
+
676
+ /* Public builder: build a CABinOp tree node for op_id over
677
+ (l_cary, r_cary). Inserts cast nodes when either operand's
678
+ data_type differs from the common data_type, then resolves
679
+ same-ndim size-1 broadcast via ca_broadcast_pair. The cast
680
+ nodes are inserted BEFORE broadcast so the cast acts on the
681
+ smallest possible element count.
682
+
683
+ Broadcast scope (bound-dimension only):
684
+ same shape → no-op
685
+ same ndim + size-1 → ca_broadcast_pair wraps the size-1 axis
686
+ operand in a stride-0 CAStride
687
+ CScalar vs array → xfer_stride walks with element_step = 0
688
+ different ndim → ArgumentError
689
+
690
+ CAREFUL: the different-ndim raise is an intentional design
691
+ rejection, not a deferred feature. Implicit cross-ndim
692
+ promotion (`(M, N) + (N,)` etc.) is permanently excluded from
693
+ CArray; callers must reshape explicitly. The eager CArray path
694
+ enforces the same rule, and re-introducing a silent
695
+ trailing-align here would diverge the lazy path from it. */
696
+ VALUE
697
+ rb_ca_binop_build (VALUE l_cary, VALUE r_cary, uint16_t op_id)
698
+ {
699
+ CArray *l, *r;
700
+ int8_t l_in_dt, r_in_dt;
701
+ volatile VALUE l_resolved, r_resolved;
702
+
703
+ /* Collapse markers on both sides. */
704
+ l_resolved = collapse_marker(l_cary);
705
+ r_resolved = collapse_marker(r_cary);
706
+
707
+ /* Promote a non-CArray Ruby value to a CScalar carrying the
708
+ other side's natural data_type. */
709
+ if ( ! rb_obj_is_carray(l_resolved) ) {
710
+ rb_check_carray_object(r_resolved);
711
+ TypedData_Get_Struct(r_resolved, CArray, &carray_data_type, r);
712
+ l_resolved = ca_lazy_wrap_scalar(l_resolved, r);
713
+ }
714
+ if ( ! rb_obj_is_carray(r_resolved) ) {
715
+ rb_check_carray_object(l_resolved);
716
+ TypedData_Get_Struct(l_resolved, CArray, &carray_data_type, l);
717
+ r_resolved = ca_lazy_wrap_scalar(r_resolved, l);
718
+ }
719
+
720
+ TypedData_Get_Struct(l_resolved, CArray, &carray_data_type, l);
721
+ TypedData_Get_Struct(r_resolved, CArray, &carray_data_type, r);
722
+
723
+ /* === Step 1: insert cast nodes. Applied to each operand before
724
+ broadcast so the cast acts on the smallest element count. */
725
+ ca_binop_kernel_input_data_types(op_id, l->data_type, r->data_type,
726
+ &l_in_dt, &r_in_dt);
727
+
728
+ if ( l_in_dt != l->data_type ) {
729
+ VALUE cast_op = INT2NUM(CA_MONOP_CAST_BASE + l_in_dt);
730
+ l_resolved = rb_funcall(rb_const_get(rb_cObject, rb_intern("CAMonOp")),
731
+ rb_intern("__build__"), 2,
732
+ l_resolved, cast_op);
733
+ TypedData_Get_Struct(l_resolved, CArray, &carray_data_type, l);
734
+ }
735
+ if ( r_in_dt != r->data_type ) {
736
+ VALUE cast_op = INT2NUM(CA_MONOP_CAST_BASE + r_in_dt);
737
+ r_resolved = rb_funcall(rb_const_get(rb_cObject, rb_intern("CAMonOp")),
738
+ rb_intern("__build__"), 2,
739
+ r_resolved, cast_op);
740
+ TypedData_Get_Struct(r_resolved, CArray, &carray_data_type, r);
741
+ }
742
+
743
+ /* === Step 2: broadcast via ca_broadcast_pair (same-ndim size-1
744
+ expansion). No-op when shapes already match or either operand
745
+ is scalar; refuses a pair that cannot be brought to one shape. */
746
+ ca_broadcast_pair(&l_resolved, &r_resolved);
747
+ TypedData_Get_Struct(l_resolved, CArray, &carray_data_type, l);
748
+ TypedData_Get_Struct(r_resolved, CArray, &carray_data_type, r);
749
+
750
+ /* === Step 3: shape sanity. After broadcast_pair, accept:
751
+ (a) equal elements (matched shape, common case),
752
+ (b) right is a 1-element CScalar (xfer_stride uses
753
+ element_step = 0),
754
+ (c) left is a 1-element CScalar — swap operands when the
755
+ op commutes (ADD / MUL / BIT_*). Non-commutative +
756
+ left scalar wraps left in a broadcast view of right's
757
+ shape so the kernel sees matched shapes; cost is N
758
+ reads of the scalar cell with stride 0. */
759
+ if ( l->elements != r->elements ) {
760
+ if ( r->elements == 1 && l->elements > 1 ) {
761
+ /* CScalar-vs-array, right. xfer_stride handles this with
762
+ right_step = 0. */
763
+ }
764
+ else if ( l->elements == 1 && r->elements > 1 ) {
765
+ int commutes = ( op_id == CA_BINOP_ADD ||
766
+ op_id == CA_BINOP_MUL ||
767
+ op_id == CA_BINOP_BIT_AND ||
768
+ op_id == CA_BINOP_BIT_OR ||
769
+ op_id == CA_BINOP_BIT_XOR );
770
+ if ( commutes ) {
771
+ VALUE tmp = l_resolved; l_resolved = r_resolved; r_resolved = tmp;
772
+ }
773
+ else {
774
+ /* Non-commutative + left scalar: broadcast the left operand
775
+ (CScalar with ndim=1 dim=[1]) up to right's shape.
776
+ ca_repeat_setup signature:
777
+ count[i] > 0 → repeat (stride-0) axis of size count[i]
778
+ count[i] == 0 → data axis (inherits from parent)
779
+ Total data axes must equal parent->ndim.
780
+ For lifting a CScalar (parent.ndim=1, dim=[1]) to right's
781
+ ndim N: count = {1, 1, ..., 1, 0} (N-1 leading dummy 1s,
782
+ 1 trailing data axis), then broadcast that (1,1,...,1)
783
+ shape to right's dim via ca_broadcast_view. */
784
+ ca_size_t count[CA_RANK_MAX];
785
+ int8_t k;
786
+ if ( r->ndim > l->ndim ) {
787
+ for ( k = 0; k < r->ndim - 1; k++ ) count[k] = 1; /* dummy */
788
+ count[r->ndim - 1] = 0; /* data */
789
+ l_resolved = rb_ca_repeat_new(l_resolved, r->ndim, count);
790
+ TypedData_Get_Struct(l_resolved, CArray, &carray_data_type, l);
791
+ }
792
+ l_resolved = ca_broadcast_view(l_resolved, r->ndim, r->dim);
793
+ TypedData_Get_Struct(l_resolved, CArray, &carray_data_type, l);
794
+ }
795
+ }
796
+ else {
797
+ /* Unreachable from Ruby: ca_broadcast_pair above refuses a pair
798
+ it cannot bring to one shape, and states the rule there. Kept
799
+ so a caller reaching this builder by another route still finds
800
+ the kernel guarded. */
801
+ rb_raise(rb_eArgError,
802
+ "CABinOp: element count mismatch (%lld vs %lld)",
803
+ (long long) l->elements, (long long) r->elements);
804
+ }
805
+ }
806
+
807
+ return rb_ca_binop_new(l_resolved, r_resolved, op_id);
808
+ }
809
+
810
+ static VALUE
811
+ rb_ca_binop_s_build (VALUE klass, VALUE l_cary, VALUE r_cary, VALUE op_id_val)
812
+ {
813
+ uint16_t op_id = (uint16_t) NUM2UINT(op_id_val);
814
+ (void) klass;
815
+ return rb_ca_binop_build(l_cary, r_cary, op_id);
816
+ }
817
+
818
+ /* op_id accessor for inspect / dump_tree consumers in Ruby. */
819
+ static VALUE
820
+ rb_ca_binop_op_id (VALUE self)
821
+ {
822
+ CABinOp *bo;
823
+ TypedData_Get_Struct(self, CABinOp, &cabinop_data_type, bo);
824
+ return UINT2NUM(bo->op_id);
825
+ }
826
+
827
+ /* Right-operand accessor — needed by inspect / dump_tree because
828
+ the right reference lives off the prefix and is not reachable
829
+ via .parent. */
830
+ static VALUE
831
+ rb_ca_binop_right (VALUE self)
832
+ {
833
+ return rb_ivar_get(self, rb_intern("__binop_right__"));
834
+ }
835
+
836
+ /* Test instrumentation (mirrors CAMonOp counters). */
837
+ static VALUE
838
+ rb_ca_binop_s_reset_scratch_counter (VALUE klass)
839
+ {
840
+ ca_binop_scratch_acquire_count = 0;
841
+ return Qnil;
842
+ }
843
+
844
+ static VALUE
845
+ rb_ca_binop_s_scratch_count (VALUE klass)
846
+ {
847
+ return SIZE2NUM(ca_binop_scratch_acquire_count);
848
+ }
849
+
850
+ static VALUE
851
+ rb_ca_binop_s_reset_materialise_counter (VALUE klass)
852
+ {
853
+ ca_binop_materialise_call_count = 0;
854
+ return Qnil;
855
+ }
856
+
857
+ static VALUE
858
+ rb_ca_binop_s_materialise_count (VALUE klass)
859
+ {
860
+ return SIZE2NUM(ca_binop_materialise_call_count);
861
+ }
862
+
863
+ static VALUE
864
+ rb_ca_binop_s_allocate (VALUE klass)
865
+ {
866
+ CABinOp *ca;
867
+ return TypedData_Make_Struct(klass, CABinOp, &cabinop_data_type, ca);
868
+ }
869
+
870
+ static VALUE
871
+ rb_ca_binop_initialize_copy (VALUE self, VALUE other)
872
+ {
873
+ CABinOp *ca, *cs;
874
+ TypedData_Get_Struct(self, CABinOp, &cabinop_data_type, ca);
875
+ TypedData_Get_Struct(other, CABinOp, &cabinop_data_type, cs);
876
+ if ( ca_func[CA_OBJ_BINOP].pool_init ) {
877
+ ca_array_pool_alloc(ca, CA_OBJ_BINOP, cs->parent->ndim);
878
+ }
879
+ ca_binop_setup(ca, cs->parent, cs->right, cs->op_id);
880
+ return self;
881
+ }
882
+
883
+ void
884
+ Init_ca_obj_binop (void)
885
+ {
886
+ rb_cCABinOp = rb_define_class("CABinOp", rb_cCAView);
887
+
888
+ ca_binop_func.struct_size = sizeof(CABinOp);
889
+ ca_binop_func.pool_bytes = ca_binop_pool_bytes;
890
+ ca_binop_func.pool_init = ca_binop_pool_init;
891
+
892
+ CA_OBJ_BINOP = ca_install_obj_type(rb_cCABinOp,
893
+ &cabinop_data_type,
894
+ rb_cCArrayMask,
895
+ &carray_mask_data_type, &ca_binop_func, sizeof(ca_binop_func));
896
+ rb_define_const(rb_cObject, "CA_OBJ_BINOP", INT2NUM(CA_OBJ_BINOP));
897
+
898
+ /* op_id constants shared with lib/carray/lazy.rb. */
899
+ rb_define_const(rb_cCABinOp, "OP_ADD", INT2NUM(CA_BINOP_ADD));
900
+ rb_define_const(rb_cCABinOp, "OP_SUB", INT2NUM(CA_BINOP_SUB));
901
+ rb_define_const(rb_cCABinOp, "OP_MUL", INT2NUM(CA_BINOP_MUL));
902
+ rb_define_const(rb_cCABinOp, "OP_DIV", INT2NUM(CA_BINOP_DIV));
903
+ rb_define_const(rb_cCABinOp, "OP_POW", INT2NUM(CA_BINOP_POW));
904
+ rb_define_const(rb_cCABinOp, "OP_BIT_AND", INT2NUM(CA_BINOP_BIT_AND));
905
+ rb_define_const(rb_cCABinOp, "OP_BIT_OR", INT2NUM(CA_BINOP_BIT_OR));
906
+ rb_define_const(rb_cCABinOp, "OP_BIT_XOR", INT2NUM(CA_BINOP_BIT_XOR));
907
+ rb_define_const(rb_cCABinOp, "OP_BIT_LSHIFT", INT2NUM(CA_BINOP_BIT_LSHIFT));
908
+ rb_define_const(rb_cCABinOp, "OP_BIT_RSHIFT", INT2NUM(CA_BINOP_BIT_RSHIFT));
909
+ rb_define_const(rb_cCABinOp, "OP_MOD", INT2NUM(CA_BINOP_MOD));
910
+ rb_define_const(rb_cCABinOp, "OP_QUO", INT2NUM(CA_BINOP_QUO));
911
+ rb_define_const(rb_cCABinOp, "OP_RCP_MUL", INT2NUM(CA_BINOP_RCP_MUL));
912
+ rb_define_const(rb_cCABinOp, "OP_IPOWER", INT2NUM(CA_BINOP_IPOWER));
913
+ rb_define_const(rb_cCABinOp, "OP_COPYSIGN", INT2NUM(CA_BINOP_COPYSIGN));
914
+ rb_define_const(rb_cCABinOp, "OP_LOGADDEXP", INT2NUM(CA_BINOP_LOGADDEXP));
915
+ rb_define_const(rb_cCABinOp, "OP_NEXTAFTER", INT2NUM(CA_BINOP_NEXTAFTER));
916
+ rb_define_const(rb_cCABinOp, "OP_FMOD", INT2NUM(CA_BINOP_FMOD));
917
+ rb_define_const(rb_cCABinOp, "OP_ATAN2", INT2NUM(CA_BINOP_ATAN2));
918
+ rb_define_const(rb_cCABinOp, "OP_HYPOT", INT2NUM(CA_BINOP_HYPOT));
919
+ rb_define_const(rb_cCABinOp, "OP_PMAX", INT2NUM(CA_BINOP_PMAX));
920
+ rb_define_const(rb_cCABinOp, "OP_PMIN", INT2NUM(CA_BINOP_PMIN));
921
+ rb_define_const(rb_cCABinOp, "OP_MAXIMUM", INT2NUM(CA_BINOP_MAXIMUM));
922
+ rb_define_const(rb_cCABinOp, "OP_MINIMUM", INT2NUM(CA_BINOP_MINIMUM));
923
+ rb_define_const(rb_cCABinOp, "OP_AND", INT2NUM(CA_BINOP_AND));
924
+ rb_define_const(rb_cCABinOp, "OP_OR", INT2NUM(CA_BINOP_OR));
925
+ rb_define_const(rb_cCABinOp, "OP_XOR", INT2NUM(CA_BINOP_XOR));
926
+
927
+ rb_define_alloc_func(rb_cCABinOp, rb_ca_binop_s_allocate);
928
+ rb_define_method(rb_cCABinOp, "initialize_copy",
929
+ rb_ca_binop_initialize_copy, 1);
930
+
931
+ rb_define_singleton_method(rb_cCABinOp, "__build__",
932
+ rb_ca_binop_s_build, 3);
933
+
934
+ rb_define_method(rb_cCABinOp, "__op_id__",
935
+ rb_ca_binop_op_id, 0);
936
+ rb_define_method(rb_cCABinOp, "__binop_right__",
937
+ rb_ca_binop_right, 0);
938
+
939
+ /* Test instrumentation, not user-facing. */
940
+ rb_define_singleton_method(rb_cCABinOp, "__reset_scratch_counter__",
941
+ rb_ca_binop_s_reset_scratch_counter, 0);
942
+ rb_define_singleton_method(rb_cCABinOp, "__scratch_count__",
943
+ rb_ca_binop_s_scratch_count, 0);
944
+ rb_define_singleton_method(rb_cCABinOp, "__reset_materialise_counter__",
945
+ rb_ca_binop_s_reset_materialise_counter, 0);
946
+ rb_define_singleton_method(rb_cCABinOp, "__materialise_count__",
947
+ rb_ca_binop_s_materialise_count, 0);
948
+ }