carray 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +5 -25
  3. data/CHANGELOG.md +16 -0
  4. data/LICENSE +1 -1
  5. data/NEWS.md +3 -0
  6. data/README.md +128 -44
  7. data/carray.gemspec +22 -24
  8. data/ext/ca_array_pool.c +91 -0
  9. data/ext/ca_axis_descriptor.h +186 -0
  10. data/ext/ca_axis_dispatch.c +924 -0
  11. data/ext/ca_axis_group.c +1208 -0
  12. data/ext/ca_bincmp_dispatch.c +76 -0
  13. data/ext/ca_bincmp_dispatch.h +85 -0
  14. data/ext/ca_binop_dispatch.c +125 -0
  15. data/ext/ca_binop_dispatch.h +159 -0
  16. data/ext/ca_categorical_iterator.c +1375 -0
  17. data/ext/ca_compare.c +94 -0
  18. data/ext/ca_compare.h +26 -0
  19. data/ext/ca_composite_dispatch.c +414 -0
  20. data/ext/ca_composite_dispatch.h +116 -0
  21. data/ext/ca_for_buffer.h +96 -0
  22. data/ext/ca_for_each_element.h +241 -0
  23. data/ext/ca_group_iter.c +304 -0
  24. data/ext/ca_iter_substrate.h +325 -0
  25. data/ext/ca_kernel_iterator.c +4321 -0
  26. data/ext/ca_kernel_iterator.h +2603 -0
  27. data/ext/ca_moncmp_dispatch.c +37 -0
  28. data/ext/ca_moncmp_dispatch.h +62 -0
  29. data/ext/ca_monop_dispatch.c +200 -0
  30. data/ext/ca_monop_dispatch.h +235 -0
  31. data/ext/ca_obj_array.c +355 -359
  32. data/ext/ca_obj_bincmp.c +809 -0
  33. data/ext/ca_obj_binop.c +892 -0
  34. data/ext/ca_obj_bitarray.c +369 -164
  35. data/ext/ca_obj_bitfield.c +294 -234
  36. data/ext/ca_obj_block.c +189 -711
  37. data/ext/ca_obj_byte_swap.c +766 -0
  38. data/ext/ca_obj_const_string.c +965 -0
  39. data/ext/ca_obj_face.c +670 -0
  40. data/ext/ca_obj_face.h +247 -0
  41. data/ext/ca_obj_fake.c +228 -100
  42. data/ext/ca_obj_farray.c +54 -441
  43. data/ext/ca_obj_field.c +82 -529
  44. data/ext/ca_obj_fixlen_string.c +306 -0
  45. data/ext/ca_obj_grid.c +858 -440
  46. data/ext/ca_obj_meld.c +1034 -0
  47. data/ext/ca_obj_moncmp.c +569 -0
  48. data/ext/ca_obj_monop.c +1111 -0
  49. data/ext/ca_obj_object.c +774 -298
  50. data/ext/ca_obj_record.c +468 -0
  51. data/ext/ca_obj_reduce.c +97 -82
  52. data/ext/ca_obj_refer.c +569 -459
  53. data/ext/ca_obj_remap.c +475 -0
  54. data/ext/ca_obj_repeat.c +92 -477
  55. data/ext/ca_obj_roll.c +616 -0
  56. data/ext/ca_obj_select.c +344 -296
  57. data/ext/ca_obj_select_axis.c +1296 -0
  58. data/ext/ca_obj_shift.c +230 -792
  59. data/ext/ca_obj_source.c +78 -0
  60. data/ext/ca_obj_stack.c +1173 -0
  61. data/ext/ca_obj_stride.c +2501 -0
  62. data/ext/ca_obj_string.c +268 -0
  63. data/ext/ca_obj_tile.c +614 -0
  64. data/ext/ca_obj_time.c +546 -0
  65. data/ext/ca_obj_timedelta.c +435 -0
  66. data/ext/ca_obj_transpose.c +62 -516
  67. data/ext/ca_obj_triop.c +746 -0
  68. data/ext/ca_obj_unbound_repeat.c +208 -241
  69. data/ext/ca_obj_window.c +1131 -563
  70. data/ext/ca_op_byte_swap.c +175 -0
  71. data/ext/ca_op_ipower.c +319 -0
  72. data/ext/ca_op_powi.h +88 -0
  73. data/ext/ca_sort_kernels.h +132 -0
  74. data/ext/ca_sweep_engine.c +430 -0
  75. data/ext/ca_sweep_engine.h +157 -0
  76. data/ext/ca_transform_common.c +228 -0
  77. data/ext/ca_triop_dispatch.c +55 -0
  78. data/ext/ca_triop_dispatch.h +62 -0
  79. data/ext/carray.h +795 -402
  80. data/ext/carray_access.c +831 -711
  81. data/ext/carray_attribute.c +98 -330
  82. data/ext/carray_bincount.c +255 -0
  83. data/ext/carray_broadcast.c +283 -0
  84. data/ext/carray_call_cfunc.c +1360 -828
  85. data/ext/carray_call_cfunc.h +160 -0
  86. data/ext/carray_cast.c +1212 -301
  87. data/ext/carray_cast_func.rb +81 -40
  88. data/ext/carray_class.c +53 -63
  89. data/ext/carray_config.h +28 -0
  90. data/ext/carray_conversion.c +350 -346
  91. data/ext/carray_copy.c +156 -268
  92. data/ext/carray_core.c +1342 -199
  93. data/ext/carray_count.c +312 -0
  94. data/ext/carray_data_type.c +43 -19
  95. data/ext/carray_element.c +585 -213
  96. data/ext/carray_factorize.c +2542 -0
  97. data/ext/carray_generate.c +230 -559
  98. data/ext/carray_histogram.c +490 -0
  99. data/ext/carray_hold.c +228 -0
  100. data/ext/carray_index_classifier.c +1035 -0
  101. data/ext/carray_index_classifier.h +27 -0
  102. data/ext/carray_internal.h +120 -0
  103. data/ext/carray_kernels_bincmp.c +4445 -0
  104. data/ext/carray_kernels_binop.c +10979 -0
  105. data/ext/carray_kernels_init.c +36 -0
  106. data/ext/carray_kernels_map.c +3466 -0
  107. data/ext/carray_kernels_moncmp.c +2096 -0
  108. data/ext/carray_kernels_monop.c +18312 -0
  109. data/ext/carray_kernels_reduce_aggregate.c +25836 -0
  110. data/ext/carray_kernels_reduce_boolean.c +329 -0
  111. data/ext/carray_kernels_reduce_cumulative.c +14592 -0
  112. data/ext/carray_kernels_reduce_extreme.c +16947 -0
  113. data/ext/carray_kernels_reduce_variance.c +3909 -0
  114. data/ext/carray_kernels_scan.c +3692 -0
  115. data/ext/carray_kernels_search.c +32137 -0
  116. data/ext/carray_kernels_sort.c +10625 -0
  117. data/ext/carray_kernels_triop.c +1391 -0
  118. data/ext/carray_lazy.c +567 -0
  119. data/ext/carray_loop.c +88 -200
  120. data/ext/carray_mask.c +848 -154
  121. data/ext/carray_math_kernel.h +120 -0
  122. data/ext/carray_mathfunc.c +10 -241
  123. data/ext/carray_median_percentile.c +1257 -0
  124. data/ext/carray_memory_view.c +1625 -0
  125. data/ext/carray_operator.c +1526 -318
  126. data/ext/carray_order.c +664 -1394
  127. data/ext/carray_partition.c +416 -0
  128. data/ext/carray_random.c +518 -0
  129. data/ext/carray_scatter.c +357 -0
  130. data/ext/carray_slab.c +1219 -0
  131. data/ext/carray_slab.h +84 -0
  132. data/ext/carray_sort.c +829 -0
  133. data/ext/carray_sort_kernel.c +620 -0
  134. data/ext/carray_struct.c +695 -0
  135. data/ext/carray_test.c +343 -229
  136. data/ext/carray_undef.c +34 -17
  137. data/ext/carray_utils.c +175 -74
  138. data/ext/extconf.rb +216 -55
  139. data/ext/mk_call_cfunc.rb +480 -0
  140. data/ext/mkkernel.rb +8842 -0
  141. data/ext/ruby_carray.c +202 -101
  142. data/ext/version.h +4 -14
  143. data/ext/version.rb +5 -13
  144. data/lib/carray/arrow_tensor.rb +401 -0
  145. data/lib/carray/attribute.rb +166 -0
  146. data/lib/carray/autoload_carray.rb +220 -0
  147. data/lib/carray/autoload_method_extension.rb +44 -0
  148. data/lib/carray/axis_group.rb +711 -0
  149. data/lib/carray/basics.rb +481 -0
  150. data/lib/carray/bincount_nd.rb +358 -0
  151. data/lib/carray/block_iterator.rb +604 -0
  152. data/lib/carray/boolean_reduce.rb +109 -0
  153. data/lib/carray/categorical.rb +561 -0
  154. data/lib/carray/categorical_iterator.rb +1062 -0
  155. data/lib/carray/complex.rb +150 -0
  156. data/lib/carray/conditional.rb +216 -0
  157. data/lib/carray/const_string.rb +228 -0
  158. data/lib/carray/construct.rb +139 -328
  159. data/lib/carray/core_extensions.rb +240 -0
  160. data/lib/carray/data_type_extension.rb +233 -0
  161. data/lib/carray/fixlen_string.rb +95 -0
  162. data/lib/carray/frame/concat.rb +132 -0
  163. data/lib/carray/frame/convert.rb +95 -0
  164. data/lib/carray/frame/csv_parser.rb +211 -0
  165. data/lib/carray/frame/frame.rb +649 -0
  166. data/lib/carray/frame/group.rb +186 -0
  167. data/lib/carray/frame/io.rb +164 -0
  168. data/lib/carray/frame/join.rb +248 -0
  169. data/lib/carray/frame/records.rb +99 -0
  170. data/lib/carray/frame/sort.rb +113 -0
  171. data/lib/carray/frame/verbs.rb +299 -0
  172. data/lib/carray/frame.rb +16 -0
  173. data/lib/carray/histogram.rb +512 -0
  174. data/lib/carray/inspect.rb +37 -20
  175. data/lib/carray/iterator.rb +57 -349
  176. data/lib/carray/lazy.rb +889 -0
  177. data/lib/carray/mask_gap_fill.rb +200 -0
  178. data/lib/carray/math.rb +78 -342
  179. data/lib/carray/meld_reduce.rb +289 -0
  180. data/lib/carray/methods/align_addr.rb +116 -0
  181. data/lib/carray/methods/bin.rb +128 -0
  182. data/lib/carray/methods/bincount.rb +87 -0
  183. data/lib/carray/methods/bit_string.rb +92 -0
  184. data/lib/carray/methods/broadcast.rb +63 -0
  185. data/lib/carray/methods/choose.rb +39 -0
  186. data/lib/carray/methods/composition.rb +280 -0
  187. data/lib/carray/methods/gather_nd.rb +206 -0
  188. data/lib/carray/methods/index.rb +39 -0
  189. data/lib/carray/methods/insert_block.rb +99 -0
  190. data/lib/carray/methods/is_in.rb +141 -0
  191. data/lib/carray/methods/join.rb +90 -0
  192. data/lib/carray/methods/locate_addr.rb +47 -0
  193. data/lib/carray/methods/mask_duplicates.rb +41 -0
  194. data/lib/carray/methods/meshgrid.rb +91 -0
  195. data/lib/carray/methods/mode.rb +126 -0
  196. data/lib/carray/methods/nunique.rb +46 -0
  197. data/lib/carray/methods/resize.rb +56 -0
  198. data/lib/carray/methods/snap.rb +156 -0
  199. data/lib/carray/methods/string_format.rb +57 -0
  200. data/lib/carray/methods/unique.rb +47 -0
  201. data/lib/carray/methods/value_counts.rb +71 -0
  202. data/lib/carray/mkmf.rb +124 -101
  203. data/lib/carray/runtime.rb +108 -0
  204. data/lib/carray/serialize.rb +478 -167
  205. data/lib/carray/slab_iterator.rb +292 -0
  206. data/lib/carray/stack.rb +291 -0
  207. data/lib/carray/string.rb +56 -180
  208. data/lib/carray/string_operation_extension.rb +289 -0
  209. data/lib/carray/struct.rb +335 -323
  210. data/lib/carray/struct_builder.rb +697 -0
  211. data/lib/carray/table.rb +41 -2
  212. data/lib/carray/time.rb +2255 -38
  213. data/lib/carray/window_iterator.rb +655 -0
  214. data/lib/carray.rb +55 -57
  215. metadata +163 -130
  216. data/Rakefile +0 -51
  217. data/TODO.md +0 -18
  218. data/ext/ca_iter_block.c +0 -257
  219. data/ext/ca_iter_dimension.c +0 -299
  220. data/ext/ca_iter_window.c +0 -214
  221. data/ext/ca_obj_mapping.c +0 -644
  222. data/ext/carray_iterator.c +0 -641
  223. data/ext/carray_math.rb +0 -850
  224. data/ext/carray_numeric.c +0 -259
  225. data/ext/carray_sort_addr.c +0 -254
  226. data/ext/carray_stat.c +0 -2100
  227. data/ext/carray_stat_proc.rb +0 -1999
  228. data/ext/mkmath.rb +0 -741
  229. data/ext/ruby_ccomplex.c +0 -509
  230. data/ext/ruby_float_func.c +0 -86
  231. data/lib/carray/array.rb +0 -8
  232. data/lib/carray/autoload/autoload_base.rb +0 -19
  233. data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
  234. data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
  235. data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
  236. data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
  237. data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
  238. data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
  239. data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
  240. data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
  241. data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
  242. data/lib/carray/autoload/autoload_gem_random.rb +0 -8
  243. data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
  244. data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
  245. data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
  246. data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
  247. data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
  248. data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
  249. data/lib/carray/autoload/autoload_object_link.rb +0 -1
  250. data/lib/carray/autoload/autoload_object_pack.rb +0 -2
  251. data/lib/carray/autoload.rb +0 -141
  252. data/lib/carray/basic.rb +0 -191
  253. data/lib/carray/broadcast.rb +0 -101
  254. data/lib/carray/compose.rb +0 -315
  255. data/lib/carray/convert.rb +0 -115
  256. data/lib/carray/info.rb +0 -110
  257. data/lib/carray/io/imagemagick.rb +0 -235
  258. data/lib/carray/mask.rb +0 -102
  259. data/lib/carray/math/histogram.rb +0 -177
  260. data/lib/carray/math/recurrence.rb +0 -93
  261. data/lib/carray/object/ca_obj_iterator.rb +0 -50
  262. data/lib/carray/object/ca_obj_link.rb +0 -50
  263. data/lib/carray/object/ca_obj_pack.rb +0 -99
  264. data/lib/carray/obsolete.rb +0 -256
  265. data/lib/carray/ordering.rb +0 -181
  266. data/lib/carray/testing.rb +0 -51
  267. data/lib/carray/transform.rb +0 -109
  268. data/misc/Methods.ja.md +0 -182
  269. data/misc/NOTE +0 -51
  270. data/spec/Classes/CABitfield_spec.rb +0 -58
  271. data/spec/Classes/CABlockIterator_spec.rb +0 -114
  272. data/spec/Classes/CABlock_spec.rb +0 -205
  273. data/spec/Classes/CAField_spec.rb +0 -39
  274. data/spec/Classes/CAGrid_spec.rb +0 -75
  275. data/spec/Classes/CAMap_spec.rb +0 -0
  276. data/spec/Classes/CAMapping_spec.rb +0 -105
  277. data/spec/Classes/CAObject_attribute_spec.rb +0 -33
  278. data/spec/Classes/CAObject_spec.rb +0 -33
  279. data/spec/Classes/CARefer_spec.rb +0 -93
  280. data/spec/Classes/CARepeat_spec.rb +0 -65
  281. data/spec/Classes/CASelect_spec.rb +0 -22
  282. data/spec/Classes/CAShift_spec.rb +0 -16
  283. data/spec/Classes/CAStruct_spec.rb +0 -71
  284. data/spec/Classes/CATranspose_spec.rb +0 -60
  285. data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
  286. data/spec/Classes/CAWindow_spec.rb +0 -54
  287. data/spec/Classes/CAWrap_spec.rb +0 -8
  288. data/spec/Classes/CArray_spec.rb +0 -184
  289. data/spec/Classes/CScalar_spec.rb +0 -55
  290. data/spec/Classes/ex1.rb +0 -46
  291. data/spec/Features/feature_130_spec.rb +0 -19
  292. data/spec/Features/feature_attributes_spec.rb +0 -280
  293. data/spec/Features/feature_boolean_spec.rb +0 -98
  294. data/spec/Features/feature_broadcast.rb +0 -116
  295. data/spec/Features/feature_cast_function.rb +0 -19
  296. data/spec/Features/feature_cast_spec.rb +0 -33
  297. data/spec/Features/feature_class_spec.rb +0 -84
  298. data/spec/Features/feature_complex_spec.rb +0 -42
  299. data/spec/Features/feature_composite_spec.rb +0 -124
  300. data/spec/Features/feature_convert_spec.rb +0 -46
  301. data/spec/Features/feature_copy_spec.rb +0 -123
  302. data/spec/Features/feature_creation_spec.rb +0 -84
  303. data/spec/Features/feature_element_spec.rb +0 -144
  304. data/spec/Features/feature_extream_spec.rb +0 -54
  305. data/spec/Features/feature_generate_spec.rb +0 -74
  306. data/spec/Features/feature_index_spec.rb +0 -69
  307. data/spec/Features/feature_mask_spec.rb +0 -580
  308. data/spec/Features/feature_math_spec.rb +0 -97
  309. data/spec/Features/feature_order_spec.rb +0 -146
  310. data/spec/Features/feature_ref_store_spec.rb +0 -209
  311. data/spec/Features/feature_serialization_spec.rb +0 -125
  312. data/spec/Features/feature_stat_spec.rb +0 -397
  313. data/spec/Features/feature_virtual_spec.rb +0 -48
  314. data/spec/Features/method_eq_spec.rb +0 -81
  315. data/spec/Features/method_is_nan_spec.rb +0 -12
  316. data/spec/Features/method_map_spec.rb +0 -54
  317. data/spec/Features/method_max_with.rb +0 -20
  318. data/spec/Features/method_min_with.rb +0 -19
  319. data/spec/Features/method_ne_spec.rb +0 -18
  320. data/spec/Features/method_project_spec.rb +0 -188
  321. data/spec/Features/method_ref_spec.rb +0 -27
  322. data/spec/Features/method_round_spec.rb +0 -11
  323. data/spec/Features/method_s_linspace_spec.rb +0 -48
  324. data/spec/Features/method_s_span_spec.rb +0 -14
  325. data/spec/Features/method_seq_spec.rb +0 -47
  326. data/spec/Features/method_sort_with.rb +0 -43
  327. data/spec/Features/method_sorted_with.rb +0 -29
  328. data/spec/Features/method_span_spec.rb +0 -42
  329. data/spec/Features/method_wrap_readonly_spec.rb +0 -43
  330. data/spec/UnitTest/test_CAVirtual.rb +0 -214
  331. data/spec/spec_all.rb +0 -10
  332. data/utils/ca_ase.rb +0 -21
  333. data/utils/ca_methods.rb +0 -15
  334. data/utils/cast_checker.rb +0 -30
  335. data/utils/convert_test.rb +0 -73
  336. data/utils/extract_yard.rb +0 -22
  337. data/utils/guess_shape.rb +0 -76
  338. data/utils/monkey_patch_methods.rb +0 -62
  339. data/utils/remove_resource_fork.sh +0 -5
