carray 3.0.2 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +568 -0
  3. data/README.md +6 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_descriptor.h +27 -24
  6. data/ext/ca_axis_dispatch.c +220 -0
  7. data/ext/ca_axis_group.c +201 -123
  8. data/ext/ca_binop_dispatch.c +46 -52
  9. data/ext/ca_binop_dispatch.h +1 -1
  10. data/ext/ca_categorical_iterator.c +249 -183
  11. data/ext/ca_compare.c +7 -12
  12. data/ext/ca_for_buffer.h +21 -68
  13. data/ext/ca_group_iter.c +38 -23
  14. data/ext/ca_kernel_iterator.c +1765 -2076
  15. data/ext/ca_kernel_iterator.h +584 -566
  16. data/ext/ca_monop_dispatch.c +86 -63
  17. data/ext/ca_monop_dispatch.h +5 -0
  18. data/ext/ca_obj_array.c +223 -56
  19. data/ext/ca_obj_bincmp.c +172 -138
  20. data/ext/ca_obj_binop.c +116 -230
  21. data/ext/ca_obj_bitarray.c +40 -26
  22. data/ext/ca_obj_bitfield.c +100 -68
  23. data/ext/ca_obj_block.c +19 -9
  24. data/ext/ca_obj_byte_swap.c +45 -46
  25. data/ext/ca_obj_const_string.c +269 -44
  26. data/ext/ca_obj_face.c +612 -89
  27. data/ext/ca_obj_face.h +111 -47
  28. data/ext/ca_obj_fake.c +36 -79
  29. data/ext/ca_obj_farray.c +7 -3
  30. data/ext/ca_obj_field.c +33 -8
  31. data/ext/ca_obj_fixlen_string.c +23 -24
  32. data/ext/ca_obj_grid.c +541 -210
  33. data/ext/ca_obj_meld.c +331 -111
  34. data/ext/ca_obj_moncmp.c +80 -65
  35. data/ext/ca_obj_monop.c +177 -149
  36. data/ext/ca_obj_object.c +664 -248
  37. data/ext/ca_obj_record.c +63 -23
  38. data/ext/ca_obj_reduce.c +18 -16
  39. data/ext/ca_obj_refer.c +140 -58
  40. data/ext/ca_obj_remap.c +85 -24
  41. data/ext/ca_obj_repeat.c +24 -9
  42. data/ext/ca_obj_roll.c +121 -16
  43. data/ext/ca_obj_select.c +61 -58
  44. data/ext/ca_obj_select_axis.c +73 -107
  45. data/ext/ca_obj_shift.c +32 -102
  46. data/ext/ca_obj_stack.c +372 -122
  47. data/ext/ca_obj_stride.c +348 -140
  48. data/ext/ca_obj_string.c +88 -22
  49. data/ext/ca_obj_tile.c +38 -21
  50. data/ext/ca_obj_time.c +45 -31
  51. data/ext/ca_obj_timedelta.c +43 -29
  52. data/ext/ca_obj_transpose.c +17 -6
  53. data/ext/ca_obj_triop.c +125 -141
  54. data/ext/ca_obj_window.c +330 -44
  55. data/ext/ca_op_ipower.c +24 -11
  56. data/ext/ca_op_powi.h +57 -30
  57. data/ext/ca_pow5_table.h +656 -0
  58. data/ext/ca_sort_kernels.h +70 -0
  59. data/ext/ca_sweep_engine.c +104 -81
  60. data/ext/ca_sweep_engine.h +7 -20
  61. data/ext/ca_triop_dispatch.c +4 -1
  62. data/ext/ca_triop_dispatch.h +5 -2
  63. data/ext/caframe_csv_reader.c +542 -0
  64. data/ext/carray.h +241 -92
  65. data/ext/carray_access.c +608 -276
  66. data/ext/carray_address_basis.c +494 -97
  67. data/ext/carray_attribute.c +129 -4
  68. data/ext/carray_bincount.c +39 -7
  69. data/ext/carray_broadcast.c +56 -19
  70. data/ext/carray_build_flags.h +1 -1
  71. data/ext/carray_call_cfunc.c +108 -28
  72. data/ext/carray_cast.c +1576 -223
  73. data/ext/carray_cast_func.rb +77 -24
  74. data/ext/carray_class.c +4 -3
  75. data/ext/carray_conversion.c +208 -75
  76. data/ext/carray_copy.c +82 -24
  77. data/ext/carray_core.c +646 -162
  78. data/ext/carray_count.c +171 -88
  79. data/ext/carray_element.c +94 -60
  80. data/ext/carray_factorize.c +670 -324
  81. data/ext/carray_generate.c +260 -83
  82. data/ext/carray_histogram.c +209 -106
  83. data/ext/carray_hold.c +14 -16
  84. data/ext/carray_index_classifier.c +422 -243
  85. data/ext/carray_internal.h +692 -3
  86. data/ext/carray_kernels_bincmp.c +7 -0
  87. data/ext/carray_kernels_binop.c +487 -120
  88. data/ext/carray_kernels_init.c +653 -50
  89. data/ext/carray_kernels_moncmp.c +15 -8
  90. data/ext/carray_kernels_monop.c +99 -43
  91. data/ext/carray_kernels_reduce_aggregate.c +3925 -6112
  92. data/ext/carray_kernels_reduce_boolean.c +112 -36
  93. data/ext/carray_kernels_reduce_cumulative.c +2127 -6903
  94. data/ext/carray_kernels_reduce_extreme.c +3032 -2892
  95. data/ext/carray_kernels_reduce_variance.c +439 -500
  96. data/ext/carray_kernels_scan.c +1097 -488
  97. data/ext/carray_kernels_search.c +3787 -2176
  98. data/ext/carray_kernels_sort.c +1200 -920
  99. data/ext/carray_kernels_triop.c +479 -7
  100. data/ext/carray_key_windows.c +216 -0
  101. data/ext/carray_lazy.c +967 -153
  102. data/ext/carray_loop.c +81 -65
  103. data/ext/carray_mask.c +910 -324
  104. data/ext/carray_math_kernel.h +1 -0
  105. data/ext/carray_median_percentile.c +299 -100
  106. data/ext/carray_memory_view.c +872 -758
  107. data/ext/carray_operator.c +1171 -364
  108. data/ext/carray_order.c +505 -133
  109. data/ext/carray_partition.c +112 -65
  110. data/ext/carray_random.c +327 -239
  111. data/ext/carray_round.c +273 -0
  112. data/ext/carray_scatter.c +346 -78
  113. data/ext/carray_segment.c +257 -0
  114. data/ext/carray_slab.c +249 -86
  115. data/ext/carray_sort.c +224 -227
  116. data/ext/carray_sort_kernel.c +8 -1
  117. data/ext/carray_struct.c +118 -39
  118. data/ext/carray_test.c +193 -69
  119. data/ext/carray_time_text.c +1182 -0
  120. data/ext/carray_undef.c +4 -0
  121. data/ext/carray_utils.c +579 -113
  122. data/ext/extconf.rb +27 -2
  123. data/ext/mk_call_cfunc.rb +19 -4
  124. data/ext/mk_pow5_table.rb +35 -0
  125. data/ext/mkkernel.rb +1202 -1014
  126. data/ext/ruby_carray.c +17 -5
  127. data/ext/version.h +4 -4
  128. data/ext/version.rb +4 -2
  129. data/lib/carray/attribute.rb +160 -17
  130. data/lib/carray/autoload_carray.rb +23 -27
  131. data/lib/carray/autoload_method_extension.rb +20 -5
  132. data/lib/carray/axis_group.rb +360 -63
  133. data/lib/carray/basics.rb +162 -28
  134. data/lib/carray/bincount_nd.rb +18 -2
  135. data/lib/carray/block_iterator.rb +231 -58
  136. data/lib/carray/boolean_reduce.rb +34 -42
  137. data/lib/carray/categorical.rb +242 -52
  138. data/lib/carray/categorical_iterator.rb +437 -827
  139. data/lib/carray/complex.rb +41 -34
  140. data/lib/carray/conditional.rb +150 -14
  141. data/lib/carray/const_string.rb +59 -4
  142. data/lib/carray/construct.rb +15 -11
  143. data/lib/carray/data_type_extension.rb +1 -0
  144. data/lib/carray/distance_limit.rb +77 -0
  145. data/lib/carray/fixlen_string.rb +7 -1
  146. data/lib/carray/frame/column_verbs.rb +70 -0
  147. data/lib/carray/frame/concat.rb +157 -6
  148. data/lib/carray/frame/convert.rb +82 -7
  149. data/lib/carray/frame/csv_parser.rb +688 -87
  150. data/lib/carray/frame/describe.rb +80 -0
  151. data/lib/carray/frame/frame.rb +369 -45
  152. data/lib/carray/frame/group.rb +631 -90
  153. data/lib/carray/frame/io.rb +506 -79
  154. data/lib/carray/frame/join.rb +411 -62
  155. data/lib/carray/frame/records.rb +116 -19
  156. data/lib/carray/frame/reshape.rb +455 -0
  157. data/lib/carray/frame/sort.rb +30 -18
  158. data/lib/carray/frame/verbs.rb +868 -61
  159. data/lib/carray/frame/windows.rb +117 -0
  160. data/lib/carray/frame.rb +4 -0
  161. data/lib/carray/fuse_source.rb +118 -2
  162. data/lib/carray/fusion.rb +257 -42
  163. data/lib/carray/histogram.rb +6 -1
  164. data/lib/carray/inspect.rb +54 -13
  165. data/lib/carray/iterator.rb +99 -9
  166. data/lib/carray/key_windows.rb +620 -0
  167. data/lib/carray/lazy.rb +216 -193
  168. data/lib/carray/mask_gap_fill.rb +112 -92
  169. data/lib/carray/math.rb +32 -19
  170. data/lib/carray/meld_reduce.rb +52 -21
  171. data/lib/carray/methods/align_addr.rb +8 -4
  172. data/lib/carray/methods/bin.rb +26 -18
  173. data/lib/carray/methods/bincount.rb +18 -2
  174. data/lib/carray/methods/bit_string.rb +15 -4
  175. data/lib/carray/methods/choose.rb +16 -3
  176. data/lib/carray/methods/composition.rb +18 -10
  177. data/lib/carray/methods/factorize.rb +2 -2
  178. data/lib/carray/methods/gather_nd.rb +25 -5
  179. data/lib/carray/methods/insert_block.rb +9 -14
  180. data/lib/carray/methods/is_between.rb +59 -0
  181. data/lib/carray/methods/is_in.rb +140 -10
  182. data/lib/carray/methods/join.rb +2 -7
  183. data/lib/carray/methods/lag.rb +49 -0
  184. data/lib/carray/methods/locate_addr.rb +182 -45
  185. data/lib/carray/methods/mode.rb +18 -1
  186. data/lib/carray/methods/nunique.rb +11 -6
  187. data/lib/carray/methods/repeat.rb +1 -20
  188. data/lib/carray/methods/resize.rb +21 -23
  189. data/lib/carray/methods/snap.rb +34 -12
  190. data/lib/carray/methods/unique.rb +11 -2
  191. data/lib/carray/methods/value_counts.rb +4 -11
  192. data/lib/carray/record_order.rb +208 -0
  193. data/lib/carray/rng.rb +3 -0
  194. data/lib/carray/runtime.rb +65 -17
  195. data/lib/carray/segment_iterator.rb +1221 -0
  196. data/lib/carray/serialize.rb +214 -30
  197. data/lib/carray/slab_iterator.rb +101 -27
  198. data/lib/carray/stack.rb +10 -4
  199. data/lib/carray/string.rb +105 -5
  200. data/lib/carray/string_operation_extension.rb +12 -4
  201. data/lib/carray/struct.rb +137 -58
  202. data/lib/carray/struct_builder.rb +124 -17
  203. data/lib/carray/time.rb +1222 -186
  204. data/lib/carray/window_iterator.rb +242 -81
  205. data/lib/carray.rb +9 -2
  206. data/yard-stubs/ca_obj_array.rb +4 -4
  207. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  208. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  209. data/yard-stubs/ca_obj_fake.rb +1 -1
  210. data/yard-stubs/ca_obj_field.rb +5 -1
  211. data/yard-stubs/ca_obj_grid.rb +21 -10
  212. data/yard-stubs/ca_obj_refer.rb +5 -4
  213. data/yard-stubs/ca_obj_roll.rb +2 -3
  214. data/yard-stubs/ca_obj_shift.rb +5 -1
  215. data/yard-stubs/ca_obj_stride.rb +10 -7
  216. data/yard-stubs/ca_obj_tile.rb +1 -1
  217. data/yard-stubs/ca_obj_transpose.rb +1 -1
  218. data/yard-stubs/ca_obj_window.rb +9 -5
  219. data/yard-stubs/carray_access.rb +11 -5
  220. data/yard-stubs/carray_attribute.rb +0 -5
  221. data/yard-stubs/carray_broadcast.rb +1 -1
  222. data/yard-stubs/carray_cast.rb +88 -43
  223. data/yard-stubs/carray_conversion.rb +15 -2
  224. data/yard-stubs/carray_copy.rb +12 -5
  225. data/yard-stubs/carray_core.rb +3 -3
  226. data/yard-stubs/carray_count.rb +9 -0
  227. data/yard-stubs/carray_element.rb +10 -4
  228. data/yard-stubs/carray_generate.rb +21 -1
  229. data/yard-stubs/carray_lazy.rb +5 -1
  230. data/yard-stubs/carray_loop.rb +4 -1
  231. data/yard-stubs/carray_mask.rb +60 -12
  232. data/yard-stubs/carray_math.rb +52 -14
  233. data/yard-stubs/carray_median_percentile.rb +15 -8
  234. data/yard-stubs/carray_memory_view.rb +40 -23
  235. data/yard-stubs/carray_order.rb +61 -29
  236. data/yard-stubs/carray_random.rb +13 -4
  237. data/yard-stubs/carray_scatter.rb +21 -9
  238. data/yard-stubs/carray_segment.rb +72 -0
  239. data/yard-stubs/carray_slab.rb +10 -2
  240. data/yard-stubs/carray_sort.rb +49 -8
  241. data/yard-stubs/carray_test.rb +15 -3
  242. data/yard-stubs/carray_utils.rb +0 -26
  243. metadata +19 -5
  244. data/ext/ca_for_each_element.h +0 -239
  245. data/ext/carray_kernels_map.c +0 -3467
  246. data/lib/carray/arrow_tensor.rb +0 -401
  247. data/lib/carray/table.rb +0 -108
@@ -50,7 +50,8 @@
50
50
  (Float#eql? treats them equal). The object lane adds one deviation from Ruby
51
51
  Hash, aligning it with the numeric lane: every Float NaN collapses to one
52
52
  distinct value (a NaN takes a fixed canonical key and matches any stored Float
53
- NaN), where Ruby Hash would keep distinct NaN objects apart.
53
+ NaN), where Ruby Hash would keep distinct NaN objects apart, and a Complex
54
+ with a NaN part is one value with any Complex whose parts match it that way.
54
55
  __factorize_appearance__ and the discovery members (unique, value_counts,
55
56
  mask_duplicates) all cover the numeric / object / fixlen lanes.
