carray 3.0.1 → 3.0.3

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 (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +824 -0
  3. data/README.md +5 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +258 -4
  6. data/ext/ca_axis_group.c +368 -184
  7. data/ext/ca_binop_dispatch.c +54 -32
  8. data/ext/ca_binop_dispatch.h +1 -0
  9. data/ext/ca_categorical_iterator.c +90 -41
  10. data/ext/ca_compare.c +8 -2
  11. data/ext/ca_for_buffer.h +21 -68
  12. data/ext/ca_group_iter.c +27 -11
  13. data/ext/ca_kernel_iterator.c +960 -245
  14. data/ext/ca_kernel_iterator.h +352 -181
  15. data/ext/ca_monop_dispatch.c +4 -4
  16. data/ext/ca_monop_dispatch.h +4 -0
  17. data/ext/ca_obj_array.c +194 -42
  18. data/ext/ca_obj_bincmp.c +152 -121
  19. data/ext/ca_obj_binop.c +107 -210
  20. data/ext/ca_obj_bitarray.c +37 -24
  21. data/ext/ca_obj_bitfield.c +95 -62
  22. data/ext/ca_obj_block.c +5 -4
  23. data/ext/ca_obj_byte_swap.c +40 -40
  24. data/ext/ca_obj_const_string.c +180 -50
  25. data/ext/ca_obj_face.c +428 -62
  26. data/ext/ca_obj_face.h +107 -47
  27. data/ext/ca_obj_fake.c +21 -50
  28. data/ext/ca_obj_farray.c +1 -0
  29. data/ext/ca_obj_field.c +16 -3
  30. data/ext/ca_obj_fixlen_string.c +39 -26
  31. data/ext/ca_obj_grid.c +181 -124
  32. data/ext/ca_obj_meld.c +235 -55
  33. data/ext/ca_obj_moncmp.c +61 -61
  34. data/ext/ca_obj_monop.c +125 -107
  35. data/ext/ca_obj_object.c +551 -233
  36. data/ext/ca_obj_record.c +51 -16
  37. data/ext/ca_obj_reduce.c +5 -8
  38. data/ext/ca_obj_refer.c +117 -33
  39. data/ext/ca_obj_remap.c +57 -17
  40. data/ext/ca_obj_repeat.c +21 -5
  41. data/ext/ca_obj_roll.c +115 -11
  42. data/ext/ca_obj_select.c +88 -75
  43. data/ext/ca_obj_select_axis.c +58 -60
  44. data/ext/ca_obj_shift.c +22 -102
  45. data/ext/ca_obj_stack.c +223 -123
  46. data/ext/ca_obj_stride.c +267 -35
  47. data/ext/ca_obj_string.c +25 -22
  48. data/ext/ca_obj_tile.c +32 -16
  49. data/ext/ca_obj_time.c +23 -25
  50. data/ext/ca_obj_timedelta.c +21 -23
  51. data/ext/ca_obj_transpose.c +12 -3
  52. data/ext/ca_obj_triop.c +110 -117
  53. data/ext/ca_obj_window.c +157 -29
  54. data/ext/ca_op_ipower.c +25 -13
  55. data/ext/ca_op_powi.h +57 -30
  56. data/ext/ca_pow5_table.h +656 -0
  57. data/ext/ca_rng_normal.h +42 -0
  58. data/ext/ca_rng_xoshiro256pp.h +105 -0
  59. data/ext/ca_sort_kernels.h +70 -0
  60. data/ext/ca_sweep_engine.c +385 -198
  61. data/ext/ca_sweep_engine.h +31 -23
  62. data/ext/ca_triop_dispatch.c +4 -1
  63. data/ext/ca_triop_dispatch.h +5 -2
  64. data/ext/caframe_csv_reader.c +463 -0
  65. data/ext/carray.h +90 -28
  66. data/ext/carray_access.c +487 -231
  67. data/ext/carray_address_basis.c +731 -0
  68. data/ext/carray_attribute.c +45 -3
  69. data/ext/carray_bincount.c +32 -2
  70. data/ext/carray_broadcast.c +49 -17
  71. data/ext/carray_call_cfunc.c +667 -511
  72. data/ext/carray_cast.c +1556 -247
  73. data/ext/carray_cast_func.rb +34 -21
  74. data/ext/carray_conversion.c +197 -73
  75. data/ext/carray_copy.c +84 -38
  76. data/ext/carray_core.c +591 -119
  77. data/ext/carray_count.c +148 -96
  78. data/ext/carray_element.c +18 -27
  79. data/ext/carray_factorize.c +604 -291
  80. data/ext/carray_generate.c +76 -40
  81. data/ext/carray_histogram.c +152 -94
  82. data/ext/carray_hold.c +2 -1
  83. data/ext/carray_index_classifier.c +63 -96
  84. data/ext/carray_internal.h +400 -1
  85. data/ext/carray_lazy.c +834 -79
  86. data/ext/carray_loop.c +88 -66
  87. data/ext/carray_mask.c +705 -324
  88. data/ext/carray_math_kernel.h +1 -0
  89. data/ext/carray_median_percentile.c +263 -63
  90. data/ext/carray_memory_view.c +254 -248
  91. data/ext/carray_operator.c +1093 -363
  92. data/ext/carray_order.c +133 -69
  93. data/ext/carray_partition.c +102 -56
  94. data/ext/carray_random.c +582 -204
  95. data/ext/carray_scatter.c +308 -65
  96. data/ext/carray_segment.c +257 -0
  97. data/ext/carray_slab.c +183 -70
  98. data/ext/carray_sort.c +235 -240
  99. data/ext/carray_sort_kernel.c +8 -1
  100. data/ext/carray_struct.c +99 -21
  101. data/ext/carray_test.c +167 -64
  102. data/ext/carray_time_text.c +1182 -0
  103. data/ext/carray_utils.c +363 -98
  104. data/ext/extconf.rb +27 -2
  105. data/ext/mk_call_cfunc.rb +103 -118
  106. data/ext/mk_pow5_table.rb +35 -0
  107. data/ext/mkkernel.rb +1349 -851
  108. data/ext/ruby_carray.c +17 -2
  109. data/ext/version.h +4 -4
  110. data/ext/version.rb +4 -2
  111. data/lib/carray/attribute.rb +39 -0
  112. data/lib/carray/autoload_carray.rb +24 -3
  113. data/lib/carray/autoload_method_extension.rb +18 -0
  114. data/lib/carray/axis_group.rb +245 -20
  115. data/lib/carray/basics.rb +95 -25
  116. data/lib/carray/bincount_nd.rb +6 -0
  117. data/lib/carray/block_iterator.rb +200 -54
  118. data/lib/carray/boolean_reduce.rb +21 -29
  119. data/lib/carray/categorical.rb +244 -59
  120. data/lib/carray/categorical_iterator.rb +391 -757
  121. data/lib/carray/complex.rb +36 -29
  122. data/lib/carray/conditional.rb +94 -7
  123. data/lib/carray/const_string.rb +177 -27
  124. data/lib/carray/construct.rb +40 -0
  125. data/lib/carray/data_type_extension.rb +3 -0
  126. data/lib/carray/data_type_limits.rb +91 -0
  127. data/lib/carray/fixlen_string.rb +1 -1
  128. data/lib/carray/frame/concat.rb +157 -6
  129. data/lib/carray/frame/convert.rb +13 -7
  130. data/lib/carray/frame/csv_parser.rb +590 -76
  131. data/lib/carray/frame/describe.rb +79 -0
  132. data/lib/carray/frame/frame.rb +205 -35
  133. data/lib/carray/frame/group.rb +199 -40
  134. data/lib/carray/frame/io.rb +452 -60
  135. data/lib/carray/frame/join.rb +134 -46
  136. data/lib/carray/frame/records.rb +54 -19
  137. data/lib/carray/frame/reshape.rb +448 -0
  138. data/lib/carray/frame/sort.rb +26 -2
  139. data/lib/carray/frame/verbs.rb +726 -56
  140. data/lib/carray/frame.rb +2 -0
  141. data/lib/carray/fuse_source.rb +111 -2
  142. data/lib/carray/fusion.rb +229 -38
  143. data/lib/carray/inspect.rb +71 -20
  144. data/lib/carray/iterator.rb +157 -2
  145. data/lib/carray/lazy.rb +54 -67
  146. data/lib/carray/mask_gap_fill.rb +66 -50
  147. data/lib/carray/math.rb +32 -19
  148. data/lib/carray/meld_reduce.rb +40 -7
  149. data/lib/carray/methods/align_addr.rb +8 -4
  150. data/lib/carray/methods/bincount.rb +2 -2
  151. data/lib/carray/methods/bit_string.rb +14 -3
  152. data/lib/carray/methods/composition.rb +16 -10
  153. data/lib/carray/methods/discovery_along.rb +74 -0
  154. data/lib/carray/methods/factorize.rb +50 -0
  155. data/lib/carray/methods/gather_nd.rb +13 -2
  156. data/lib/carray/methods/insert_block.rb +9 -14
  157. data/lib/carray/methods/is_in.rb +130 -6
  158. data/lib/carray/methods/join.rb +1 -1
  159. data/lib/carray/methods/locate_addr.rb +235 -21
  160. data/lib/carray/methods/mask_duplicates.rb +35 -1
  161. data/lib/carray/methods/mode.rb +18 -1
  162. data/lib/carray/methods/nunique.rb +22 -1
  163. data/lib/carray/methods/repeat.rb +91 -0
  164. data/lib/carray/methods/resize.rb +21 -23
  165. data/lib/carray/methods/snap.rb +34 -12
  166. data/lib/carray/methods/unique.rb +52 -3
  167. data/lib/carray/record_order.rb +208 -0
  168. data/lib/carray/rng.rb +86 -0
  169. data/lib/carray/runtime.rb +14 -11
  170. data/lib/carray/segment_iterator.rb +990 -0
  171. data/lib/carray/serialize.rb +197 -27
  172. data/lib/carray/slab_iterator.rb +109 -40
  173. data/lib/carray/stack.rb +10 -4
  174. data/lib/carray/string_operation_extension.rb +9 -4
  175. data/lib/carray/struct.rb +142 -51
  176. data/lib/carray/struct_builder.rb +110 -14
  177. data/lib/carray/table.rb +2 -2
  178. data/lib/carray/time.rb +535 -105
  179. data/lib/carray/window_iterator.rb +259 -82
  180. data/lib/carray.rb +4 -2
  181. data/yard-stubs/ca_obj_array.rb +4 -4
  182. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  183. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  184. data/yard-stubs/ca_obj_block.rb +2 -7
  185. data/yard-stubs/ca_obj_fake.rb +1 -1
  186. data/yard-stubs/ca_obj_field.rb +1 -1
  187. data/yard-stubs/ca_obj_refer.rb +3 -3
  188. data/yard-stubs/ca_obj_roll.rb +0 -1
  189. data/yard-stubs/ca_obj_stride.rb +10 -7
  190. data/yard-stubs/ca_obj_transpose.rb +1 -1
  191. data/yard-stubs/ca_obj_window.rb +11 -4
  192. data/yard-stubs/carray_access.rb +6 -5
  193. data/yard-stubs/carray_broadcast.rb +2 -2
  194. data/yard-stubs/carray_cast.rb +78 -43
  195. data/yard-stubs/carray_conversion.rb +13 -1
  196. data/yard-stubs/carray_copy.rb +8 -3
  197. data/yard-stubs/carray_core.rb +3 -83
  198. data/yard-stubs/carray_count.rb +16 -2
  199. data/yard-stubs/carray_lazy.rb +210 -1
  200. data/yard-stubs/carray_loop.rb +4 -1
  201. data/yard-stubs/carray_mask.rb +34 -10
  202. data/yard-stubs/carray_math.rb +1499 -4
  203. data/yard-stubs/carray_median_percentile.rb +26 -8
  204. data/yard-stubs/carray_order.rb +43 -82
  205. data/yard-stubs/carray_scatter.rb +10 -3
  206. data/yard-stubs/carray_segment.rb +72 -0
  207. data/yard-stubs/carray_slab.rb +11 -8
  208. data/yard-stubs/carray_sort.rb +50 -8
  209. data/yard-stubs/carray_test.rb +10 -0
  210. data/yard-stubs/carray_utils.rb +0 -26
  211. metadata +19 -19
  212. data/ext/ca_for_each_element.h +0 -239
  213. data/ext/carray_build_flags.h +0 -3
  214. data/ext/carray_config.h +0 -28
  215. data/ext/carray_kernels_bincmp.c +0 -4446
  216. data/ext/carray_kernels_binop.c +0 -11001
  217. data/ext/carray_kernels_init.c +0 -1131
  218. data/ext/carray_kernels_map.c +0 -3467
  219. data/ext/carray_kernels_moncmp.c +0 -2097
  220. data/ext/carray_kernels_monop.c +0 -18313
  221. data/ext/carray_kernels_reduce_aggregate.c +0 -25837
  222. data/ext/carray_kernels_reduce_boolean.c +0 -330
  223. data/ext/carray_kernels_reduce_cumulative.c +0 -14593
  224. data/ext/carray_kernels_reduce_extreme.c +0 -16948
  225. data/ext/carray_kernels_reduce_variance.c +0 -3910
  226. data/ext/carray_kernels_scan.c +0 -3693
  227. data/ext/carray_kernels_search.c +0 -32138
  228. data/ext/carray_kernels_sort.c +0 -10626
  229. data/ext/carray_kernels_triop.c +0 -1392
@@ -102,30 +102,137 @@
102
102
  discovery surface rather than this gate (see
103
103
  devel/PROPOSAL_DISCOVERY_FAMILY_FACE_GATE.md §4). */
104
104
 
105
+ /* The value-hash family decides whether two cells hold the same value: every
106
+ NaN is one value, and -0.0 is the same value as +0.0. A float cell's key
107
+ is the bytes of its canonical form (one NaN, +0.0 for a zero), so equal
108
+ values have equal keys; fz_canonical_* writes that form back as a value. */
109
+ #define FZ_NAN_KEY_DOUBLE 0x7ff8000000000000ULL
110
+ #define FZ_NAN_KEY_FLOAT 0x7fc00000UL
111
+
112
+ static inline uint64_t
113
+ fz_key_double (double v)
114
+ {
115
+ uint64_t b;
116
+ if ( v != v ) return FZ_NAN_KEY_DOUBLE;
117
+ if ( v == 0.0 ) v = 0.0;
118
+ memcpy(&b, &v, sizeof(b));
119
+ return b;
120
+ }
121
+
122
+ static inline uint64_t
123
+ fz_key_float (float v)
124
+ {
125
+ uint32_t b;
126
+ if ( v != v ) return FZ_NAN_KEY_FLOAT;
127
+ if ( v == 0.0f ) v = 0.0f;
128
+ memcpy(&b, &v, sizeof(b));
129
+ return (uint64_t) b;
130
+ }
131
+
132
+ static inline double
133
+ fz_canonical_double (double v)
134
+ {
135
+ uint64_t b = fz_key_double(v);
136
+ memcpy(&v, &b, sizeof(v));
137
+ return v;
138
+ }
139
+
140
+ static inline float
141
+ fz_canonical_float (float v)
142
+ {
143
+ uint32_t b = (uint32_t) fz_key_float(v);
144
+ memcpy(&v, &b, sizeof(v));
145
+ return v;
146
+ }
147
+
148
+ /* For the kernel macros, which take the cell type T. */
149
+ #define FZ_FLOAT_KEY(T, v) \
150
+ _Generic((T) 0, float: fz_key_float, double: fz_key_double)(v)
151
+
152
+ /* A complex array as hash keys: its cells as fixlen blobs of the same
153
+ width, each component written so that equal values have equal bytes
154
+ (-0.0 as 0.0, every NaN as one NaN), with the mask kept. The fixlen lane
155
+ then answers equality for complex values. */
156
+ static VALUE
157
+ fz_complex_key (VALUE self)
158
+ {
159
+ volatile VALUE out;
160
+ CArray *ca, *co;
161
+ ca_size_t i, n;
162
+ GetCArray(self, ca);
163
+ out = rb_carray_new(CA_FIXLEN, ca->ndim, ca->dim, ca->bytes, NULL);
164
+ GetCArray(out, co);
165
+ ca_copy_data(ca, co->ptr);
166
+ n = 2 * ca->elements;
167
+ if ( ca->data_type == CA_CMPLX128 ) {
168
+ double *p = (double *) co->ptr;
169
+ for ( i = 0; i < n; i++ ) p[i] = fz_canonical_double(p[i]);
170
+ }
171
+ else {
172
+ float *p = (float *) co->ptr;
173
+ for ( i = 0; i < n; i++ ) p[i] = fz_canonical_float(p[i]);
174
+ }
175
+ if ( ca_has_mask(ca) ) {
176
+ ca_copy_mask(co, ca);
177
+ }
178
+ return out;
179
+ }
180
+
181
+ /* The members whose output carries no values (a mask, a count) read a
182
+ complex array through its keys and need nothing back. */
183
+ static VALUE
184
+ fz_complex_as_key (VALUE self)
185
+ {
186
+ CArray *ca;
187
+ GetCArray(self, ca);
188
+ if ( ! ca_is_face(ca) && ca_is_complex_type(ca) ) {
189
+ return fz_complex_key(self);
190
+ }
191
+ return self;
192
+ }
193
+
105
194
  /* 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). */
195
+ for the re-lift (Qnil when there is nothing to put back). A complex array
196
+ is turned into hash keys (fz_complex_key) and *pface keeps it, so the
197
+ re-lift reads the values back as complex. */
107
198
  static VALUE
108
- fz_face_descend (VALUE self, volatile VALUE *pface)
199
+ fz_face_descend (VALUE self, volatile VALUE *pface, const char *name)
109
200
  {
110
201
  CArray *ca;
111
202
  GetCArray(self, ca);
112
- if ( ca_is_face(ca) && ca_test_flag(ca, CA_FLAG_FACE_ORDERABLE_STORAGE) ) {
203
+ if ( ! ca_is_face(ca) && ca_is_complex_type(ca) ) {
113
204
  *pface = self;
114
- return rb_ca_strip_face_value(self);
205
+ return fz_complex_key(self);
206
+ }
207
+ if ( ca_is_face(ca) ) {
208
+ /* A record is its bytes: two records are the same value exactly when
209
+ their bytes are, so the fixlen lane answers for it. */
210
+ if ( ca_test_flag(ca, CA_FLAG_FACE_ORDERABLE_STORAGE)
211
+ || ca->obj_type == CA_OBJ_RECORD ) {
212
+ *pface = self;
213
+ return rb_ca_strip_face_value(self);
214
+ }
215
+ /* A Face that cannot be descended must not flow on undescended: whatever
216
+ reads it next reads its storage anyway, which is the encoding rather
217
+ than the values. A Face in this position normally never arrives --
218
+ CAConstString and CACategorical define their own members in Ruby -- but
219
+ the overrides only fire when the Face is the receiver, and this same
220
+ helper descends the *reference* for locate_addr, where it is the
221
+ argument. */
222
+ rb_raise(rb_eArgError,
223
+ "%s: %s is not orderable by storage (a cell encodes the value "
224
+ "rather than being it); convert it first -- %s -- or pass "
225
+ ".parent to work in storage space",
226
+ name, rb_obj_classname(self), ca_face_conversion_hint(self));
115
227
  }
116
228
  *pface = Qnil;
117
229
  return self;
118
230
  }
119
231
 
120
232
  /* Bring `operand` into the space of `reference` (the *pre-strip* Face) and
121
- descend it, so both sides hash in one storage space.
122
-
123
- `to_comparable` exists because a Face can carry a *unit* -- an alternative
124
- space for the same value -- so a reference that defines it reconciles any
125
- operand type through it (and refuses what it cannot convert, which is how a
126
- bare storage number stays out). A reference with one space only (COMPARABLE,
127
- or a Face with no unit algebra at all, e.g. CAString) has nothing to
128
- reconcile between: descending both sides is the whole job. */
233
+ descend it, so both sides hash in one storage space
234
+ (ca_face_reconcile_operand). A complex reference instead turns the
235
+ operand into keys of its own complex type. */
129
236
  static VALUE
130
237
  fz_face_reconcile (VALUE reference, VALUE operand, const char *name)
131
238
  {
@@ -134,30 +241,37 @@ fz_face_reconcile (VALUE reference, VALUE operand, const char *name)
134
241
  return operand;
135
242
  }
136
243
  GetCArray(reference, ca);
137
- if ( ! ca_test_flag(ca, CA_FLAG_FACE_COMPARABLE_STORAGE)
138
- && rb_respond_to(reference, rb_intern("to_comparable")) ) {
139
- return rb_ca_strip_face_value(rb_funcall(reference,
140
- rb_intern("to_comparable"),
141
- 1, operand));
142
- }
143
- if ( rb_obj_is_carray(operand) ) {
244
+ if ( ! ca_is_face(ca) ) {
245
+ /* A complex reference: the operand becomes keys of the same kind, in the
246
+ reference's complex type. */
144
247
  CArray *op;
248
+ if ( ! rb_obj_is_carray(operand) ) {
249
+ return operand;
250
+ }
145
251
  GetCArray(operand, op);
146
- if ( ca_is_face(op) ) {
147
- return rb_ca_strip_face_value(operand);
252
+ if ( op->data_type != ca->data_type ) {
253
+ operand = rb_ca_to_type(operand, INT2NUM(ca->data_type), Qnil);
148
254
  }
255
+ return fz_complex_key(operand);
149
256
  }
150
- (void) name;
151
- return operand;
257
+ return ca_face_reconcile_operand(reference, operand, name);
152
258
  }
153
259
 
154
- /* Put the Face back on a value-carrying output. */
260
+ /* Put the Face back on a value-carrying output, or read complex keys back
261
+ as complex values. */
155
262
  static VALUE
156
263
  fz_face_relift (VALUE out, VALUE face)
157
264
  {
265
+ CArray *cf, *co;
158
266
  if ( NIL_P(face) ) {
159
267
  return out;
160
268
  }
269
+ GetCArray(face, cf);
270
+ if ( ! ca_is_face(cf) ) {
271
+ GetCArray(out, co);
272
+ return rb_ca_copy(rb_ca_refer_new(out, cf->data_type, co->ndim, co->dim,
273
+ cf->bytes, 0));
274
+ }
161
275
  return ca_face_lift(out, face);
162
276
  }
163
277
 
@@ -170,16 +284,95 @@ typedef struct {
170
284
  VALUE *val; /* object lane only: the interned VALUE, kept for an eql? re-check
171
285
  on a hash collision (NULL in the numeric lane, where the widened
172
286
  key is lossless so a key match already proves value equality).
173
- Its VALUEs are always elements of the live receiver, so the GC
174
- reaches them through `self`; this array is not separately marked. */
287
+ A receiver that computes its cells (a CAObject, a lazy
288
+ expression) hands each fiber over in a scratch that is refilled,
289
+ so the owner marks this array (fz_tmp_values). */
175
290
  char *raw; /* fixlen lane only: cap*esz bytes, the interned element bytes kept
176
291
  for a memcmp re-check on a hash collision (NULL otherwise). */
177
292
  int esz; /* fixlen lane: element width in bytes (0 otherwise) */
293
+ int has_val; /* object lane: val is kept (0 otherwise) */
178
294
  ca_size_t cap; /* power of two */
179
295
  ca_size_t n; /* distinct keys interned so far */
180
296
  int shift; /* 64 - log2(cap); the home slot is the top log2(cap) bits */
297
+ VALUE hbuf; /* owner of the tables, one block (fz_tmp_alloc) */
181
298
  } fz_hash;
182
299
 
300
+ /* The tables live in buffers owned by hidden Ruby objects rather than bare
301
+ xmalloc. The object lane calls #hash and #eql? for every cell, and a raise
302
+ there leaves the tables with nothing to free them; the owner is reclaimed
303
+ by the GC once the frame holding it is gone, and frees its buffer then.
304
+ An owner whose buffer keeps VALUEs (fz_tmp_values) marks them: the object
305
+ lane's cells come from a scratch the walk refills, not only from the
306
+ receiver, so nothing else holds the ones already interned. */
307
+ typedef struct {
308
+ char *buf;
309
+ ca_size_t voff; /* byte offset of the VALUE run in buf */
310
+ ca_size_t nvals; /* VALUEs in the run (0: nothing to mark) */
311
+ } fz_buffer;
312
+
313
+ static void
314
+ fz_buffer_mark (void *ptr)
315
+ {
316
+ fz_buffer *b = (fz_buffer *) ptr;
317
+ if ( b->buf && b->nvals > 0 ) {
318
+ VALUE *v = (VALUE *) (b->buf + b->voff);
319
+ ca_size_t i;
320
+ for ( i = 0; i < b->nvals; i++ ) rb_gc_mark(v[i]);
321
+ }
322
+ }
323
+
324
+ static void
325
+ fz_buffer_free (void *ptr)
326
+ {
327
+ fz_buffer *b = (fz_buffer *) ptr;
328
+ xfree(b->buf);
329
+ xfree(b);
330
+ }
331
+
332
+ static const rb_data_type_t fz_buffer_type = {
333
+ "carray_factorize_buffer",
334
+ { fz_buffer_mark, fz_buffer_free, NULL, },
335
+ NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY,
336
+ };
337
+
338
+ static void *
339
+ fz_tmp_alloc (VALUE *store, size_t bytes)
340
+ {
341
+ fz_buffer *b;
342
+ *store = TypedData_Make_Struct(0, fz_buffer, &fz_buffer_type, b);
343
+ b->buf = (char *) xmalloc(bytes);
344
+ return b->buf;
345
+ }
346
+
347
+ static void *
348
+ fz_tmp_realloc (VALUE store, size_t bytes)
349
+ {
350
+ fz_buffer *b = (fz_buffer *) DATA_PTR(store);
351
+ b->buf = (char *) xrealloc(b->buf, bytes);
352
+ return b->buf;
353
+ }
354
+
355
+ /* Mark the n VALUEs at vals (inside store's buffer); they must be valid. */
356
+ static void
357
+ fz_tmp_values (VALUE store, void *vals, ca_size_t n)
358
+ {
359
+ fz_buffer *b = (fz_buffer *) DATA_PTR(store);
360
+ b->voff = (ca_size_t) ((char *) vals - b->buf);
361
+ b->nvals = n;
362
+ }
363
+
364
+ static void
365
+ fz_tmp_free (VALUE *store)
366
+ {
367
+ if ( *store ) {
368
+ fz_buffer *b = (fz_buffer *) DATA_PTR(*store);
369
+ b->nvals = 0;
370
+ xfree(b->buf);
371
+ b->buf = NULL;
372
+ *store = 0;
373
+ }
374
+ }
375
+
183
376
  /* Multiplicative hashing (Knuth, TAOCP vol. 3 sec. 6.4): multiply by
184
377
  2^64 / golden-ratio and take the TOP log2(cap) bits of the product -- the
185
378
  high bits are where a multiply mixes best (the low bits carry little
@@ -189,19 +382,49 @@ typedef struct {
189
382
  #define FZ_GOLDEN 0x9E3779B97F4A7C15ULL
190
383
  #define FZ_HOME(h, key) ((ca_size_t) (((uint64_t)(key) * FZ_GOLDEN) >> (h)->shift))
191
384
 
385
+ /* Points the tables of h at one new block for cap slots: key, then the
386
+ object lane's val, code, used, and the fixlen lane's raw bytes. */
192
387
  static void
193
- fz_hash_init (fz_hash *h)
388
+ fz_hash_alloc (fz_hash *h, ca_size_t cap)
389
+ {
390
+ int has_val = h->has_val;
391
+ size_t nkey = sizeof(uint64_t) * (size_t) cap;
392
+ size_t nval = has_val ? sizeof(VALUE) * (size_t) cap : 0;
393
+ size_t ncode = sizeof(int32_t) * (size_t) cap;
394
+ size_t nused = sizeof(uint8_t) * (size_t) cap;
395
+ size_t nraw = (size_t) cap * (size_t) h->esz;
396
+ char *b = fz_tmp_alloc(&h->hbuf, nkey + nval + ncode + nused + nraw);
397
+ h->key = (uint64_t *) b; b += nkey;
398
+ h->val = has_val ? (VALUE *) b : NULL; b += nval;
399
+ h->code = (int32_t *) b; b += ncode;
400
+ h->used = (uint8_t *) b; b += nused;
401
+ h->raw = h->esz ? b : NULL;
402
+ h->cap = cap;
403
+ MEMZERO(h->used, uint8_t, cap);
404
+ if ( has_val ) {
405
+ ca_size_t i;
406
+ for ( i = 0; i < cap; i++ ) h->val[i] = Qnil;
407
+ fz_tmp_values(h->hbuf, h->val, cap);
408
+ }
409
+ }
410
+
411
+ /* has_val: the object lane (a slot keeps its VALUE); esz > 0: the fixlen
412
+ lane (a slot keeps esz bytes). */
413
+ static void
414
+ fz_hash_setup (fz_hash *h, int has_val, int esz)
194
415
  {
195
- h->cap = 1024;
196
416
  h->shift = 64 - 10; /* log2(1024) = 10 */
197
417
  h->n = 0;
198
- h->key = ALLOC_N(uint64_t, h->cap);
199
- h->code = ALLOC_N(int32_t, h->cap);
200
- h->used = ALLOC_N(uint8_t, h->cap);
201
- h->val = NULL; /* numeric lane: no VALUE re-check needed */
202
- h->raw = NULL;
203
- h->esz = 0;
204
- MEMZERO(h->used, uint8_t, h->cap);
418
+ h->hbuf = 0;
419
+ h->has_val = has_val;
420
+ h->esz = esz;
421
+ fz_hash_alloc(h, 1024);
422
+ }
423
+
424
+ static void
425
+ fz_hash_init (fz_hash *h)
426
+ {
427
+ fz_hash_setup(h, 0, 0); /* numeric lane: no re-check needed */
205
428
  }
206
429
 
207
430
  /* Object lane: the widened key is `rb_hash` (lossy), so a slot also stores the
@@ -210,8 +433,7 @@ fz_hash_init (fz_hash *h)
210
433
  static void
211
434
  fz_hash_init_obj (fz_hash *h)
212
435
  {
213
- fz_hash_init(h);
214
- h->val = ALLOC_N(VALUE, h->cap);
436
+ fz_hash_setup(h, 1, 0);
215
437
  }
216
438
 
217
439
  /* Fixlen lane: the key is a byte-hash of the esz-wide element (lossy), so a slot
@@ -221,19 +443,18 @@ fz_hash_init_obj (fz_hash *h)
221
443
  static void
222
444
  fz_hash_init_mem (fz_hash *h, int esz)
223
445
  {
224
- fz_hash_init(h);
225
- h->esz = esz;
226
- h->raw = ALLOC_N(char, h->cap * esz);
446
+ fz_hash_setup(h, 0, esz);
227
447
  }
228
448
 
229
449
  static void
230
450
  fz_hash_free (fz_hash *h)
231
451
  {
232
- if ( h->key ) { xfree(h->key); h->key = NULL; }
233
- if ( h->code ) { xfree(h->code); h->code = NULL; }
234
- if ( h->used ) { xfree(h->used); h->used = NULL; }
235
- if ( h->val ) { xfree(h->val); h->val = NULL; }
236
- if ( h->raw ) { xfree(h->raw); h->raw = NULL; }
452
+ fz_tmp_free(&h->hbuf);
453
+ h->key = NULL;
454
+ h->code = NULL;
455
+ h->used = NULL;
456
+ h->val = NULL;
457
+ h->raw = NULL;
237
458
  }
238
459
 
239
460
  /* FNV-1a over esz bytes: a lossy 64-bit key for the fixlen lane. */
@@ -266,14 +487,10 @@ fz_hash_grow (fz_hash *h)
266
487
  VALUE *ov = h->val;
267
488
  char *orw = h->raw;
268
489
  int esz = h->esz;
269
- h->cap = newcap;
270
- h->shift -= 1; /* log2(cap) grew by one */
271
- h->key = ALLOC_N(uint64_t, newcap);
272
- h->code = ALLOC_N(int32_t, newcap);
273
- h->used = ALLOC_N(uint8_t, newcap);
274
- if ( ov ) { h->val = ALLOC_N(VALUE, newcap); }
275
- if ( orw ) { h->raw = ALLOC_N(char, newcap * esz); }
276
- MEMZERO(h->used, uint8_t, newcap);
490
+ VALUE hold = h->hbuf; /* keeps the old block until it is freed below */
491
+ h->hbuf = 0;
492
+ h->shift -= 1; /* log2(cap) grew by one */
493
+ fz_hash_alloc(h, newcap);
277
494
  ca_size_t mask = newcap - 1;
278
495
  for ( ca_size_t s = 0; s < oldcap; s++ ) {
279
496
  if ( ! ou[s] ) { continue; }
@@ -285,9 +502,7 @@ fz_hash_grow (fz_hash *h)
285
502
  if ( ov ) { h->val[slot] = ov[s]; }
286
503
  if ( orw ) { memcpy(h->raw + slot * esz, orw + s * esz, esz); }
287
504
  }
288
- xfree(ok); xfree(oc); xfree(ou);
289
- if ( ov ) { xfree(ov); }
290
- if ( orw ) { xfree(orw); }
505
+ fz_tmp_free(&hold);
291
506
  }
292
507
 
293
508
  /* True when v is a Float holding NaN (any bit pattern). */
@@ -309,7 +524,7 @@ fz_hash_intern_obj (fz_hash *h, VALUE v, int *is_new)
309
524
  {
310
525
  if ( (h->n + 1) * 10 >= h->cap * 7 ) { fz_hash_grow(h); } /* load factor 0.7 */
311
526
  int v_nan = fz_is_float_nan(v);
312
- uint64_t key = v_nan ? 0x7ff8000000000000ULL
527
+ uint64_t key = v_nan ? FZ_NAN_KEY_DOUBLE
313
528
  : (uint64_t) NUM2LL(rb_hash(v));
314
529
  ca_size_t mask = h->cap - 1;
315
530
  ca_size_t slot = FZ_HOME(h, key);
@@ -407,7 +622,7 @@ static int
407
622
  fz_hash_lookup_obj (fz_hash *h, VALUE v, int32_t *code_out)
408
623
  {
409
624
  int v_nan = fz_is_float_nan(v);
410
- uint64_t key = v_nan ? 0x7ff8000000000000ULL
625
+ uint64_t key = v_nan ? FZ_NAN_KEY_DOUBLE
411
626
  : (uint64_t) NUM2LL(rb_hash(v));
412
627
  ca_size_t mask = h->cap - 1;
413
628
  ca_size_t slot = FZ_HOME(h, key);
@@ -446,23 +661,44 @@ typedef struct {
446
661
  ca_size_t cap; /* capacity in elements */
447
662
  ca_size_t n; /* elements stored */
448
663
  int esz; /* element bytes */
664
+ VALUE hp; /* owner of p (see fz_tmp_alloc) */
665
+ int obj; /* p holds VALUEs, marked by hp */
449
666
  } fz_levels;
450
667
 
668
+ /* Fill the VALUE slots from..cap with Qnil and mark all cap of them. */
451
669
  static void
452
- fz_levels_init (fz_levels *l, int esz)
670
+ fz_levels_values (fz_levels *l, ca_size_t from)
671
+ {
672
+ VALUE *v = (VALUE *) l->p;
673
+ ca_size_t i;
674
+ for ( i = from; i < l->cap; i++ ) v[i] = Qnil;
675
+ fz_tmp_values(l->hp, l->p, l->cap);
676
+ }
677
+
678
+ static void
679
+ fz_levels_init (fz_levels *l, int esz, int obj)
453
680
  {
454
681
  l->esz = esz;
455
682
  l->cap = 16;
456
683
  l->n = 0;
457
- l->p = ALLOC_N(char, l->cap * esz);
684
+ l->hp = 0;
685
+ l->obj = obj;
686
+ l->p = fz_tmp_alloc(&l->hp, (size_t) l->cap * esz);
687
+ if ( obj ) {
688
+ fz_levels_values(l, 0);
689
+ }
458
690
  }
459
691
 
460
692
  static void
461
693
  fz_levels_push (fz_levels *l, const void *e)
462
694
  {
463
695
  if ( l->n == l->cap ) {
696
+ ca_size_t old = l->cap;
464
697
  l->cap <<= 1;
465
- REALLOC_N(l->p, char, l->cap * l->esz);
698
+ l->p = fz_tmp_realloc(l->hp, (size_t) l->cap * l->esz);
699
+ if ( l->obj ) {
700
+ fz_levels_values(l, old);
701
+ }
466
702
  }
467
703
  memcpy(l->p + l->n * l->esz, e, l->esz);
468
704
  l->n++;
@@ -471,7 +707,35 @@ fz_levels_push (fz_levels *l, const void *e)
471
707
  static void
472
708
  fz_levels_free (fz_levels *l)
473
709
  {
474
- if ( l->p ) { xfree(l->p); l->p = NULL; }
710
+ fz_tmp_free(&l->hp);
711
+ l->p = NULL;
712
+ }
713
+
714
+ /* ---- the frame a discovery method runs in ---------------------------------
715
+
716
+ The object lane calls #hash and #eql? for every cell, and either can raise
717
+ in the middle of a walk. The walks' states live in this frame, outside the
718
+ method's body, and ca_iter_ensure finishes them however the body leaves;
719
+ the tables have owners the unwind collects (fz_tmp_alloc). Slots 0
720
+ and 1 hold a method's own walks, slot 2 the walk of fz_intern_all. */
721
+
722
+ typedef struct {
723
+ ca_iter_state st[3];
724
+ VALUE self;
725
+ VALUE a;
726
+ VALUE b;
727
+ int i;
728
+ } fz_frame;
729
+
730
+ static VALUE
731
+ fz_run (VALUE (*body)(VALUE), VALUE self, VALUE a, VALUE b, int i)
732
+ {
733
+ fz_frame fr;
734
+ fr.self = self;
735
+ fr.a = a;
736
+ fr.b = b;
737
+ fr.i = i;
738
+ return ca_iter_ensure(3, fr.st, body, (VALUE) &fr);
475
739
  }
476
740
 
477
741
  /* @overload __factorize_appearance__
@@ -491,11 +755,13 @@ fz_levels_free (fz_levels *l)
491
755
  (row-major flatten) order.
492
756
  */
493
757
  static VALUE
494
- rb_ca_factorize_appearance (VALUE self)
758
+ fz_factorize_appearance_body (VALUE arg)
495
759
  {
760
+ fz_frame *fr = (fz_frame *) arg;
761
+ VALUE self = fr->self;
496
762
  CArray *ca;
497
763
  volatile VALUE face;
498
- self = fz_face_descend(self, &face);
764
+ self = fz_face_descend(self, &face, "categorize");
499
765
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
500
766
 
501
767
  int8_t dt = ca->data_type;
@@ -526,11 +792,11 @@ rb_ca_factorize_appearance (VALUE self)
526
792
  if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
527
793
  else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
528
794
  else { fz_hash_init(&h); }
529
- fz_levels_init(&lv, (int) ca->bytes);
795
+ fz_levels_init(&lv, (int) ca->bytes, ca->data_type == CA_OBJECT);
530
796
 
531
797
  int8_t axis = (int8_t) (ca->ndim - 1); /* innermost fiber = row-major flatten */
532
798
 
533
- ca_iter_state st_in, st_out;
799
+ ca_iter_state *st_in = &fr->st[0], *st_out = &fr->st[1];
534
800
  char *p_in, *p_out;
535
801
  boolean8_t *m;
536
802
  ca_size_t n;
@@ -552,25 +818,18 @@ rb_ca_factorize_appearance (VALUE self)
552
818
  } while (0)
553
819
 
554
820
  /* Float lane: reproduce `==` with two value-based exceptions matching the
555
- discovery family -- all NaN collapse to one canonical key (v != v -> NANKEY),
821
+ discovery family -- all NaN collapse to one canonical key (FZ_FLOAT_KEY),
556
822
  and -0.0 == +0.0 (a zero normalizes to +0.0 before the bitwise key). The
557
823
  level pushed is the raw first-seen element (an appearance-first NaN / -0.0 is
558
824
  kept as-is), so masked source cells store the sentinel. */
559
- #define FZ_LOOP_FLOAT(T, UINT, NANKEY) \
825
+ #define FZ_LOOP_FLOAT(T) \
560
826
  do { \
561
827
  const T *ip = (const T *) p_in; \
562
828
  uint32_t *op = (uint32_t *) p_out; \
563
829
  for ( ca_size_t i = 0; i < n; i++ ) { \
564
830
  if ( m && m[i] ) { op[i] = 0xFFFFFFFFu; continue; } \
565
831
  T v = ip[i]; \
566
- uint64_t key; \
567
- if ( v != v ) { key = (NANKEY); } \
568
- else { \
569
- if ( v == (T) 0 ) { v = (T) 0; } \
570
- UINT bits; \
571
- memcpy(&bits, &v, sizeof(bits)); \
572
- key = (uint64_t) bits; \
573
- } \
832
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
574
833
  int is_new; \
575
834
  int32_t code = fz_hash_intern(&h, key, &is_new); \
576
835
  if ( is_new ) { fz_levels_push(&lv, &ip[i]); } \
@@ -609,7 +868,7 @@ rb_ca_factorize_appearance (VALUE self)
609
868
  } \
610
869
  } while (0)
611
870
 
612
- CA_FOR_EACH_FIBER_INOUT_MASKED(st_in, st_out, ca, cu32, axis,
871
+ CA_FOR_EACH_FIBER_INOUT_MASKED(*st_in, *st_out, ca, cu32, axis,
613
872
  CA_KERNEL_READ, p_in, p_out, n, m) {
614
873
  switch ( dt ) {
615
874
  case CA_INT8: FZ_LOOP(int8_t, (int64_t)); break;
@@ -620,8 +879,8 @@ rb_ca_factorize_appearance (VALUE self)
620
879
  case CA_UINT16: FZ_LOOP(uint16_t, (uint64_t)); break;
621
880
  case CA_UINT32: FZ_LOOP(uint32_t, (uint64_t)); break;
622
881
  case CA_UINT64: FZ_LOOP(uint64_t, (uint64_t)); break;
623
- case CA_FLOAT32: FZ_LOOP_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
624
- case CA_FLOAT64: FZ_LOOP_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
882
+ case CA_FLOAT32: FZ_LOOP_FLOAT(float); break;
883
+ case CA_FLOAT64: FZ_LOOP_FLOAT(double); break;
625
884
  case CA_OBJECT: FZ_LOOP_OBJ; break;
626
885
  case CA_FIXLEN: FZ_LOOP_MEM; break;
627
886
  }
@@ -680,6 +939,12 @@ rb_ca_factorize_appearance (VALUE self)
680
939
  return rb_ary_new3(2, vcodes, fz_face_relift(vlev, face));
681
940
  }
682
941
 
942
+ static VALUE
943
+ rb_ca_factorize_appearance (VALUE self)
944
+ {
945
+ return fz_run(fz_factorize_appearance_body, self, Qnil, Qnil, 0);
946
+ }
947
+
683
948
  /* @overload __mask_duplicates__(axis)
684
949
 
685
950
  INTERNAL (mask_duplicates). Mark each cell whose value duplicates an
@@ -700,8 +965,11 @@ rb_ca_factorize_appearance (VALUE self)
700
965
  value (so the second and later NaN are duplicates, as in the numeric lane).
701
966
  */
702
967
  static VALUE
703
- rb_ca_mask_duplicates (VALUE self, VALUE vaxis)
968
+ fz_mask_duplicates_body (VALUE arg)
704
969
  {
970
+ fz_frame *fr = (fz_frame *) arg;
971
+ VALUE self = fr->self;
972
+ VALUE vaxis = fr->a;
705
973
  CArray *ca;
706
974
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
707
975
 
@@ -735,7 +1003,7 @@ rb_ca_mask_duplicates (VALUE self, VALUE vaxis)
735
1003
  else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
736
1004
  else { fz_hash_init(&h); }
737
1005
 
738
- ca_iter_state st_in, st_out;
1006
+ ca_iter_state *st_in = &fr->st[0], *st_out = &fr->st[1];
739
1007
  char *p_in, *p_out;
740
1008
  boolean8_t *m;
741
1009
  ca_size_t n;
@@ -758,24 +1026,17 @@ rb_ca_mask_duplicates (VALUE self, VALUE vaxis)
758
1026
 
759
1027
  /* Float variant: reproduce `==` with two value-based exceptions so the
760
1028
  distinct-value judgement matches uniq / the discovery family. All NaN
761
- collapse to one canonical key (v != v -> NANKEY), so the second and later
1029
+ collapse to one canonical key (FZ_FLOAT_KEY), so the second and later
762
1030
  NaN in a fiber are duplicates; -0.0 == +0.0, so a zero normalizes to +0.0
763
1031
  before the bitwise key. */
764
- #define MD_LOOP_FLOAT(T, UINT, NANKEY) \
1032
+ #define MD_LOOP_FLOAT(T) \
765
1033
  do { \
766
1034
  const T *ip = (const T *) p_in; \
767
1035
  boolean8_t *op = (boolean8_t *) p_out; \
768
1036
  for ( ca_size_t i = 0; i < n; i++ ) { \
769
1037
  if ( m && m[i] ) { op[i] = 0; continue; } \
770
1038
  T v = ip[i]; \
771
- uint64_t key; \
772
- if ( v != v ) { key = (NANKEY); } \
773
- else { \
774
- if ( v == (T) 0 ) { v = (T) 0; } \
775
- UINT bits; \
776
- memcpy(&bits, &v, sizeof(bits)); \
777
- key = (uint64_t) bits; \
778
- } \
1039
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
779
1040
  int is_new; \
780
1041
  fz_hash_intern(&h, key, &is_new); \
781
1042
  op[i] = is_new ? 0 : 1; \
@@ -808,7 +1069,7 @@ rb_ca_mask_duplicates (VALUE self, VALUE vaxis)
808
1069
  } \
809
1070
  } while (0)
810
1071
 
811
- CA_FOR_EACH_FIBER_INOUT_MASKED(st_in, st_out, ca, cout, (int8_t) axis,
1072
+ CA_FOR_EACH_FIBER_INOUT_MASKED(*st_in, *st_out, ca, cout, (int8_t) axis,
812
1073
  CA_KERNEL_READ, p_in, p_out, n, m) {
813
1074
  fz_hash_reset(&h); /* independent seen-set per fiber */
814
1075
  switch ( dt ) {
@@ -820,8 +1081,8 @@ rb_ca_mask_duplicates (VALUE self, VALUE vaxis)
820
1081
  case CA_UINT16: MD_LOOP(uint16_t, (uint64_t)); break;
821
1082
  case CA_UINT32: MD_LOOP(uint32_t, (uint64_t)); break;
822
1083
  case CA_UINT64: MD_LOOP(uint64_t, (uint64_t)); break;
823
- case CA_FLOAT32: MD_LOOP_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
824
- case CA_FLOAT64: MD_LOOP_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1084
+ case CA_FLOAT32: MD_LOOP_FLOAT(float); break;
1085
+ case CA_FLOAT64: MD_LOOP_FLOAT(double); break;
825
1086
  case CA_OBJECT: MD_LOOP_OBJ; break;
826
1087
  case CA_FIXLEN: MD_LOOP_MEM; break;
827
1088
  }
@@ -835,6 +1096,12 @@ rb_ca_mask_duplicates (VALUE self, VALUE vaxis)
835
1096
  return vout;
836
1097
  }
837
1098
 
1099
+ static VALUE
1100
+ rb_ca_mask_duplicates (VALUE self, VALUE vaxis)
1101
+ {
1102
+ return fz_run(fz_mask_duplicates_body, fz_complex_as_key(self), vaxis, Qnil, 0);
1103
+ }
1104
+
838
1105
  /* @overload __unique_flat__
839
1106
 
840
1107
  INTERNAL (CArray#unique). Collect the distinct values of self in
@@ -859,11 +1126,13 @@ rb_ca_mask_duplicates (VALUE self, VALUE vaxis)
859
1126
  uniform-width binary cells the fixlen seen-set used.
860
1127
  */
861
1128
  static VALUE
862
- rb_ca_unique_flat (VALUE self)
1129
+ fz_unique_flat_body (VALUE arg)
863
1130
  {
1131
+ fz_frame *fr = (fz_frame *) arg;
1132
+ VALUE self = fr->self;
864
1133
  CArray *ca;
865
1134
  volatile VALUE face;
866
- self = fz_face_descend(self, &face);
1135
+ self = fz_face_descend(self, &face, "unique");
867
1136
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
868
1137
 
869
1138
  int8_t dt = ca->data_type;
@@ -887,11 +1156,11 @@ rb_ca_unique_flat (VALUE self)
887
1156
  if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
888
1157
  else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
889
1158
  else { fz_hash_init(&h); }
890
- fz_levels_init(&lv, (int) ca->bytes);
1159
+ fz_levels_init(&lv, (int) ca->bytes, ca->data_type == CA_OBJECT);
891
1160
 
892
1161
  int8_t axis = (int8_t) (ca->ndim - 1); /* innermost fiber; hash not reset =
893
1162
  one seen-set over the whole array */
894
- ca_iter_state st_in;
1163
+ ca_iter_state *st_in = &fr->st[0];
895
1164
  char *p_in;
896
1165
  boolean8_t *m;
897
1166
  ca_size_t n;
@@ -910,20 +1179,13 @@ rb_ca_unique_flat (VALUE self)
910
1179
 
911
1180
  /* Float: NaN collapses to one canonical key; -0.0 / +0.0 share a key. The
912
1181
  pushed level is the raw first-seen element. */
913
- #define UQ_LOOP_FLOAT(T, UINT, NANKEY) \
1182
+ #define UQ_LOOP_FLOAT(T) \
914
1183
  do { \
915
1184
  const T *ip = (const T *) p_in; \
916
1185
  for ( ca_size_t i = 0; i < n; i++ ) { \
917
1186
  if ( m && m[i] ) { continue; } \
918
1187
  T v = ip[i]; \
919
- uint64_t key; \
920
- if ( v != v ) { key = (NANKEY); } \
921
- else { \
922
- if ( v == (T) 0 ) { v = (T) 0; } \
923
- UINT bits; \
924
- memcpy(&bits, &v, sizeof(bits)); \
925
- key = (uint64_t) bits; \
926
- } \
1188
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
927
1189
  int is_new; \
928
1190
  fz_hash_intern(&h, key, &is_new); \
929
1191
  if ( is_new ) { fz_levels_push(&lv, &ip[i]); } \
@@ -958,7 +1220,7 @@ rb_ca_unique_flat (VALUE self)
958
1220
  } \
959
1221
  } while (0)
960
1222
 
961
- CA_FOR_EACH_FIBER_MASKED(st_in, ca, axis, CA_KERNEL_READ, p_in, n, m) {
1223
+ CA_FOR_EACH_FIBER_MASKED(*st_in, ca, axis, CA_KERNEL_READ, p_in, n, m) {
962
1224
  switch ( dt ) {
963
1225
  case CA_INT8: UQ_LOOP(int8_t, (int64_t)); break;
964
1226
  case CA_INT16: UQ_LOOP(int16_t, (int64_t)); break;
@@ -968,8 +1230,8 @@ rb_ca_unique_flat (VALUE self)
968
1230
  case CA_UINT16: UQ_LOOP(uint16_t, (uint64_t)); break;
969
1231
  case CA_UINT32: UQ_LOOP(uint32_t, (uint64_t)); break;
970
1232
  case CA_UINT64: UQ_LOOP(uint64_t, (uint64_t)); break;
971
- case CA_FLOAT32: UQ_LOOP_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
972
- case CA_FLOAT64: UQ_LOOP_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1233
+ case CA_FLOAT32: UQ_LOOP_FLOAT(float); break;
1234
+ case CA_FLOAT64: UQ_LOOP_FLOAT(double); break;
973
1235
  case CA_OBJECT: UQ_LOOP_OBJ; break;
974
1236
  case CA_FIXLEN: UQ_LOOP_MEM; break;
975
1237
  }
@@ -996,6 +1258,12 @@ rb_ca_unique_flat (VALUE self)
996
1258
  return fz_face_relift(vlev, face); /* distinct *values*: give the Face back */
997
1259
  }
998
1260
 
1261
+ static VALUE
1262
+ rb_ca_unique_flat (VALUE self)
1263
+ {
1264
+ return fz_run(fz_unique_flat_body, self, Qnil, Qnil, 0);
1265
+ }
1266
+
999
1267
  /* Intern every non-masked cell of cv into h, one seen-set over the whole array
1000
1268
  (the hash is not reset between fibers). When lv is non-NULL, the first-seen
1001
1269
  raw element of each distinct value is pushed to it (appearance order), so the
@@ -1005,11 +1273,10 @@ rb_ca_unique_flat (VALUE self)
1005
1273
  fixlen byte-hash + memcmp. Used to build the probe set (is_in, set relations)
1006
1274
  and to accumulate distinct values (union). */
1007
1275
  static void
1008
- fz_intern_all (fz_hash *h, CArray *cv, fz_levels *lv)
1276
+ fz_intern_all (ca_iter_state *st_v, fz_hash *h, CArray *cv, fz_levels *lv)
1009
1277
  {
1010
1278
  int8_t dt = cv->data_type;
1011
1279
  int8_t vaxis = (int8_t) (cv->ndim - 1);
1012
- ca_iter_state st_v;
1013
1280
  char *p_v;
1014
1281
  boolean8_t *mv;
1015
1282
  ca_size_t nv;
@@ -1026,20 +1293,13 @@ fz_intern_all (fz_hash *h, CArray *cv, fz_levels *lv)
1026
1293
  } \
1027
1294
  } while (0)
1028
1295
 
1029
- #define FZ_IA_FLOAT(T, UINT, NANKEY) \
1296
+ #define FZ_IA_FLOAT(T) \
1030
1297
  do { \
1031
1298
  const T *ip = (const T *) p_v; \
1032
1299
  for ( ca_size_t i = 0; i < nv; i++ ) { \
1033
1300
  if ( mv && mv[i] ) { continue; } \
1034
1301
  T v = ip[i]; \
1035
- uint64_t key; \
1036
- if ( v != v ) { key = (NANKEY); } \
1037
- else { \
1038
- if ( v == (T) 0 ) { v = (T) 0; } \
1039
- UINT bits; \
1040
- memcpy(&bits, &v, sizeof(bits)); \
1041
- key = (uint64_t) bits; \
1042
- } \
1302
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
1043
1303
  int is_new; \
1044
1304
  fz_hash_intern(h, key, &is_new); \
1045
1305
  if ( is_new && lv ) { fz_levels_push(lv, &ip[i]); } \
@@ -1069,7 +1329,7 @@ fz_intern_all (fz_hash *h, CArray *cv, fz_levels *lv)
1069
1329
  } \
1070
1330
  } while (0)
1071
1331
 
1072
- CA_FOR_EACH_FIBER_MASKED(st_v, cv, vaxis, CA_KERNEL_READ, p_v, nv, mv) {
1332
+ CA_FOR_EACH_FIBER_MASKED(*st_v, cv, vaxis, CA_KERNEL_READ, p_v, nv, mv) {
1073
1333
  switch ( dt ) {
1074
1334
  case CA_INT8: FZ_IA(int8_t, (int64_t)); break;
1075
1335
  case CA_INT16: FZ_IA(int16_t, (int64_t)); break;
@@ -1079,8 +1339,8 @@ fz_intern_all (fz_hash *h, CArray *cv, fz_levels *lv)
1079
1339
  case CA_UINT16: FZ_IA(uint16_t, (uint64_t)); break;
1080
1340
  case CA_UINT32: FZ_IA(uint32_t, (uint64_t)); break;
1081
1341
  case CA_UINT64: FZ_IA(uint64_t, (uint64_t)); break;
1082
- case CA_FLOAT32: FZ_IA_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
1083
- case CA_FLOAT64: FZ_IA_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1342
+ case CA_FLOAT32: FZ_IA_FLOAT(float); break;
1343
+ case CA_FLOAT64: FZ_IA_FLOAT(double); break;
1084
1344
  case CA_OBJECT: FZ_IA_OBJ; break;
1085
1345
  case CA_FIXLEN: FZ_IA_MEM; break;
1086
1346
  }
@@ -1109,11 +1369,14 @@ fz_intern_all (fz_hash *h, CArray *cv, fz_levels *lv)
1109
1369
  fixlen byte equality (byte-hash + memcmp).
1110
1370
  */
1111
1371
  static VALUE
1112
- rb_ca_is_in (VALUE self, VALUE rvalues)
1372
+ fz_is_in_body (VALUE arg)
1113
1373
  {
1374
+ fz_frame *fr = (fz_frame *) arg;
1375
+ VALUE self = fr->self;
1376
+ VALUE rvalues = fr->a;
1114
1377
  CArray *ca, *cv;
1115
1378
  volatile VALUE face;
1116
- self = fz_face_descend(self, &face);
1379
+ self = fz_face_descend(self, &face, "is_in");
1117
1380
  rvalues = fz_face_reconcile(face, rvalues, "is_in");
1118
1381
  GetCArray(self, ca);
1119
1382
 
@@ -1137,9 +1400,16 @@ rb_ca_is_in (VALUE self, VALUE rvalues)
1137
1400
  rb_raise(rb_eArgError, "__is_in__: values must be a CArray");
1138
1401
  }
1139
1402
  GetCArray(rvalues, cv);
1140
- if ( cv->data_type != dt || (dt == CA_FIXLEN && cv->bytes != ca->bytes) ) {
1403
+ if ( cv->data_type != dt ) {
1141
1404
  rb_raise(rb_eCADataTypeError,
1142
- "__is_in__: values data type must match self (%d)", dt);
1405
+ "is_in: values data type (%s) must match self (%s)",
1406
+ ca_type_name[cv->data_type], ca_type_name[dt]);
1407
+ }
1408
+ if ( dt == CA_FIXLEN && cv->bytes != ca->bytes ) {
1409
+ rb_raise(rb_eCADataTypeError,
1410
+ "is_in: a fixlen value is a cell-width blob, so the values "
1411
+ "must be %ld bytes wide like self, not %ld",
1412
+ (long) ca->bytes, (long) cv->bytes);
1143
1413
  }
1144
1414
  if ( cv->ndim < 1 ) {
1145
1415
  rb_raise(rb_eRuntimeError, "__is_in__: values need ndim >= 1");
@@ -1151,14 +1421,14 @@ rb_ca_is_in (VALUE self, VALUE rvalues)
1151
1421
  else { fz_hash_init(&h); }
1152
1422
 
1153
1423
  /* ---- Phase 1: build the seen-set from every non-masked cell of `values`. */
1154
- fz_intern_all(&h, cv, NULL);
1424
+ fz_intern_all(&fr->st[2], &h, cv, NULL);
1155
1425
 
1156
1426
  /* ---- Phase 2: probe every cell of self, writing the boolean membership. */
1157
1427
  VALUE vout = rb_carray_new(CA_BOOLEAN, ca->ndim, ca->dim, 0, NULL);
1158
1428
  CArray *cout;
1159
1429
  GetCArray(vout, cout);
1160
1430
  {
1161
- ca_iter_state st_in, st_out;
1431
+ ca_iter_state *st_in = &fr->st[0], *st_out = &fr->st[1];
1162
1432
  char *p_in, *p_out;
1163
1433
  boolean8_t *m;
1164
1434
  ca_size_t n;
@@ -1175,21 +1445,14 @@ rb_ca_is_in (VALUE self, VALUE rvalues)
1175
1445
  } \
1176
1446
  } while (0)
1177
1447
 
1178
- #define IN_PROBE_FLOAT(T, UINT, NANKEY) \
1448
+ #define IN_PROBE_FLOAT(T) \
1179
1449
  do { \
1180
1450
  const T *ip = (const T *) p_in; \
1181
1451
  boolean8_t *op = (boolean8_t *) p_out; \
1182
1452
  for ( ca_size_t i = 0; i < n; i++ ) { \
1183
1453
  if ( m && m[i] ) { op[i] = 0; continue; } \
1184
1454
  T v = ip[i]; \
1185
- uint64_t key; \
1186
- if ( v != v ) { key = (NANKEY); } \
1187
- else { \
1188
- if ( v == (T) 0 ) { v = (T) 0; } \
1189
- UINT bits; \
1190
- memcpy(&bits, &v, sizeof(bits)); \
1191
- key = (uint64_t) bits; \
1192
- } \
1455
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
1193
1456
  op[i] = (boolean8_t) fz_hash_lookup(&h, key, NULL); \
1194
1457
  } \
1195
1458
  } while (0)
@@ -1214,7 +1477,7 @@ rb_ca_is_in (VALUE self, VALUE rvalues)
1214
1477
  } \
1215
1478
  } while (0)
1216
1479
 
1217
- CA_FOR_EACH_FIBER_INOUT_MASKED(st_in, st_out, ca, cout, axis,
1480
+ CA_FOR_EACH_FIBER_INOUT_MASKED(*st_in, *st_out, ca, cout, axis,
1218
1481
  CA_KERNEL_READ, p_in, p_out, n, m) {
1219
1482
  switch ( dt ) {
1220
1483
  case CA_INT8: IN_PROBE(int8_t, (int64_t)); break;
@@ -1225,8 +1488,8 @@ rb_ca_is_in (VALUE self, VALUE rvalues)
1225
1488
  case CA_UINT16: IN_PROBE(uint16_t, (uint64_t)); break;
1226
1489
  case CA_UINT32: IN_PROBE(uint32_t, (uint64_t)); break;
1227
1490
  case CA_UINT64: IN_PROBE(uint64_t, (uint64_t)); break;
1228
- case CA_FLOAT32: IN_PROBE_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
1229
- case CA_FLOAT64: IN_PROBE_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1491
+ case CA_FLOAT32: IN_PROBE_FLOAT(float); break;
1492
+ case CA_FLOAT64: IN_PROBE_FLOAT(double); break;
1230
1493
  case CA_OBJECT: IN_PROBE_OBJ; break;
1231
1494
  case CA_FIXLEN: IN_PROBE_MEM; break;
1232
1495
  }
@@ -1246,6 +1509,12 @@ rb_ca_is_in (VALUE self, VALUE rvalues)
1246
1509
  return vout;
1247
1510
  }
1248
1511
 
1512
+ static VALUE
1513
+ rb_ca_is_in (VALUE self, VALUE rvalues)
1514
+ {
1515
+ return fz_run(fz_is_in_body, self, rvalues, Qnil, 0);
1516
+ }
1517
+
1249
1518
  /* @overload __locate_addr__(ref)
1250
1519
 
1251
1520
  INTERNAL (CArray#locate_addr, exact hash lane). For each cell of self, the
@@ -1263,15 +1532,18 @@ rb_ca_is_in (VALUE self, VALUE rvalues)
1263
1532
  appearance order (matching the discovery family); on a `ref` with duplicate
1264
1533
  values this is the earliest address, which can differ from the bsearch lane. */
1265
1534
  static VALUE
1266
- rb_ca_locate_addr (VALUE self, VALUE rref)
1535
+ fz_locate_addr_body (VALUE arg)
1267
1536
  {
1537
+ fz_frame *fr = (fz_frame *) arg;
1538
+ VALUE self = fr->self;
1539
+ VALUE rref = fr->a;
1268
1540
  CArray *ca, *cr;
1269
1541
  /* Here the *reference* is the argument (self is the query being placed on
1270
1542
  rref), so the gate runs the other way round: rref reconciles self. The
1271
1543
  output is an address, so nothing is lifted back. */
1272
1544
  {
1273
1545
  volatile VALUE ref_face;
1274
- rref = fz_face_descend(rref, &ref_face);
1546
+ rref = fz_face_descend(rref, &ref_face, "locate_addr");
1275
1547
  self = fz_face_reconcile(ref_face, self, "locate_addr");
1276
1548
  }
1277
1549
  GetCArray(self, ca);
@@ -1295,9 +1567,16 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1295
1567
  rb_raise(rb_eArgError, "__locate_addr__: ref must be a CArray");
1296
1568
  }
1297
1569
  GetCArray(rref, cr);
1298
- if ( cr->data_type != dt || (dt == CA_FIXLEN && cr->bytes != ca->bytes) ) {
1570
+ if ( cr->data_type != dt ) {
1299
1571
  rb_raise(rb_eCADataTypeError,
1300
- "__locate_addr__: ref data type must match self (%d)", dt);
1572
+ "locate_addr: ref data type (%s) must match self (%s)",
1573
+ ca_type_name[cr->data_type], ca_type_name[dt]);
1574
+ }
1575
+ if ( dt == CA_FIXLEN && cr->bytes != ca->bytes ) {
1576
+ rb_raise(rb_eCADataTypeError,
1577
+ "locate_addr: a fixlen value is a cell-width blob, so ref "
1578
+ "must be %ld bytes wide like self, not %ld",
1579
+ (long) ca->bytes, (long) cr->bytes);
1301
1580
  }
1302
1581
  if ( cr->ndim < 1 ) {
1303
1582
  rb_raise(rb_eRuntimeError, "__locate_addr__: ref need ndim >= 1");
@@ -1312,11 +1591,11 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1312
1591
  the ref value carrying dense code `code`. Codes are assigned 0,1,2,... in
1313
1592
  insertion order, so pushing on is_new fills addr.p in code order. */
1314
1593
  fz_levels addr;
1315
- fz_levels_init(&addr, (int) sizeof(int64_t));
1594
+ fz_levels_init(&addr, (int) sizeof(int64_t), 0);
1316
1595
 
1317
1596
  /* ---- Phase 1: build value -> first-address map from ref (row-major). ----- */
1318
1597
  {
1319
- ca_iter_state st_r;
1598
+ ca_iter_state *st_r = &fr->st[0];
1320
1599
  char *p_r;
1321
1600
  boolean8_t *mr;
1322
1601
  ca_size_t nr;
@@ -1336,20 +1615,13 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1336
1615
  } \
1337
1616
  } while (0)
1338
1617
 
1339
- #define LOC_BUILD_FLOAT(T, UINT, NANKEY) \
1618
+ #define LOC_BUILD_FLOAT(T) \
1340
1619
  do { \
1341
1620
  const T *ip = (const T *) p_r; \
1342
1621
  for ( ca_size_t i = 0; i < nr; i++ ) { \
1343
1622
  if ( mr && mr[i] ) { continue; } \
1344
1623
  T v = ip[i]; \
1345
- uint64_t key; \
1346
- if ( v != v ) { key = (NANKEY); } \
1347
- else { \
1348
- if ( v == (T) 0 ) { v = (T) 0; } \
1349
- UINT bits; \
1350
- memcpy(&bits, &v, sizeof(bits)); \
1351
- key = (uint64_t) bits; \
1352
- } \
1624
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
1353
1625
  int is_new; \
1354
1626
  fz_hash_intern(&h, key, &is_new); \
1355
1627
  if ( is_new ) { int64_t a = (int64_t) (base + i); \
@@ -1381,7 +1653,7 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1381
1653
  } \
1382
1654
  } while (0)
1383
1655
 
1384
- CA_FOR_EACH_FIBER_MASKED(st_r, cr, raxis, CA_KERNEL_READ, p_r, nr, mr) {
1656
+ CA_FOR_EACH_FIBER_MASKED(*st_r, cr, raxis, CA_KERNEL_READ, p_r, nr, mr) {
1385
1657
  switch ( dt ) {
1386
1658
  case CA_INT8: LOC_BUILD(int8_t, (int64_t)); break;
1387
1659
  case CA_INT16: LOC_BUILD(int16_t, (int64_t)); break;
@@ -1391,8 +1663,8 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1391
1663
  case CA_UINT16: LOC_BUILD(uint16_t, (uint64_t)); break;
1392
1664
  case CA_UINT32: LOC_BUILD(uint32_t, (uint64_t)); break;
1393
1665
  case CA_UINT64: LOC_BUILD(uint64_t, (uint64_t)); break;
1394
- case CA_FLOAT32: LOC_BUILD_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
1395
- case CA_FLOAT64: LOC_BUILD_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1666
+ case CA_FLOAT32: LOC_BUILD_FLOAT(float); break;
1667
+ case CA_FLOAT64: LOC_BUILD_FLOAT(double); break;
1396
1668
  case CA_OBJECT: LOC_BUILD_OBJ; break;
1397
1669
  case CA_FIXLEN: LOC_BUILD_MEM; break;
1398
1670
  }
@@ -1409,10 +1681,11 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1409
1681
  CArray *cout;
1410
1682
  GetCArray(vout, cout);
1411
1683
  int64_t *out = (int64_t *) cout->ptr;
1412
- boolean8_t *um = ALLOC_N(boolean8_t, ca->elements); /* undef flags scratch */
1684
+ volatile VALUE hum = 0;
1685
+ boolean8_t *um = ALLOCV_N(boolean8_t, hum, ca->elements); /* undef flags scratch */
1413
1686
  ca_size_t n_undef = 0;
1414
1687
  {
1415
- ca_iter_state st_in;
1688
+ ca_iter_state *st_in = &fr->st[1];
1416
1689
  char *p_in;
1417
1690
  boolean8_t *m;
1418
1691
  ca_size_t n;
@@ -1433,7 +1706,7 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1433
1706
  } \
1434
1707
  } while (0)
1435
1708
 
1436
- #define LOC_PROBE_FLOAT(T, UINT, NANKEY) \
1709
+ #define LOC_PROBE_FLOAT(T) \
1437
1710
  do { \
1438
1711
  const T *ip = (const T *) p_in; \
1439
1712
  const int64_t *ab = (const int64_t *) addr.p; \
@@ -1441,14 +1714,7 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1441
1714
  ca_size_t o = base + i; \
1442
1715
  if ( m && m[i] ) { out[o] = 0; um[o] = 1; n_undef++; continue; } \
1443
1716
  T v = ip[i]; \
1444
- uint64_t key; \
1445
- if ( v != v ) { key = (NANKEY); } \
1446
- else { \
1447
- if ( v == (T) 0 ) { v = (T) 0; } \
1448
- UINT bits; \
1449
- memcpy(&bits, &v, sizeof(bits)); \
1450
- key = (uint64_t) bits; \
1451
- } \
1717
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
1452
1718
  int32_t code; \
1453
1719
  if ( fz_hash_lookup(&h, key, &code) ) { out[o] = ab[code]; um[o] = 0; } \
1454
1720
  else { out[o] = 0; um[o] = 1; n_undef++; } \
@@ -1481,7 +1747,7 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1481
1747
  } \
1482
1748
  } while (0)
1483
1749
 
1484
- CA_FOR_EACH_FIBER_MASKED(st_in, ca, axis, CA_KERNEL_READ, p_in, n, m) {
1750
+ CA_FOR_EACH_FIBER_MASKED(*st_in, ca, axis, CA_KERNEL_READ, p_in, n, m) {
1485
1751
  switch ( dt ) {
1486
1752
  case CA_INT8: LOC_PROBE(int8_t, (int64_t)); break;
1487
1753
  case CA_INT16: LOC_PROBE(int16_t, (int64_t)); break;
@@ -1491,8 +1757,8 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1491
1757
  case CA_UINT16: LOC_PROBE(uint16_t, (uint64_t)); break;
1492
1758
  case CA_UINT32: LOC_PROBE(uint32_t, (uint64_t)); break;
1493
1759
  case CA_UINT64: LOC_PROBE(uint64_t, (uint64_t)); break;
1494
- case CA_FLOAT32: LOC_PROBE_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
1495
- case CA_FLOAT64: LOC_PROBE_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1760
+ case CA_FLOAT32: LOC_PROBE_FLOAT(float); break;
1761
+ case CA_FLOAT64: LOC_PROBE_FLOAT(double); break;
1496
1762
  case CA_OBJECT: LOC_PROBE_OBJ; break;
1497
1763
  case CA_FIXLEN: LOC_PROBE_MEM; break;
1498
1764
  }
@@ -1513,11 +1779,17 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1513
1779
  ca_create_mask(cout);
1514
1780
  memcpy(cout->mask->ptr, um, (size_t) ca->elements);
1515
1781
  }
1516
- xfree(um);
1782
+ ALLOCV_END(hum);
1517
1783
 
1518
1784
  return vout;
1519
1785
  }
1520
1786
 
1787
+ static VALUE
1788
+ rb_ca_locate_addr (VALUE self, VALUE rref)
1789
+ {
1790
+ return fz_run(fz_locate_addr_body, self, rref, Qnil, 0);
1791
+ }
1792
+
1521
1793
  /* Shared body of __intersection__ (keep_when_hit = 1) and __difference__
1522
1794
  (keep_when_hit = 0): the distinct values of self that are (resp. are not)
1523
1795
  present in `other`, in self's first-appearance order, as a 1-D CArray of
@@ -1527,11 +1799,15 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1527
1799
  lane (numeric `==` + NaN collapse + -0.0 == +0.0, object hash/eql? + NaN
1528
1800
  collapse, fixlen byte equality). */
1529
1801
  static VALUE
1530
- fz_set_relation (VALUE self, VALUE rother, int keep_when_hit)
1802
+ fz_set_relation_body (VALUE arg)
1531
1803
  {
1804
+ fz_frame *fr = (fz_frame *) arg;
1805
+ VALUE self = fr->self;
1806
+ VALUE rother = fr->a;
1807
+ int keep_when_hit = fr->i;
1532
1808
  CArray *ca, *co;
1533
1809
  volatile VALUE face;
1534
- self = fz_face_descend(self, &face);
1810
+ self = fz_face_descend(self, &face, "set relation");
1535
1811
  rother = fz_face_reconcile(face, rother, "set relation");
1536
1812
  GetCArray(self, ca);
1537
1813
 
@@ -1554,8 +1830,16 @@ fz_set_relation (VALUE self, VALUE rother, int keep_when_hit)
1554
1830
  rb_raise(rb_eArgError, "set relation: other must be a CArray");
1555
1831
  }
1556
1832
  GetCArray(rother, co);
1557
- if ( co->data_type != dt || (dt == CA_FIXLEN && co->bytes != ca->bytes) ) {
1558
- rb_raise(rb_eCADataTypeError, "set relation: other data type must match self (%d)", dt);
1833
+ if ( co->data_type != dt ) {
1834
+ rb_raise(rb_eCADataTypeError,
1835
+ "set relation: other data type (%s) must match self (%s)",
1836
+ ca_type_name[co->data_type], ca_type_name[dt]);
1837
+ }
1838
+ if ( dt == CA_FIXLEN && co->bytes != ca->bytes ) {
1839
+ rb_raise(rb_eCADataTypeError,
1840
+ "set relation: a fixlen value is a cell-width blob, so other "
1841
+ "must be %ld bytes wide like self, not %ld",
1842
+ (long) ca->bytes, (long) co->bytes);
1559
1843
  }
1560
1844
  if ( co->ndim < 1 ) {
1561
1845
  rb_raise(rb_eRuntimeError, "set relation: other need ndim >= 1");
@@ -1573,15 +1857,15 @@ fz_set_relation (VALUE self, VALUE rother, int keep_when_hit)
1573
1857
  }
1574
1858
 
1575
1859
  fz_levels lv;
1576
- fz_levels_init(&lv, (int) ca->bytes);
1860
+ fz_levels_init(&lv, (int) ca->bytes, ca->data_type == CA_OBJECT);
1577
1861
 
1578
1862
  /* Build the probe set from every non-masked cell of other. */
1579
- fz_intern_all(&hoth, co, NULL);
1863
+ fz_intern_all(&fr->st[2], &hoth, co, NULL);
1580
1864
 
1581
1865
  /* Walk self: hself dedups, so each distinct self value is decided once; keep
1582
1866
  it when its membership in other equals keep_when_hit. */
1583
1867
  int8_t axis = (int8_t) (ca->ndim - 1);
1584
- ca_iter_state st;
1868
+ ca_iter_state *st = &fr->st[0];
1585
1869
  char *p;
1586
1870
  boolean8_t *m;
1587
1871
  ca_size_t n;
@@ -1600,20 +1884,13 @@ fz_set_relation (VALUE self, VALUE rother, int keep_when_hit)
1600
1884
  } \
1601
1885
  } while (0)
1602
1886
 
1603
- #define SR_FLOAT(T, UINT, NANKEY) \
1887
+ #define SR_FLOAT(T) \
1604
1888
  do { \
1605
1889
  const T *ip = (const T *) p; \
1606
1890
  for ( ca_size_t i = 0; i < n; i++ ) { \
1607
1891
  if ( m && m[i] ) { continue; } \
1608
1892
  T v = ip[i]; \
1609
- uint64_t key; \
1610
- if ( v != v ) { key = (NANKEY); } \
1611
- else { \
1612
- if ( v == (T) 0 ) { v = (T) 0; } \
1613
- UINT bits; \
1614
- memcpy(&bits, &v, sizeof(bits)); \
1615
- key = (uint64_t) bits; \
1616
- } \
1893
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
1617
1894
  int is_new; \
1618
1895
  fz_hash_intern(&hself, key, &is_new); \
1619
1896
  if ( is_new && fz_hash_lookup(&hoth, key, NULL) == keep_when_hit ) { \
@@ -1649,7 +1926,7 @@ fz_set_relation (VALUE self, VALUE rother, int keep_when_hit)
1649
1926
  } \
1650
1927
  } while (0)
1651
1928
 
1652
- CA_FOR_EACH_FIBER_MASKED(st, ca, axis, CA_KERNEL_READ, p, n, m) {
1929
+ CA_FOR_EACH_FIBER_MASKED(*st, ca, axis, CA_KERNEL_READ, p, n, m) {
1653
1930
  switch ( dt ) {
1654
1931
  case CA_INT8: SR(int8_t, (int64_t)); break;
1655
1932
  case CA_INT16: SR(int16_t, (int64_t)); break;
@@ -1659,8 +1936,8 @@ fz_set_relation (VALUE self, VALUE rother, int keep_when_hit)
1659
1936
  case CA_UINT16: SR(uint16_t, (uint64_t)); break;
1660
1937
  case CA_UINT32: SR(uint32_t, (uint64_t)); break;
1661
1938
  case CA_UINT64: SR(uint64_t, (uint64_t)); break;
1662
- case CA_FLOAT32: SR_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
1663
- case CA_FLOAT64: SR_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1939
+ case CA_FLOAT32: SR_FLOAT(float); break;
1940
+ case CA_FLOAT64: SR_FLOAT(double); break;
1664
1941
  case CA_OBJECT: SR_OBJ; break;
1665
1942
  case CA_FIXLEN: SR_MEM; break;
1666
1943
  }
@@ -1687,6 +1964,12 @@ fz_set_relation (VALUE self, VALUE rother, int keep_when_hit)
1687
1964
  return fz_face_relift(vlev, face); /* set *values*: give the Face back */
1688
1965
  }
1689
1966
 
1967
+ static VALUE
1968
+ fz_set_relation (VALUE self, VALUE rother, int keep_when_hit)
1969
+ {
1970
+ return fz_run(fz_set_relation_body, self, rother, Qnil, keep_when_hit);
1971
+ }
1972
+
1690
1973
  /* @overload __intersection__(other)
1691
1974
  INTERNAL (CArray#intersection). 1-D CArray of the distinct values present in
1692
1975
  both self and other, in self's first-appearance order. */
@@ -1712,11 +1995,14 @@ rb_ca_difference (VALUE self, VALUE rother)
1712
1995
  distinct values first, then other's not-yet-seen ones). Masked cells of
1713
1996
  either array do not participate. */
1714
1997
  static VALUE
1715
- rb_ca_set_union (VALUE self, VALUE rother)
1998
+ fz_set_union_body (VALUE arg)
1716
1999
  {
2000
+ fz_frame *fr = (fz_frame *) arg;
2001
+ VALUE self = fr->self;
2002
+ VALUE rother = fr->a;
1717
2003
  CArray *ca, *co;
1718
2004
  volatile VALUE face;
1719
- self = fz_face_descend(self, &face);
2005
+ self = fz_face_descend(self, &face, "union");
1720
2006
  rother = fz_face_reconcile(face, rother, "union");
1721
2007
  GetCArray(self, ca);
1722
2008
 
@@ -1739,8 +2025,16 @@ rb_ca_set_union (VALUE self, VALUE rother)
1739
2025
  rb_raise(rb_eArgError, "__union__: other must be a CArray");
1740
2026
  }
1741
2027
  GetCArray(rother, co);
1742
- if ( co->data_type != dt || (dt == CA_FIXLEN && co->bytes != ca->bytes) ) {
1743
- rb_raise(rb_eCADataTypeError, "__union__: other data type must match self (%d)", dt);
2028
+ if ( co->data_type != dt ) {
2029
+ rb_raise(rb_eCADataTypeError,
2030
+ "union: other data type (%s) must match self (%s)",
2031
+ ca_type_name[co->data_type], ca_type_name[dt]);
2032
+ }
2033
+ if ( dt == CA_FIXLEN && co->bytes != ca->bytes ) {
2034
+ rb_raise(rb_eCADataTypeError,
2035
+ "union: a fixlen value is a cell-width blob, so other "
2036
+ "must be %ld bytes wide like self, not %ld",
2037
+ (long) ca->bytes, (long) co->bytes);
1744
2038
  }
1745
2039
  if ( co->ndim < 1 ) {
1746
2040
  rb_raise(rb_eRuntimeError, "__union__: other need ndim >= 1");
@@ -1752,10 +2046,10 @@ rb_ca_set_union (VALUE self, VALUE rother)
1752
2046
  else { fz_hash_init(&h); }
1753
2047
 
1754
2048
  fz_levels lv;
1755
- fz_levels_init(&lv, (int) ca->bytes);
2049
+ fz_levels_init(&lv, (int) ca->bytes, ca->data_type == CA_OBJECT);
1756
2050
 
1757
- fz_intern_all(&h, ca, &lv); /* self's distinct values, appearance order */
1758
- fz_intern_all(&h, co, &lv); /* + other's not-yet-seen distinct values */
2051
+ fz_intern_all(&fr->st[2], &h, ca, &lv); /* self's distinct values, appearance order */
2052
+ fz_intern_all(&fr->st[2], &h, co, &lv); /* + other's not-yet-seen distinct values */
1759
2053
 
1760
2054
  ca_size_t k = lv.n;
1761
2055
  fz_hash_free(&h);
@@ -1773,6 +2067,12 @@ rb_ca_set_union (VALUE self, VALUE rother)
1773
2067
  return fz_face_relift(vlev, face); /* set *values*: give the Face back */
1774
2068
  }
1775
2069
 
2070
+ static VALUE
2071
+ rb_ca_set_union (VALUE self, VALUE rother)
2072
+ {
2073
+ return fz_run(fz_set_union_body, self, rother, Qnil, 0);
2074
+ }
2075
+
1776
2076
  /* @overload __value_counts_flat__
1777
2077
 
1778
2078
  INTERNAL (CArray#value_counts). Collect the distinct values of self in
@@ -1789,11 +2089,13 @@ rb_ca_set_union (VALUE self, VALUE rother)
1789
2089
  collapses every Float NaN to one value (their counts add up, as numeric does).
1790
2090
  */
1791
2091
  static VALUE
1792
- rb_ca_value_counts_flat (VALUE self)
2092
+ fz_value_counts_flat_body (VALUE arg)
1793
2093
  {
2094
+ fz_frame *fr = (fz_frame *) arg;
2095
+ VALUE self = fr->self;
1794
2096
  CArray *ca;
1795
2097
  volatile VALUE face;
1796
- self = fz_face_descend(self, &face);
2098
+ self = fz_face_descend(self, &face, "value_counts");
1797
2099
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
1798
2100
 
1799
2101
  int8_t dt = ca->data_type;
@@ -1818,11 +2120,11 @@ rb_ca_value_counts_flat (VALUE self)
1818
2120
  if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
1819
2121
  else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
1820
2122
  else { fz_hash_init(&h); }
1821
- fz_levels_init(&lv, (int) ca->bytes);
1822
- fz_levels_init(&ct, (int) sizeof(int64_t));
2123
+ fz_levels_init(&lv, (int) ca->bytes, ca->data_type == CA_OBJECT);
2124
+ fz_levels_init(&ct, (int) sizeof(int64_t), 0);
1823
2125
 
1824
2126
  int8_t axis = (int8_t) (ca->ndim - 1); /* one seen-set over the whole array */
1825
- ca_iter_state st_in;
2127
+ ca_iter_state *st_in = &fr->st[0];
1826
2128
  char *p_in;
1827
2129
  boolean8_t *m;
1828
2130
  ca_size_t n;
@@ -1845,20 +2147,13 @@ rb_ca_value_counts_flat (VALUE self)
1845
2147
  } \
1846
2148
  } while (0)
1847
2149
 
1848
- #define VC_LOOP_FLOAT(T, UINT, NANKEY) \
2150
+ #define VC_LOOP_FLOAT(T) \
1849
2151
  do { \
1850
2152
  const T *ip = (const T *) p_in; \
1851
2153
  for ( ca_size_t i = 0; i < n; i++ ) { \
1852
2154
  if ( m && m[i] ) { continue; } \
1853
2155
  T v = ip[i]; \
1854
- uint64_t key; \
1855
- if ( v != v ) { key = (NANKEY); } \
1856
- else { \
1857
- if ( v == (T) 0 ) { v = (T) 0; } \
1858
- UINT bits; \
1859
- memcpy(&bits, &v, sizeof(bits)); \
1860
- key = (uint64_t) bits; \
1861
- } \
2156
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
1862
2157
  int is_new; \
1863
2158
  int32_t code = fz_hash_intern(&h, key, &is_new); \
1864
2159
  if ( is_new ) { \
@@ -1908,7 +2203,7 @@ rb_ca_value_counts_flat (VALUE self)
1908
2203
  } \
1909
2204
  } while (0)
1910
2205
 
1911
- CA_FOR_EACH_FIBER_MASKED(st_in, ca, axis, CA_KERNEL_READ, p_in, n, m) {
2206
+ CA_FOR_EACH_FIBER_MASKED(*st_in, ca, axis, CA_KERNEL_READ, p_in, n, m) {
1912
2207
  switch ( dt ) {
1913
2208
  case CA_INT8: VC_LOOP(int8_t, (int64_t)); break;
1914
2209
  case CA_INT16: VC_LOOP(int16_t, (int64_t)); break;
@@ -1918,8 +2213,8 @@ rb_ca_value_counts_flat (VALUE self)
1918
2213
  case CA_UINT16: VC_LOOP(uint16_t, (uint64_t)); break;
1919
2214
  case CA_UINT32: VC_LOOP(uint32_t, (uint64_t)); break;
1920
2215
  case CA_UINT64: VC_LOOP(uint64_t, (uint64_t)); break;
1921
- case CA_FLOAT32: VC_LOOP_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
1922
- case CA_FLOAT64: VC_LOOP_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
2216
+ case CA_FLOAT32: VC_LOOP_FLOAT(float); break;
2217
+ case CA_FLOAT64: VC_LOOP_FLOAT(double); break;
1923
2218
  case CA_OBJECT: VC_LOOP_OBJ; break;
1924
2219
  case CA_FIXLEN: VC_LOOP_MEM; break;
1925
2220
  }
@@ -1952,6 +2247,12 @@ rb_ca_value_counts_flat (VALUE self)
1952
2247
  return rb_ary_new3(2, fz_face_relift(vlev, face), vcnt);
1953
2248
  }
1954
2249
 
2250
+ static VALUE
2251
+ rb_ca_value_counts_flat (VALUE self)
2252
+ {
2253
+ return fz_run(fz_value_counts_flat_body, self, Qnil, Qnil, 0);
2254
+ }
2255
+
1955
2256
  /* @overload __nunique__(axis, keep_axis)
1956
2257
 
1957
2258
  INTERNAL (CArray#nunique). Count the distinct values along axis, one linear
@@ -1964,8 +2265,12 @@ rb_ca_value_counts_flat (VALUE self)
1964
2265
  Ruby Hash distinctness; the object lane also collapses every Float NaN to one.
1965
2266
  */
1966
2267
  static VALUE
1967
- rb_ca_nunique (VALUE self, VALUE vaxis, VALUE vkeep)
2268
+ fz_nunique_body (VALUE arg)
1968
2269
  {
2270
+ fz_frame *fr = (fz_frame *) arg;
2271
+ VALUE self = fr->self;
2272
+ VALUE vaxis = fr->a;
2273
+ VALUE vkeep = fr->b;
1969
2274
  CArray *ca;
1970
2275
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
1971
2276
 
@@ -2002,8 +2307,8 @@ rb_ca_nunique (VALUE self, VALUE vaxis, VALUE vkeep)
2002
2307
  else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
2003
2308
  else { fz_hash_init(&h); }
2004
2309
 
2005
- ca_iter_state st;
2006
- int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES, &ax, 1, 0);
2310
+ ca_iter_state *st = &fr->st[0];
2311
+ int rc = ca_iter_state_init_l2(st, ca, CA_SLAB_AXES, &ax, 1, 0);
2007
2312
  if ( rc != CA_ITER_OK ) {
2008
2313
  fz_hash_free(&h);
2009
2314
  rb_raise(rb_eRuntimeError, "__nunique__: kernel_iterator init failed rc=%d", rc);
@@ -2019,17 +2324,10 @@ rb_ca_nunique (VALUE self, VALUE vaxis, VALUE vkeep)
2019
2324
  -0.0 before the bitwise key. */
2020
2325
  #define NU_INT(WIDEN) \
2021
2326
  do { int _n; fz_hash_intern(&h, (uint64_t) ((WIDEN) v), &_n); } while (0)
2022
- #define NU_FLOAT(T, UINT, NANKEY) \
2327
+ #define NU_FLOAT(T) \
2023
2328
  do { \
2024
2329
  T _v = v; \
2025
- uint64_t _key; \
2026
- if ( _v != _v ) { _key = (NANKEY); } \
2027
- else { \
2028
- if ( _v == (T) 0 ) { _v = (T) 0; } \
2029
- UINT _b; \
2030
- memcpy(&_b, &_v, sizeof(_b)); \
2031
- _key = (uint64_t) _b; \
2032
- } \
2330
+ uint64_t _key = FZ_FLOAT_KEY(T, _v); \
2033
2331
  int _n; fz_hash_intern(&h, _key, &_n); \
2034
2332
  } while (0)
2035
2333
  /* Object: intern the VALUE via the object lane (v is bound by the macro). */
@@ -2041,43 +2339,43 @@ rb_ca_nunique (VALUE self, VALUE vaxis, VALUE vkeep)
2041
2339
  each esz-wide element via the fixlen lane. */
2042
2340
  #define NU_MEM_WALK \
2043
2341
  do { \
2044
- int8_t K = st.slab_ndim; \
2342
+ int8_t K = st->slab_ndim; \
2045
2343
  ca_size_t idx[CA_RANK_MAX] = { 0 }; \
2046
- ca_size_t total = st.slab_elements; \
2344
+ ca_size_t total = st->slab_elements; \
2047
2345
  for ( ca_size_t e = 0; e < total; e++ ) { \
2048
2346
  ca_size_t doff = 0, moff = 0; \
2049
2347
  for ( int8_t kk = 0; kk < K; kk++ ) { \
2050
- doff += idx[kk] * st.slab_strides[kk]; \
2051
- moff += idx[kk] * st.slab_mask_strides[kk]; \
2348
+ doff += idx[kk] * st->slab_strides[kk]; \
2349
+ moff += idx[kk] * st->slab_mask_strides[kk]; \
2052
2350
  } \
2053
2351
  if ( ! (m && m[moff]) ) { \
2054
2352
  int _n; fz_hash_intern_mem(&h, p + doff, &_n); \
2055
2353
  } \
2056
2354
  for ( int8_t kk = (int8_t)(K - 1); kk >= 0; kk-- ) { \
2057
- if ( ++idx[kk] < st.slab_dims[kk] ) break; \
2355
+ if ( ++idx[kk] < st->slab_dims[kk] ) break; \
2058
2356
  idx[kk] = 0; \
2059
2357
  } \
2060
2358
  } \
2061
2359
  } while (0)
2062
2360
 
2063
- while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2361
+ while ( ca_iter_state_next_slab_axes(st, &p, &m) ) {
2064
2362
  fz_hash_reset(&h); /* independent seen-set per fiber */
2065
2363
  switch ( dt ) {
2066
- case CA_INT8: CA_SLAB_REDUCE_T(int8_t, st, p, m, dummy, 0, NU_INT(int64_t)); break;
2067
- case CA_INT16: CA_SLAB_REDUCE_T(int16_t, st, p, m, dummy, 0, NU_INT(int64_t)); break;
2068
- case CA_INT32: CA_SLAB_REDUCE_T(int32_t, st, p, m, dummy, 0, NU_INT(int64_t)); break;
2069
- case CA_INT64: CA_SLAB_REDUCE_T(int64_t, st, p, m, dummy, 0, NU_INT(int64_t)); break;
2070
- case CA_BOOLEAN: case CA_UINT8: CA_SLAB_REDUCE_T(uint8_t, st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2071
- case CA_UINT16: CA_SLAB_REDUCE_T(uint16_t, st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2072
- case CA_UINT32: CA_SLAB_REDUCE_T(uint32_t, st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2073
- case CA_UINT64: CA_SLAB_REDUCE_T(uint64_t, st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2364
+ case CA_INT8: CA_SLAB_REDUCE_T(int8_t, *st, p, m, dummy, 0, NU_INT(int64_t)); break;
2365
+ case CA_INT16: CA_SLAB_REDUCE_T(int16_t, *st, p, m, dummy, 0, NU_INT(int64_t)); break;
2366
+ case CA_INT32: CA_SLAB_REDUCE_T(int32_t, *st, p, m, dummy, 0, NU_INT(int64_t)); break;
2367
+ case CA_INT64: CA_SLAB_REDUCE_T(int64_t, *st, p, m, dummy, 0, NU_INT(int64_t)); break;
2368
+ case CA_BOOLEAN: case CA_UINT8: CA_SLAB_REDUCE_T(uint8_t, *st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2369
+ case CA_UINT16: CA_SLAB_REDUCE_T(uint16_t, *st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2370
+ case CA_UINT32: CA_SLAB_REDUCE_T(uint32_t, *st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2371
+ case CA_UINT64: CA_SLAB_REDUCE_T(uint64_t, *st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2074
2372
  case CA_FLOAT32:
2075
- CA_SLAB_REDUCE_T(float, st, p, m, dummy, 0, NU_FLOAT(float, uint32_t, 0x7fc00000ULL));
2373
+ CA_SLAB_REDUCE_T(float, *st, p, m, dummy, 0, NU_FLOAT(float));
2076
2374
  break;
2077
2375
  case CA_FLOAT64:
2078
- CA_SLAB_REDUCE_T(double, st, p, m, dummy, 0, NU_FLOAT(double, uint64_t, 0x7ff8000000000000ULL));
2376
+ CA_SLAB_REDUCE_T(double, *st, p, m, dummy, 0, NU_FLOAT(double));
2079
2377
  break;
2080
- case CA_OBJECT: CA_SLAB_REDUCE_T(VALUE, st, p, m, dummy, 0, NU_OBJ); break;
2378
+ case CA_OBJECT: CA_SLAB_REDUCE_T(VALUE, *st, p, m, dummy, 0, NU_OBJ); break;
2081
2379
  case CA_FIXLEN: NU_MEM_WALK; break;
2082
2380
  }
2083
2381
  op[out_i++] = (int64_t) h.n;
@@ -2088,11 +2386,17 @@ rb_ca_nunique (VALUE self, VALUE vaxis, VALUE vkeep)
2088
2386
  #undef NU_MEM_WALK
2089
2387
  (void) dummy;
2090
2388
 
2091
- ca_iter_state_finish(&st);
2389
+ ca_iter_state_finish(st);
2092
2390
  fz_hash_free(&h);
2093
2391
  return vout;
2094
2392
  }
2095
2393
 
2394
+ static VALUE
2395
+ rb_ca_nunique (VALUE self, VALUE vaxis, VALUE vkeep)
2396
+ {
2397
+ return fz_run(fz_nunique_body, fz_complex_as_key(self), vaxis, vkeep, 0);
2398
+ }
2399
+
2096
2400
  /* @overload __is_mode__(axis)
2097
2401
 
2098
2402
  INTERNAL (CArray#is_mode). Mark each cell that holds a modal value along axis
@@ -2111,8 +2415,11 @@ rb_ca_nunique (VALUE self, VALUE vaxis, VALUE vkeep)
2111
2415
  Callers pass flat input by flattening first (a single fiber over axis 0).
2112
2416
  */
2113
2417
  static VALUE
2114
- rb_ca_is_mode (VALUE self, VALUE vaxis)
2418
+ fz_is_mode_body (VALUE arg)
2115
2419
  {
2420
+ fz_frame *fr = (fz_frame *) arg;
2421
+ VALUE self = fr->self;
2422
+ VALUE vaxis = fr->a;
2116
2423
  CArray *ca;
2117
2424
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
2118
2425
 
@@ -2145,9 +2452,9 @@ rb_ca_is_mode (VALUE self, VALUE vaxis)
2145
2452
  if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
2146
2453
  else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
2147
2454
  else { fz_hash_init(&h); }
2148
- fz_levels_init(&ct, (int) sizeof(int64_t));
2455
+ fz_levels_init(&ct, (int) sizeof(int64_t), 0);
2149
2456
 
2150
- ca_iter_state st_in, st_out;
2457
+ ca_iter_state *st_in = &fr->st[0], *st_out = &fr->st[1];
2151
2458
  char *p_in, *p_out;
2152
2459
  boolean8_t *m;
2153
2460
  ca_size_t n;
@@ -2180,7 +2487,7 @@ rb_ca_is_mode (VALUE self, VALUE vaxis)
2180
2487
  } while (0)
2181
2488
 
2182
2489
  /* Float: NaN collapses to one canonical key; -0.0 / +0.0 share a key. */
2183
- #define IM_BODY_FLOAT(T, UINT, NANKEY) \
2490
+ #define IM_BODY_FLOAT(T) \
2184
2491
  do { \
2185
2492
  const T *ip = (const T *) p_in; \
2186
2493
  boolean8_t *op = (boolean8_t *) p_out; \
@@ -2195,14 +2502,7 @@ rb_ca_is_mode (VALUE self, VALUE vaxis)
2195
2502
  for ( ca_size_t i = 0; i < n; i++ ) { \
2196
2503
  if ( m && m[i] ) { if ( pass == 1 ) { op[i] = 0; } continue; } \
2197
2504
  T v = ip[i]; \
2198
- uint64_t key; \
2199
- if ( v != v ) { key = (NANKEY); } \
2200
- else { \
2201
- if ( v == (T) 0 ) { v = (T) 0; } \
2202
- UINT bits; \
2203
- memcpy(&bits, &v, sizeof(bits)); \
2204
- key = (uint64_t) bits; \
2205
- } \
2505
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
2206
2506
  int is_new; \
2207
2507
  int32_t code = fz_hash_intern(&h, key, &is_new); \
2208
2508
  if ( pass == 0 ) { \
@@ -2266,7 +2566,7 @@ rb_ca_is_mode (VALUE self, VALUE vaxis)
2266
2566
  } \
2267
2567
  } while (0)
2268
2568
 
2269
- CA_FOR_EACH_FIBER_INOUT_MASKED(st_in, st_out, ca, cout, (int8_t) axis,
2569
+ CA_FOR_EACH_FIBER_INOUT_MASKED(*st_in, *st_out, ca, cout, (int8_t) axis,
2270
2570
  CA_KERNEL_READ, p_in, p_out, n, m) {
2271
2571
  fz_hash_reset(&h); /* independent frequency table per fiber */
2272
2572
  ct.n = 0;
@@ -2279,8 +2579,8 @@ rb_ca_is_mode (VALUE self, VALUE vaxis)
2279
2579
  case CA_UINT16: IM_BODY(uint16_t, (uint64_t)); break;
2280
2580
  case CA_UINT32: IM_BODY(uint32_t, (uint64_t)); break;
2281
2581
  case CA_UINT64: IM_BODY(uint64_t, (uint64_t)); break;
2282
- case CA_FLOAT32: IM_BODY_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
2283
- case CA_FLOAT64: IM_BODY_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
2582
+ case CA_FLOAT32: IM_BODY_FLOAT(float); break;
2583
+ case CA_FLOAT64: IM_BODY_FLOAT(double); break;
2284
2584
  case CA_OBJECT: IM_BODY_OBJ; break;
2285
2585
  case CA_FIXLEN: IM_BODY_MEM; break;
2286
2586
  }
@@ -2295,6 +2595,12 @@ rb_ca_is_mode (VALUE self, VALUE vaxis)
2295
2595
  return vout;
2296
2596
  }
2297
2597
 
2598
+ static VALUE
2599
+ rb_ca_is_mode (VALUE self, VALUE vaxis)
2600
+ {
2601
+ return fz_run(fz_is_mode_body, fz_complex_as_key(self), vaxis, Qnil, 0);
2602
+ }
2603
+
2298
2604
  /* Ascending comparison for the modal-value sort, NaN ordered last so a
2299
2605
  collapsed NaN key (at most one per fiber) trails the real values, matching
2300
2606
  CArray#sort. */
@@ -2328,11 +2634,14 @@ static int mode_gt_f64 (double a, double b) { if ( a != a ) return b == b; if (
2328
2634
  its sign), matching the discovery family.
2329
2635
  */
2330
2636
  static VALUE
2331
- rb_ca_mode_axis (VALUE self, VALUE vaxis)
2637
+ fz_mode_axis_body (VALUE arg)
2332
2638
  {
2639
+ fz_frame *fr = (fz_frame *) arg;
2640
+ VALUE self = fr->self;
2641
+ VALUE vaxis = fr->a;
2333
2642
  CArray *ca;
2334
2643
  volatile VALUE face;
2335
- self = fz_face_descend(self, &face);
2644
+ self = fz_face_descend(self, &face, "mode");
2336
2645
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
2337
2646
 
2338
2647
  int8_t dt = ca->data_type;
@@ -2366,20 +2675,21 @@ rb_ca_mode_axis (VALUE self, VALUE vaxis)
2366
2675
  fz_levels mod; /* per-fiber modal values, collected then sorted (reset) */
2367
2676
  fz_levels flat; /* all fibers' modal values concatenated, fiber order */
2368
2677
  fz_hash_init(&h);
2369
- fz_levels_init(&lv, esz);
2370
- fz_levels_init(&ct, (int) sizeof(int64_t));
2371
- fz_levels_init(&mod, esz);
2372
- fz_levels_init(&flat, esz);
2678
+ fz_levels_init(&lv, esz, 0);
2679
+ fz_levels_init(&ct, (int) sizeof(int64_t), 0);
2680
+ fz_levels_init(&mod, esz, 0);
2681
+ fz_levels_init(&flat, esz, 0);
2373
2682
 
2374
- ca_size_t *foff = ALLOC_N(ca_size_t, M + 1); /* prefix offsets into flat */
2683
+ volatile VALUE hfoff = 0;
2684
+ ca_size_t *foff = ALLOCV_N(ca_size_t, hfoff, M + 1); /* prefix offsets into flat */
2375
2685
  foff[0] = 0;
2376
2686
 
2377
2687
  int8_t ax = (int8_t) axis;
2378
- ca_iter_state st;
2379
- int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES, &ax, 1, 0);
2688
+ ca_iter_state *st = &fr->st[0];
2689
+ int rc = ca_iter_state_init_l2(st, ca, CA_SLAB_AXES, &ax, 1, 0);
2380
2690
  if ( rc != CA_ITER_OK ) {
2381
2691
  fz_hash_free(&h); fz_levels_free(&lv); fz_levels_free(&ct);
2382
- fz_levels_free(&mod); fz_levels_free(&flat); xfree(foff);
2692
+ fz_levels_free(&mod); fz_levels_free(&flat); ALLOCV_END(hfoff);
2383
2693
  rb_raise(rb_eRuntimeError, "__mode_axis__: kernel_iterator init failed rc=%d", rc);
2384
2694
  }
2385
2695
 
@@ -2400,12 +2710,9 @@ rb_ca_mode_axis (VALUE self, VALUE vaxis)
2400
2710
  fz_levels_push(&ct, &one); } \
2401
2711
  else { ((int64_t *) ct.p)[code]++; } \
2402
2712
  } while (0)
2403
- #define MB_FLOAT(T, UINT, NANKEY) \
2713
+ #define MB_FLOAT(T) \
2404
2714
  do { \
2405
- T vn = v; uint64_t key; \
2406
- if ( vn != vn ) { key = (NANKEY); } \
2407
- else { if ( vn == (T) 0 ) { vn = (T) 0; } \
2408
- UINT bits; memcpy(&bits, &vn, sizeof(bits)); key = (uint64_t) bits; } \
2715
+ uint64_t key = FZ_FLOAT_KEY(T, v); \
2409
2716
  int is_new; int32_t code = fz_hash_intern(&h, key, &is_new); \
2410
2717
  if ( is_new ) { int64_t one = 1; fz_levels_push(&lv, &v); \
2411
2718
  fz_levels_push(&ct, &one); } \
@@ -2429,24 +2736,24 @@ rb_ca_mode_axis (VALUE self, VALUE vaxis)
2429
2736
  for ( ca_size_t x = 0; x < cm; x++ ) { fz_levels_push(&flat, &dp[x]); } \
2430
2737
  } while (0)
2431
2738
 
2432
- while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2739
+ while ( ca_iter_state_next_slab_axes(st, &p, &m) ) {
2433
2740
  fz_hash_reset(&h);
2434
2741
  lv.n = 0; ct.n = 0; mod.n = 0;
2435
2742
 
2436
2743
  switch ( dt ) {
2437
- case CA_INT8: CA_SLAB_REDUCE_T(int8_t, st, p, m, dummy, 0, MB_INT(int64_t)); break;
2438
- case CA_INT16: CA_SLAB_REDUCE_T(int16_t, st, p, m, dummy, 0, MB_INT(int64_t)); break;
2439
- case CA_INT32: CA_SLAB_REDUCE_T(int32_t, st, p, m, dummy, 0, MB_INT(int64_t)); break;
2440
- case CA_INT64: CA_SLAB_REDUCE_T(int64_t, st, p, m, dummy, 0, MB_INT(int64_t)); break;
2441
- case CA_BOOLEAN: case CA_UINT8: CA_SLAB_REDUCE_T(uint8_t, st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2442
- case CA_UINT16: CA_SLAB_REDUCE_T(uint16_t, st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2443
- case CA_UINT32: CA_SLAB_REDUCE_T(uint32_t, st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2444
- case CA_UINT64: CA_SLAB_REDUCE_T(uint64_t, st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2744
+ case CA_INT8: CA_SLAB_REDUCE_T(int8_t, *st, p, m, dummy, 0, MB_INT(int64_t)); break;
2745
+ case CA_INT16: CA_SLAB_REDUCE_T(int16_t, *st, p, m, dummy, 0, MB_INT(int64_t)); break;
2746
+ case CA_INT32: CA_SLAB_REDUCE_T(int32_t, *st, p, m, dummy, 0, MB_INT(int64_t)); break;
2747
+ case CA_INT64: CA_SLAB_REDUCE_T(int64_t, *st, p, m, dummy, 0, MB_INT(int64_t)); break;
2748
+ case CA_BOOLEAN: case CA_UINT8: CA_SLAB_REDUCE_T(uint8_t, *st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2749
+ case CA_UINT16: CA_SLAB_REDUCE_T(uint16_t, *st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2750
+ case CA_UINT32: CA_SLAB_REDUCE_T(uint32_t, *st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2751
+ case CA_UINT64: CA_SLAB_REDUCE_T(uint64_t, *st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2445
2752
  case CA_FLOAT32:
2446
- CA_SLAB_REDUCE_T(float, st, p, m, dummy, 0, MB_FLOAT(float, uint32_t, 0x7fc00000ULL));
2753
+ CA_SLAB_REDUCE_T(float, *st, p, m, dummy, 0, MB_FLOAT(float));
2447
2754
  break;
2448
2755
  case CA_FLOAT64:
2449
- CA_SLAB_REDUCE_T(double, st, p, m, dummy, 0, MB_FLOAT(double, uint64_t, 0x7ff8000000000000ULL));
2756
+ CA_SLAB_REDUCE_T(double, *st, p, m, dummy, 0, MB_FLOAT(double));
2450
2757
  break;
2451
2758
  }
2452
2759
 
@@ -2478,7 +2785,7 @@ rb_ca_mode_axis (VALUE self, VALUE vaxis)
2478
2785
  #undef MODE_EMIT
2479
2786
  (void) dummy;
2480
2787
 
2481
- ca_iter_state_finish(&st);
2788
+ ca_iter_state_finish(st);
2482
2789
  fz_hash_free(&h);
2483
2790
  fz_levels_free(&lv);
2484
2791
  fz_levels_free(&ct);
@@ -2508,10 +2815,16 @@ rb_ca_mode_axis (VALUE self, VALUE vaxis)
2508
2815
  }
2509
2816
 
2510
2817
  fz_levels_free(&flat);
2511
- xfree(foff);
2818
+ ALLOCV_END(hfoff);
2512
2819
  return result;
2513
2820
  }
2514
2821
 
2822
+ static VALUE
2823
+ rb_ca_mode_axis (VALUE self, VALUE vaxis)
2824
+ {
2825
+ return fz_run(fz_mode_axis_body, self, vaxis, Qnil, 0);
2826
+ }
2827
+
2515
2828
  void
2516
2829
  Init_carray_factorize (void)
2517
2830
  {