data/ext/carray_slab.c ADDED
@@ -0,0 +1,1219 @@
1
+ /* ---------------------------------------------------------------------------
2
+
3
+ Slab-iterator engine.
4
+
5
+ Public surface (all on rb_cCArray, defined in Init_carray_slab):
6
+
7
+ CArray#map_slab (axis:, data_type: nil) { |slab| ... } -> CArray
8
+ CArray#reduce_slab (axis:, init: <opt>, data_type: nil) { |...| ... } -> CArray
9
+ CArray#each_slab (axis:) { |slab| ... } -> self
10
+ CArray#each_slab (axis:) -> Enumerator
11
+
12
+ axis: accepts Integer / Array<Integer> / nil (= full-view slab = all axes).
13
+
14
+ Internals:
15
+ - ca_slab_iter_state_t (see carray_slab.h) is the per-call state struct.
16
+ - Each entry point stack-allocates a zeroed state, populates it via
17
+ slab_state_init (= nofail; only validation + scalar field fills),
18
+ then rb_ensure-runs slab_state_run_body (= acquires T1 substrate +
19
+ scratch + output, runs per-form loop) and slab_state_finish (= frees
20
+ T1 state + scratch + nils the VALUE handles).
21
+ - All heap acquisition lives in run_body so init-time rb_raise paths
22
+ cannot leak; rb_ensure guarantees finish on both normal and
23
+ exception exit.
24
+ - Conservative stack scan keeps the state struct's VALUE fields alive
25
+ across rb_yield; no dmark callback is required because the state is
26
+ not Ruby-wrapped.
27
+
28
+ --------------------------------------------------------------------------- */
29
+
30
+ #include "carray.h"
31
+ #include "carray_slab.h"
32
+
33
+ /* Forward declarations: per-form loop bodies are defined later in this
34
+ file and take a state pointer directly. */
35
+ static VALUE ca_slab_run_map (ca_slab_iter_state_t *st);
36
+ static VALUE ca_slab_run_reduce_slab (ca_slab_iter_state_t *st);
37
+ static VALUE ca_slab_run_reduce_fiber (ca_slab_iter_state_t *st);
38
+ static VALUE ca_slab_run_each (ca_slab_iter_state_t *st);
39
+
40
+ /* ------------------------------------------------------------------- */
41
+ /* axis parsing helper */
42
+ /* ------------------------------------------------------------------- */
43
+
44
+ /* Parse `axis_arg` (Integer / Array<Integer> / nil) into slab_axes[] /
45
+ slab_ndim on `st`. Negative indices are normalised against src_ndim.
46
+ Raises ArgumentError on out-of-range or duplicate axes. */
47
+ static void
48
+ ca_slab_parse_axes (ca_slab_iter_state_t *st, VALUE axis_arg, int8_t src_ndim)
49
+ {
50
+ int i, j, n;
51
+ VALUE arr;
52
+
53
+ if ( NIL_P(axis_arg) ) {
54
+ /* axis: nil = full-view slab (= all axes are slab axes) */
55
+ st->slab_ndim = src_ndim;
56
+ for ( i = 0; i < src_ndim; i++ ) st->slab_axes[i] = (int8_t) i;
57
+ return;
58
+ }
59
+
60
+ if ( TYPE(axis_arg) == T_ARRAY ) {
61
+ arr = axis_arg;
62
+ }
63
+ else {
64
+ arr = rb_ary_new3(1, axis_arg);
65
+ }
66
+
67
+ n = (int) RARRAY_LEN(arr);
68
+ if ( n < 1 || n > CA_RANK_MAX ) {
69
+ rb_raise(rb_eArgError,
70
+ "CArray::SlabIterator: axis count %d out of range [1..%d]",
71
+ n, CA_RANK_MAX);
72
+ }
73
+
74
+ for ( i = 0; i < n; i++ ) {
75
+ int k = NUM2INT(rb_ary_entry(arr, i));
76
+ if ( k < 0 ) k += src_ndim;
77
+ if ( k < 0 || k >= src_ndim ) {
78
+ rb_raise(rb_eArgError,
79
+ "CArray::SlabIterator: axis %d out of range for ndim %d",
80
+ NUM2INT(rb_ary_entry(arr, i)), src_ndim);
81
+ }
82
+ for ( j = 0; j < i; j++ ) {
83
+ if ( st->slab_axes[j] == (int8_t) k ) {
84
+ rb_raise(rb_eArgError,
85
+ "CArray::SlabIterator: duplicate axis %d", k);
86
+ }
87
+ }
88
+ st->slab_axes[i] = (int8_t) k;
89
+ }
90
+ st->slab_ndim = (int8_t) n;
91
+ }
92
+
93
+ /* ------------------------------------------------------------------- */
94
+ /* slab_state_init -- nofail population of a stack-allocated state. */
95
+ /* ------------------------------------------------------------------- */
96
+
97
+ /* Populate `st` (= caller's stack-allocated, zero-initialised state) with
98
+ form / source / axes / data_type / init_val. Validation that may
99
+ `rb_raise` (= axis range, duplicate axis, bad data_type symbol) runs
100
+ here, BEFORE any heap acquisition — so an init-time raise leaks
101
+ nothing. All xmalloc / rb_carray_new / ca_iter_state_init_l2 calls
102
+ live downstream in slab_state_run_body / ca_slab_run_*, where
103
+ rb_ensure protects them via slab_state_finish.
104
+
105
+ form : CA_SLAB_FORM_{MAP, REDUCE_FIBER, REDUCE_SLAB, EACH}
106
+ source : VALUE pointing to the source CArray (or subclass)
107
+ axis_arg : VALUE — Integer / Array / nil
108
+ data_type : VALUE — Symbol / Integer / nil (= source.data_type).
109
+ Ignored when form == EACH.
110
+ init_val : VALUE — REDUCE_FIBER initial accumulator, or Qundef
111
+ if not applicable. Stored as Qnil if Qundef. */
112
+ static void
113
+ slab_state_init (ca_slab_iter_state_t *st, VALUE source, int8_t form,
114
+ VALUE axis_arg, VALUE data_type, VALUE init_val)
115
+ {
116
+ CArray *src;
117
+
118
+ /* Caller stack-allocates with `{0}` which sets VALUE fields to Qfalse
119
+ (= 0). Body code uses `== Qnil` to distinguish unset slots from
120
+ populated CArray handles, so we must promote them to Qnil here. */
121
+ st->self = Qnil;
122
+ st->slab_view = Qnil;
123
+ st->carrier = Qnil;
124
+ st->mask_carrier = Qnil;
125
+ st->output = Qnil;
126
+ st->output_slab_view = Qnil;
127
+ st->init_val = Qnil;
128
+
129
+ rb_check_carray_object(source);
130
+ TypedData_Get_Struct(source, CArray, &carray_data_type, src);
131
+
132
+ st->form = form;
133
+ st->self = source;
134
+
135
+ ca_slab_parse_axes(st, axis_arg, src->ndim);
136
+
137
+ if ( form == CA_SLAB_FORM_MAP ||
138
+ form == CA_SLAB_FORM_REDUCE_FIBER ||
139
+ form == CA_SLAB_FORM_REDUCE_SLAB ) {
140
+ if ( NIL_P(data_type) ) {
141
+ st->out_data_type = src->data_type;
142
+ st->out_bytes = src->bytes;
143
+ }
144
+ else {
145
+ int8_t dt;
146
+ ca_size_t bytes;
147
+ rb_ca_guess_type_and_bytes(data_type, Qnil, &dt, &bytes);
148
+ st->out_data_type = dt;
149
+ st->out_bytes = bytes;
150
+ }
151
+
152
+ if ( form == CA_SLAB_FORM_REDUCE_FIBER ) {
153
+ st->init_val = (init_val == Qundef) ? Qnil : init_val;
154
+ }
155
+ }
156
+ }
157
+
158
+ /* ------------------------------------------------------------------- */
159
+ /* MAP form loop */
160
+ /* ------------------------------------------------------------------- */
161
+
162
+ /* Slab-view modes:
163
+
164
+ ALIAS mode : slab_view.parent = src, base_offset mutates per iter
165
+ (+ ptr mutates for fast path)
166
+ SCRATCH mode: slab_view.parent = carrier, base_offset = 0 (constant)
167
+ (carrier = internal CAWrap borrowing T1's scratch buffer;
168
+ per-iter ptr mutation tracks T1's possibly-realloc'd scratch)
169
+
170
+ Both modes use CAStride (not a bare CAWrap) so that:
171
+ - ca_stride_func_xfer_index's ptr fast path hits when ptr is
172
+ non-NULL -- no compose-fold work per cell read.
173
+ - ca_stride_func_clone preserves (parent, base_offset, strides), so
174
+ `row.dup` and other derived views see the CURRENT iter's data:
175
+ * ALIAS : clone reads src memory directly (base_offset is
176
+ just-mutated, strides are src-native = correct)
177
+ * SCRATCH: clone reads carrier (= a CAWrap borrowing T1 scratch),
178
+ base_offset=0, strides=contig — also correct because
179
+ carrier->ptr was set to the iter's scratch addr.
180
+ - a bare CAWrap-as-slab would be correct in-block but lose the
181
+ parent-slot GC pin in escape scenarios.
182
+
183
+ CAREFUL: in ALIAS mode mutate both ptr AND base_offset per iter.
184
+ The ptr fast path alone would suffice for xfer_index, but
185
+ ca_stride_func_clone bypasses ptr and reads (parent, base_offset,
186
+ strides) directly -- a stale base_offset there yields wrong data for
187
+ `slab.dup` etc. */
188
+
189
+ /* ------------------------------------------------------------------- */
190
+ /* K-D gather/scatter helpers for non-contig multi-axis */
191
+ /* ------------------------------------------------------------------- */
192
+
193
+ /* Test whether `strides` describe a row-major contig layout over `dims`
194
+ in `bytes`-element units. innermost stride must be `bytes` and each
195
+ outer stride must be `bytes * Π later_dims`. Single-axis collapses
196
+ to `strides[0] == bytes`. */
197
+ static int
198
+ ca_slab_strides_are_row_major_contig (int8_t ndim, ca_size_t *dims,
199
+ ca_size_t *strides, ca_size_t bytes)
200
+ {
201
+ ca_size_t expected = bytes;
202
+ int8_t k;
203
+ for ( k = ndim - 1; k >= 0; k-- ) {
204
+ if ( strides[k] != expected ) return 0;
205
+ expected *= dims[k];
206
+ }
207
+ return 1;
208
+ }
209
+
210
+ /* K-D gather: row-major walk over (ndim, dims), copy each cell from
211
+ src_base + Σ idx[k] * src_strides[k] into dst + i * bytes. dst is
212
+ a contig buffer of size (Π dims) * bytes. */
213
+ static void
214
+ ca_slab_gather_k_d (char *dst, char *src_base,
215
+ int8_t ndim, ca_size_t *dims,
216
+ ca_size_t *src_strides, ca_size_t bytes)
217
+ {
218
+ ca_size_t kidx[CA_RANK_MAX] = { 0 };
219
+ ca_size_t total = 1;
220
+ ca_size_t i;
221
+ int8_t k;
222
+ for ( k = 0; k < ndim; k++ ) total *= dims[k];
223
+ for ( i = 0; i < total; i++ ) {
224
+ ca_size_t off = 0;
225
+ for ( k = 0; k < ndim; k++ ) off += kidx[k] * src_strides[k];
226
+ memcpy(dst + i * bytes, src_base + off, bytes);
227
+ for ( k = ndim - 1; k >= 0; k-- ) {
228
+ if ( ++kidx[k] < dims[k] ) break;
229
+ kidx[k] = 0;
230
+ }
231
+ }
232
+ }
233
+
234
+ /* K-D scatter: row-major walk from contig src to dst_base via dst_strides. */
235
+ static void
236
+ ca_slab_scatter_k_d (char *dst_base, char *src,
237
+ int8_t ndim, ca_size_t *dims,
238
+ ca_size_t *dst_strides, ca_size_t bytes)
239
+ {
240
+ ca_size_t kidx[CA_RANK_MAX] = { 0 };
241
+ ca_size_t total = 1;
242
+ ca_size_t i;
243
+ int8_t k;
244
+ for ( k = 0; k < ndim; k++ ) total *= dims[k];
245
+ for ( i = 0; i < total; i++ ) {
246
+ ca_size_t off = 0;
247
+ for ( k = 0; k < ndim; k++ ) off += kidx[k] * dst_strides[k];
248
+ memcpy(dst_base + off, src + i * bytes, bytes);
249
+ for ( k = ndim - 1; k >= 0; k-- ) {
250
+ if ( ++kidx[k] < dims[k] ) break;
251
+ kidx[k] = 0;
252
+ }
253
+ }
254
+ }
255
+
256
+ /* Build a CAStride wrapping `parent_value` (= a CArray VALUE that the
257
+ caller has already created and GC-pinned). ndim = 1, dim = [fiber_len],
258
+ strides = [stride]. base_offset = 0; the caller mutates it per iter
259
+ in ALIAS mode. ptr is NULL initially; caller mutates it per iter to
260
+ T1's contig delivery. attach = 1 forces user-side ca_attach into
261
+ the increment-only path. */
262
+ static VALUE
263
+ ca_slab_build_slab_view (VALUE parent_value, int8_t data_type,
264
+ ca_size_t bytes, int8_t ndim,
265
+ ca_size_t *dim, ca_size_t *strides)
266
+ {
267
+ CArray *parent;
268
+ CAStride *cs;
269
+ VALUE obj;
270
+ TypedData_Get_Struct(parent_value, CArray, &carray_data_type, parent);
271
+ cs = ca_stride_new(CA_OBJ_STRIDE, parent, data_type, bytes,
272
+ ndim, dim, strides, 0);
273
+ cs->attach = 1;
274
+ cs->ptr = NULL;
275
+ /* Sync mask's view attach counter to match the data side. Without
276
+ this, a user `slab.to_a` (= ca_attach + ca_detach pair on slab_view)
277
+ stays net-zero on the data side (attach 1->2->1) but over-detaches
278
+ on the mask side (attach 0->1->0 invokes the mask's func.detach,
279
+ NULLing mask->ptr that T1 owns and decrementing root mask attach
280
+ unilaterally). */
281
+ if ( cs->mask != NULL && ca_is_view(cs->mask) ) {
282
+ CAVIEW(cs->mask)->attach = 1;
283
+ }
284
+ obj = ca_wrap_struct(cs);
285
+ rb_ca_set_parent(obj, parent_value);
286
+ return obj;
287
+ }
288
+
289
+ /* Build an internal CAWrap that will borrow T1's scratch buffer in
290
+ SCRATCH mode. This carrier is the parent slot of slab_view, so
291
+ GC-pinning slab_view (via iter state ivar) keeps the carrier alive
292
+ for the entire walk. Carrier never escapes to user surface. T1 owns
293
+ the scratch; the slab view borrows it. free_ca_wrap leaves ptr
294
+ untouched, so dropping the carrier ivar at finish does NOT xfree T1's
295
+ scratch.
296
+
297
+ When `has_mask` is non-zero, the carrier is paired with an internal
298
+ boolean8_t CAWrap mask whose `ptr` will track T1's fiber_mask_scratch
299
+ per iter. The mask is wired into `carrier->mask` directly (= bypass
300
+ ca_setup_mask, which would try to sync data from a NULL ptr). The
301
+ carrier owns the mask via free_ca_wrap → ca_free(carrier->mask), so
302
+ we DO NOT also keep the mask as a separate Ruby VALUE (= avoid double
303
+ free via two GC chains). */
304
+ static VALUE
305
+ ca_slab_build_carrier (int8_t data_type, ca_size_t bytes,
306
+ ca_size_t fiber_len, int has_mask)
307
+ {
308
+ ca_size_t dim[1];
309
+ CAWrap *cw;
310
+ dim[0] = fiber_len;
311
+ cw = ca_wrap_new_null(data_type, 1, dim, bytes, NULL);
312
+ if ( has_mask ) {
313
+ /* Build a mask sibling. Direct field assignment bypasses
314
+ ca_setup_mask (= which calls ca_sync_data on a NULL ptr and
315
+ crashes). mask_carrier is owned by carrier via free_ca_wrap chain;
316
+ no separate Ruby pinning needed. */
317
+ CAWrap *mask_cw = ca_wrap_new_null(CA_BOOLEAN, 1, dim, 1, NULL);
318
+ cw->mask = (CArray *) mask_cw;
319
+ ca_set_flag(mask_cw, CA_FLAG_MASK_ARRAY);
320
+ }
321
+ return TypedData_Wrap_Struct(rb_cCAWrap, &cawrap_data_type, cw);
322
+ }
323
+
324
+ /* Build the OUTPUT slab view as a simple CAWrap with mutable ptr.
325
+ Output is internal — user never derives a view from it — so the
326
+ parent-slot GC pin / clone correctness arguments don't apply.
327
+
328
+ ndim/dim accept K-D shape so the OUTPUT slab matches the input slab
329
+ for multi-axis map_slab (= the block returns a K-D CArray of the same
330
+ shape as the input slab; ca_xfer_all + p_out works as a contig memcpy
331
+ when the output slab is row-major contig in output memory, which is
332
+ the case for innermost-K axes on a freshly-allocated entity). */
333
+ static VALUE
334
+ ca_slab_build_output_view (int8_t data_type, ca_size_t bytes,
335
+ int8_t ndim, ca_size_t *dim)
336
+ {
337
+ CAWrap *cw;
338
+ cw = ca_wrap_new_null(data_type, ndim, dim, bytes, NULL);
339
+ return TypedData_Wrap_Struct(rb_cCAWrap, &cawrap_data_type, cw);
340
+ }
341
+
342
+ /* Common setup of the input slab view for all 4 forms. Decides ALIAS
343
+ vs SCRATCH from T1's slab_strides[0] vs src->bytes equality, builds
344
+ the slab_view + (when SCRATCH) carrier in place. The mask travels
345
+ alongside data automatically (= mask has the same shape as the value
346
+ array, so the same ALIAS / SCRATCH dispatch is used; ca_stride_setup
347
+ auto-creates slab_view->mask over parent->mask in both modes).
348
+ Returns 1 if ALIAS mode, 0 if SCRATCH. */
349
+ static int
350
+ ca_slab_setup_input_slab_view (ca_slab_iter_state_t *st, CArray *src,
351
+ ca_size_t fiber_len)
352
+ {
353
+ int8_t slab_ndim = st->t1->slab_ndim;
354
+ int is_multi = (slab_ndim > 1);
355
+ int has_mask = ca_has_mask(src);
356
+ int in_alias;
357
+
358
+ if ( is_multi ) {
359
+ /* Multi-axis slab.
360
+
361
+ Two paths:
362
+ (a) contig case (= slab_strides row-major over slab_dims): slab
363
+ is naturally contig in src memory, enable ptr fast path with
364
+ parent=src, base_offset mutated per iter.
365
+ (b) non-contig case (= e.g., axis: [0, 2] on a 3-D row-major
366
+ source): build OUR OWN gather scratch carrier (= CAWrap whose
367
+ ptr is an xmalloc'd buffer we own), gather K-D per iter from
368
+ p_in via slab_strides into the scratch, present slab_view as
369
+ a CAStride over the carrier with contig strides.
370
+
371
+ Both paths require ALIAS T1 mode (= p_in is in src memory).
372
+ Non-ALIAS multi-axis T1 modes (= PER_SLAB scratch where T1 picks
373
+ its own gather for descriptor-framework sources) remain
374
+ NotImpError until a follow-on substrate phase. */
375
+ uint8_t am = st->t1->alias_mode;
376
+ int contig;
377
+ if ( am != CA_ITER_ALIAS_CONTIG &&
378
+ am != CA_ITER_ALIAS_STRIDED &&
379
+ am != CA_ITER_ALIAS_ATTACH ) {
380
+ rb_raise(rb_eNotImpError,
381
+ "multi-axis slab with non-ALIAS T1 mode not implemented yet "
382
+ "(alias_mode = %d)", (int) am);
383
+ }
384
+ contig = ca_slab_strides_are_row_major_contig(slab_ndim,
385
+ st->t1->slab_dims,
386
+ st->t1->slab_strides,
387
+ src->bytes);
388
+ if ( contig ) {
389
+ /* (a) contig multi-axis ALIAS: parent=src, K-D strides match src. */
390
+ st->slab_view = ca_slab_build_slab_view(st->self, src->data_type,
391
+ src->bytes, slab_ndim,
392
+ st->t1->slab_dims,
393
+ st->t1->slab_strides);
394
+ in_alias = 1;
395
+ } else {
396
+ /* (b) non-contig multi-axis: alloc own scratch + carrier; build
397
+ slab_view atop carrier with row-major contig strides. Per-iter
398
+ gather lives in ca_slab_advance_slab_view (= K-D walk). */
399
+ ca_size_t elements = st->t1->slab_elements;
400
+ ca_size_t contig_strides[CA_RANK_MAX];
401
+ ca_size_t exp = src->bytes;
402
+ int8_t k;
403
+ st->own_scratch_elements = elements;
404
+ st->own_data_scratch = (char *) xmalloc(elements * src->bytes);
405
+ if ( has_mask ) {
406
+ st->own_mask_scratch = (boolean8_t *) xmalloc(elements);
407
+ }
408
+ /* Carrier wraps our own scratch. Direct field assignment of
409
+ carrier->ptr (= bypasses ca_setup_mask which would crash on
410
+ NULL mask ptr). Mask sibling is similarly wired post-build. */
411
+ {
412
+ CAWrap *cw = ca_wrap_new_null(src->data_type, slab_ndim,
413
+ st->t1->slab_dims, src->bytes, NULL);
414
+ cw->ptr = st->own_data_scratch;
415
+ if ( has_mask ) {
416
+ CAWrap *mcw = ca_wrap_new_null(CA_BOOLEAN, slab_ndim,
417
+ st->t1->slab_dims, 1, NULL);
418
+ mcw->ptr = (char *) st->own_mask_scratch;
419
+ cw->mask = (CArray *) mcw;
420
+ ca_set_flag(mcw, CA_FLAG_MASK_ARRAY);
421
+ }
422
+ st->carrier = TypedData_Wrap_Struct(rb_cCAWrap, &cawrap_data_type, cw);
423
+ }
424
+ /* Build slab_view's strides as row-major contig over slab_dims. */
425
+ for ( k = slab_ndim - 1; k >= 0; k-- ) {
426
+ contig_strides[k] = exp;
427
+ exp *= st->t1->slab_dims[k];
428
+ }
429
+ st->slab_view = ca_slab_build_slab_view(st->carrier, src->data_type,
430
+ src->bytes, slab_ndim,
431
+ st->t1->slab_dims,
432
+ contig_strides);
433
+ in_alias = 0;
434
+ }
435
+ (void) fiber_len; /* unused for multi-axis */
436
+ }
437
+ else {
438
+ ca_size_t in_stride = st->t1->slab_strides[0];
439
+ ca_size_t dim[1];
440
+ ca_size_t strides[1];
441
+ dim[0] = fiber_len;
442
+ strides[0] = src->bytes;
443
+ in_alias = (in_stride == src->bytes);
444
+
445
+ if ( in_alias ) {
446
+ /* ALIAS: parent = src. ca_stride_setup auto-creates slab_view->mask
447
+ as a CAStride over src->mask when src has mask. Per-iter mutates
448
+ (ptr, base_offset) on both data and mask sides. */
449
+ st->slab_view = ca_slab_build_slab_view(st->self, src->data_type,
450
+ src->bytes, 1, dim, strides);
451
+ }
452
+ else {
453
+ /* SCRATCH: carrier wraps T1's scratch + (when src has mask) wires
454
+ an internal mask sibling. slab_view is then auto-created with a
455
+ mask CAStride over carrier->mask = mask sibling. */
456
+ st->carrier = ca_slab_build_carrier(src->data_type, src->bytes,
457
+ fiber_len, has_mask);
458
+ st->slab_view = ca_slab_build_slab_view(st->carrier, src->data_type,
459
+ src->bytes, 1, dim, strides);
460
+ }
461
+ }
462
+
463
+ return in_alias;
464
+ }
465
+
466
+ /* Per-iter update of the slab_view + mask pointers. Called from inside
467
+ the form-specific loop after ca_iter_state_next_slab_axes returns the
468
+ data ptr `p_in` and mask ptr `m_in` (= NULL for unmasked sources).
469
+ For SCRATCH mode the data carrier's ptr is also tracked so the clone
470
+ path (= compose-fold via carrier) reads the current iter buffer. */
471
+ static void
472
+ ca_slab_advance_slab_view (ca_slab_iter_state_t *st,
473
+ CAStride *slab_cs, CAWrap *carrier_cw,
474
+ CArray *src, char *p_in, boolean8_t *m_in,
475
+ int in_alias)
476
+ {
477
+ if ( in_alias ) {
478
+ /* ALIAS — single-axis OR multi-axis contig. ptr fast path uses
479
+ ca_index2addr * bytes + ptr (= safe since slab is row-major
480
+ contig in src memory). base_offset is also updated so clone /
481
+ compose-fold derived views read the same cells via src memory. */
482
+ slab_cs->ptr = p_in;
483
+ slab_cs->base_offset = (ca_size_t) (p_in - src->ptr);
484
+ if ( slab_cs->mask != NULL ) {
485
+ CAStride *mask_cs = (CAStride *) slab_cs->mask;
486
+ mask_cs->ptr = (char *) m_in;
487
+ mask_cs->base_offset = (ca_size_t) ((char *) m_in - src->mask->ptr);
488
+ }
489
+ } else if ( st->own_data_scratch != NULL ) {
490
+ /* Non-contig multi-axis: K-D gather from src memory (via
491
+ p_in + slab_strides) into our own contig scratch. carrier_cw->
492
+ ptr is already pointed at the scratch from setup, no mutation
493
+ needed. slab_view shares carrier's ptr (= contig). */
494
+ ca_slab_gather_k_d(st->own_data_scratch, p_in,
495
+ st->t1->slab_ndim, st->t1->slab_dims,
496
+ st->t1->slab_strides, src->bytes);
497
+ if ( st->own_mask_scratch != NULL ) {
498
+ ca_slab_gather_k_d((char *) st->own_mask_scratch, (char *) m_in,
499
+ st->t1->slab_ndim, st->t1->slab_dims,
500
+ st->t1->slab_mask_strides, 1);
501
+ }
502
+ } else {
503
+ /* SCRATCH (single-axis): T1 owns the contig scratch; we just point
504
+ both slab_view and carrier at the iter's delivered addr. */
505
+ slab_cs->ptr = p_in;
506
+ carrier_cw->ptr = p_in;
507
+ if ( slab_cs->mask != NULL ) {
508
+ CAStride *mask_cs = (CAStride *) slab_cs->mask;
509
+ mask_cs->ptr = (char *) m_in;
510
+ carrier_cw->mask->ptr = (char *) m_in;
511
+ }
512
+ }
513
+ }
514
+
515
+ /* Convert a Ruby Numeric scalar into bytes of slab_view's data_type and
516
+ fill the entire slab buffer at out_ptr. Used when the user block
517
+ returns a scalar instead of a CArray (= broadcast over the slab).
518
+ `slab_elements` = Π slab_dims[] (= total cells, multi-axis K-D). */
519
+ static void
520
+ ca_slab_fill_scalar (VALUE source, char *out_ptr, ca_size_t slab_elements,
521
+ int8_t out_data_type, ca_size_t out_bytes, VALUE val)
522
+ {
523
+ char buf[64];
524
+ char *scratch = (out_bytes <= (ca_size_t) sizeof(buf)) ? buf
525
+ : xmalloc(out_bytes);
526
+ ca_size_t i;
527
+ /* Use source's data_type-aware conversion (rb_ca_obj2ptr looks at the
528
+ receiver's data_type). Build a stand-in scalar buffer via the
529
+ source CArray when out_data_type matches source data_type; otherwise we
530
+ need a CArray of the output data_type to drive the conversion. We
531
+ accept the source-data_type default plus an explicit data_type kwarg
532
+ that may differ — in either case the easiest path is to pull
533
+ conversion through the actual output entity (= we already have
534
+ `source`-keyed obj2ptr in carray_cast.c). Here we construct an
535
+ ephemeral CScalar of the output data_type to anchor the cast. */
536
+ VALUE cs_anchor =
537
+ rb_cscalar_new_with_value((int) out_data_type, (int) out_bytes, val);
538
+ CScalar *anchor_ca;
539
+ (void) source;
540
+ TypedData_Get_Struct(cs_anchor, CScalar, &cscalar_data_type, anchor_ca);
541
+ ca_attach(anchor_ca);
542
+ memcpy(scratch, anchor_ca->ptr, out_bytes);
543
+ ca_detach(anchor_ca);
544
+ for ( i = 0; i < slab_elements; i++ ) {
545
+ memcpy(out_ptr + i * out_bytes, scratch, out_bytes);
546
+ }
547
+ if ( scratch != buf ) xfree(scratch);
548
+ }
549
+
550
+ static VALUE
551
+ ca_slab_run_map (ca_slab_iter_state_t *st)
552
+ {
553
+ CArray *src;
554
+ CArray *out;
555
+ uint32_t flags_in;
556
+ uint32_t flags_out;
557
+ ca_size_t slab_elements;
558
+ int rc;
559
+ int is_multi = (st->slab_ndim > 1);
560
+
561
+ TypedData_Get_Struct(st->self, CArray, &carray_data_type, src);
562
+
563
+ /* Mask transparent carry: input mask is exposed via slab_view
564
+ (= user's block can see slab.mask / slab.has_mask?). Output mask
565
+ is intentionally NOT scattered — block return CArray mask info is
566
+ dropped (= simplest contract). */
567
+
568
+ /* Step 1: allocate output entity (same shape as src, data_type from opts). */
569
+ st->output = rb_carray_new(st->out_data_type, src->ndim, src->dim,
570
+ st->out_bytes, NULL);
571
+ TypedData_Get_Struct(st->output, CArray, &carray_data_type, out);
572
+ ca_allocate(out); /* entity ptr usable as both read + write target */
573
+
574
+ /* Step 2: T1 init on both sides. FIBER_CONTIG is naxes==1 only;
575
+ multi-axis omits it. Output side gets CA_KERNEL_WRITE. */
576
+ st->t1 = (ca_iter_state *) xmalloc(sizeof(ca_iter_state));
577
+ st->t1_out = (ca_iter_state *) xmalloc(sizeof(ca_iter_state));
578
+
579
+ flags_in = is_multi ? 0 : CA_KERNEL_FIBER_CONTIG;
580
+ flags_out = (is_multi ? 0 : CA_KERNEL_FIBER_CONTIG) | CA_KERNEL_WRITE;
581
+
582
+ rc = ca_iter_state_init_l2(st->t1, src, CA_SLAB_AXES,
583
+ st->slab_axes, st->slab_ndim, flags_in);
584
+ if ( rc != CA_ITER_OK ) {
585
+ rb_raise(rb_eRuntimeError,
586
+ "CArray#map_slab: T1 init (READ side) failed rc=%d", rc);
587
+ }
588
+ st->t1_started = 1;
589
+
590
+ rc = ca_iter_state_init_l2(st->t1_out, out, CA_SLAB_AXES,
591
+ st->slab_axes, st->slab_ndim, flags_out);
592
+ if ( rc != CA_ITER_OK ) {
593
+ rb_raise(rb_eRuntimeError,
594
+ "CArray#map_slab: T1 init (WRITE side) failed rc=%d", rc);
595
+ }
596
+ st->t1_out_started = 1;
597
+
598
+ slab_elements = st->t1->slab_elements;
599
+
600
+ /* Step 3: input slab view + carrier + mask plumbing (= centralises
601
+ ALIAS / SCRATCH dispatch + mask carry + multi-axis contig +
602
+ non-contig paths via own_data_scratch). */
603
+ ca_slab_setup_input_slab_view(st, src, st->t1->slab_dims[0]);
604
+
605
+ /* Output slab view: K-D CAWrap matching the input slab shape (= same
606
+ slab_dims). For non-contig multi-axis (= input went through own-
607
+ scratch gather), allocate parallel own_out_scratch so we can scatter
608
+ the block's result back to non-contig output memory via the K-D
609
+ scatter helper. For the contig case, output_slab_view wraps p_out
610
+ directly each iter (= existing fast path). */
611
+ st->output_slab_view = ca_slab_build_output_view(out->data_type,
612
+ out->bytes,
613
+ st->t1->slab_ndim,
614
+ st->t1->slab_dims);
615
+ if ( st->own_data_scratch != NULL ) {
616
+ st->own_out_scratch =
617
+ (char *) xmalloc(st->own_scratch_elements * st->out_bytes);
618
+ }
619
+
620
+ /* Step 4: per-slab loop.
621
+ CAREFUL: never call ca_detach(slab_view) here — the T1 buffer is
622
+ borrowed; the slab_view's ptr would otherwise be mis-identified as
623
+ slab_view-owned at finish/dfree and double-freed. GC reclaims the
624
+ CAStride / CAWrap wrappers via ivar drop in run_ensure (free_ca_stride
625
+ and free_ca_wrap both leave ptr untouched). */
626
+ {
627
+ char *p_in, *p_out;
628
+ boolean8_t *m_in, *m_out;
629
+ CAStride *slab_cs;
630
+ CAWrap *carrier_cw = NULL;
631
+ CAWrap *out_slab_cw;
632
+ int in_alias = (st->carrier == Qnil);
633
+ TypedData_Get_Struct(st->slab_view, CAStride,
634
+ &castride_data_type, slab_cs);
635
+ if ( ! in_alias ) {
636
+ TypedData_Get_Struct(st->carrier, CAWrap,
637
+ &cawrap_data_type, carrier_cw);
638
+ }
639
+ TypedData_Get_Struct(st->output_slab_view, CAWrap,
640
+ &cawrap_data_type, out_slab_cw);
641
+
642
+ while ( ca_iter_state_next_slab_axes(st->t1, &p_in, &m_in) &&
643
+ ca_iter_state_next_slab_axes(st->t1_out, &p_out, &m_out) ) {
644
+ VALUE result;
645
+
646
+ ca_slab_advance_slab_view(st, slab_cs, carrier_cw,
647
+ src, p_in, m_in, in_alias);
648
+ /* Output write target per iter:
649
+ - contig case (= no own_out_scratch): write directly into
650
+ p_out (= output memory at this slab's offset)
651
+ - non-contig case (= own_out_scratch alloc'd): write into our
652
+ contig scratch, then K-D scatter back to p_out via t1_out
653
+ slab_strides */
654
+ {
655
+ char *write_target = (st->own_out_scratch != NULL)
656
+ ? st->own_out_scratch : p_out;
657
+ out_slab_cw->ptr = write_target;
658
+
659
+ result = rb_yield(st->slab_view);
660
+
661
+ /* Strict shape check. */
662
+ if ( rb_obj_is_kind_of(result, rb_cCArray) ) {
663
+ CArray *res;
664
+ int shape_ok = 0;
665
+ TypedData_Get_Struct(result, CArray, &carray_data_type, res);
666
+ if ( res->elements == slab_elements ) {
667
+ if ( res->ndim == slab_cs->ndim ) {
668
+ int8_t k;
669
+ shape_ok = 1;
670
+ for ( k = 0; k < res->ndim; k++ ) {
671
+ if ( res->dim[k] != slab_cs->dim[k] ) { shape_ok = 0; break; }
672
+ }
673
+ }
674
+ else if ( ! is_multi && res->ndim == 1 &&
675
+ res->dim[0] == slab_cs->dim[0] ) {
676
+ shape_ok = 1;
677
+ }
678
+ }
679
+ if ( ! shape_ok ) {
680
+ rb_raise(rb_eArgError,
681
+ "CArray#map_slab: block result shape mismatch "
682
+ "(got ndim=%d, elements=%lld; expected %lld elements "
683
+ "in shape matching the slab, ndim=%d)",
684
+ (int) res->ndim, (long long) res->elements,
685
+ (long long) slab_elements, (int) slab_cs->ndim);
686
+ }
687
+ if ( res->data_type == st->out_data_type && res->bytes == st->out_bytes ) {
688
+ ca_attach(res);
689
+ ca_xfer_all(res, write_target, CA_XFER_GET);
690
+ ca_detach(res);
691
+ }
692
+ else {
693
+ /* Cast-on-scatter via per-cell obj2ptr. Writes to write_target
694
+ (= either p_out direct or own_out_scratch). */
695
+ char buf[64];
696
+ char *scratch = (st->out_bytes <= (ca_size_t) sizeof(buf))
697
+ ? buf : xmalloc(st->out_bytes);
698
+ ca_size_t i;
699
+ ca_attach(res);
700
+ for ( i = 0; i < slab_elements; i++ ) {
701
+ VALUE elem;
702
+ ca_size_t idx[CA_RANK_MAX] = { 0 };
703
+ ca_size_t flat = i;
704
+ int8_t k;
705
+ for ( k = res->ndim - 1; k >= 0; k-- ) {
706
+ idx[k] = flat % res->dim[k];
707
+ flat /= res->dim[k];
708
+ }
709
+ elem = rb_ca_fetch_index(result, idx);
710
+ rb_ca_obj2ptr(st->output_slab_view, elem, scratch);
711
+ memcpy(write_target + i * st->out_bytes, scratch, st->out_bytes);
712
+ }
713
+ ca_detach(res);
714
+ if ( scratch != buf ) xfree(scratch);
715
+ }
716
+ }
717
+ else if ( rb_obj_is_kind_of(result, rb_cNumeric) ||
718
+ result == Qtrue || result == Qfalse ||
719
+ result == Qnil || ca_is_object_type(out) ) {
720
+ /* Scalar broadcast fill into write_target. */
721
+ ca_slab_fill_scalar(st->self, write_target, slab_elements,
722
+ st->out_data_type, st->out_bytes, result);
723
+ }
724
+ else {
725
+ rb_raise(rb_eArgError,
726
+ "CArray#map_slab: block must return CArray (same shape) "
727
+ "or Numeric scalar, got %"PRIsVALUE,
728
+ rb_obj_class(result));
729
+ }
730
+
731
+ /* Non-contig multi-axis: K-D scatter from own_out_scratch back to
732
+ output memory at p_out via t1_out's slab_strides. */
733
+ if ( st->own_out_scratch != NULL ) {
734
+ ca_slab_scatter_k_d(p_out, st->own_out_scratch,
735
+ st->t1_out->slab_ndim, st->t1_out->slab_dims,
736
+ st->t1_out->slab_strides, st->out_bytes);
737
+ }
738
+ }
739
+
740
+ ca_iter_state_sync_slab(st->t1_out);
741
+ }
742
+ }
743
+
744
+ return st->output;
745
+ }
746
+
747
+ /* ------------------------------------------------------------------- */
748
+ /* REDUCE forms */
749
+ /* ------------------------------------------------------------------- */
750
+
751
+ /* Common setup for the two REDUCE forms: validate scope (single-axis,
752
+ no mask), allocate the reduced-shape output entity, init T1 READ
753
+ side, build the input slab view with ALIAS / SCRATCH dispatch.
754
+ Sets st->output / st->t1 / st->slab_view / st->carrier and returns
755
+ the source CArray ptr via *out_src + the fiber length via *out_n.
756
+
757
+ REDUCE has no WRITE-side T1; the output is filled cell-by-cell from
758
+ the block return value via rb_ca_obj2ptr. The output's dim is
759
+ src->dim with the slab axis removed (1-D src reducing axis 0 = scalar
760
+ form (ndim=1, dim=[1])). */
761
+ static void
762
+ ca_slab_reduce_setup (ca_slab_iter_state_t *st, CArray **out_src,
763
+ ca_size_t *out_n)
764
+ {
765
+ CArray *src;
766
+ CArray *out;
767
+ ca_size_t fiber_len;
768
+ ca_size_t out_dim[CA_RANK_MAX];
769
+ int8_t out_ndim;
770
+ int rc;
771
+ int8_t i;
772
+ int8_t j;
773
+ uint32_t flags;
774
+ int is_multi = (st->slab_ndim > 1);
775
+
776
+ TypedData_Get_Struct(st->self, CArray, &carray_data_type, src);
777
+
778
+ /* Mask transparent carry: input mask is visible to the user's
779
+ block via slab.mask / slab.has_mask?. For REDUCE_FIBER form
780
+ (= per-element inject), masked cells are yielded as CA::UNDEF —
781
+ matching CArray's standard per-cell access semantics. For
782
+ REDUCE_SLAB form, the user-provided block sees the masked slab
783
+ and can call slab.sum, .mean, etc., which honor mask via the
784
+ existing mask-aware reduction kernels. */
785
+
786
+ /* Compute outer-shape output: drop dim at every slab axis. Reducing
787
+ all axes (= flatten reduce, K = src->ndim) collapses to 0-D; we
788
+ represent this as ndim=1, dim=[1] (= scalar-like, accessible via
789
+ output[0]). CScalar is not exposed for this case. */
790
+ out_ndim = src->ndim - st->slab_ndim;
791
+ if ( out_ndim == 0 ) {
792
+ out_ndim = 1;
793
+ out_dim[0] = 1;
794
+ }
795
+ else {
796
+ j = 0;
797
+ for ( i = 0; i < src->ndim; i++ ) {
798
+ int8_t k;
799
+ int is_slab_axis = 0;
800
+ for ( k = 0; k < st->slab_ndim; k++ ) {
801
+ if ( st->slab_axes[k] == i ) { is_slab_axis = 1; break; }
802
+ }
803
+ if ( ! is_slab_axis ) out_dim[j++] = src->dim[i];
804
+ }
805
+ }
806
+
807
+ st->output = rb_carray_new(st->out_data_type, out_ndim, out_dim,
808
+ st->out_bytes, NULL);
809
+ TypedData_Get_Struct(st->output, CArray, &carray_data_type, out);
810
+ ca_allocate(out);
811
+
812
+ /* FIBER_CONTIG is naxes==1 only (T1 substrate contract). Multi-axis
813
+ omits the flag and uses bare CA_SLAB_AXES; T1 picks an in-src
814
+ alias_mode for entity / CAStride sources (= the only supported case;
815
+ non-ALIAS modes raise NotImpError in setup_input_slab_view). */
816
+ flags = is_multi ? 0 : CA_KERNEL_FIBER_CONTIG;
817
+ st->t1 = (ca_iter_state *) xmalloc(sizeof(ca_iter_state));
818
+ rc = ca_iter_state_init_l2(st->t1, src, CA_SLAB_AXES,
819
+ st->slab_axes, st->slab_ndim, flags);
820
+ if ( rc != CA_ITER_OK ) {
821
+ rb_raise(rb_eRuntimeError,
822
+ "CArray#reduce_slab: T1 init failed rc=%d", rc);
823
+ }
824
+ st->t1_started = 1;
825
+
826
+ fiber_len = st->t1->slab_elements;
827
+
828
+ /* Centralised ALIAS / SCRATCH + multi-axis + mask carry dispatch. */
829
+ ca_slab_setup_input_slab_view(st, src, st->t1->slab_dims[0]);
830
+
831
+ *out_src = src;
832
+ *out_n = fiber_len;
833
+ }
834
+
835
+ /* Per-slab block form: block receives the slab CArray, returns a
836
+ scalar; each scalar is cast into the corresponding output cell.
837
+ (`init:` absent ⇒ this path) */
838
+ static VALUE
839
+ ca_slab_run_reduce_slab (ca_slab_iter_state_t *st)
840
+ {
841
+ CArray *src;
842
+ CArray *out;
843
+ ca_size_t fiber_len;
844
+ char *p_in;
845
+ boolean8_t *m_in;
846
+ CAStride *slab_cs;
847
+ CAWrap *carrier_cw = NULL;
848
+ int in_alias;
849
+ ca_size_t out_idx = 0;
850
+ char buf[64];
851
+ char *scratch;
852
+
853
+ ca_slab_reduce_setup(st, &src, &fiber_len);
854
+ TypedData_Get_Struct(st->output, CArray, &carray_data_type, out);
855
+ TypedData_Get_Struct(st->slab_view, CAStride, &castride_data_type, slab_cs);
856
+ in_alias = (st->carrier == Qnil);
857
+ if ( ! in_alias ) {
858
+ TypedData_Get_Struct(st->carrier, CAWrap, &cawrap_data_type, carrier_cw);
859
+ }
860
+
861
+ scratch = (st->out_bytes <= (ca_size_t) sizeof(buf)) ? buf
862
+ : xmalloc(st->out_bytes);
863
+
864
+ while ( ca_iter_state_next_slab_axes(st->t1, &p_in, &m_in) ) {
865
+ VALUE result;
866
+
867
+ ca_slab_advance_slab_view(st, slab_cs, carrier_cw,
868
+ src, p_in, m_in, in_alias);
869
+
870
+ result = rb_yield(st->slab_view);
871
+
872
+ /* Strict scalar contract: block must return a scalar Ruby value
873
+ (Numeric / Object / etc.). A CArray is a
874
+ contract violation; even a 1-element CArray is rejected, since
875
+ the user almost always means to return the underlying scalar
876
+ value via `slab[0]`. Better surfaced as an error than silently
877
+ coerced through obj2ptr. */
878
+ if ( rb_obj_is_kind_of(result, rb_cCArray) ) {
879
+ CArray *res;
880
+ TypedData_Get_Struct(result, CArray, &carray_data_type, res);
881
+ if ( scratch != buf ) xfree(scratch);
882
+ rb_raise(rb_eArgError,
883
+ "CArray#reduce_slab: per-slab block must return a scalar "
884
+ "(got CArray with %lld element%s; use `slab[0]` or "
885
+ "`slab.sum` etc. to extract the scalar)",
886
+ (long long) res->elements,
887
+ res->elements == 1 ? "" : "s");
888
+ }
889
+
890
+ rb_ca_obj2ptr(st->output, result, scratch);
891
+ memcpy(out->ptr + out_idx * st->out_bytes, scratch, st->out_bytes);
892
+ out_idx++;
893
+ }
894
+
895
+ if ( scratch != buf ) xfree(scratch);
896
+ return st->output;
897
+ }
898
+
899
+ /* Per-element fiber form: block receives (acc, x) for each element in
900
+ the slab, returns the new accumulator. The final accumulator is
901
+ cast into the output cell. (`init:` given ⇒ this path) */
902
+ static VALUE
903
+ ca_slab_run_reduce_fiber (ca_slab_iter_state_t *st)
904
+ {
905
+ CArray *src;
906
+ CArray *out;
907
+ ca_size_t fiber_len;
908
+ char *p_in;
909
+ boolean8_t *m_in;
910
+ CAStride *slab_cs;
911
+ CAWrap *carrier_cw = NULL;
912
+ int in_alias;
913
+ ca_size_t out_idx = 0;
914
+ char buf[64];
915
+ char *scratch;
916
+
917
+ ca_slab_reduce_setup(st, &src, &fiber_len);
918
+ TypedData_Get_Struct(st->output, CArray, &carray_data_type, out);
919
+ TypedData_Get_Struct(st->slab_view, CAStride, &castride_data_type, slab_cs);
920
+ in_alias = (st->carrier == Qnil);
921
+ if ( ! in_alias ) {
922
+ TypedData_Get_Struct(st->carrier, CAWrap, &cawrap_data_type, carrier_cw);
923
+ }
924
+
925
+ scratch = (st->out_bytes <= (ca_size_t) sizeof(buf)) ? buf
926
+ : xmalloc(st->out_bytes);
927
+
928
+ while ( ca_iter_state_next_slab_axes(st->t1, &p_in, &m_in) ) {
929
+ VALUE acc;
930
+ ca_size_t i;
931
+
932
+ ca_slab_advance_slab_view(st, slab_cs, carrier_cw,
933
+ src, p_in, m_in, in_alias);
934
+
935
+ acc = st->init_val;
936
+ {
937
+ /* K-D row-major walk over the slab. For single-axis (slab_ndim=1)
938
+ this collapses to a 1-D fiber loop; for multi-axis it visits
939
+ cells in row-major order over slab_dims. */
940
+ int8_t k;
941
+ ca_size_t kidx[CA_RANK_MAX] = { 0 };
942
+ ca_size_t total = st->t1->slab_elements;
943
+ for ( i = 0; i < total; i++ ) {
944
+ VALUE x;
945
+ x = rb_ca_fetch_index(st->slab_view, kidx);
946
+ acc = rb_yield_values(2, acc, x);
947
+ /* Increment kidx[] row-major. */
948
+ for ( k = slab_cs->ndim - 1; k >= 0; k-- ) {
949
+ if ( ++kidx[k] < slab_cs->dim[k] ) break;
950
+ kidx[k] = 0;
951
+ }
952
+ }
953
+ (void) fiber_len;
954
+ }
955
+
956
+ rb_ca_obj2ptr(st->output, acc, scratch);
957
+ memcpy(out->ptr + out_idx * st->out_bytes, scratch, st->out_bytes);
958
+ out_idx++;
959
+ }
960
+
961
+ if ( scratch != buf ) xfree(scratch);
962
+ return st->output;
963
+ }
964
+
965
+ /* ------------------------------------------------------------------- */
966
+ /* EACH form */
967
+ /* ------------------------------------------------------------------- */
968
+
969
+ /* Side-effect only iteration: yield each slab to the block, discard
970
+ the return value, return self when done. No output entity, no
971
+ scatter — the simplest of the four forms.
972
+
973
+ Slab lifetime: block-only. If the user captures the slab object
974
+ across iterations (= `vals = []; ca.each_slab(...) { |s| vals << s }`),
975
+ every entry will reflect the LAST iter's data (capture-out is a
976
+ documented contract, no runtime check).
977
+
978
+ `break` / `next` / `return` interact with rb_ensure correctly: the
979
+ ensure_fn runs cleanup before the non-local exit propagates. */
980
+ static VALUE
981
+ ca_slab_run_each (ca_slab_iter_state_t *st)
982
+ {
983
+ CArray *src;
984
+ char *p_in;
985
+ boolean8_t *m_in;
986
+ CAStride *slab_cs;
987
+ CAWrap *carrier_cw = NULL;
988
+ int in_alias;
989
+ int rc;
990
+
991
+ TypedData_Get_Struct(st->self, CArray, &carray_data_type, src);
992
+
993
+ /* Mask transparent carry: user's block sees slab.mask /
994
+ slab.has_mask?; the block's return value is discarded by each_slab
995
+ so output mask is irrelevant. */
996
+
997
+ /* FIBER_CONTIG is naxes==1 only; multi-axis omits the flag. */
998
+ {
999
+ uint32_t flags = (st->slab_ndim == 1) ? CA_KERNEL_FIBER_CONTIG : 0;
1000
+ st->t1 = (ca_iter_state *) xmalloc(sizeof(ca_iter_state));
1001
+ rc = ca_iter_state_init_l2(st->t1, src, CA_SLAB_AXES,
1002
+ st->slab_axes, st->slab_ndim, flags);
1003
+ if ( rc != CA_ITER_OK ) {
1004
+ rb_raise(rb_eRuntimeError,
1005
+ "CArray#each_slab: T1 init failed rc=%d", rc);
1006
+ }
1007
+ st->t1_started = 1;
1008
+ }
1009
+
1010
+ in_alias = ca_slab_setup_input_slab_view(st, src, st->t1->slab_dims[0]);
1011
+
1012
+ TypedData_Get_Struct(st->slab_view, CAStride, &castride_data_type, slab_cs);
1013
+ if ( ! in_alias ) {
1014
+ TypedData_Get_Struct(st->carrier, CAWrap, &cawrap_data_type, carrier_cw);
1015
+ }
1016
+
1017
+ while ( ca_iter_state_next_slab_axes(st->t1, &p_in, &m_in) ) {
1018
+ ca_slab_advance_slab_view(st, slab_cs, carrier_cw,
1019
+ src, p_in, m_in, in_alias);
1020
+ rb_yield(st->slab_view); /* return value discarded */
1021
+ }
1022
+
1023
+ return st->self;
1024
+ }
1025
+
1026
+ /* ------------------------------------------------------------------- */
1027
+ /* rb_ensure callbacks (= run_body / finish), state-pointer based. */
1028
+ /* ------------------------------------------------------------------- */
1029
+
1030
+ static VALUE
1031
+ slab_state_run_body (VALUE arg)
1032
+ {
1033
+ ca_slab_iter_state_t *st = (ca_slab_iter_state_t *)(uintptr_t) arg;
1034
+ switch ( st->form ) {
1035
+ case CA_SLAB_FORM_MAP: return ca_slab_run_map(st);
1036
+ case CA_SLAB_FORM_REDUCE_SLAB: return ca_slab_run_reduce_slab(st);
1037
+ case CA_SLAB_FORM_REDUCE_FIBER: return ca_slab_run_reduce_fiber(st);
1038
+ case CA_SLAB_FORM_EACH: return ca_slab_run_each(st);
1039
+ default:
1040
+ rb_raise(rb_eNotImpError, "CArray slab: unknown form %d", (int) st->form);
1041
+ }
1042
+ return Qnil;
1043
+ }
1044
+
1045
+ static VALUE
1046
+ slab_state_finish (VALUE arg)
1047
+ {
1048
+ ca_slab_iter_state_t *st = (ca_slab_iter_state_t *)(uintptr_t) arg;
1049
+
1050
+ /* T1 substrate teardown.
1051
+ CAREFUL: ca_detach(slab_view) is NEVER called — T1 owns the buffer;
1052
+ dropping the VALUE handles lets GC reclaim the CAStride wrappers
1053
+ without xfreeing the borrowed ptr. */
1054
+ if ( st->t1_started ) ca_iter_state_finish(st->t1);
1055
+ if ( st->t1_out_started ) ca_iter_state_finish(st->t1_out);
1056
+
1057
+ /* Free heap-owned state (each guarded; partial-acquire paths land here
1058
+ too, so NULL checks matter). */
1059
+ if ( st->t1 ) { xfree(st->t1); st->t1 = NULL; }
1060
+ if ( st->t1_out ) { xfree(st->t1_out); st->t1_out = NULL; }
1061
+ if ( st->own_data_scratch ) { xfree(st->own_data_scratch); st->own_data_scratch = NULL; }
1062
+ if ( st->own_mask_scratch ) { xfree(st->own_mask_scratch); st->own_mask_scratch = NULL; }
1063
+ if ( st->own_out_scratch ) { xfree(st->own_out_scratch); st->own_out_scratch = NULL; }
1064
+
1065
+ /* Drop VALUE handles so the rest of the entry function does not hold
1066
+ references to scratch / slab views that GC may now collect. */
1067
+ st->slab_view = Qnil;
1068
+ st->carrier = Qnil;
1069
+ st->mask_carrier = Qnil;
1070
+ st->output_slab_view = Qnil;
1071
+
1072
+ return Qnil;
1073
+ }
1074
+
1075
+ /* ------------------------------------------------------------------- */
1076
+ /* Entry points: CArray#map_slab / #reduce_slab / #each_slab */
1077
+ /* ------------------------------------------------------------------- */
1078
+
1079
+ /* Shared kwarg parse: returns axis (required) + data_type / init via
1080
+ out-params. init_out is set to Qundef if the `:init` key was absent
1081
+ (= caller distinguishes per-slab vs per-element reduce forms). */
1082
+ static void
1083
+ slab_parse_kwargs (VALUE kw, int allow_init, int allow_data_type,
1084
+ const char *method_name,
1085
+ VALUE *axis_out, VALUE *data_type_out, VALUE *init_out)
1086
+ {
1087
+ ID keys[3];
1088
+ VALUE vals[3];
1089
+ int n_optional = 0;
1090
+
1091
+ keys[0] = rb_intern("axis");
1092
+ if ( allow_data_type ) { keys[1 + n_optional] = rb_intern("data_type"); n_optional++; }
1093
+ if ( allow_init ) { keys[1 + n_optional] = rb_intern("init"); n_optional++; }
1094
+
1095
+ if ( NIL_P(kw) ) {
1096
+ rb_raise(rb_eArgError, "%s: axis: keyword required", method_name);
1097
+ }
1098
+ rb_get_kwargs(kw, keys, 1, n_optional, vals);
1099
+
1100
+ *axis_out = vals[0];
1101
+ *data_type_out = (allow_data_type && vals[1] != Qundef) ? vals[1] : Qnil;
1102
+ *init_out = Qundef;
1103
+ if ( allow_init ) {
1104
+ int init_idx = allow_data_type ? 2 : 1;
1105
+ *init_out = vals[init_idx]; /* Qundef if `:init` not given */
1106
+ }
1107
+ }
1108
+
1109
+ /* Helper: split argv into kwarg hash (or Qnil). Entries take only kwargs. */
1110
+ static VALUE
1111
+ slab_extract_kwargs (int argc, VALUE *argv, const char *method_name)
1112
+ {
1113
+ VALUE kw = Qnil;
1114
+ if ( argc == 1 && RB_TYPE_P(argv[0], T_HASH) ) {
1115
+ kw = argv[0];
1116
+ }
1117
+ else if ( argc != 0 ) {
1118
+ rb_raise(rb_eArgError, "%s: expected only keyword arguments", method_name);
1119
+ }
1120
+ return kw;
1121
+ }
1122
+
1123
+ static VALUE
1124
+ rb_ca_map_slab (int argc, VALUE *argv, VALUE self)
1125
+ {
1126
+ ca_slab_iter_state_t st = {0};
1127
+ VALUE kw, axis, data_type, init_unused;
1128
+ VALUE result;
1129
+
1130
+ kw = slab_extract_kwargs(argc, argv, "CArray#map_slab");
1131
+ slab_parse_kwargs(kw, /*allow_init=*/0, /*allow_data_type=*/1,
1132
+ "CArray#map_slab",
1133
+ &axis, &data_type, &init_unused);
1134
+
1135
+ if ( ! rb_block_given_p() ) {
1136
+ rb_raise(rb_eLocalJumpError, "CArray#map_slab: block required");
1137
+ }
1138
+
1139
+ slab_state_init(&st, self, CA_SLAB_FORM_MAP, axis, data_type, Qundef);
1140
+
1141
+ result = rb_ensure(slab_state_run_body, (VALUE)(uintptr_t)&st,
1142
+ slab_state_finish, (VALUE)(uintptr_t)&st);
1143
+ RB_GC_GUARD(self);
1144
+ return result;
1145
+ }
1146
+
1147
+ static VALUE
1148
+ rb_ca_reduce_slab (int argc, VALUE *argv, VALUE self)
1149
+ {
1150
+ ca_slab_iter_state_t st = {0};
1151
+ VALUE kw, axis, data_type, init_val;
1152
+ VALUE result;
1153
+ int8_t form;
1154
+
1155
+ kw = slab_extract_kwargs(argc, argv, "CArray#reduce_slab");
1156
+ slab_parse_kwargs(kw, /*allow_init=*/1, /*allow_data_type=*/1,
1157
+ "CArray#reduce_slab",
1158
+ &axis, &data_type, &init_val);
1159
+
1160
+ if ( ! rb_block_given_p() ) {
1161
+ rb_raise(rb_eLocalJumpError, "CArray#reduce_slab: block required");
1162
+ }
1163
+
1164
+ /* `init:` absent => per-slab block form; present => per-element fiber form. */
1165
+ form = (init_val == Qundef) ? CA_SLAB_FORM_REDUCE_SLAB
1166
+ : CA_SLAB_FORM_REDUCE_FIBER;
1167
+
1168
+ slab_state_init(&st, self, form, axis, data_type, init_val);
1169
+
1170
+ result = rb_ensure(slab_state_run_body, (VALUE)(uintptr_t)&st,
1171
+ slab_state_finish, (VALUE)(uintptr_t)&st);
1172
+ RB_GC_GUARD(self);
1173
+ return result;
1174
+ }
1175
+
1176
+ static VALUE
1177
+ rb_ca_each_slab (int argc, VALUE *argv, VALUE self)
1178
+ {
1179
+ ca_slab_iter_state_t st = {0};
1180
+ VALUE kw, axis, data_type_unused, init_unused;
1181
+ VALUE result;
1182
+
1183
+ kw = slab_extract_kwargs(argc, argv, "CArray#each_slab");
1184
+ slab_parse_kwargs(kw, /*allow_init=*/0, /*allow_data_type=*/0,
1185
+ "CArray#each_slab",
1186
+ &axis, &data_type_unused, &init_unused);
1187
+
1188
+ if ( ! rb_block_given_p() ) {
1189
+ /* to_enum hop must mark kw as kwargs (Ruby 3 strict separation),
1190
+ otherwise the resumed enumerator dispatches each_slab(positional_hash)
1191
+ and raises ArgumentError, silently losing `axis:`. */
1192
+ VALUE enum_kw = rb_hash_new();
1193
+ VALUE args[2];
1194
+ rb_hash_aset(enum_kw, ID2SYM(rb_intern("axis")), axis);
1195
+ args[0] = ID2SYM(rb_intern("each_slab"));
1196
+ args[1] = enum_kw;
1197
+ return rb_funcallv_kw(self, rb_intern("to_enum"),
1198
+ 2, args, RB_PASS_KEYWORDS);
1199
+ }
1200
+
1201
+ slab_state_init(&st, self, CA_SLAB_FORM_EACH, axis, Qnil, Qundef);
1202
+
1203
+ result = rb_ensure(slab_state_run_body, (VALUE)(uintptr_t)&st,
1204
+ slab_state_finish, (VALUE)(uintptr_t)&st);
1205
+ RB_GC_GUARD(self);
1206
+ return result;
1207
+ }
1208
+
1209
+ /* ------------------------------------------------------------------- */
1210
+ /* Init_carray_slab */
1211
+ /* ------------------------------------------------------------------- */
1212
+
1213
+ void
1214
+ Init_carray_slab (void)
1215
+ {
1216
+ rb_define_method(rb_cCArray, "map_slab", rb_ca_map_slab, -1);
1217
+ rb_define_method(rb_cCArray, "reduce_slab", rb_ca_reduce_slab, -1);
1218
+ rb_define_method(rb_cCArray, "each_slab", rb_ca_each_slab, -1);
1219
+ }