56
57
 
@@ -77,6 +78,7 @@
77
78
 
78
79
  #include "carray.h"
79
80
  #include "ca_obj_face.h"
81
+ #include "carray_internal.h" /* ca_object_cells_clear */
80
82
  #include <string.h>
81
83
  #include <math.h>
82
84
 
@@ -102,44 +104,133 @@
102
104
  discovery surface rather than this gate (see
103
105
  devel/PROPOSAL_DISCOVERY_FAMILY_FACE_GATE.md §4). */
104
106
 
107
+ /* The value-hash family decides whether two cells hold the same value: every
108
+ NaN is one value, and -0.0 is the same value as +0.0. A float cell's key
109
+ is the bytes of its canonical form (one NaN, +0.0 for a zero), so equal
110
+ values have equal keys; fz_canonical_* writes that form back as a value. */
111
+ #define FZ_NAN_KEY_DOUBLE 0x7ff8000000000000ULL
112
+ #define FZ_NAN_KEY_FLOAT 0x7fc00000UL
113
+
114
+ static inline uint64_t
115
+ fz_key_double (double v)
116
+ {
117
+ uint64_t b;
118
+ if ( v != v ) return FZ_NAN_KEY_DOUBLE;
119
+ if ( v == 0.0 ) v = 0.0;
120
+ memcpy(&b, &v, sizeof(b));
121
+ return b;
122
+ }
123
+
124
+ static inline uint64_t
125
+ fz_key_float (float v)
126
+ {
127
+ uint32_t b;
128
+ if ( v != v ) return FZ_NAN_KEY_FLOAT;
129
+ if ( v == 0.0f ) v = 0.0f;
130
+ memcpy(&b, &v, sizeof(b));
131
+ return (uint64_t) b;
132
+ }
133
+
134
+ static inline double
135
+ fz_canonical_double (double v)
136
+ {
137
+ uint64_t b = fz_key_double(v);
138
+ memcpy(&v, &b, sizeof(v));
139
+ return v;
140
+ }
141
+
142
+ static inline float
143
+ fz_canonical_float (float v)
144
+ {
145
+ uint32_t b = (uint32_t) fz_key_float(v);
146
+ memcpy(&v, &b, sizeof(v));
147
+ return v;
148
+ }
149
+
150
+ /* For the kernel macros, which take the cell type T. */
151
+ #define FZ_FLOAT_KEY(T, v) \
152
+ _Generic((T) 0, float: fz_key_float, double: fz_key_double)(v)
153
+
154
+ /* A complex array as hash keys: its cells as fixlen blobs of the same
155
+ width, each component written so that equal values have equal bytes
156
+ (-0.0 as 0.0, every NaN as one NaN), with the mask kept. The fixlen lane
157
+ then answers equality for complex values. */
158
+ static VALUE
159
+ fz_complex_key (VALUE self)
160
+ {
161
+ volatile VALUE out;
162
+ CArray *ca, *co;
163
+ ca_size_t i, n;
164
+ GetCArray(self, ca);
165
+ out = rb_carray_new(CA_FIXLEN, ca->ndim, ca->dim, ca->bytes, NULL);
166
+ GetCArray(out, co);
167
+ ca_copy_data(ca, co->ptr);
168
+ n = 2 * ca->elements;
169
+ if ( ca->data_type == CA_CMPLX128 ) {
170
+ double *p = (double *) co->ptr;
171
+ for ( i = 0; i < n; i++ ) p[i] = fz_canonical_double(p[i]);
172
+ }
173
+ else {
174
+ float *p = (float *) co->ptr;
175
+ for ( i = 0; i < n; i++ ) p[i] = fz_canonical_float(p[i]);
176
+ }
177
+ if ( ca_has_mask(ca) ) {
178
+ ca_copy_mask(co, ca);
179
+ }
180
+ RB_GC_GUARD(self); /* read through ca after rb_carray_new allocates */
181
+ return out;
182
+ }
183
+
184
+ /* The members whose output carries no values (a mask, a count) read a
185
+ complex array through its keys and need nothing back. */
186
+ static VALUE
187
+ fz_complex_as_key (VALUE self)
188
+ {
189
+ CArray *ca;
190
+ GetCArray(self, ca);
191
+ if ( ! ca_is_face(ca) && ca_is_complex_type(ca) ) {
192
+ return fz_complex_key(self);
193
+ }
194
+ return self;
195
+ }
196
+
105
197
  /* Descend `self` when it is an ORDERABLE Face; *pface keeps the pre-strip Face
106
- for the re-lift (Qnil when there is nothing to put back). */
198
+ for the re-lift (Qnil when there is nothing to put back). A complex array
199
+ is turned into hash keys (fz_complex_key) and *pface keeps it, so the
200
+ re-lift reads the values back as complex. */
107
201
  static VALUE
108
202
  fz_face_descend (VALUE self, volatile VALUE *pface, const char *name)
109
203
  {
110
204
  CArray *ca;
111
205
  GetCArray(self, ca);
206
+ if ( ! ca_is_face(ca) && ca_is_complex_type(ca) ) {
207
+ *pface = self;
208
+ return fz_complex_key(self);
209
+ }
112
210
  if ( ca_is_face(ca) ) {
113
- if ( ca_test_flag(ca, CA_FLAG_FACE_ORDERABLE_STORAGE) ) {
114
- *pface = self;
115
- return rb_ca_strip_face_value(self);
211
+ /* A record is its bytes: two records are the same value exactly when
212
+ their bytes are, so the fixlen lane answers for it. Any other Face
213
+ is descended only when ORDERABLE. One that cannot be descended must
214
+ not flow on undescended: whatever reads it next reads its storage
215
+ anyway, which is the encoding rather than the values. A Face in this
216
+ position normally never arrives -- CAConstString and CACategorical
217
+ define their own members in Ruby -- but the overrides only fire when
218
+ the Face is the receiver, and this same helper descends the
219
+ *reference* for locate_addr, where it is the argument. */
220
+ if ( ca->obj_type != CA_OBJ_RECORD ) {
221
+ ca_face_require_orderable(self, name, "compare");
116
222
  }
117
- /* A Face that cannot be descended must not flow on undescended: whatever
118
- reads it next reads its storage anyway, which is the encoding rather
119
- than the values. A Face in this position normally never arrives --
120
- CAConstString and CACategorical define their own members in Ruby -- but
121
- the overrides only fire when the Face is the receiver, and this same
122
- helper descends the *reference* for locate_addr, where it is the
123
- argument. */
124
- rb_raise(rb_eArgError,
125
- "%s: %s is not orderable by storage (a cell encodes the value "
126
- "rather than being it); convert it first -- #to_string for a "
127
- "string Face -- or pass .parent to work in storage space",
128
- name, rb_obj_classname(self));
223
+ *pface = self;
224
+ return rb_ca_strip_face_value(self);
129
225
  }
130
226
  *pface = Qnil;
131
227
  return self;
132
228
  }
133
229
 
134
230
  /* Bring `operand` into the space of `reference` (the *pre-strip* Face) and
135
- descend it, so both sides hash in one storage space.
136
-
137
- `to_comparable` exists because a Face can carry a *unit* -- an alternative
138
- space for the same value -- so a reference that defines it reconciles any
139
- operand type through it (and refuses what it cannot convert, which is how a
140
- bare storage number stays out). A reference with one space only (COMPARABLE,
141
- or a Face with no unit algebra at all, e.g. CAString) has nothing to
142
- reconcile between: descending both sides is the whole job. */
231
+ descend it, so both sides hash in one storage space
232
+ (ca_face_reconcile_operand). A complex reference instead turns the
233
+ operand into keys of its own complex type. */
143
234
  static VALUE
144
235
  fz_face_reconcile (VALUE reference, VALUE operand, const char *name)
145
236
  {
@@ -148,22 +239,38 @@ fz_face_reconcile (VALUE reference, VALUE operand, const char *name)
148
239
  return operand;
149
240
  }
150
241
  GetCArray(reference, ca);
151
- if ( ! ca_test_flag(ca, CA_FLAG_FACE_COMPARABLE_STORAGE)
152
- && rb_respond_to(reference, rb_intern("to_comparable")) ) {
153
- return rb_ca_strip_face_value(rb_funcall(reference,
154
- rb_intern("to_comparable"),
155
- 1, operand));
242
+ if ( ! ca_is_face(ca) ) {
243
+ /* A complex reference: the operand becomes keys of the same kind, in the
244
+ reference's complex type. */
245
+ CArray *op;
246
+ if ( ! rb_obj_is_carray(operand) ) {
247
+ return operand;
248
+ }
249
+ GetCArray(operand, op);
250
+ if ( op->data_type != ca->data_type ) {
251
+ /* coerce: the callers have met both sides at result_type already */
252
+ operand = rb_ca_to_type(operand, INT2NUM(ca->data_type), Qnil);
253
+ }
254
+ return fz_complex_key(operand);
156
255
  }
157
- return ca_face_operand_descend(operand, name);
256
+ return ca_face_reconcile_operand(reference, operand, name);
158
257
  }
159
258
 
160
- /* Put the Face back on a value-carrying output. */
259
+ /* Put the Face back on a value-carrying output, or read complex keys back
260
+ as complex values. */
161
261
  static VALUE
162
262
  fz_face_relift (VALUE out, VALUE face)
163
263
  {
264
+ CArray *cf, *co;
164
265
  if ( NIL_P(face) ) {
165
266
  return out;
166
267
  }
268
+ GetCArray(face, cf);
269
+ if ( ! ca_is_face(cf) ) {
270
+ GetCArray(out, co);
271
+ return rb_ca_copy(rb_ca_refer_new(out, cf->data_type, co->ndim, co->dim,
272
+ cf->bytes, 0));
273
+ }
167
274
  return ca_face_lift(out, face);
168
275
  }
169
276
 
@@ -176,16 +283,95 @@ typedef struct {
176
283
  VALUE *val; /* object lane only: the interned VALUE, kept for an eql? re-check
177
284
  on a hash collision (NULL in the numeric lane, where the widened
178
285
  key is lossless so a key match already proves value equality).
179
- Its VALUEs are always elements of the live receiver, so the GC
180
- reaches them through `self`; this array is not separately marked. */
286
+ A receiver that computes its cells (a CAObject, a lazy
287
+ expression) hands each fiber over in a scratch that is refilled,
288
+ so the owner marks this array (fz_tmp_values). */
181
289
  char *raw; /* fixlen lane only: cap*esz bytes, the interned element bytes kept
182
290
  for a memcmp re-check on a hash collision (NULL otherwise). */
183
291
  int esz; /* fixlen lane: element width in bytes (0 otherwise) */
292
+ int has_val; /* object lane: val is kept (0 otherwise) */
184
293
  ca_size_t cap; /* power of two */
185
294
  ca_size_t n; /* distinct keys interned so far */
186
295
  int shift; /* 64 - log2(cap); the home slot is the top log2(cap) bits */
296
+ VALUE hbuf; /* owner of the tables, one block (fz_tmp_alloc) */
187
297
  } fz_hash;
188
298
 
299
+ /* The tables live in buffers owned by hidden Ruby objects rather than bare
300
+ xmalloc. The object lane calls #hash and #eql? for every cell, and a raise
301
+ there leaves the tables with nothing to free them; the owner is reclaimed
302
+ by the GC once the frame holding it is gone, and frees its buffer then.
303
+ An owner whose buffer keeps VALUEs (fz_tmp_values) marks them: the object
304
+ lane's cells come from a scratch the walk refills, not only from the
305
+ receiver, so nothing else holds the ones already interned. */
306
+ typedef struct {
307
+ char *buf;
308
+ ca_size_t voff; /* byte offset of the VALUE run in buf */
309
+ ca_size_t nvals; /* VALUEs in the run (0: nothing to mark) */
310
+ } fz_buffer;
311
+
312
+ static void
313
+ fz_buffer_mark (void *ptr)
314
+ {
315
+ fz_buffer *b = (fz_buffer *) ptr;
316
+ if ( b->buf && b->nvals > 0 ) {
317
+ VALUE *v = (VALUE *) (b->buf + b->voff);
318
+ ca_size_t i;
319
+ for ( i = 0; i < b->nvals; i++ ) rb_gc_mark(v[i]);
320
+ }
321
+ }
322
+
323
+ static void
324
+ fz_buffer_free (void *ptr)
325
+ {
326
+ fz_buffer *b = (fz_buffer *) ptr;
327
+ xfree(b->buf);
328
+ xfree(b);
329
+ }
330
+
331
+ static const rb_data_type_t fz_buffer_type = {
332
+ "carray_factorize_buffer",
333
+ { fz_buffer_mark, fz_buffer_free, NULL, },
334
+ NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY,
335
+ };
336
+
337
+ static void *
338
+ fz_tmp_alloc (VALUE *store, size_t bytes)
339
+ {
340
+ fz_buffer *b;
341
+ *store = TypedData_Make_Struct(0, fz_buffer, &fz_buffer_type, b);
342
+ b->buf = (char *) xmalloc(bytes);
343
+ return b->buf;
344
+ }
345
+
346
+ static void *
347
+ fz_tmp_realloc (VALUE store, size_t bytes)
348
+ {
349
+ fz_buffer *b = (fz_buffer *) DATA_PTR(store);
350
+ b->buf = (char *) xrealloc(b->buf, bytes);
351
+ return b->buf;
352
+ }
353
+
354
+ /* Mark the n VALUEs at vals (inside store's buffer); they must be valid. */
355
+ static void
356
+ fz_tmp_values (VALUE store, void *vals, ca_size_t n)
357
+ {
358
+ fz_buffer *b = (fz_buffer *) DATA_PTR(store);
359
+ b->voff = (ca_size_t) ((char *) vals - b->buf);
360
+ b->nvals = n;
361
+ }
362
+
363
+ static void
364
+ fz_tmp_free (VALUE *store)
365
+ {
366
+ if ( *store ) {
367
+ fz_buffer *b = (fz_buffer *) DATA_PTR(*store);
368
+ b->nvals = 0;
369
+ xfree(b->buf);
370
+ b->buf = NULL;
371
+ *store = 0;
372
+ }
373
+ }
374
+
189
375
  /* Multiplicative hashing (Knuth, TAOCP vol. 3 sec. 6.4): multiply by
190
376
  2^64 / golden-ratio and take the TOP log2(cap) bits of the product -- the
191
377
  high bits are where a multiply mixes best (the low bits carry little
@@ -195,19 +381,48 @@ typedef struct {
195
381
  #define FZ_GOLDEN 0x9E3779B97F4A7C15ULL
196
382
  #define FZ_HOME(h, key) ((ca_size_t) (((uint64_t)(key) * FZ_GOLDEN) >> (h)->shift))
197
383
 
384
+ /* Points the tables of h at one new block for cap slots: key, then the
385
+ object lane's val, code, used, and the fixlen lane's raw bytes. */
198
386
  static void
199
- fz_hash_init (fz_hash *h)
387
+ fz_hash_alloc (fz_hash *h, ca_size_t cap)
388
+ {
389
+ int has_val = h->has_val;
390
+ size_t nkey = sizeof(uint64_t) * (size_t) cap;
391
+ size_t nval = has_val ? sizeof(VALUE) * (size_t) cap : 0;
392
+ size_t ncode = sizeof(int32_t) * (size_t) cap;
393
+ size_t nused = sizeof(uint8_t) * (size_t) cap;
394
+ size_t nraw = (size_t) cap * (size_t) h->esz;
395
+ char *b = fz_tmp_alloc(&h->hbuf, nkey + nval + ncode + nused + nraw);
396
+ h->key = (uint64_t *) b; b += nkey;
397
+ h->val = has_val ? (VALUE *) b : NULL; b += nval;
398
+ h->code = (int32_t *) b; b += ncode;
399
+ h->used = (uint8_t *) b; b += nused;
400
+ h->raw = h->esz ? b : NULL;
401
+ h->cap = cap;
402
+ MEMZERO(h->used, uint8_t, cap);
403
+ if ( has_val ) {
404
+ ca_object_cells_clear(h->val, cap);
405
+ fz_tmp_values(h->hbuf, h->val, cap);
406
+ }
407
+ }
408
+
409
+ /* has_val: the object lane (a slot keeps its VALUE); esz > 0: the fixlen
410
+ lane (a slot keeps esz bytes). */
411
+ static void
412
+ fz_hash_setup (fz_hash *h, int has_val, int esz)
200
413
  {
201
- h->cap = 1024;
202
414
  h->shift = 64 - 10; /* log2(1024) = 10 */
203
415
  h->n = 0;
204
- h->key = ALLOC_N(uint64_t, h->cap);
205
- h->code = ALLOC_N(int32_t, h->cap);
206
- h->used = ALLOC_N(uint8_t, h->cap);
207
- h->val = NULL; /* numeric lane: no VALUE re-check needed */
208
- h->raw = NULL;
209
- h->esz = 0;
210
- MEMZERO(h->used, uint8_t, h->cap);
416
+ h->hbuf = 0;
417
+ h->has_val = has_val;
418
+ h->esz = esz;
419
+ fz_hash_alloc(h, 1024);
420
+ }
421
+
422
+ static void
423
+ fz_hash_init (fz_hash *h)
424
+ {
425
+ fz_hash_setup(h, 0, 0); /* numeric lane: no re-check needed */
211
426
  }
212
427
 
213
428
  /* Object lane: the widened key is `rb_hash` (lossy), so a slot also stores the
@@ -216,8 +431,7 @@ fz_hash_init (fz_hash *h)
216
431
  static void
217
432
  fz_hash_init_obj (fz_hash *h)
218
433
  {
219
- fz_hash_init(h);
220
- h->val = ALLOC_N(VALUE, h->cap);
434
+ fz_hash_setup(h, 1, 0);
221
435
  }
222
436
 
223
437
  /* Fixlen lane: the key is a byte-hash of the esz-wide element (lossy), so a slot
@@ -227,19 +441,18 @@ fz_hash_init_obj (fz_hash *h)
227
441
  static void
228
442
  fz_hash_init_mem (fz_hash *h, int esz)
229
443
  {
230
- fz_hash_init(h);
231
- h->esz = esz;
232
- h->raw = ALLOC_N(char, h->cap * esz);
444
+ fz_hash_setup(h, 0, esz);
233
445
  }
234
446
 
235
447
  static void
236
448
  fz_hash_free (fz_hash *h)
237
449
  {
238
- if ( h->key ) { xfree(h->key); h->key = NULL; }
239
- if ( h->code ) { xfree(h->code); h->code = NULL; }
240
- if ( h->used ) { xfree(h->used); h->used = NULL; }
241
- if ( h->val ) { xfree(h->val); h->val = NULL; }
242
- if ( h->raw ) { xfree(h->raw); h->raw = NULL; }
450
+ fz_tmp_free(&h->hbuf);
451
+ h->key = NULL;
452
+ h->code = NULL;
453
+ h->used = NULL;
454
+ h->val = NULL;
455
+ h->raw = NULL;
243
456
  }
244
457
 
245
458
  /* FNV-1a over esz bytes: a lossy 64-bit key for the fixlen lane. */
@@ -272,14 +485,10 @@ fz_hash_grow (fz_hash *h)
272
485
  VALUE *ov = h->val;
273
486
  char *orw = h->raw;
274
487
  int esz = h->esz;
275
- h->cap = newcap;
276
- h->shift -= 1; /* log2(cap) grew by one */
277
- h->key = ALLOC_N(uint64_t, newcap);
278
- h->code = ALLOC_N(int32_t, newcap);
279
- h->used = ALLOC_N(uint8_t, newcap);
280
- if ( ov ) { h->val = ALLOC_N(VALUE, newcap); }
281
- if ( orw ) { h->raw = ALLOC_N(char, newcap * esz); }
282
- MEMZERO(h->used, uint8_t, newcap);
488
+ VALUE hold = h->hbuf; /* keeps the old block until it is freed below */
489
+ h->hbuf = 0;
490
+ h->shift -= 1; /* log2(cap) grew by one */
491
+ fz_hash_alloc(h, newcap);
283
492
  ca_size_t mask = newcap - 1;
284
493
  for ( ca_size_t s = 0; s < oldcap; s++ ) {
285
494
  if ( ! ou[s] ) { continue; }
@@ -291,9 +500,7 @@ fz_hash_grow (fz_hash *h)
291
500
  if ( ov ) { h->val[slot] = ov[s]; }
292
501
  if ( orw ) { memcpy(h->raw + slot * esz, orw + s * esz, esz); }
293
502
  }
294
- xfree(ok); xfree(oc); xfree(ou);
295
- if ( ov ) { xfree(ov); }
296
- if ( orw ) { xfree(orw); }
503
+ fz_tmp_free(&hold);
297
504
  }
298
505
 
299
506
  /* True when v is a Float holding NaN (any bit pattern). */
@@ -303,25 +510,84 @@ fz_is_float_nan (VALUE v)
303
510
  return RB_FLOAT_TYPE_P(v) && isnan(RFLOAT_VALUE(v));
304
511
  }
305
512
 
513
+ /* True when v holds a NaN that eql? would keep apart from an equal value:
514
+ a Float NaN, or a Complex with a Float NaN part. */
515
+ static int
516
+ fz_obj_has_nan (VALUE v)
517
+ {
518
+ if ( fz_is_float_nan(v) ) {
519
+ return 1;
520
+ }
521
+ return RB_TYPE_P(v, T_COMPLEX)
522
+ && ( fz_is_float_nan(rb_complex_real(v))
523
+ || fz_is_float_nan(rb_complex_imag(v)) );
524
+ }
525
+
526
+ /* A part of a NaN-bearing value: every NaN is one value, the rest go by
527
+ eql?, as in Ruby Hash. */
528
+ static uint64_t
529
+ fz_obj_part_key (VALUE v)
530
+ {
531
+ return fz_is_float_nan(v) ? FZ_NAN_KEY_DOUBLE : (uint64_t) NUM2LL(rb_hash(v));
532
+ }
533
+
534
+ static int
535
+ fz_obj_part_eql (VALUE a, VALUE b)
536
+ {
537
+ if ( fz_is_float_nan(a) || fz_is_float_nan(b) ) {
538
+ return fz_is_float_nan(a) && fz_is_float_nan(b);
539
+ }
540
+ return rb_eql(a, b);
541
+ }
542
+
543
+ /* The hash key of an object cell. A NaN-bearing value is keyed part by
544
+ part with every NaN as one value, so it matches the numeric lane: all
545
+ NaN are one value, and a complex value is one value when its parts are. */
546
+ static uint64_t
547
+ fz_obj_key (VALUE v, int has_nan)
548
+ {
549
+ if ( ! has_nan ) {
550
+ return (uint64_t) NUM2LL(rb_hash(v));
551
+ }
552
+ if ( RB_TYPE_P(v, T_COMPLEX) ) {
553
+ return fz_obj_part_key(rb_complex_real(v)) * 31u
554
+ ^ fz_obj_part_key(rb_complex_imag(v)) ^ 0x9e3779b97f4a7c15ULL;
555
+ }
556
+ return FZ_NAN_KEY_DOUBLE;
557
+ }
558
+
559
+ /* Whether a stored cell is the same value as v (has_nan from fz_obj_has_nan). */
560
+ static int
561
+ fz_obj_same (VALUE stored, VALUE v, int has_nan)
562
+ {
563
+ if ( ! has_nan ) {
564
+ return rb_eql(stored, v);
565
+ }
566
+ if ( RB_TYPE_P(v, T_COMPLEX) ) {
567
+ return RB_TYPE_P(stored, T_COMPLEX)
568
+ && fz_obj_part_eql(rb_complex_real(stored), rb_complex_real(v))
569
+ && fz_obj_part_eql(rb_complex_imag(stored), rb_complex_imag(v));
570
+ }
571
+ return fz_is_float_nan(stored);
572
+ }
573
+
306
574
  /* Object-lane intern: key = rb_hash(v) (lossy), collision re-check via rb_eql.
307
575
  Matches Ruby Hash distinctness (`hash` + `eql?`) element for element, with one
308
576
  value-based exception aligning with the numeric lane: every Float NaN collapses
309
577
  to a single distinct value (Ruby Hash keeps distinct NaN objects apart via
310
- eql?, but the discovery family unifies them). A NaN takes a fixed canonical
311
- key and matches any stored Float NaN, so its second and later occurrences fold
312
- into the first. */
578
+ eql?, but the discovery family unifies them), and so does a Complex whose
579
+ parts are the same with every NaN as one (fz_obj_key / fz_obj_same). */
313
580
  static int32_t
314
581
  fz_hash_intern_obj (fz_hash *h, VALUE v, int *is_new)
315
582
  {
316
583
  if ( (h->n + 1) * 10 >= h->cap * 7 ) { fz_hash_grow(h); } /* load factor 0.7 */
317
- int v_nan = fz_is_float_nan(v);
318
- uint64_t key = v_nan ? 0x7ff8000000000000ULL
319
- : (uint64_t) NUM2LL(rb_hash(v));
584
+ int v_nan = fz_obj_has_nan(v);
585
+ uint64_t key = fz_obj_key(v, v_nan);
320
586
  ca_size_t mask = h->cap - 1;
321
587
  ca_size_t slot = FZ_HOME(h, key);
322
588
  while ( h->used[slot] ) {
323
589
  if ( h->key[slot] == key &&
324
- ( v_nan ? fz_is_float_nan(h->val[slot]) : rb_eql(h->val[slot], v) ) ) {
590
+ fz_obj_same(h->val[slot], v, v_nan) ) {
325
591
  *is_new = 0; return h->code[slot];
326
592
  }
327
593
  slot = (slot + 1) & mask;
@@ -412,14 +678,13 @@ fz_hash_lookup (fz_hash *h, uint64_t key, int32_t *code_out)
412
678
  static int
413
679
  fz_hash_lookup_obj (fz_hash *h, VALUE v, int32_t *code_out)
414
680
  {
415
- int v_nan = fz_is_float_nan(v);
416
- uint64_t key = v_nan ? 0x7ff8000000000000ULL
417
- : (uint64_t) NUM2LL(rb_hash(v));
681
+ int v_nan = fz_obj_has_nan(v);
682
+ uint64_t key = fz_obj_key(v, v_nan);
418
683
  ca_size_t mask = h->cap - 1;
419
684
  ca_size_t slot = FZ_HOME(h, key);
420
685
  while ( h->used[slot] ) {
421
686
  if ( h->key[slot] == key &&
422
- ( v_nan ? fz_is_float_nan(h->val[slot]) : rb_eql(h->val[slot], v) ) ) {
687
+ fz_obj_same(h->val[slot], v, v_nan) ) {
423
688
  if ( code_out ) { *code_out = h->code[slot]; }
424
689
  return 1;
425
690
  }
@@ -452,23 +717,42 @@ typedef struct {
452
717
  ca_size_t cap; /* capacity in elements */
453
718
  ca_size_t n; /* elements stored */
454
719
  int esz; /* element bytes */
720
+ VALUE hp; /* owner of p (see fz_tmp_alloc) */
721
+ int obj; /* p holds VALUEs, marked by hp */
455
722
  } fz_levels;
456
723
 
724
+ /* Fill the VALUE slots from..cap with Qnil and mark all cap of them. */
457
725
  static void
458
- fz_levels_init (fz_levels *l, int esz)
726
+ fz_levels_values (fz_levels *l, ca_size_t from)
727
+ {
728
+ ca_object_cells_clear((VALUE *) l->p + from, l->cap - from);
729
+ fz_tmp_values(l->hp, l->p, l->cap);
730
+ }
731
+
732
+ static void
733
+ fz_levels_init (fz_levels *l, int esz, int obj)
459
734
  {
460
735
  l->esz = esz;
461
736
  l->cap = 16;
462
737
  l->n = 0;
463
- l->p = ALLOC_N(char, l->cap * esz);
738
+ l->hp = 0;
739
+ l->obj = obj;
740
+ l->p = fz_tmp_alloc(&l->hp, (size_t) l->cap * esz);
741
+ if ( obj ) {
742
+ fz_levels_values(l, 0);
743
+ }
464
744
  }
465
745
 
466
746
  static void
467
747
  fz_levels_push (fz_levels *l, const void *e)
468
748
  {
469
749
  if ( l->n == l->cap ) {
750
+ ca_size_t old = l->cap;
470
751
  l->cap <<= 1;
471
- REALLOC_N(l->p, char, l->cap * l->esz);
752
+ l->p = fz_tmp_realloc(l->hp, (size_t) l->cap * l->esz);
753
+ if ( l->obj ) {
754
+ fz_levels_values(l, old);
755
+ }
472
756
  }
473
757
  memcpy(l->p + l->n * l->esz, e, l->esz);
474
758
  l->n++;
@@ -477,7 +761,41 @@ fz_levels_push (fz_levels *l, const void *e)
477
761
  static void
478
762
  fz_levels_free (fz_levels *l)
479
763
  {
480
- if ( l->p ) { xfree(l->p); l->p = NULL; }
764
+ fz_tmp_free(&l->hp);
765
+ l->p = NULL;
766
+ }
767
+
768
+ /* ---- the frame a discovery method runs in ---------------------------------
769
+
770
+ The object lane calls #hash and #eql? for every cell, and either can raise
771
+ in the middle of a walk. The walks' states live in this frame, outside the
772
+ method's body, and ca_iter_ensure finishes them however the body leaves;
773
+ the tables have owners the unwind collects (fz_tmp_alloc). Slots 0
774
+ and 1 hold a method's own walks, slot 2 the walk of fz_intern_all.
775
+
776
+ A body that replaces self or an operand with an array it made (complex
777
+ keys, a Face's storage, an operand brought into self's space) writes the
778
+ new VALUE back into the frame. The walks read it only through its
779
+ CArray *, so a C local alone would leave it to the collector halfway
780
+ through. */
781
+
782
+ typedef struct {
783
+ ca_iter_state st[3];
784
+ VALUE self;
785
+ VALUE a;
786
+ VALUE b;
787
+ int i;
788
+ } fz_frame;
789
+
790
+ static VALUE
791
+ fz_run (VALUE (*body)(VALUE), VALUE self, VALUE a, VALUE b, int i)
792
+ {
793
+ fz_frame fr;
794
+ fr.self = self;
795
+ fr.a = a;
796
+ fr.b = b;
797
+ fr.i = i;
798
+ return ca_iter_ensure(3, fr.st, body, (VALUE) &fr);
481
799
  }
482
800
 
483
801
  /* @overload __factorize_appearance__
@@ -497,11 +815,13 @@ fz_levels_free (fz_levels *l)
497
815
  (row-major flatten) order.
498
816
  */
499
817
  static VALUE
500
- rb_ca_factorize_appearance (VALUE self)
818
+ fz_factorize_appearance_body (VALUE arg)
501
819
  {
820
+ fz_frame *fr = (fz_frame *) arg;
821
+ VALUE self = fr->self;
502
822
  CArray *ca;
503
823
  volatile VALUE face;
504
- self = fz_face_descend(self, &face, "categorize");
824
+ fr->self = self = fz_face_descend(self, &face, "categorize");
505
825
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
506
826
 
507
827
  int8_t dt = ca->data_type;
@@ -532,11 +852,11 @@ rb_ca_factorize_appearance (VALUE self)
532
852
  if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
533
853
  else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
534
854
  else { fz_hash_init(&h); }
535
- fz_levels_init(&lv, (int) ca->bytes);
855
+ fz_levels_init(&lv, (int) ca->bytes, ca->data_type == CA_OBJECT);
536
856
 
537
857
  int8_t axis = (int8_t) (ca->ndim - 1); /* innermost fiber = row-major flatten */
538
858
 
539
- ca_iter_state st_in, st_out;
859
+ ca_iter_state *st_in = &fr->st[0], *st_out = &fr->st[1];
540
860
  char *p_in, *p_out;
541
861
  boolean8_t *m;
542
862
  ca_size_t n;
@@ -558,25 +878,18 @@ rb_ca_factorize_appearance (VALUE self)
558
878
  } while (0)
559
879
 
560
880
  /* Float lane: reproduce `==` with two value-based exceptions matching the
561
- discovery family -- all NaN collapse to one canonical key (v != v -> NANKEY),
881
+ discovery family -- all NaN collapse to one canonical key (FZ_FLOAT_KEY),
562
882
  and -0.0 == +0.0 (a zero normalizes to +0.0 before the bitwise key). The
563
883
  level pushed is the raw first-seen element (an appearance-first NaN / -0.0 is
564
884
  kept as-is), so masked source cells store the sentinel. */
565
- #define FZ_LOOP_FLOAT(T, UINT, NANKEY) \
885
+ #define FZ_LOOP_FLOAT(T) \
566
886
  do { \
567
887
  const T *ip = (const T *) p_in; \
568
888
  uint32_t *op = (uint32_t *) p_out; \
569
889
  for ( ca_size_t i = 0; i < n; i++ ) { \
570
890
  if ( m && m[i] ) { op[i] = 0xFFFFFFFFu; continue; } \
571
891
  T v = ip[i]; \
572
- uint64_t key; \
573
- if ( v != v ) { key = (NANKEY); } \
574
- else { \
575
- if ( v == (T) 0 ) { v = (T) 0; } \
576
- UINT bits; \
577
- memcpy(&bits, &v, sizeof(bits)); \
578
- key = (uint64_t) bits; \
579
- } \
892
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
580
893
  int is_new; \
581
894
  int32_t code = fz_hash_intern(&h, key, &is_new); \
582
895
  if ( is_new ) { fz_levels_push(&lv, &ip[i]); } \
@@ -615,7 +928,7 @@ rb_ca_factorize_appearance (VALUE self)
615
928
  } \
616
929
  } while (0)
617
930
 
618
- CA_FOR_EACH_FIBER_INOUT_MASKED(st_in, st_out, ca, cu32, axis,
931
+ CA_FOR_EACH_FIBER_INOUT_MASKED(*st_in, *st_out, ca, cu32, axis,
619
932
  CA_KERNEL_READ, p_in, p_out, n, m) {
620
933
  switch ( dt ) {
621
934
  case CA_INT8: FZ_LOOP(int8_t, (int64_t)); break;
@@ -626,8 +939,8 @@ rb_ca_factorize_appearance (VALUE self)
626
939
  case CA_UINT16: FZ_LOOP(uint16_t, (uint64_t)); break;
627
940
  case CA_UINT32: FZ_LOOP(uint32_t, (uint64_t)); break;
628
941
  case CA_UINT64: FZ_LOOP(uint64_t, (uint64_t)); break;
629
- case CA_FLOAT32: FZ_LOOP_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
630
- case CA_FLOAT64: FZ_LOOP_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
942
+ case CA_FLOAT32: FZ_LOOP_FLOAT(float); break;
943
+ case CA_FLOAT64: FZ_LOOP_FLOAT(double); break;
631
944
  case CA_OBJECT: FZ_LOOP_OBJ; break;
632
945
  case CA_FIXLEN: FZ_LOOP_MEM; break;
633
946
  }
@@ -686,6 +999,12 @@ rb_ca_factorize_appearance (VALUE self)
686
999
  return rb_ary_new3(2, vcodes, fz_face_relift(vlev, face));
687
1000
  }
688
1001
 
1002
+ static VALUE
1003
+ rb_ca_factorize_appearance (VALUE self)
1004
+ {
1005
+ return fz_run(fz_factorize_appearance_body, self, Qnil, Qnil, 0);
1006
+ }
1007
+
689
1008
  /* @overload __mask_duplicates__(axis)
690
1009
 
691
1010
  INTERNAL (mask_duplicates). Mark each cell whose value duplicates an
@@ -706,8 +1025,11 @@ rb_ca_factorize_appearance (VALUE self)
706
1025
  value (so the second and later NaN are duplicates, as in the numeric lane).
707
1026
  */
708
1027
  static VALUE
709
- rb_ca_mask_duplicates (VALUE self, VALUE vaxis)
1028
+ fz_mask_duplicates_body (VALUE arg)
710
1029
  {
1030
+ fz_frame *fr = (fz_frame *) arg;
1031
+ VALUE self = fr->self;
1032
+ VALUE vaxis = fr->a;
711
1033
  CArray *ca;
712
1034
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
713
1035
 
@@ -741,7 +1063,7 @@ rb_ca_mask_duplicates (VALUE self, VALUE vaxis)
741
1063
  else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
742
1064
  else { fz_hash_init(&h); }
743
1065
 
744
- ca_iter_state st_in, st_out;
1066
+ ca_iter_state *st_in = &fr->st[0], *st_out = &fr->st[1];
745
1067
  char *p_in, *p_out;
746
1068
  boolean8_t *m;
747
1069
  ca_size_t n;
@@ -764,24 +1086,17 @@ rb_ca_mask_duplicates (VALUE self, VALUE vaxis)
764
1086
 
765
1087
  /* Float variant: reproduce `==` with two value-based exceptions so the
766
1088
  distinct-value judgement matches uniq / the discovery family. All NaN
767
- collapse to one canonical key (v != v -> NANKEY), so the second and later
1089
+ collapse to one canonical key (FZ_FLOAT_KEY), so the second and later
768
1090
  NaN in a fiber are duplicates; -0.0 == +0.0, so a zero normalizes to +0.0
769
1091
  before the bitwise key. */
770
- #define MD_LOOP_FLOAT(T, UINT, NANKEY) \
1092
+ #define MD_LOOP_FLOAT(T) \
771
1093
  do { \
772
1094
  const T *ip = (const T *) p_in; \
773
1095
  boolean8_t *op = (boolean8_t *) p_out; \
774
1096
  for ( ca_size_t i = 0; i < n; i++ ) { \
775
1097
  if ( m && m[i] ) { op[i] = 0; continue; } \
776
1098
  T v = ip[i]; \
777
- uint64_t key; \
778
- if ( v != v ) { key = (NANKEY); } \
779
- else { \
780
- if ( v == (T) 0 ) { v = (T) 0; } \
781
- UINT bits; \
782
- memcpy(&bits, &v, sizeof(bits)); \
783
- key = (uint64_t) bits; \
784
- } \
1099
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
785
1100
  int is_new; \
786
1101
  fz_hash_intern(&h, key, &is_new); \
787
1102
  op[i] = is_new ? 0 : 1; \
@@ -814,7 +1129,7 @@ rb_ca_mask_duplicates (VALUE self, VALUE vaxis)
814
1129
  } \
815
1130
  } while (0)
816
1131
 
817
- CA_FOR_EACH_FIBER_INOUT_MASKED(st_in, st_out, ca, cout, (int8_t) axis,
1132
+ CA_FOR_EACH_FIBER_INOUT_MASKED(*st_in, *st_out, ca, cout, (int8_t) axis,
818
1133
  CA_KERNEL_READ, p_in, p_out, n, m) {
819
1134
  fz_hash_reset(&h); /* independent seen-set per fiber */
820
1135
  switch ( dt ) {
@@ -826,8 +1141,8 @@ rb_ca_mask_duplicates (VALUE self, VALUE vaxis)
826
1141
  case CA_UINT16: MD_LOOP(uint16_t, (uint64_t)); break;
827
1142
  case CA_UINT32: MD_LOOP(uint32_t, (uint64_t)); break;
828
1143
  case CA_UINT64: MD_LOOP(uint64_t, (uint64_t)); break;
829
- case CA_FLOAT32: MD_LOOP_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
830
- case CA_FLOAT64: MD_LOOP_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1144
+ case CA_FLOAT32: MD_LOOP_FLOAT(float); break;
1145
+ case CA_FLOAT64: MD_LOOP_FLOAT(double); break;
831
1146
  case CA_OBJECT: MD_LOOP_OBJ; break;
832
1147
  case CA_FIXLEN: MD_LOOP_MEM; break;
833
1148
  }
@@ -841,6 +1156,12 @@ rb_ca_mask_duplicates (VALUE self, VALUE vaxis)
841
1156
  return vout;
842
1157
  }
843
1158
 
1159
+ static VALUE
1160
+ rb_ca_mask_duplicates (VALUE self, VALUE vaxis)
1161
+ {
1162
+ return fz_run(fz_mask_duplicates_body, fz_complex_as_key(self), vaxis, Qnil, 0);
1163
+ }
1164
+
844
1165
  /* @overload __unique_flat__
845
1166
 
846
1167
  INTERNAL (CArray#unique). Collect the distinct values of self in
@@ -865,11 +1186,13 @@ rb_ca_mask_duplicates (VALUE self, VALUE vaxis)
865
1186
  uniform-width binary cells the fixlen seen-set used.
866
1187
  */
867
1188
  static VALUE
868
- rb_ca_unique_flat (VALUE self)
1189
+ fz_unique_flat_body (VALUE arg)
869
1190
  {
1191
+ fz_frame *fr = (fz_frame *) arg;
1192
+ VALUE self = fr->self;
870
1193
  CArray *ca;
871
1194
  volatile VALUE face;
872
- self = fz_face_descend(self, &face, "unique");
1195
+ fr->self = self = fz_face_descend(self, &face, "unique");
873
1196
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
874
1197
 
875
1198
  int8_t dt = ca->data_type;
@@ -893,11 +1216,9 @@ rb_ca_unique_flat (VALUE self)
893
1216
  if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
894
1217
  else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
895
1218
  else { fz_hash_init(&h); }
896
- fz_levels_init(&lv, (int) ca->bytes);
1219
+ fz_levels_init(&lv, (int) ca->bytes, ca->data_type == CA_OBJECT);
897
1220
 
898
- int8_t axis = (int8_t) (ca->ndim - 1); /* innermost fiber; hash not reset =
899
- one seen-set over the whole array */
900
- ca_iter_state st_in;
1221
+ ca_iter_state *st_in = &fr->st[0];
901
1222
  char *p_in;
902
1223
  boolean8_t *m;
903
1224
  ca_size_t n;
@@ -916,20 +1237,13 @@ rb_ca_unique_flat (VALUE self)
916
1237
 
917
1238
  /* Float: NaN collapses to one canonical key; -0.0 / +0.0 share a key. The
918
1239
  pushed level is the raw first-seen element. */
919
- #define UQ_LOOP_FLOAT(T, UINT, NANKEY) \
1240
+ #define UQ_LOOP_FLOAT(T) \
920
1241
  do { \
921
1242
  const T *ip = (const T *) p_in; \
922
1243
  for ( ca_size_t i = 0; i < n; i++ ) { \
923
1244
  if ( m && m[i] ) { continue; } \
924
1245
  T v = ip[i]; \
925
- uint64_t key; \
926
- if ( v != v ) { key = (NANKEY); } \
927
- else { \
928
- if ( v == (T) 0 ) { v = (T) 0; } \
929
- UINT bits; \
930
- memcpy(&bits, &v, sizeof(bits)); \
931
- key = (uint64_t) bits; \
932
- } \
1246
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
933
1247
  int is_new; \
934
1248
  fz_hash_intern(&h, key, &is_new); \
935
1249
  if ( is_new ) { fz_levels_push(&lv, &ip[i]); } \
@@ -964,7 +1278,7 @@ rb_ca_unique_flat (VALUE self)
964
1278
  } \
965
1279
  } while (0)
966
1280
 
967
- CA_FOR_EACH_FIBER_MASKED(st_in, ca, axis, CA_KERNEL_READ, p_in, n, m) {
1281
+ CA_FOR_EACH_WHOLE_MASKED(*st_in, ca, CA_KERNEL_READ, p_in, n, m) {
968
1282
  switch ( dt ) {
969
1283
  case CA_INT8: UQ_LOOP(int8_t, (int64_t)); break;
970
1284
  case CA_INT16: UQ_LOOP(int16_t, (int64_t)); break;
@@ -974,8 +1288,8 @@ rb_ca_unique_flat (VALUE self)
974
1288
  case CA_UINT16: UQ_LOOP(uint16_t, (uint64_t)); break;
975
1289
  case CA_UINT32: UQ_LOOP(uint32_t, (uint64_t)); break;
976
1290
  case CA_UINT64: UQ_LOOP(uint64_t, (uint64_t)); break;
977
- case CA_FLOAT32: UQ_LOOP_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
978
- case CA_FLOAT64: UQ_LOOP_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1291
+ case CA_FLOAT32: UQ_LOOP_FLOAT(float); break;
1292
+ case CA_FLOAT64: UQ_LOOP_FLOAT(double); break;
979
1293
  case CA_OBJECT: UQ_LOOP_OBJ; break;
980
1294
  case CA_FIXLEN: UQ_LOOP_MEM; break;
981
1295
  }
@@ -1002,8 +1316,14 @@ rb_ca_unique_flat (VALUE self)
1002
1316
  return fz_face_relift(vlev, face); /* distinct *values*: give the Face back */
1003
1317
  }
1004
1318
 
1319
+ static VALUE
1320
+ rb_ca_unique_flat (VALUE self)
1321
+ {
1322
+ return fz_run(fz_unique_flat_body, self, Qnil, Qnil, 0);
1323
+ }
1324
+
1005
1325
  /* Intern every non-masked cell of cv into h, one seen-set over the whole array
1006
- (the hash is not reset between fibers). When lv is non-NULL, the first-seen
1326
+ walked in row-major order. When lv is non-NULL, the first-seen
1007
1327
  raw element of each distinct value is pushed to it (appearance order), so the
1008
1328
  same pass builds both a probe set and the distinct-value list. Dispatches the
1009
1329
  three key lanes exactly as the discovery family: numeric widen with all NaN
@@ -1011,11 +1331,9 @@ rb_ca_unique_flat (VALUE self)
1011
1331
  fixlen byte-hash + memcmp. Used to build the probe set (is_in, set relations)
1012
1332
  and to accumulate distinct values (union). */
1013
1333
  static void
1014
- fz_intern_all (fz_hash *h, CArray *cv, fz_levels *lv)
1334
+ fz_intern_all (ca_iter_state *st_v, fz_hash *h, CArray *cv, fz_levels *lv)
1015
1335
  {
1016
1336
  int8_t dt = cv->data_type;
1017
- int8_t vaxis = (int8_t) (cv->ndim - 1);
1018
- ca_iter_state st_v;
1019
1337
  char *p_v;
1020
1338
  boolean8_t *mv;
1021
1339
  ca_size_t nv;
@@ -1032,20 +1350,13 @@ fz_intern_all (fz_hash *h, CArray *cv, fz_levels *lv)
1032
1350
  } \
1033
1351
  } while (0)
1034
1352
 
1035
- #define FZ_IA_FLOAT(T, UINT, NANKEY) \
1353
+ #define FZ_IA_FLOAT(T) \
1036
1354
  do { \
1037
1355
  const T *ip = (const T *) p_v; \
1038
1356
  for ( ca_size_t i = 0; i < nv; i++ ) { \
1039
1357
  if ( mv && mv[i] ) { continue; } \
1040
1358
  T v = ip[i]; \
1041
- uint64_t key; \
1042
- if ( v != v ) { key = (NANKEY); } \
1043
- else { \
1044
- if ( v == (T) 0 ) { v = (T) 0; } \
1045
- UINT bits; \
1046
- memcpy(&bits, &v, sizeof(bits)); \
1047
- key = (uint64_t) bits; \
1048
- } \
1359
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
1049
1360
  int is_new; \
1050
1361
  fz_hash_intern(h, key, &is_new); \
1051
1362
  if ( is_new && lv ) { fz_levels_push(lv, &ip[i]); } \
@@ -1075,7 +1386,7 @@ fz_intern_all (fz_hash *h, CArray *cv, fz_levels *lv)
1075
1386
  } \
1076
1387
  } while (0)
1077
1388
 
1078
- CA_FOR_EACH_FIBER_MASKED(st_v, cv, vaxis, CA_KERNEL_READ, p_v, nv, mv) {
1389
+ CA_FOR_EACH_WHOLE_MASKED(*st_v, cv, CA_KERNEL_READ, p_v, nv, mv) {
1079
1390
  switch ( dt ) {
1080
1391
  case CA_INT8: FZ_IA(int8_t, (int64_t)); break;
1081
1392
  case CA_INT16: FZ_IA(int16_t, (int64_t)); break;
@@ -1085,8 +1396,8 @@ fz_intern_all (fz_hash *h, CArray *cv, fz_levels *lv)
1085
1396
  case CA_UINT16: FZ_IA(uint16_t, (uint64_t)); break;
1086
1397
  case CA_UINT32: FZ_IA(uint32_t, (uint64_t)); break;
1087
1398
  case CA_UINT64: FZ_IA(uint64_t, (uint64_t)); break;
1088
- case CA_FLOAT32: FZ_IA_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
1089
- case CA_FLOAT64: FZ_IA_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1399
+ case CA_FLOAT32: FZ_IA_FLOAT(float); break;
1400
+ case CA_FLOAT64: FZ_IA_FLOAT(double); break;
1090
1401
  case CA_OBJECT: FZ_IA_OBJ; break;
1091
1402
  case CA_FIXLEN: FZ_IA_MEM; break;
1092
1403
  }
@@ -1115,12 +1426,15 @@ fz_intern_all (fz_hash *h, CArray *cv, fz_levels *lv)
1115
1426
  fixlen byte equality (byte-hash + memcmp).
1116
1427
  */
1117
1428
  static VALUE
1118
- rb_ca_is_in (VALUE self, VALUE rvalues)
1429
+ fz_is_in_body (VALUE arg)
1119
1430
  {
1431
+ fz_frame *fr = (fz_frame *) arg;
1432
+ VALUE self = fr->self;
1433
+ VALUE rvalues = fr->a;
1120
1434
  CArray *ca, *cv;
1121
1435
  volatile VALUE face;
1122
- self = fz_face_descend(self, &face, "is_in");
1123
- rvalues = fz_face_reconcile(face, rvalues, "is_in");
1436
+ fr->self = self = fz_face_descend(self, &face, "is_in");
1437
+ fr->a = rvalues = fz_face_reconcile(face, rvalues, "is_in");
1124
1438
  GetCArray(self, ca);
1125
1439
 
1126
1440
  int8_t dt = ca->data_type;
@@ -1145,12 +1459,12 @@ rb_ca_is_in (VALUE self, VALUE rvalues)
1145
1459
  GetCArray(rvalues, cv);
1146
1460
  if ( cv->data_type != dt ) {
1147
1461
  rb_raise(rb_eCADataTypeError,
1148
- "__is_in__: values data type (%s) must match self (%s)",
1462
+ "is_in: values data type (%s) must match self (%s)",
1149
1463
  ca_type_name[cv->data_type], ca_type_name[dt]);
1150
1464
  }
1151
1465
  if ( dt == CA_FIXLEN && cv->bytes != ca->bytes ) {
1152
1466
  rb_raise(rb_eCADataTypeError,
1153
- "__is_in__: a fixlen value is a cell-width blob, so the values "
1467
+ "is_in: a fixlen value is a cell-width blob, so the values "
1154
1468
  "must be %ld bytes wide like self, not %ld",
1155
1469
  (long) ca->bytes, (long) cv->bytes);
1156
1470
  }
@@ -1164,14 +1478,14 @@ rb_ca_is_in (VALUE self, VALUE rvalues)
1164
1478
  else { fz_hash_init(&h); }
1165
1479
 
1166
1480
  /* ---- Phase 1: build the seen-set from every non-masked cell of `values`. */
1167
- fz_intern_all(&h, cv, NULL);
1481
+ fz_intern_all(&fr->st[2], &h, cv, NULL);
1168
1482
 
1169
1483
  /* ---- Phase 2: probe every cell of self, writing the boolean membership. */
1170
1484
  VALUE vout = rb_carray_new(CA_BOOLEAN, ca->ndim, ca->dim, 0, NULL);
1171
1485
  CArray *cout;
1172
1486
  GetCArray(vout, cout);
1173
1487
  {
1174
- ca_iter_state st_in, st_out;
1488
+ ca_iter_state *st_in = &fr->st[0], *st_out = &fr->st[1];
1175
1489
  char *p_in, *p_out;
1176
1490
  boolean8_t *m;
1177
1491
  ca_size_t n;
@@ -1188,21 +1502,14 @@ rb_ca_is_in (VALUE self, VALUE rvalues)
1188
1502
  } \
1189
1503
  } while (0)
1190
1504
 
1191
- #define IN_PROBE_FLOAT(T, UINT, NANKEY) \
1505
+ #define IN_PROBE_FLOAT(T) \
1192
1506
  do { \
1193
1507
  const T *ip = (const T *) p_in; \
1194
1508
  boolean8_t *op = (boolean8_t *) p_out; \
1195
1509
  for ( ca_size_t i = 0; i < n; i++ ) { \
1196
1510
  if ( m && m[i] ) { op[i] = 0; continue; } \
1197
1511
  T v = ip[i]; \
1198
- uint64_t key; \
1199
- if ( v != v ) { key = (NANKEY); } \
1200
- else { \
1201
- if ( v == (T) 0 ) { v = (T) 0; } \
1202
- UINT bits; \
1203
- memcpy(&bits, &v, sizeof(bits)); \
1204
- key = (uint64_t) bits; \
1205
- } \
1512
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
1206
1513
  op[i] = (boolean8_t) fz_hash_lookup(&h, key, NULL); \
1207
1514
  } \
1208
1515
  } while (0)
@@ -1227,7 +1534,7 @@ rb_ca_is_in (VALUE self, VALUE rvalues)
1227
1534
  } \
1228
1535
  } while (0)
1229
1536
 
1230
- CA_FOR_EACH_FIBER_INOUT_MASKED(st_in, st_out, ca, cout, axis,
1537
+ CA_FOR_EACH_FIBER_INOUT_MASKED(*st_in, *st_out, ca, cout, axis,
1231
1538
  CA_KERNEL_READ, p_in, p_out, n, m) {
1232
1539
  switch ( dt ) {
1233
1540
  case CA_INT8: IN_PROBE(int8_t, (int64_t)); break;
@@ -1238,8 +1545,8 @@ rb_ca_is_in (VALUE self, VALUE rvalues)
1238
1545
  case CA_UINT16: IN_PROBE(uint16_t, (uint64_t)); break;
1239
1546
  case CA_UINT32: IN_PROBE(uint32_t, (uint64_t)); break;
1240
1547
  case CA_UINT64: IN_PROBE(uint64_t, (uint64_t)); break;
1241
- case CA_FLOAT32: IN_PROBE_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
1242
- case CA_FLOAT64: IN_PROBE_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1548
+ case CA_FLOAT32: IN_PROBE_FLOAT(float); break;
1549
+ case CA_FLOAT64: IN_PROBE_FLOAT(double); break;
1243
1550
  case CA_OBJECT: IN_PROBE_OBJ; break;
1244
1551
  case CA_FIXLEN: IN_PROBE_MEM; break;
1245
1552
  }
@@ -1259,6 +1566,12 @@ rb_ca_is_in (VALUE self, VALUE rvalues)
1259
1566
  return vout;
1260
1567
  }
1261
1568
 
1569
+ static VALUE
1570
+ rb_ca_is_in (VALUE self, VALUE rvalues)
1571
+ {
1572
+ return fz_run(fz_is_in_body, self, rvalues, Qnil, 0);
1573
+ }
1574
+
1262
1575
  /* @overload __locate_addr__(ref)
1263
1576
 
1264
1577
  INTERNAL (CArray#locate_addr, exact hash lane). For each cell of self, the
@@ -1276,16 +1589,19 @@ rb_ca_is_in (VALUE self, VALUE rvalues)
1276
1589
  appearance order (matching the discovery family); on a `ref` with duplicate
1277
1590
  values this is the earliest address, which can differ from the bsearch lane. */
1278
1591
  static VALUE
1279
- rb_ca_locate_addr (VALUE self, VALUE rref)
1592
+ fz_locate_addr_body (VALUE arg)
1280
1593
  {
1594
+ fz_frame *fr = (fz_frame *) arg;
1595
+ VALUE self = fr->self;
1596
+ VALUE rref = fr->a;
1281
1597
  CArray *ca, *cr;
1282
1598
  /* Here the *reference* is the argument (self is the query being placed on
1283
1599
  rref), so the gate runs the other way round: rref reconciles self. The
1284
1600
  output is an address, so nothing is lifted back. */
1285
1601
  {
1286
1602
  volatile VALUE ref_face;
1287
- rref = fz_face_descend(rref, &ref_face, "locate_addr");
1288
- self = fz_face_reconcile(ref_face, self, "locate_addr");
1603
+ fr->a = rref = fz_face_descend(rref, &ref_face, "locate_addr");
1604
+ fr->self = self = fz_face_reconcile(ref_face, self, "locate_addr");
1289
1605
  }
1290
1606
  GetCArray(self, ca);
1291
1607
 
@@ -1308,9 +1624,16 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1308
1624
  rb_raise(rb_eArgError, "__locate_addr__: ref must be a CArray");
1309
1625
  }
1310
1626
  GetCArray(rref, cr);
1311
- if ( cr->data_type != dt || (dt == CA_FIXLEN && cr->bytes != ca->bytes) ) {
1627
+ if ( cr->data_type != dt ) {
1312
1628
  rb_raise(rb_eCADataTypeError,
1313
- "__locate_addr__: ref data type must match self (%d)", dt);
1629
+ "locate_addr: ref data type (%s) must match self (%s)",
1630
+ ca_type_name[cr->data_type], ca_type_name[dt]);
1631
+ }
1632
+ if ( dt == CA_FIXLEN && cr->bytes != ca->bytes ) {
1633
+ rb_raise(rb_eCADataTypeError,
1634
+ "locate_addr: a fixlen value is a cell-width blob, so ref "
1635
+ "must be %ld bytes wide like self, not %ld",
1636
+ (long) ca->bytes, (long) cr->bytes);
1314
1637
  }
1315
1638
  if ( cr->ndim < 1 ) {
1316
1639
  rb_raise(rb_eRuntimeError, "__locate_addr__: ref need ndim >= 1");
@@ -1325,11 +1648,11 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1325
1648
  the ref value carrying dense code `code`. Codes are assigned 0,1,2,... in
1326
1649
  insertion order, so pushing on is_new fills addr.p in code order. */
1327
1650
  fz_levels addr;
1328
- fz_levels_init(&addr, (int) sizeof(int64_t));
1651
+ fz_levels_init(&addr, (int) sizeof(int64_t), 0);
1329
1652
 
1330
1653
  /* ---- Phase 1: build value -> first-address map from ref (row-major). ----- */
1331
1654
  {
1332
- ca_iter_state st_r;
1655
+ ca_iter_state *st_r = &fr->st[0];
1333
1656
  char *p_r;
1334
1657
  boolean8_t *mr;
1335
1658
  ca_size_t nr;
@@ -1349,20 +1672,13 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1349
1672
  } \
1350
1673
  } while (0)
1351
1674
 
1352
- #define LOC_BUILD_FLOAT(T, UINT, NANKEY) \
1675
+ #define LOC_BUILD_FLOAT(T) \
1353
1676
  do { \
1354
1677
  const T *ip = (const T *) p_r; \
1355
1678
  for ( ca_size_t i = 0; i < nr; i++ ) { \
1356
1679
  if ( mr && mr[i] ) { continue; } \
1357
1680
  T v = ip[i]; \
1358
- uint64_t key; \
1359
- if ( v != v ) { key = (NANKEY); } \
1360
- else { \
1361
- if ( v == (T) 0 ) { v = (T) 0; } \
1362
- UINT bits; \
1363
- memcpy(&bits, &v, sizeof(bits)); \
1364
- key = (uint64_t) bits; \
1365
- } \
1681
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
1366
1682
  int is_new; \
1367
1683
  fz_hash_intern(&h, key, &is_new); \
1368
1684
  if ( is_new ) { int64_t a = (int64_t) (base + i); \
@@ -1394,7 +1710,7 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1394
1710
  } \
1395
1711
  } while (0)
1396
1712
 
1397
- CA_FOR_EACH_FIBER_MASKED(st_r, cr, raxis, CA_KERNEL_READ, p_r, nr, mr) {
1713
+ CA_FOR_EACH_FIBER_MASKED(*st_r, cr, raxis, CA_KERNEL_READ, p_r, nr, mr) {
1398
1714
  switch ( dt ) {
1399
1715
  case CA_INT8: LOC_BUILD(int8_t, (int64_t)); break;
1400
1716
  case CA_INT16: LOC_BUILD(int16_t, (int64_t)); break;
@@ -1404,8 +1720,8 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1404
1720
  case CA_UINT16: LOC_BUILD(uint16_t, (uint64_t)); break;
1405
1721
  case CA_UINT32: LOC_BUILD(uint32_t, (uint64_t)); break;
1406
1722
  case CA_UINT64: LOC_BUILD(uint64_t, (uint64_t)); break;
1407
- case CA_FLOAT32: LOC_BUILD_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
1408
- case CA_FLOAT64: LOC_BUILD_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1723
+ case CA_FLOAT32: LOC_BUILD_FLOAT(float); break;
1724
+ case CA_FLOAT64: LOC_BUILD_FLOAT(double); break;
1409
1725
  case CA_OBJECT: LOC_BUILD_OBJ; break;
1410
1726
  case CA_FIXLEN: LOC_BUILD_MEM; break;
1411
1727
  }
@@ -1422,10 +1738,11 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1422
1738
  CArray *cout;
1423
1739
  GetCArray(vout, cout);
1424
1740
  int64_t *out = (int64_t *) cout->ptr;
1425
- boolean8_t *um = ALLOC_N(boolean8_t, ca->elements); /* undef flags scratch */
1741
+ volatile VALUE hum = 0;
1742
+ boolean8_t *um = ALLOCV_N(boolean8_t, hum, ca->elements); /* undef flags scratch */
1426
1743
  ca_size_t n_undef = 0;
1427
1744
  {
1428
- ca_iter_state st_in;
1745
+ ca_iter_state *st_in = &fr->st[1];
1429
1746
  char *p_in;
1430
1747
  boolean8_t *m;
1431
1748
  ca_size_t n;
@@ -1446,7 +1763,7 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1446
1763
  } \
1447
1764
  } while (0)
1448
1765
 
1449
- #define LOC_PROBE_FLOAT(T, UINT, NANKEY) \
1766
+ #define LOC_PROBE_FLOAT(T) \
1450
1767
  do { \
1451
1768
  const T *ip = (const T *) p_in; \
1452
1769
  const int64_t *ab = (const int64_t *) addr.p; \
@@ -1454,14 +1771,7 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1454
1771
  ca_size_t o = base + i; \
1455
1772
  if ( m && m[i] ) { out[o] = 0; um[o] = 1; n_undef++; continue; } \
1456
1773
  T v = ip[i]; \
1457
- uint64_t key; \
1458
- if ( v != v ) { key = (NANKEY); } \
1459
- else { \
1460
- if ( v == (T) 0 ) { v = (T) 0; } \
1461
- UINT bits; \
1462
- memcpy(&bits, &v, sizeof(bits)); \
1463
- key = (uint64_t) bits; \
1464
- } \
1774
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
1465
1775
  int32_t code; \
1466
1776
  if ( fz_hash_lookup(&h, key, &code) ) { out[o] = ab[code]; um[o] = 0; } \
1467
1777
  else { out[o] = 0; um[o] = 1; n_undef++; } \
@@ -1494,7 +1804,7 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1494
1804
  } \
1495
1805
  } while (0)
1496
1806
 
1497
- CA_FOR_EACH_FIBER_MASKED(st_in, ca, axis, CA_KERNEL_READ, p_in, n, m) {
1807
+ CA_FOR_EACH_FIBER_MASKED(*st_in, ca, axis, CA_KERNEL_READ, p_in, n, m) {
1498
1808
  switch ( dt ) {
1499
1809
  case CA_INT8: LOC_PROBE(int8_t, (int64_t)); break;
1500
1810
  case CA_INT16: LOC_PROBE(int16_t, (int64_t)); break;
@@ -1504,8 +1814,8 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1504
1814
  case CA_UINT16: LOC_PROBE(uint16_t, (uint64_t)); break;
1505
1815
  case CA_UINT32: LOC_PROBE(uint32_t, (uint64_t)); break;
1506
1816
  case CA_UINT64: LOC_PROBE(uint64_t, (uint64_t)); break;
1507
- case CA_FLOAT32: LOC_PROBE_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
1508
- case CA_FLOAT64: LOC_PROBE_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1817
+ case CA_FLOAT32: LOC_PROBE_FLOAT(float); break;
1818
+ case CA_FLOAT64: LOC_PROBE_FLOAT(double); break;
1509
1819
  case CA_OBJECT: LOC_PROBE_OBJ; break;
1510
1820
  case CA_FIXLEN: LOC_PROBE_MEM; break;
1511
1821
  }
@@ -1526,11 +1836,17 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1526
1836
  ca_create_mask(cout);
1527
1837
  memcpy(cout->mask->ptr, um, (size_t) ca->elements);
1528
1838
  }
1529
- xfree(um);
1839
+ ALLOCV_END(hum);
1530
1840
 
1531
1841
  return vout;
1532
1842
  }
1533
1843
 
1844
+ static VALUE
1845
+ rb_ca_locate_addr (VALUE self, VALUE rref)
1846
+ {
1847
+ return fz_run(fz_locate_addr_body, self, rref, Qnil, 0);
1848
+ }
1849
+
1534
1850
  /* Shared body of __intersection__ (keep_when_hit = 1) and __difference__
1535
1851
  (keep_when_hit = 0): the distinct values of self that are (resp. are not)
1536
1852
  present in `other`, in self's first-appearance order, as a 1-D CArray of
@@ -1540,12 +1856,16 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1540
1856
  lane (numeric `==` + NaN collapse + -0.0 == +0.0, object hash/eql? + NaN
1541
1857
  collapse, fixlen byte equality). */
1542
1858
  static VALUE
1543
- fz_set_relation (VALUE self, VALUE rother, int keep_when_hit)
1859
+ fz_set_relation_body (VALUE arg)
1544
1860
  {
1861
+ fz_frame *fr = (fz_frame *) arg;
1862
+ VALUE self = fr->self;
1863
+ VALUE rother = fr->a;
1864
+ int keep_when_hit = fr->i;
1545
1865
  CArray *ca, *co;
1546
1866
  volatile VALUE face;
1547
- self = fz_face_descend(self, &face, "set relation");
1548
- rother = fz_face_reconcile(face, rother, "set relation");
1867
+ fr->self = self = fz_face_descend(self, &face, "set relation");
1868
+ fr->a = rother = fz_face_reconcile(face, rother, "set relation");
1549
1869
  GetCArray(self, ca);
1550
1870
 
1551
1871
  int8_t dt = ca->data_type;
@@ -1594,15 +1914,15 @@ fz_set_relation (VALUE self, VALUE rother, int keep_when_hit)
1594
1914
  }
1595
1915
 
1596
1916
  fz_levels lv;
1597
- fz_levels_init(&lv, (int) ca->bytes);
1917
+ fz_levels_init(&lv, (int) ca->bytes, ca->data_type == CA_OBJECT);
1598
1918
 
1599
1919
  /* Build the probe set from every non-masked cell of other. */
1600
- fz_intern_all(&hoth, co, NULL);
1920
+ fz_intern_all(&fr->st[2], &hoth, co, NULL);
1601
1921
 
1602
1922
  /* Walk self: hself dedups, so each distinct self value is decided once; keep
1603
1923
  it when its membership in other equals keep_when_hit. */
1604
1924
  int8_t axis = (int8_t) (ca->ndim - 1);
1605
- ca_iter_state st;
1925
+ ca_iter_state *st = &fr->st[0];
1606
1926
  char *p;
1607
1927
  boolean8_t *m;
1608
1928
  ca_size_t n;
@@ -1621,20 +1941,13 @@ fz_set_relation (VALUE self, VALUE rother, int keep_when_hit)
1621
1941
  } \
1622
1942
  } while (0)
1623
1943
 
1624
- #define SR_FLOAT(T, UINT, NANKEY) \
1944
+ #define SR_FLOAT(T) \
1625
1945
  do { \
1626
1946
  const T *ip = (const T *) p; \
1627
1947
  for ( ca_size_t i = 0; i < n; i++ ) { \
1628
1948
  if ( m && m[i] ) { continue; } \
1629
1949
  T v = ip[i]; \
1630
- uint64_t key; \
1631
- if ( v != v ) { key = (NANKEY); } \
1632
- else { \
1633
- if ( v == (T) 0 ) { v = (T) 0; } \
1634
- UINT bits; \
1635
- memcpy(&bits, &v, sizeof(bits)); \
1636
- key = (uint64_t) bits; \
1637
- } \
1950
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
1638
1951
  int is_new; \
1639
1952
  fz_hash_intern(&hself, key, &is_new); \
1640
1953
  if ( is_new && fz_hash_lookup(&hoth, key, NULL) == keep_when_hit ) { \
@@ -1670,7 +1983,7 @@ fz_set_relation (VALUE self, VALUE rother, int keep_when_hit)
1670
1983
  } \
1671
1984
  } while (0)
1672
1985
 
1673
- CA_FOR_EACH_FIBER_MASKED(st, ca, axis, CA_KERNEL_READ, p, n, m) {
1986
+ CA_FOR_EACH_FIBER_MASKED(*st, ca, axis, CA_KERNEL_READ, p, n, m) {
1674
1987
  switch ( dt ) {
1675
1988
  case CA_INT8: SR(int8_t, (int64_t)); break;
1676
1989
  case CA_INT16: SR(int16_t, (int64_t)); break;
@@ -1680,8 +1993,8 @@ fz_set_relation (VALUE self, VALUE rother, int keep_when_hit)
1680
1993
  case CA_UINT16: SR(uint16_t, (uint64_t)); break;
1681
1994
  case CA_UINT32: SR(uint32_t, (uint64_t)); break;
1682
1995
  case CA_UINT64: SR(uint64_t, (uint64_t)); break;
1683
- case CA_FLOAT32: SR_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
1684
- case CA_FLOAT64: SR_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1996
+ case CA_FLOAT32: SR_FLOAT(float); break;
1997
+ case CA_FLOAT64: SR_FLOAT(double); break;
1685
1998
  case CA_OBJECT: SR_OBJ; break;
1686
1999
  case CA_FIXLEN: SR_MEM; break;
1687
2000
  }
@@ -1708,6 +2021,12 @@ fz_set_relation (VALUE self, VALUE rother, int keep_when_hit)
1708
2021
  return fz_face_relift(vlev, face); /* set *values*: give the Face back */
1709
2022
  }
1710
2023
 
2024
+ static VALUE
2025
+ fz_set_relation (VALUE self, VALUE rother, int keep_when_hit)
2026
+ {
2027
+ return fz_run(fz_set_relation_body, self, rother, Qnil, keep_when_hit);
2028
+ }
2029
+
1711
2030
  /* @overload __intersection__(other)
1712
2031
  INTERNAL (CArray#intersection). 1-D CArray of the distinct values present in
1713
2032
  both self and other, in self's first-appearance order. */
@@ -1733,12 +2052,15 @@ rb_ca_difference (VALUE self, VALUE rother)
1733
2052
  distinct values first, then other's not-yet-seen ones). Masked cells of
1734
2053
  either array do not participate. */
1735
2054
  static VALUE
1736
- rb_ca_set_union (VALUE self, VALUE rother)
2055
+ fz_set_union_body (VALUE arg)
1737
2056
  {
2057
+ fz_frame *fr = (fz_frame *) arg;
2058
+ VALUE self = fr->self;
2059
+ VALUE rother = fr->a;
1738
2060
  CArray *ca, *co;
1739
2061
  volatile VALUE face;
1740
- self = fz_face_descend(self, &face, "union");
1741
- rother = fz_face_reconcile(face, rother, "union");
2062
+ fr->self = self = fz_face_descend(self, &face, "union");
2063
+ fr->a = rother = fz_face_reconcile(face, rother, "union");
1742
2064
  GetCArray(self, ca);
1743
2065
 
1744
2066
  int8_t dt = ca->data_type;
@@ -1760,8 +2082,16 @@ rb_ca_set_union (VALUE self, VALUE rother)
1760
2082
  rb_raise(rb_eArgError, "__union__: other must be a CArray");
1761
2083
  }
1762
2084
  GetCArray(rother, co);
1763
- if ( co->data_type != dt || (dt == CA_FIXLEN && co->bytes != ca->bytes) ) {
1764
- rb_raise(rb_eCADataTypeError, "__union__: other data type must match self (%d)", dt);
2085
+ if ( co->data_type != dt ) {
2086
+ rb_raise(rb_eCADataTypeError,
2087
+ "union: other data type (%s) must match self (%s)",
2088
+ ca_type_name[co->data_type], ca_type_name[dt]);
2089
+ }
2090
+ if ( dt == CA_FIXLEN && co->bytes != ca->bytes ) {
2091
+ rb_raise(rb_eCADataTypeError,
2092
+ "union: a fixlen value is a cell-width blob, so other "
2093
+ "must be %ld bytes wide like self, not %ld",
2094
+ (long) ca->bytes, (long) co->bytes);
1765
2095
  }
1766
2096
  if ( co->ndim < 1 ) {
1767
2097
  rb_raise(rb_eRuntimeError, "__union__: other need ndim >= 1");
@@ -1773,10 +2103,10 @@ rb_ca_set_union (VALUE self, VALUE rother)
1773
2103
  else { fz_hash_init(&h); }
1774
2104
 
1775
2105
  fz_levels lv;
1776
- fz_levels_init(&lv, (int) ca->bytes);
2106
+ fz_levels_init(&lv, (int) ca->bytes, ca->data_type == CA_OBJECT);
1777
2107
 
1778
- fz_intern_all(&h, ca, &lv); /* self's distinct values, appearance order */
1779
- fz_intern_all(&h, co, &lv); /* + other's not-yet-seen distinct values */
2108
+ fz_intern_all(&fr->st[2], &h, ca, &lv); /* self's distinct values, appearance order */
2109
+ fz_intern_all(&fr->st[2], &h, co, &lv); /* + other's not-yet-seen distinct values */
1780
2110
 
1781
2111
  ca_size_t k = lv.n;
1782
2112
  fz_hash_free(&h);
@@ -1794,6 +2124,12 @@ rb_ca_set_union (VALUE self, VALUE rother)
1794
2124
  return fz_face_relift(vlev, face); /* set *values*: give the Face back */
1795
2125
  }
1796
2126
 
2127
+ static VALUE
2128
+ rb_ca_set_union (VALUE self, VALUE rother)
2129
+ {
2130
+ return fz_run(fz_set_union_body, self, rother, Qnil, 0);
2131
+ }
2132
+
1797
2133
  /* @overload __value_counts_flat__
1798
2134
 
1799
2135
  INTERNAL (CArray#value_counts). Collect the distinct values of self in
@@ -1810,11 +2146,13 @@ rb_ca_set_union (VALUE self, VALUE rother)
1810
2146
  collapses every Float NaN to one value (their counts add up, as numeric does).
1811
2147
  */
1812
2148
  static VALUE
1813
- rb_ca_value_counts_flat (VALUE self)
2149
+ fz_value_counts_flat_body (VALUE arg)
1814
2150
  {
2151
+ fz_frame *fr = (fz_frame *) arg;
2152
+ VALUE self = fr->self;
1815
2153
  CArray *ca;
1816
2154
  volatile VALUE face;
1817
- self = fz_face_descend(self, &face, "value_counts");
2155
+ fr->self = self = fz_face_descend(self, &face, "value_counts");
1818
2156
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
1819
2157
 
1820
2158
  int8_t dt = ca->data_type;
@@ -1839,11 +2177,11 @@ rb_ca_value_counts_flat (VALUE self)
1839
2177
  if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
1840
2178
  else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
1841
2179
  else { fz_hash_init(&h); }
1842
- fz_levels_init(&lv, (int) ca->bytes);
1843
- fz_levels_init(&ct, (int) sizeof(int64_t));
2180
+ fz_levels_init(&lv, (int) ca->bytes, ca->data_type == CA_OBJECT);
2181
+ fz_levels_init(&ct, (int) sizeof(int64_t), 0);
1844
2182
 
1845
2183
  int8_t axis = (int8_t) (ca->ndim - 1); /* one seen-set over the whole array */
1846
- ca_iter_state st_in;
2184
+ ca_iter_state *st_in = &fr->st[0];
1847
2185
  char *p_in;
1848
2186
  boolean8_t *m;
1849
2187
  ca_size_t n;
@@ -1866,20 +2204,13 @@ rb_ca_value_counts_flat (VALUE self)
1866
2204
  } \
1867
2205
  } while (0)
1868
2206
 
1869
- #define VC_LOOP_FLOAT(T, UINT, NANKEY) \
2207
+ #define VC_LOOP_FLOAT(T) \
1870
2208
  do { \
1871
2209
  const T *ip = (const T *) p_in; \
1872
2210
  for ( ca_size_t i = 0; i < n; i++ ) { \
1873
2211
  if ( m && m[i] ) { continue; } \
1874
2212
  T v = ip[i]; \
1875
- uint64_t key; \
1876
- if ( v != v ) { key = (NANKEY); } \
1877
- else { \
1878
- if ( v == (T) 0 ) { v = (T) 0; } \
1879
- UINT bits; \
1880
- memcpy(&bits, &v, sizeof(bits)); \
1881
- key = (uint64_t) bits; \
1882
- } \
2213
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
1883
2214
  int is_new; \
1884
2215
  int32_t code = fz_hash_intern(&h, key, &is_new); \
1885
2216
  if ( is_new ) { \
@@ -1929,7 +2260,7 @@ rb_ca_value_counts_flat (VALUE self)
1929
2260
  } \
1930
2261
  } while (0)
1931
2262
 
1932
- CA_FOR_EACH_FIBER_MASKED(st_in, ca, axis, CA_KERNEL_READ, p_in, n, m) {
2263
+ CA_FOR_EACH_FIBER_MASKED(*st_in, ca, axis, CA_KERNEL_READ, p_in, n, m) {
1933
2264
  switch ( dt ) {
1934
2265
  case CA_INT8: VC_LOOP(int8_t, (int64_t)); break;
1935
2266
  case CA_INT16: VC_LOOP(int16_t, (int64_t)); break;
@@ -1939,8 +2270,8 @@ rb_ca_value_counts_flat (VALUE self)
1939
2270
  case CA_UINT16: VC_LOOP(uint16_t, (uint64_t)); break;
1940
2271
  case CA_UINT32: VC_LOOP(uint32_t, (uint64_t)); break;
1941
2272
  case CA_UINT64: VC_LOOP(uint64_t, (uint64_t)); break;
1942
- case CA_FLOAT32: VC_LOOP_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
1943
- case CA_FLOAT64: VC_LOOP_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
2273
+ case CA_FLOAT32: VC_LOOP_FLOAT(float); break;
2274
+ case CA_FLOAT64: VC_LOOP_FLOAT(double); break;
1944
2275
  case CA_OBJECT: VC_LOOP_OBJ; break;
1945
2276
  case CA_FIXLEN: VC_LOOP_MEM; break;
1946
2277
  }
@@ -1973,20 +2304,30 @@ rb_ca_value_counts_flat (VALUE self)
1973
2304
  return rb_ary_new3(2, fz_face_relift(vlev, face), vcnt);
1974
2305
  }
1975
2306
 
2307
+ static VALUE
2308
+ rb_ca_value_counts_flat (VALUE self)
2309
+ {
2310
+ return fz_run(fz_value_counts_flat_body, self, Qnil, Qnil, 0);
2311
+ }
2312
+
1976
2313
  /* @overload __nunique__(axis, keep_axis)
1977
2314
 
1978
- INTERNAL (CArray#nunique). Count the distinct values along axis, one linear
1979
- pass per fiber with an independent seen-set. Returns a reduced
1980
- CA_INT64 CArray of self.shape with axis removed (or kept as length-1 when
1981
- keep_axis). Masked cells do not participate; an all-masked fiber counts 0
2315
+ INTERNAL (CArray#nunique). Count the distinct values along axis (an Integer
2316
+ or an Array of them), one linear pass per fiber with an independent
2317
+ seen-set. Returns a reduced CA_INT64 CArray of self.shape with the axes
2318
+ removed (or kept as length-1 when keep_axis). Masked cells do not participate; an all-masked fiber counts 0
1982
2319
  (an empty set has zero distinct values, not UNDEF -- nunique has identity 0).
1983
2320
  Numeric distinctness collapses all NaN to one value and treats -0.0 == +0.0;
1984
2321
  object keys on rb_hash + rb_eql and fixlen on a byte-hash + memcmp, reproducing
1985
2322
  Ruby Hash distinctness; the object lane also collapses every Float NaN to one.
1986
2323
  */
1987
2324
  static VALUE
1988
- rb_ca_nunique (VALUE self, VALUE vaxis, VALUE vkeep)
2325
+ fz_nunique_body (VALUE arg)
1989
2326
  {
2327
+ fz_frame *fr = (fz_frame *) arg;
2328
+ VALUE self = fr->self;
2329
+ VALUE vaxis = fr->a;
2330
+ VALUE vkeep = fr->b;
1990
2331
  CArray *ca;
1991
2332
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
1992
2333
 
@@ -2006,14 +2347,11 @@ rb_ca_nunique (VALUE self, VALUE vaxis, VALUE vkeep)
2006
2347
  rb_raise(rb_eRuntimeError, "__nunique__: need ndim >= 1");
2007
2348
  }
2008
2349
 
2009
- int axis = NUM2INT(vaxis);
2010
- if ( axis < 0 || axis >= ca->ndim ) {
2011
- rb_raise(rb_eArgError, "__nunique__: axis %d out of range", axis);
2012
- }
2350
+ int8_t axes[CA_RANK_MAX];
2351
+ int8_t naxes = rb_ca_parse_reduce_axes_kw_ctx(vaxis, ca, axes, "nunique");
2013
2352
  int keep_axis = RTEST(vkeep);
2014
2353
 
2015
- int8_t ax = (int8_t) axis;
2016
- VALUE vout = rb_ca_new_reduced(self, &ax, 1, CA_INT64, keep_axis);
2354
+ VALUE vout = rb_ca_new_reduced(self, axes, naxes, CA_INT64, keep_axis);
2017
2355
  CArray *cout;
2018
2356
  TypedData_Get_Struct(vout, CArray, &carray_data_type, cout);
2019
2357
  int64_t *op = (int64_t *) cout->ptr;
@@ -2023,8 +2361,8 @@ rb_ca_nunique (VALUE self, VALUE vaxis, VALUE vkeep)
2023
2361
  else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
2024
2362
  else { fz_hash_init(&h); }
2025
2363
 
2026
- ca_iter_state st;
2027
- int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES, &ax, 1, 0);
2364
+ ca_iter_state *st = &fr->st[0];
2365
+ int rc = ca_iter_state_init_l2(st, ca, CA_SLAB_AXES, axes, naxes, 0);
2028
2366
  if ( rc != CA_ITER_OK ) {
2029
2367
  fz_hash_free(&h);
2030
2368
  rb_raise(rb_eRuntimeError, "__nunique__: kernel_iterator init failed rc=%d", rc);
@@ -2040,17 +2378,10 @@ rb_ca_nunique (VALUE self, VALUE vaxis, VALUE vkeep)
2040
2378
  -0.0 before the bitwise key. */
2041
2379
  #define NU_INT(WIDEN) \
2042
2380
  do { int _n; fz_hash_intern(&h, (uint64_t) ((WIDEN) v), &_n); } while (0)
2043
- #define NU_FLOAT(T, UINT, NANKEY) \
2381
+ #define NU_FLOAT(T) \
2044
2382
  do { \
2045
2383
  T _v = v; \
2046
- uint64_t _key; \
2047
- if ( _v != _v ) { _key = (NANKEY); } \
2048
- else { \
2049
- if ( _v == (T) 0 ) { _v = (T) 0; } \
2050
- UINT _b; \
2051
- memcpy(&_b, &_v, sizeof(_b)); \
2052
- _key = (uint64_t) _b; \
2053
- } \
2384
+ uint64_t _key = FZ_FLOAT_KEY(T, _v); \
2054
2385
  int _n; fz_hash_intern(&h, _key, &_n); \
2055
2386
  } while (0)
2056
2387
  /* Object: intern the VALUE via the object lane (v is bound by the macro). */
@@ -2062,43 +2393,43 @@ rb_ca_nunique (VALUE self, VALUE vaxis, VALUE vkeep)
2062
2393
  each esz-wide element via the fixlen lane. */
2063
2394
  #define NU_MEM_WALK \
2064
2395
  do { \
2065
- int8_t K = st.slab_ndim; \
2396
+ int8_t K = st->slab_ndim; \
2066
2397
  ca_size_t idx[CA_RANK_MAX] = { 0 }; \
2067
- ca_size_t total = st.slab_elements; \
2398
+ ca_size_t total = st->slab_elements; \
2068
2399
  for ( ca_size_t e = 0; e < total; e++ ) { \
2069
2400
  ca_size_t doff = 0, moff = 0; \
2070
2401
  for ( int8_t kk = 0; kk < K; kk++ ) { \
2071
- doff += idx[kk] * st.slab_strides[kk]; \
2072
- moff += idx[kk] * st.slab_mask_strides[kk]; \
2402
+ doff += idx[kk] * st->slab_strides[kk]; \
2403
+ moff += idx[kk] * st->slab_mask_strides[kk]; \
2073
2404
  } \
2074
2405
  if ( ! (m && m[moff]) ) { \
2075
2406
  int _n; fz_hash_intern_mem(&h, p + doff, &_n); \
2076
2407
  } \
2077
2408
  for ( int8_t kk = (int8_t)(K - 1); kk >= 0; kk-- ) { \
2078
- if ( ++idx[kk] < st.slab_dims[kk] ) break; \
2409
+ if ( ++idx[kk] < st->slab_dims[kk] ) break; \
2079
2410
  idx[kk] = 0; \
2080
2411
  } \
2081
2412
  } \
2082
2413
  } while (0)
2083
2414
 
2084
- while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2415
+ while ( ca_iter_state_next_slab_axes(st, &p, &m) ) {
2085
2416
  fz_hash_reset(&h); /* independent seen-set per fiber */
2086
2417
  switch ( dt ) {
2087
- case CA_INT8: CA_SLAB_REDUCE_T(int8_t, st, p, m, dummy, 0, NU_INT(int64_t)); break;
2088
- case CA_INT16: CA_SLAB_REDUCE_T(int16_t, st, p, m, dummy, 0, NU_INT(int64_t)); break;
2089
- case CA_INT32: CA_SLAB_REDUCE_T(int32_t, st, p, m, dummy, 0, NU_INT(int64_t)); break;
2090
- case CA_INT64: CA_SLAB_REDUCE_T(int64_t, st, p, m, dummy, 0, NU_INT(int64_t)); break;
2091
- case CA_BOOLEAN: case CA_UINT8: CA_SLAB_REDUCE_T(uint8_t, st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2092
- case CA_UINT16: CA_SLAB_REDUCE_T(uint16_t, st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2093
- case CA_UINT32: CA_SLAB_REDUCE_T(uint32_t, st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2094
- case CA_UINT64: CA_SLAB_REDUCE_T(uint64_t, st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2418
+ case CA_INT8: CA_SLAB_REDUCE_T(int8_t, *st, p, m, dummy, 0, NU_INT(int64_t)); break;
2419
+ case CA_INT16: CA_SLAB_REDUCE_T(int16_t, *st, p, m, dummy, 0, NU_INT(int64_t)); break;
2420
+ case CA_INT32: CA_SLAB_REDUCE_T(int32_t, *st, p, m, dummy, 0, NU_INT(int64_t)); break;
2421
+ case CA_INT64: CA_SLAB_REDUCE_T(int64_t, *st, p, m, dummy, 0, NU_INT(int64_t)); break;
2422
+ case CA_BOOLEAN: case CA_UINT8: CA_SLAB_REDUCE_T(uint8_t, *st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2423
+ case CA_UINT16: CA_SLAB_REDUCE_T(uint16_t, *st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2424
+ case CA_UINT32: CA_SLAB_REDUCE_T(uint32_t, *st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2425
+ case CA_UINT64: CA_SLAB_REDUCE_T(uint64_t, *st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2095
2426
  case CA_FLOAT32:
2096
- CA_SLAB_REDUCE_T(float, st, p, m, dummy, 0, NU_FLOAT(float, uint32_t, 0x7fc00000ULL));
2427
+ CA_SLAB_REDUCE_T(float, *st, p, m, dummy, 0, NU_FLOAT(float));
2097
2428
  break;
2098
2429
  case CA_FLOAT64:
2099
- CA_SLAB_REDUCE_T(double, st, p, m, dummy, 0, NU_FLOAT(double, uint64_t, 0x7ff8000000000000ULL));
2430
+ CA_SLAB_REDUCE_T(double, *st, p, m, dummy, 0, NU_FLOAT(double));
2100
2431
  break;
2101
- case CA_OBJECT: CA_SLAB_REDUCE_T(VALUE, st, p, m, dummy, 0, NU_OBJ); break;
2432
+ case CA_OBJECT: CA_SLAB_REDUCE_T(VALUE, *st, p, m, dummy, 0, NU_OBJ); break;
2102
2433
  case CA_FIXLEN: NU_MEM_WALK; break;
2103
2434
  }
2104
2435
  op[out_i++] = (int64_t) h.n;
@@ -2109,11 +2440,17 @@ rb_ca_nunique (VALUE self, VALUE vaxis, VALUE vkeep)
2109
2440
  #undef NU_MEM_WALK
2110
2441
  (void) dummy;
2111
2442
 
2112
- ca_iter_state_finish(&st);
2443
+ ca_iter_state_finish(st);
2113
2444
  fz_hash_free(&h);
2114
2445
  return vout;
2115
2446
  }
2116
2447
 
2448
+ static VALUE
2449
+ rb_ca_nunique (VALUE self, VALUE vaxis, VALUE vkeep)
2450
+ {
2451
+ return fz_run(fz_nunique_body, fz_complex_as_key(self), vaxis, vkeep, 0);
2452
+ }
2453
+
2117
2454
  /* @overload __is_mode__(axis)
2118
2455
 
2119
2456
  INTERNAL (CArray#is_mode). Mark each cell that holds a modal value along axis
@@ -2132,8 +2469,11 @@ rb_ca_nunique (VALUE self, VALUE vaxis, VALUE vkeep)
2132
2469
  Callers pass flat input by flattening first (a single fiber over axis 0).
2133
2470
  */
2134
2471
  static VALUE
2135
- rb_ca_is_mode (VALUE self, VALUE vaxis)
2472
+ fz_is_mode_body (VALUE arg)
2136
2473
  {
2474
+ fz_frame *fr = (fz_frame *) arg;
2475
+ VALUE self = fr->self;
2476
+ VALUE vaxis = fr->a;
2137
2477
  CArray *ca;
2138
2478
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
2139
2479
 
@@ -2166,9 +2506,9 @@ rb_ca_is_mode (VALUE self, VALUE vaxis)
2166
2506
  if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
2167
2507
  else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
2168
2508
  else { fz_hash_init(&h); }
2169
- fz_levels_init(&ct, (int) sizeof(int64_t));
2509
+ fz_levels_init(&ct, (int) sizeof(int64_t), 0);
2170
2510
 
2171
- ca_iter_state st_in, st_out;
2511
+ ca_iter_state *st_in = &fr->st[0], *st_out = &fr->st[1];
2172
2512
  char *p_in, *p_out;
2173
2513
  boolean8_t *m;
2174
2514
  ca_size_t n;
@@ -2201,7 +2541,7 @@ rb_ca_is_mode (VALUE self, VALUE vaxis)
2201
2541
  } while (0)
2202
2542
 
2203
2543
  /* Float: NaN collapses to one canonical key; -0.0 / +0.0 share a key. */
2204
- #define IM_BODY_FLOAT(T, UINT, NANKEY) \
2544
+ #define IM_BODY_FLOAT(T) \
2205
2545
  do { \
2206
2546
  const T *ip = (const T *) p_in; \
2207
2547
  boolean8_t *op = (boolean8_t *) p_out; \
@@ -2216,14 +2556,7 @@ rb_ca_is_mode (VALUE self, VALUE vaxis)
2216
2556
  for ( ca_size_t i = 0; i < n; i++ ) { \
2217
2557
  if ( m && m[i] ) { if ( pass == 1 ) { op[i] = 0; } continue; } \
2218
2558
  T v = ip[i]; \
2219
- uint64_t key; \
2220
- if ( v != v ) { key = (NANKEY); } \
2221
- else { \
2222
- if ( v == (T) 0 ) { v = (T) 0; } \
2223
- UINT bits; \
2224
- memcpy(&bits, &v, sizeof(bits)); \
2225
- key = (uint64_t) bits; \
2226
- } \
2559
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
2227
2560
  int is_new; \
2228
2561
  int32_t code = fz_hash_intern(&h, key, &is_new); \
2229
2562
  if ( pass == 0 ) { \
@@ -2287,7 +2620,7 @@ rb_ca_is_mode (VALUE self, VALUE vaxis)
2287
2620
  } \
2288
2621
  } while (0)
2289
2622
 
2290
- CA_FOR_EACH_FIBER_INOUT_MASKED(st_in, st_out, ca, cout, (int8_t) axis,
2623
+ CA_FOR_EACH_FIBER_INOUT_MASKED(*st_in, *st_out, ca, cout, (int8_t) axis,
2291
2624
  CA_KERNEL_READ, p_in, p_out, n, m) {
2292
2625
  fz_hash_reset(&h); /* independent frequency table per fiber */
2293
2626
  ct.n = 0;
@@ -2300,8 +2633,8 @@ rb_ca_is_mode (VALUE self, VALUE vaxis)
2300
2633
  case CA_UINT16: IM_BODY(uint16_t, (uint64_t)); break;
2301
2634
  case CA_UINT32: IM_BODY(uint32_t, (uint64_t)); break;
2302
2635
  case CA_UINT64: IM_BODY(uint64_t, (uint64_t)); break;
2303
- case CA_FLOAT32: IM_BODY_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
2304
- case CA_FLOAT64: IM_BODY_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
2636
+ case CA_FLOAT32: IM_BODY_FLOAT(float); break;
2637
+ case CA_FLOAT64: IM_BODY_FLOAT(double); break;
2305
2638
  case CA_OBJECT: IM_BODY_OBJ; break;
2306
2639
  case CA_FIXLEN: IM_BODY_MEM; break;
2307
2640
  }
@@ -2316,6 +2649,12 @@ rb_ca_is_mode (VALUE self, VALUE vaxis)
2316
2649
  return vout;
2317
2650
  }
2318
2651
 
2652
+ static VALUE
2653
+ rb_ca_is_mode (VALUE self, VALUE vaxis)
2654
+ {
2655
+ return fz_run(fz_is_mode_body, fz_complex_as_key(self), vaxis, Qnil, 0);
2656
+ }
2657
+
2319
2658
  /* Ascending comparison for the modal-value sort, NaN ordered last so a
2320
2659
  collapsed NaN key (at most one per fiber) trails the real values, matching
2321
2660
  CArray#sort. */
@@ -2349,11 +2688,14 @@ static int mode_gt_f64 (double a, double b) { if ( a != a ) return b == b; if (
2349
2688
  its sign), matching the discovery family.
2350
2689
  */
2351
2690
  static VALUE
2352
- rb_ca_mode_axis (VALUE self, VALUE vaxis)
2691
+ fz_mode_axis_body (VALUE arg)
2353
2692
  {
2693
+ fz_frame *fr = (fz_frame *) arg;
2694
+ VALUE self = fr->self;
2695
+ VALUE vaxis = fr->a;
2354
2696
  CArray *ca;
2355
2697
  volatile VALUE face;
2356
- self = fz_face_descend(self, &face, "mode");
2698
+ fr->self = self = fz_face_descend(self, &face, "mode");
2357
2699
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
2358
2700
 
2359
2701
  int8_t dt = ca->data_type;
@@ -2387,20 +2729,21 @@ rb_ca_mode_axis (VALUE self, VALUE vaxis)
2387
2729
  fz_levels mod; /* per-fiber modal values, collected then sorted (reset) */
2388
2730
  fz_levels flat; /* all fibers' modal values concatenated, fiber order */
2389
2731
  fz_hash_init(&h);
2390
- fz_levels_init(&lv, esz);
2391
- fz_levels_init(&ct, (int) sizeof(int64_t));
2392
- fz_levels_init(&mod, esz);
2393
- fz_levels_init(&flat, esz);
2732
+ fz_levels_init(&lv, esz, 0);
2733
+ fz_levels_init(&ct, (int) sizeof(int64_t), 0);
2734
+ fz_levels_init(&mod, esz, 0);
2735
+ fz_levels_init(&flat, esz, 0);
2394
2736
 
2395
- ca_size_t *foff = ALLOC_N(ca_size_t, M + 1); /* prefix offsets into flat */
2737
+ volatile VALUE hfoff = 0;
2738
+ ca_size_t *foff = ALLOCV_N(ca_size_t, hfoff, M + 1); /* prefix offsets into flat */
2396
2739
  foff[0] = 0;
2397
2740
 
2398
2741
  int8_t ax = (int8_t) axis;
2399
- ca_iter_state st;
2400
- int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES, &ax, 1, 0);
2742
+ ca_iter_state *st = &fr->st[0];
2743
+ int rc = ca_iter_state_init_l2(st, ca, CA_SLAB_AXES, &ax, 1, 0);
2401
2744
  if ( rc != CA_ITER_OK ) {
2402
2745
  fz_hash_free(&h); fz_levels_free(&lv); fz_levels_free(&ct);
2403
- fz_levels_free(&mod); fz_levels_free(&flat); xfree(foff);
2746
+ fz_levels_free(&mod); fz_levels_free(&flat); ALLOCV_END(hfoff);
2404
2747
  rb_raise(rb_eRuntimeError, "__mode_axis__: kernel_iterator init failed rc=%d", rc);
2405
2748
  }
2406
2749
 
@@ -2421,12 +2764,9 @@ rb_ca_mode_axis (VALUE self, VALUE vaxis)
2421
2764
  fz_levels_push(&ct, &one); } \
2422
2765
  else { ((int64_t *) ct.p)[code]++; } \
2423
2766
  } while (0)
2424
- #define MB_FLOAT(T, UINT, NANKEY) \
2767
+ #define MB_FLOAT(T) \
2425
2768
  do { \
2426
- T vn = v; uint64_t key; \
2427
- if ( vn != vn ) { key = (NANKEY); } \
2428
- else { if ( vn == (T) 0 ) { vn = (T) 0; } \
2429
- UINT bits; memcpy(&bits, &vn, sizeof(bits)); key = (uint64_t) bits; } \
2769
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
2430
2770
  int is_new; int32_t code = fz_hash_intern(&h, key, &is_new); \
2431
2771
  if ( is_new ) { int64_t one = 1; fz_levels_push(&lv, &v); \
2432
2772
  fz_levels_push(&ct, &one); } \
@@ -2450,24 +2790,24 @@ rb_ca_mode_axis (VALUE self, VALUE vaxis)
2450
2790
  for ( ca_size_t x = 0; x < cm; x++ ) { fz_levels_push(&flat, &dp[x]); } \
2451
2791
  } while (0)
2452
2792
 
2453
- while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2793
+ while ( ca_iter_state_next_slab_axes(st, &p, &m) ) {
2454
2794
  fz_hash_reset(&h);
2455
2795
  lv.n = 0; ct.n = 0; mod.n = 0;
2456
2796
 
2457
2797
  switch ( dt ) {
2458
- case CA_INT8: CA_SLAB_REDUCE_T(int8_t, st, p, m, dummy, 0, MB_INT(int64_t)); break;
2459
- case CA_INT16: CA_SLAB_REDUCE_T(int16_t, st, p, m, dummy, 0, MB_INT(int64_t)); break;
2460
- case CA_INT32: CA_SLAB_REDUCE_T(int32_t, st, p, m, dummy, 0, MB_INT(int64_t)); break;
2461
- case CA_INT64: CA_SLAB_REDUCE_T(int64_t, st, p, m, dummy, 0, MB_INT(int64_t)); break;
2462
- case CA_BOOLEAN: case CA_UINT8: CA_SLAB_REDUCE_T(uint8_t, st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2463
- case CA_UINT16: CA_SLAB_REDUCE_T(uint16_t, st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2464
- case CA_UINT32: CA_SLAB_REDUCE_T(uint32_t, st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2465
- case CA_UINT64: CA_SLAB_REDUCE_T(uint64_t, st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2798
+ case CA_INT8: CA_SLAB_REDUCE_T(int8_t, *st, p, m, dummy, 0, MB_INT(int64_t)); break;
2799
+ case CA_INT16: CA_SLAB_REDUCE_T(int16_t, *st, p, m, dummy, 0, MB_INT(int64_t)); break;
2800
+ case CA_INT32: CA_SLAB_REDUCE_T(int32_t, *st, p, m, dummy, 0, MB_INT(int64_t)); break;
2801
+ case CA_INT64: CA_SLAB_REDUCE_T(int64_t, *st, p, m, dummy, 0, MB_INT(int64_t)); break;
2802
+ case CA_BOOLEAN: case CA_UINT8: CA_SLAB_REDUCE_T(uint8_t, *st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2803
+ case CA_UINT16: CA_SLAB_REDUCE_T(uint16_t, *st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2804
+ case CA_UINT32: CA_SLAB_REDUCE_T(uint32_t, *st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2805
+ case CA_UINT64: CA_SLAB_REDUCE_T(uint64_t, *st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2466
2806
  case CA_FLOAT32:
2467
- CA_SLAB_REDUCE_T(float, st, p, m, dummy, 0, MB_FLOAT(float, uint32_t, 0x7fc00000ULL));
2807
+ CA_SLAB_REDUCE_T(float, *st, p, m, dummy, 0, MB_FLOAT(float));
2468
2808
  break;
2469
2809
  case CA_FLOAT64:
2470
- CA_SLAB_REDUCE_T(double, st, p, m, dummy, 0, MB_FLOAT(double, uint64_t, 0x7ff8000000000000ULL));
2810
+ CA_SLAB_REDUCE_T(double, *st, p, m, dummy, 0, MB_FLOAT(double));
2471
2811
  break;
2472
2812
  }
2473
2813
 
@@ -2499,7 +2839,7 @@ rb_ca_mode_axis (VALUE self, VALUE vaxis)
2499
2839
  #undef MODE_EMIT
2500
2840
  (void) dummy;
2501
2841
 
2502
- ca_iter_state_finish(&st);
2842
+ ca_iter_state_finish(st);
2503
2843
  fz_hash_free(&h);
2504
2844
  fz_levels_free(&lv);
2505
2845
  fz_levels_free(&ct);
@@ -2529,10 +2869,16 @@ rb_ca_mode_axis (VALUE self, VALUE vaxis)
2529
2869
  }
2530
2870
 
2531
2871
  fz_levels_free(&flat);
2532
- xfree(foff);
2872
+ ALLOCV_END(hfoff);
2533
2873
  return result;
2534
2874
  }
2535
2875
 
2876
+ static VALUE
2877
+ rb_ca_mode_axis (VALUE self, VALUE vaxis)
2878
+ {
2879
+ return fz_run(fz_mode_axis_body, self, vaxis, Qnil, 0);
2880
+ }
2881
+
2536
2882
  void
2537
2883
  Init_carray_factorize (void)
2538
2884
  {