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
data/ext/carray_sort.c CHANGED
@@ -8,11 +8,12 @@
8
8
 
9
9
  #include "ruby.h"
10
10
  #include "carray.h"
11
- #include "ca_for_buffer.h" /* CA_WITH_BUFFER (scoped attach/detach) */
11
+ #include "ca_for_buffer.h" /* rb_ca_call_with_buffer */
12
12
  #include "ca_kernel_iterator.h" /* CA_FOR_EACH_FIBER_INOUT (sort_copy) */
13
13
  #include "ca_obj_face.h" /* CA_FACE_LIFT_IF_FACE */
14
14
  #include "ca_sort_kernels.h" /* ca_sort_quick_* / ca_sort_merge_* / ca_partition_nan_* */
15
15
  #include "ca_compare.h" /* ca_elem_cmp[] -- shared element comparators */
16
+ #include "carray_internal.h" /* ca_attach_window */
16
17
  #include <math.h>
17
18
  #include <float.h>
18
19
 
@@ -93,6 +94,40 @@ sort_addr_cmp (struct sort_addr_key *a, struct sort_addr_key *b)
93
94
  return ( ia > ib ) ? 1 : -1; /* for stable sort */
94
95
  }
95
96
 
97
+ /* An object key compares through <=>, which can raise, and a raise must
98
+ not unwind through libc's mergesort / qsort. Those keys sort with this
99
+ mergesort instead, whose frames hold nothing; the numeric ones keep
100
+ libc's, which their comparisons cannot leave. */
101
+ static void
102
+ sort_addr_merge (struct sort_addr_key *a, struct sort_addr_key *aux,
103
+ ca_size_t n, int (*cmp)(const void *, const void *))
104
+ CA_SORT_MERGE_BY_CMP(struct sort_addr_key)
105
+
106
+ typedef struct {
107
+ struct sort_addr_key *data;
108
+ struct sort_addr_key *aux; /* NULL: no object key, libc sorts */
109
+ ca_size_t elements;
110
+ } sort_addr_run_t;
111
+
112
+ static VALUE
113
+ sort_addr_run (VALUE arg)
114
+ {
115
+ sort_addr_run_t *r = (sort_addr_run_t *) arg;
116
+ if ( r->aux ) {
117
+ sort_addr_merge(r->data, r->aux, r->elements,
118
+ (int (*)(const void*,const void*)) sort_addr_cmp);
119
+ return Qnil;
120
+ }
121
+ #ifdef HAVE_MERGESORT
122
+ mergesort(r->data, r->elements, sizeof(struct sort_addr_key),
123
+ (int (*)(const void*,const void*)) sort_addr_cmp);
124
+ #else
125
+ qsort(r->data, r->elements, sizeof(struct sort_addr_key),
126
+ (int (*)(const void*,const void*)) sort_addr_cmp);
127
+ #endif
128
+ return Qnil;
129
+ }
130
+
96
131
  /* CArray.sort_addr(*args, masked_position: :last) — multi-key lex
97
132
  * sort. Returns a 1-D CA_SIZE array of indices that sorts the
98
133
  * arguments in priority order (a > b > c):
@@ -104,34 +139,25 @@ sort_addr_cmp (struct sort_addr_key *a, struct sort_addr_key *b)
104
139
  * an incomparable sentinel clustered at masked_position: (:last
105
140
  * default, or :first); ties are broken by original index (stable). */
106
141
  static VALUE
107
- rb_ca_s_sort_addr (int argc, VALUE *argv, VALUE self)
142
+ rb_ca_s_sort_addr_kw (int argc, VALUE *argv, VALUE ropt, VALUE self)
108
143
  {
109
144
  volatile VALUE out;
110
145
  CArray *co;
111
- struct cmp_base *base;
146
+ struct cmp_base base_s, *base = &base_s;
112
147
  struct sort_addr_key *data;
148
+ volatile VALUE hlist = 0, hdata = 0, haux = 0, hmodes = 0;
149
+ sort_addr_run_t run;
150
+ char *modes;
151
+ int has_object = 0;
113
152
  ca_size_t elements;
114
153
  ca_size_t *q;
115
154
  int j;
116
155
  ca_size_t i;
117
156
 
118
- VALUE ropt = rb_pop_options(&argc, &argv);
119
157
  VALUE vmasked_position = Qnil;
120
158
  rb_scan_options(ropt, "masked_position", &vmasked_position);
121
- int masked_last = 1;
122
- if ( !NIL_P(vmasked_position) ) {
123
- static ID sym_first = 0, sym_last = 0;
124
- if ( !sym_first ) sym_first = rb_intern("first");
125
- if ( !sym_last ) sym_last = rb_intern("last");
126
- ID mp_id = SYM2ID(vmasked_position);
127
- if ( mp_id == sym_last ) masked_last = 1;
128
- else if ( mp_id == sym_first ) masked_last = 0;
129
- else {
130
- rb_raise(rb_eArgError,
131
- "sort_addr: unknown masked_position %s (expected :first or :last)",
132
- rb_id2name(mp_id));
133
- }
134
- }
159
+ int masked_last = NIL_P(vmasked_position) ? 1
160
+ : ca_symbol_choice(vmasked_position, "masked_position", "first", "last", NULL);
135
161
 
136
162
  if ( argc <= 0 ) {
137
163
  rb_raise(rb_eArgError, "no arg given");
@@ -146,53 +172,68 @@ rb_ca_s_sort_addr (int argc, VALUE *argv, VALUE self)
146
172
  if ( elements != NUM2SIZE(rb_ca_elements(argv[j])) ) {
147
173
  rb_raise(rb_eArgError, "elements mismatch");
148
174
  }
149
- /* Face gate: descend a Face to its storage and build attach / comparator
150
- / template output on that plain storage (this also removes the SEGV
151
- from a face-lifted NULL-ptr index output). A fixlen storage sorts by
152
- memcmp (the default order for fixlen, as for a plain fixlen array); a
153
- numeric storage requires ORDERABLE so the numeric order equals the
154
- surface order, else it raises. */
175
+ /* Face gate: an ORDERABLE Face sorts by its storage, so it descends to
176
+ it (attach / comparator / template output then run on plain storage,
177
+ which also avoids a face-lifted NULL-ptr index output). A fixlen
178
+ storage that is not ORDERABLE (CAConstString's offsets, a record's
179
+ bytes) has no order in its bytes; the Face's own order stands in for
180
+ it, as a dense rank, so ties stay ties for the next key. A Face that
181
+ has no order (CARecord without order_by:) raises there. Any other
182
+ storage raises here. */
155
183
  TypedData_Get_Struct(argv[j], CArray, &carray_data_type, ca_j);
156
184
  if ( ca_is_face(ca_j) ) {
157
185
  int orderable = ca_test_flag(ca_j, CA_FLAG_FACE_ORDERABLE_STORAGE);
158
186
  VALUE stripped = rb_ca_strip_face_value(argv[j]);
159
187
  CArray *sc;
160
188
  TypedData_Get_Struct(stripped, CArray, &carray_data_type, sc);
161
- if ( sc->data_type != CA_FIXLEN && ! orderable ) {
162
- rb_raise(rb_eArgError,
163
- "sort_addr: Face-typed input (%s) is not orderable by "
164
- "storage; use ca.parent to descend to storage",
165
- rb_obj_classname(argv[j]));
189
+ if ( sc->data_type == CA_FIXLEN && ! orderable ) {
190
+ VALUE opts = rb_hash_new();
191
+ rb_hash_aset(opts, ID2SYM(rb_intern("method")), ID2SYM(rb_intern("dense")));
192
+ argv[j] = rb_funcallv_kw(argv[j], rb_intern("order"), 1, &opts,
193
+ RB_PASS_KEYWORDS);
194
+ continue;
195
+ }
196
+ if ( ! orderable ) {
197
+ ca_face_refuse_order("sort_addr", argv[j], "order");
166
198
  }
167
199
  argv[j] = stripped;
168
200
  }
201
+ {
202
+ CArray *k;
203
+ TypedData_Get_Struct(argv[j], CArray, &carray_data_type, k);
204
+ if ( ca_is_complex_type(k) ) {
205
+ rb_raise(rb_eCADataTypeError,
206
+ "sort_addr: a %s key has no order (complex numbers are not "
207
+ "ordered); sort by its real part, imaginary part or abs",
208
+ ca_type_name[k->data_type]);
209
+ }
210
+ }
169
211
  }
170
212
 
171
- base = xmalloc(sizeof(struct cmp_base));
213
+ /* Scratch the unwind collects: the keys are read inside an attach
214
+ window, and a comparison of object keys can raise. */
172
215
  base->n = argc;
173
216
  base->masked_last = masked_last;
174
- base->ca = xmalloc(sizeof(CArray *)*base->n);
217
+ base->ca = ALLOCV_N(CArray *, hlist, base->n);
218
+ modes = ALLOCV_N(char, hmodes, argc + 1);
175
219
 
176
220
  for (j=0; j<argc; j++) {
177
- CArray *ca;
178
- TypedData_Get_Struct(argv[j], CArray, &carray_data_type, ca);
179
- base->ca[j] = ca;
180
- ca_attach(ca);
221
+ TypedData_Get_Struct(argv[j], CArray, &carray_data_type, base->ca[j]);
222
+ has_object |= ( base->ca[j]->data_type == CA_OBJECT );
223
+ modes[j] = 'r';
181
224
  }
225
+ modes[argc] = '\0';
182
226
 
183
- data = xmalloc(sizeof(struct sort_addr_key)*elements);
227
+ data = ALLOCV_N(struct sort_addr_key, hdata, elements);
184
228
  for (i=0; i<elements; i++) {
185
229
  data[i].i = i;
186
230
  data[i].base = base;
187
231
  }
188
232
 
189
- #ifdef HAVE_MERGESORT
190
- mergesort(data, elements, sizeof(struct sort_addr_key),
191
- (int (*)(const void*,const void*)) sort_addr_cmp);
192
- #else
193
- qsort(data, elements, sizeof(struct sort_addr_key),
194
- (int (*)(const void*,const void*)) sort_addr_cmp);
195
- #endif
233
+ run.data = data;
234
+ run.aux = has_object ? ALLOCV_N(struct sort_addr_key, haux, elements) : NULL;
235
+ run.elements = elements;
236
+ ca_attach_window(argc, base->ca, modes, sort_addr_run, (VALUE) &run);
196
237
 
197
238
  out = rb_ca_template_with_type(argv[0], INT2NUM(CA_SIZE), INT2NUM(0));
198
239
  TypedData_Get_Struct(out, CArray, &carray_data_type, co);
@@ -203,27 +244,26 @@ rb_ca_s_sort_addr (int argc, VALUE *argv, VALUE self)
203
244
  q++;
204
245
  }
205
246
 
206
- for (j=0; j<argc; j++) {
207
- ca_detach(base->ca[j]);
208
- }
209
-
210
- xfree(data);
211
- xfree(base->ca);
212
- xfree(base);
247
+ ALLOCV_END(haux);
248
+ ALLOCV_END(hdata);
249
+ ALLOCV_END(hmodes);
250
+ ALLOCV_END(hlist);
213
251
 
214
252
  return out;
215
253
  }
216
254
 
217
- /* Internal sort_addr_ki kernel entries (C-level only, no Ruby binding;
218
- * declared in the generated carray_kernels.c). The _quick and _stable
219
- * variants implement the kind: dispatch; _ki itself is a 2-arg alias of
220
- * _quick retained for older callers. The _mp ("masked position") twins
221
- * take an explicit masked_last so `sort` / `sort_copy` can pass the
222
- * masked_position: kwarg through (see MASKED_POSITION rev1 in
223
- * mkkernel.rb's MkKernel.sort doc). */
224
- extern VALUE rb_ca_sort_addr_ki (VALUE self, VALUE vaxis);
225
- extern VALUE rb_ca_sort_addr_ki_quick (VALUE self, VALUE vaxis);
226
- extern VALUE rb_ca_sort_addr_ki_stable (VALUE self, VALUE vaxis);
255
+ static VALUE
256
+ rb_ca_s_sort_addr (int argc, VALUE *argv, VALUE self)
257
+ {
258
+ VALUE ropt = rb_pop_options(&argc, &argv);
259
+ return rb_ca_s_sort_addr_kw(argc, argv, ropt, self);
260
+ }
261
+
262
+ /* Internal sort_addr kernel entries (C-level only, no Ruby binding;
263
+ * defined in the generated carray_kernels_sort.c). The _quick and
264
+ * _stable variants implement the kind: dispatch; both take an explicit
265
+ * masked_last so `sort` / `sort_copy` can pass the masked_position:
266
+ * kwarg through (see MkKernel.sort in mkkernel.rb). */
227
267
  extern VALUE rb_ca_sort_addr_ki_quick_mp (VALUE self, VALUE vaxis, int masked_last);
228
268
  extern VALUE rb_ca_sort_addr_ki_stable_mp (VALUE self, VALUE vaxis, int masked_last);
229
269
  extern VALUE rb_ca_remap_new (VALUE cary, VALUE rmapper); /* ca_obj_remap.c (sort view) */
@@ -274,16 +314,12 @@ rb_ca_sort_addr_c (VALUE self, VALUE axis, int stable, int masked_last)
274
314
  if ( NIL_P(axis) ) {
275
315
  /* Legacy flat: equivalent to CArray.sort_addr(self, masked_position:).
276
316
  kind: ignored for now (the flat path uses qsort/mergesort
277
- internally). rb_ca_s_sort_addr's rb_pop_options handles a trailing
278
- Hash the same way regardless of call-frame keyword-splat state (it
279
- type-checks the last positional arg, not rb_keyword_given_p), so
280
- this raw C call is safe unlike the rb_scan_args "1:" pattern used
281
- elsewhere in this file. */
282
- VALUE kw = rb_hash_new();
317
+ internally). */
318
+ volatile VALUE kw = rb_hash_new();
283
319
  rb_hash_aset(kw, ID2SYM(rb_intern("masked_position")),
284
320
  masked_last ? ID2SYM(rb_intern("last")) : ID2SYM(rb_intern("first")));
285
- VALUE flat_argv[2] = { self, kw };
286
- return rb_ca_s_sort_addr(2, flat_argv, rb_cCArray);
321
+ VALUE flat_argv[1] = { self };
322
+ return rb_ca_s_sort_addr_kw(1, flat_argv, kw, rb_cCArray);
287
323
  }
288
324
  return stable ? rb_ca_sort_addr_ki_stable_mp(self, axis, masked_last)
289
325
  : rb_ca_sort_addr_ki_quick_mp (self, axis, masked_last);
@@ -302,35 +338,11 @@ rb_ca_sort_addr (int argc, VALUE *argv, VALUE self)
302
338
  rb_scan_args(argc, argv, "0:", &opts);
303
339
  rb_scan_options(opts, "axis,kind,masked_position", &axis, &kind, &vmasked_position);
304
340
 
305
- int do_stable = 0;
306
- if ( ! NIL_P(kind) ) {
307
- static ID sym_quick = 0, sym_stable = 0;
308
- if ( ! sym_quick ) sym_quick = rb_intern("quick");
309
- if ( ! sym_stable ) sym_stable = rb_intern("stable");
310
- ID kind_id = SYM2ID(kind);
311
- if ( kind_id == sym_quick ) do_stable = 0;
312
- else if ( kind_id == sym_stable ) do_stable = 1;
313
- else {
314
- rb_raise(rb_eArgError,
315
- "sort_addr: unknown kind %s (expected :quick or :stable)",
316
- rb_id2name(kind_id));
317
- }
318
- }
341
+ int do_stable = NIL_P(kind) ? 0
342
+ : ca_symbol_choice(kind, "kind", "quick", "stable", NULL);
319
343
 
320
- int masked_last = 1;
321
- if ( ! NIL_P(vmasked_position) ) {
322
- static ID sym_first = 0, sym_last = 0;
323
- if ( !sym_first ) sym_first = rb_intern("first");
324
- if ( !sym_last ) sym_last = rb_intern("last");
325
- ID mp_id = SYM2ID(vmasked_position);
326
- if ( mp_id == sym_last ) masked_last = 1;
327
- else if ( mp_id == sym_first ) masked_last = 0;
328
- else {
329
- rb_raise(rb_eArgError,
330
- "sort_addr: unknown masked_position %s (expected :first or :last)",
331
- rb_id2name(mp_id));
332
- }
333
- }
344
+ int masked_last = NIL_P(vmasked_position) ? 1
345
+ : ca_symbol_choice(vmasked_position, "masked_position", "first", "last", NULL);
334
346
 
335
347
  return rb_ca_sort_addr_c(self, axis, do_stable, masked_last);
336
348
  }
@@ -362,12 +374,58 @@ rb_ca_sort_addr (int argc, VALUE *argv, VALUE self)
362
374
  *
363
375
  * indices data_type: any integer kind (zero-copy if already CA_SIZE).
364
376
  * Negative indices: Python-style (-1 = last), normalized internally.
365
- * OOB indices: raises RangeError. Default axis: 0; negative axis
377
+ * OOB indices: raises IndexError. Default axis: 0; negative axis
366
378
  * Python-style.
367
379
  *
368
380
  * Returns: CArray of CA_SIZE, same shape as indices.
369
381
  */
370
382
 
383
+ typedef struct {
384
+ CArray *idx_ca;
385
+ ca_size_t *out_ptr;
386
+ ca_size_t *strides;
387
+ int axis;
388
+ ca_size_t axis_size;
389
+ boolean8_t *mask; /* the indices' mask, or NULL */
390
+ } axis2addr_ctx_t;
391
+
392
+ /* Body of rb_ca_axis2addr_c: one flat address per index cell. */
393
+ static void
394
+ axis2addr_body (void *user_data, void *ptr, ca_size_t n)
395
+ {
396
+ axis2addr_ctx_t *c = (axis2addr_ctx_t *) user_data;
397
+ ca_size_t *idx_ptr = (ca_size_t *) ptr;
398
+ int ndim = c->idx_ca->ndim;
399
+ ca_size_t coord[CA_RANK_MAX];
400
+
401
+ for ( int8_t j = 0; j < ndim; j++ ) coord[j] = 0;
402
+ for ( ca_size_t k = 0; k < n; k++ ) {
403
+ /* A masked index has no address: the cell keeps the mask (copied from
404
+ the indices) and is skipped here, its stored value unread. */
405
+ if ( c->mask && c->mask[k] ) {
406
+ c->out_ptr[k] = 0;
407
+ goto advance;
408
+ }
409
+ ca_size_t norm = ca_index_in_range(idx_ptr[k], c->axis_size, "index");
410
+ /* Compute flat addr: sum c_j * stride_j (with c_axis = norm). */
411
+ ca_size_t addr = 0;
412
+ for ( int8_t j = 0; j < ndim; j++ ) {
413
+ if ( j == c->axis ) {
414
+ addr += norm * c->strides[j];
415
+ } else {
416
+ addr += coord[j] * c->strides[j];
417
+ }
418
+ }
419
+ c->out_ptr[k] = addr;
420
+ advance:
421
+ /* Advance coord row-major (last axis ticks fastest). */
422
+ for ( int8_t j = (int8_t)(ndim - 1); j >= 0; j-- ) {
423
+ if ( ++coord[j] < c->idx_ca->dim[j] ) break;
424
+ coord[j] = 0;
425
+ }
426
+ }
427
+ }
428
+
371
429
  /* C-callable entry: skip rb_scan_args (call-frame state dependency).
372
430
  * vaxis = Qnil treated as axis 0; Integer is taken as-is (negative
373
431
  * axis normalized internally).
@@ -385,27 +443,27 @@ rb_ca_axis2addr_c (VALUE self, VALUE vindices, VALUE vaxis)
385
443
  TypedData_Get_Struct(vindices, CArray, &carray_data_type, idx_ca);
386
444
 
387
445
  /* axis normalization + range check. */
388
- int axis_raw = NIL_P(vaxis) ? 0 : NUM2INT(vaxis);
446
+ int axis_raw = NIL_P(vaxis) ? 0 : (int) ca_axis_integer(vaxis, NULL);
389
447
  int axis = (axis_raw < 0) ? ((int) ca->ndim + axis_raw) : axis_raw;
390
448
  if ( axis < 0 || axis >= ca->ndim ) {
391
- rb_raise(rb_eIndexError,
392
- "axis2addr: axis %d out of range for ndim %d",
393
- axis_raw, (int) ca->ndim);
449
+ rb_raise(rb_eArgError,
450
+ "%s: axis %d out of range for ndim %d",
451
+ ca_calling_method_name(), axis_raw, (int) ca->ndim);
394
452
  }
395
453
 
396
454
  /* Shape rule: indices.ndim == self.ndim, dims match except at axis. */
397
455
  if ( idx_ca->ndim != ca->ndim ) {
398
456
  rb_raise(rb_eArgError,
399
- "axis2addr: indices.ndim (%d) must equal self.ndim (%d)",
400
- (int) idx_ca->ndim, (int) ca->ndim);
457
+ "%s: indices.ndim (%d) must equal self.ndim (%d)",
458
+ ca_calling_method_name(), (int) idx_ca->ndim, (int) ca->ndim);
401
459
  }
402
460
  for ( int8_t j = 0; j < ca->ndim; j++ ) {
403
461
  if ( j == axis ) continue;
404
462
  if ( idx_ca->dim[j] != ca->dim[j] ) {
405
463
  rb_raise(rb_eArgError,
406
- "axis2addr: indices.dim[%d] (%lld) must equal "
464
+ "%s: indices.dim[%d] (%lld) must equal "
407
465
  "self.dim[%d] (%lld)",
408
- (int) j, (long long) idx_ca->dim[j],
466
+ ca_calling_method_name(), (int) j, (long long) idx_ca->dim[j],
409
467
  (int) j, (long long) ca->dim[j]);
410
468
  }
411
469
  }
@@ -414,8 +472,8 @@ rb_ca_axis2addr_c (VALUE self, VALUE vindices, VALUE vaxis)
414
472
  (zero-copy when already CA_SIZE via to_type identity). */
415
473
  if ( ! ca_is_integer_type(idx_ca) ) {
416
474
  rb_raise(rb_eArgError,
417
- "axis2addr: indices data_type must be integer kind (got %d)",
418
- (int) idx_ca->data_type);
475
+ "%s: indices data_type must be integer kind (got %d)",
476
+ ca_calling_method_name(), (int) idx_ca->data_type);
419
477
  }
420
478
  volatile VALUE vidx_cast = vindices;
421
479
  CArray *idx_cast = idx_ca;
@@ -439,59 +497,26 @@ rb_ca_axis2addr_c (VALUE self, VALUE vindices, VALUE vaxis)
439
497
  rb_ca_template_with_type(vidx_cast, INT2NUM(CA_SIZE), INT2NUM(0));
440
498
  TypedData_Get_Struct(vout, CArray, &carray_data_type, out_ca);
441
499
 
442
- /* out_ca is a freshly allocated entity (ptr already valid), so it needs
443
- no attach/sync/detach. idx_cast may still be a view (CA_SIZE identity
444
- branch above), so its contig buffer is delivered via CA_WITH_BUFFER,
445
- which aliases when contig / materialises a view into scratch and scopes
446
- the attach/detach to the block. */
447
- ca_size_t *out_ptr = (ca_size_t *) out_ca->ptr;
448
-
449
- ca_size_t coord[CA_RANK_MAX];
450
- int ndim = idx_cast->ndim;
451
-
452
- /* OOB is reported after the block: rb_raise from inside CA_WITH_BUFFER
453
- would longjmp past the scoped ca_detach and leak the attach (= doc
454
- constraint "restructure to break"). Record the offender, break, raise
455
- once the buffer lifecycle has closed. */
456
- ca_size_t bad_k = -1;
457
- ca_size_t bad_raw = 0;
458
-
459
- ca_size_t *idx_ptr;
460
- ca_size_t n;
461
- CA_WITH_BUFFER(idx_cast, ca_size_t, idx_ptr, n) {
462
- for ( int8_t j = 0; j < ndim; j++ ) coord[j] = 0;
463
- for ( ca_size_t k = 0; k < n; k++ ) {
464
- /* Negative normalize + OOB check (raises on out-of-bounds). */
465
- ca_size_t raw = idx_ptr[k];
466
- ca_size_t norm = (raw < 0) ? (raw + axis_size) : raw;
467
- if ( norm < 0 || norm >= axis_size ) {
468
- bad_k = k; bad_raw = raw;
469
- break;
470
- }
471
- /* Compute flat addr: sum c_j * stride_j (with c_axis = norm). */
472
- ca_size_t addr = 0;
473
- for ( int8_t j = 0; j < ndim; j++ ) {
474
- if ( j == axis ) {
475
- addr += norm * strides[j];
476
- } else {
477
- addr += coord[j] * strides[j];
478
- }
479
- }
480
- out_ptr[k] = addr;
481
- /* Advance coord row-major (last axis ticks fastest). */
482
- for ( int8_t j = (int8_t)(ndim - 1); j >= 0; j-- ) {
483
- if ( ++coord[j] < idx_cast->dim[j] ) break;
484
- coord[j] = 0;
485
- }
486
- }
487
- }
488
-
489
- if ( bad_k >= 0 ) {
490
- rb_raise(rb_eRangeError,
491
- "axis2addr: indices[%lld] = %lld out of range [0, %lld) "
492
- "(after negative normalize)",
493
- (long long) bad_k, (long long) bad_raw, (long long) axis_size);
500
+ /* out_ca is a freshly allocated entity of the indices' shape and cell
501
+ width, so the indices are copied into it (a view is gathered without
502
+ being attached) and turned into addresses in place: cell k is read
503
+ before it is written. */
504
+ axis2addr_ctx_t ctx;
505
+ ctx.idx_ca = idx_cast;
506
+ ctx.out_ptr = (ca_size_t *) out_ca->ptr;
507
+ ctx.strides = strides;
508
+ ctx.axis = axis;
509
+ ctx.axis_size = axis_size;
510
+ ctx.mask = NULL;
511
+
512
+ ca_copy_data(idx_cast, out_ca->ptr);
513
+ /* A masked index gives a masked address: readers propagate it (UNDEF in
514
+ that cell), writers skip it. */
515
+ if ( ca_has_mask(idx_cast) ) {
516
+ ca_copy_mask_overlay(out_ca, out_ca->elements, 1, idx_cast);
517
+ ctx.mask = (boolean8_t *) out_ca->mask->ptr;
494
518
  }
519
+ axis2addr_body(&ctx, out_ca->ptr, out_ca->elements);
495
520
 
496
521
  return vout;
497
522
  }
@@ -548,50 +573,24 @@ rb_ca_axis2addr (int argc, VALUE *argv, VALUE self)
548
573
  * numeric, both no-axis and axis: forms.
549
574
  */
550
575
  static VALUE
551
- rb_ca_sorted_view (int argc, VALUE *argv, VALUE self)
576
+ rb_ca_sorted_view_kw (VALUE rkw, VALUE self)
552
577
  {
553
- VALUE rkw = Qnil;
554
578
  VALUE vaxis = Qnil;
555
579
  VALUE vkind = Qnil;
556
580
  VALUE vmasked_position = Qnil;
557
581
 
558
- /* Parse kwargs: `sort` accepts `axis:`, `kind:`, `masked_position:`. */
559
- rb_scan_args(argc, argv, "0:", &rkw);
582
+ /* `sort` accepts `axis:`, `kind:`, `masked_position:`. */
560
583
  rb_scan_options(rkw, "axis,kind,masked_position", &vaxis, &vkind, &vmasked_position);
561
584
 
562
585
  /* Resolve kind: -> do_stable. :quick (default) = introsort; :stable
563
586
  = bottom-up mergesort. Both share the same pair layout and produce
564
587
  identical orderings; only the algorithm differs. */
565
- int do_stable = 0;
566
- if ( !NIL_P(vkind) ) {
567
- static ID sym_quick = 0, sym_stable = 0;
568
- if ( !sym_quick ) sym_quick = rb_intern("quick");
569
- if ( !sym_stable ) sym_stable = rb_intern("stable");
570
- ID kind_id = SYM2ID(vkind);
571
- if ( kind_id == sym_quick ) do_stable = 0;
572
- else if ( kind_id == sym_stable ) do_stable = 1;
573
- else {
574
- rb_raise(rb_eArgError,
575
- "sort: unknown kind %s (expected :quick or :stable)",
576
- rb_id2name(kind_id));
577
- }
578
- }
588
+ int do_stable = NIL_P(vkind) ? 0
589
+ : ca_symbol_choice(vkind, "kind", "quick", "stable", NULL);
579
590
 
580
591
  /* Resolve masked_position: -> masked_last. :last (default) or :first. */
581
- int masked_last = 1;
582
- if ( !NIL_P(vmasked_position) ) {
583
- static ID sym_first = 0, sym_last = 0;
584
- if ( !sym_first ) sym_first = rb_intern("first");
585
- if ( !sym_last ) sym_last = rb_intern("last");
586
- ID mp_id = SYM2ID(vmasked_position);
587
- if ( mp_id == sym_last ) masked_last = 1;
588
- else if ( mp_id == sym_first ) masked_last = 0;
589
- else {
590
- rb_raise(rb_eArgError,
591
- "sort: unknown masked_position %s (expected :first or :last)",
592
- rb_id2name(mp_id));
593
- }
594
- }
592
+ int masked_last = NIL_P(vmasked_position) ? 1
593
+ : ca_symbol_choice(vmasked_position, "masked_position", "first", "last", NULL);
595
594
 
596
595
  CArray *ca;
597
596
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
@@ -600,7 +599,7 @@ rb_ca_sorted_view (int argc, VALUE *argv, VALUE self)
600
599
  before mask handling: rb_ca_flatten re-propagates the mask field
601
600
  from the parent, so any mask stripping must happen on the post-
602
601
  flatten target. */
603
- VALUE target;
602
+ volatile VALUE target; /* cat points into it; the walk can run a GC */
604
603
  VALUE vaxis_use;
605
604
  if ( NIL_P(vaxis) ) {
606
605
  target = rb_ca_flatten(self);
@@ -628,6 +627,14 @@ rb_ca_sorted_view (int argc, VALUE *argv, VALUE self)
628
627
  }
629
628
  }
630
629
 
630
+ static VALUE
631
+ rb_ca_sorted_view (int argc, VALUE *argv, VALUE self)
632
+ {
633
+ VALUE rkw = Qnil;
634
+ rb_scan_args(argc, argv, "0:", &rkw);
635
+ return rb_ca_sorted_view_kw(rkw, self);
636
+ }
637
+
631
638
  /* sort_copy(axis: nil, kind: :quick, masked_position: :last) — eager
632
639
  * counterpart to {sort}. Returns a fresh entity CArray of the same
633
640
  * shape and data_type as self, with elements sorted along the given
@@ -639,12 +646,20 @@ rb_ca_sorted_view (int argc, VALUE *argv, VALUE self)
639
646
  * fiber, no pair struct, no view layer. This fast path is numeric-only
640
647
  * and mask-free (CA_KERNEL_NO_MASK below).
641
648
  *
642
- * CA_FIXLEN and masked input both delegate to {sort} + copy instead of
643
- * duplicating the fixlen dialect / mask-position split in this per-
644
- * fiber loop: masked_position: is forwarded unchanged. Masked cells
645
- * keep their masked-ness (the view's remap gather carries the mask bit
646
- * through, and .copy materializes it), clustered at masked_position:
647
- * within each fiber -- same contract as {sort}.
649
+ * Everything the fast path does not cover -- any data_type outside
650
+ * CA_INT8..CA_FLOAT64 (fixlen, boolean, complex, object), and masked
651
+ * input of any type -- delegates to {sort} + copy rather than duplicate
652
+ * the fixlen dialect / the object comparator / the mask-position split
653
+ * in this per-fiber loop; masked_position: is forwarded unchanged.
654
+ * Masked cells keep their masked-ness (the view's remap gather carries
655
+ * the mask bit through, and .copy materializes it), clustered at
656
+ * masked_position: within each fiber -- same contract as {sort}.
657
+ *
658
+ * So sort_copy answers for exactly what sort answers for, which is what
659
+ * being its eager counterpart means. It once refused everything the
660
+ * fast path could not take: object and boolean sorted through {sort}
661
+ * but not through this, and complex reported its refusal in two
662
+ * different ways depending on which of the pair was asked.
648
663
  */
649
664
  static VALUE
650
665
  rb_ca_sort_copy (int argc, VALUE *argv, VALUE self)
@@ -659,36 +674,28 @@ rb_ca_sort_copy (int argc, VALUE *argv, VALUE self)
659
674
 
660
675
  /* Resolve kind:: :quick (default) = portable textbook quicksort,
661
676
  :stable = portable textbook bottom-up mergesort. */
662
- int do_stable = 0;
663
- if ( !NIL_P(vkind) ) {
664
- static ID sym_quick = 0, sym_stable = 0;
665
- if ( !sym_quick ) sym_quick = rb_intern("quick");
666
- if ( !sym_stable ) sym_stable = rb_intern("stable");
667
- ID kind_id = SYM2ID(vkind);
668
- if ( kind_id == sym_quick ) do_stable = 0;
669
- else if ( kind_id == sym_stable ) do_stable = 1;
670
- else {
671
- rb_raise(rb_eArgError,
672
- "sort_copy: unknown kind %s (expected :quick or :stable)",
673
- rb_id2name(kind_id));
674
- }
677
+ int do_stable = NIL_P(vkind) ? 0
678
+ : ca_symbol_choice(vkind, "kind", "quick", "stable", NULL);
679
+ /* The unmasked path below has no use for masked_position:, but a value
680
+ sort would refuse is refused here too. */
681
+ if ( ! NIL_P(vmasked_position) ) {
682
+ ca_symbol_choice(vmasked_position, "masked_position", "first", "last", NULL);
675
683
  }
676
684
 
677
685
  CArray *ca;
678
686
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
679
687
 
680
- /* CA_FIXLEN and masked input: the per-fiber path below covers
681
- unmasked numeric data types only. Delegate to {sort} (which handles
682
- both the fixlen dialect and the masked_position split) + copy to
683
- get the same shape/class contract as the fast path. */
684
- if ( ca_is_fixlen_type(ca) || ca_has_mask(ca) ) {
685
- VALUE sv_kw = rb_hash_new();
688
+ /* The per-fiber path below covers unmasked CA_INT8..CA_FLOAT64 only.
689
+ Everything else goes to {sort} (which handles the fixlen dialect,
690
+ the object comparator and the masked_position split) + copy, for the
691
+ same shape/class contract as the fast path. */
692
+ if ( ca->data_type < CA_INT8 || ca->data_type > CA_FLOAT64
693
+ || ca_has_mask(ca) ) {
694
+ volatile VALUE sv_kw = rb_hash_new();
686
695
  if ( !NIL_P(vaxis) ) rb_hash_aset(sv_kw, ID2SYM(rb_intern("axis")), vaxis);
687
696
  if ( !NIL_P(vkind) ) rb_hash_aset(sv_kw, ID2SYM(rb_intern("kind")), vkind);
688
697
  if ( !NIL_P(vmasked_position) ) rb_hash_aset(sv_kw, ID2SYM(rb_intern("masked_position")), vmasked_position);
689
- VALUE sv_argv[1] = { sv_kw };
690
- int sv_argc = ( NIL_P(vaxis) && NIL_P(vkind) && NIL_P(vmasked_position) ) ? 0 : 1;
691
- VALUE obj = rb_ca_copy(rb_ca_sorted_view(sv_argc, sv_argv, self));
698
+ VALUE obj = rb_ca_copy(rb_ca_sorted_view_kw(sv_kw, self));
692
699
  CA_FACE_LIFT_IF_FACE(obj, self, ca);
693
700
  return obj;
694
701
  }
@@ -696,7 +703,7 @@ rb_ca_sort_copy (int argc, VALUE *argv, VALUE self)
696
703
  /* Build target view (flatten for no-arg, identity for axis: kwarg).
697
704
  Unmasked past this point (guarded above), so no mask handling is
698
705
  needed here -- see the analogous block in rb_ca_sorted_view. */
699
- VALUE target;
706
+ volatile VALUE target; /* cat points into it; the walk can run a GC */
700
707
  VALUE vaxis_use;
701
708
  if ( NIL_P(vaxis) ) {
702
709
  target = rb_ca_flatten(self);
@@ -710,23 +717,8 @@ rb_ca_sort_copy (int argc, VALUE *argv, VALUE self)
710
717
  TypedData_Get_Struct(target, CArray, &carray_data_type, cat);
711
718
 
712
719
  /* Normalize axis (negative -> +ndim, range check). */
713
- int axis = NUM2INT(vaxis_use);
714
- if ( axis < 0 ) axis += cat->ndim;
715
- if ( axis < 0 || axis >= cat->ndim ) {
716
- rb_raise(rb_eArgError, "sort_copy: axis %d out of range for ndim %d",
717
- NUM2INT(vaxis_use), cat->ndim);
718
- }
719
-
720
- /* data_type check: ALL_NUMERIC only (CA_INT8..CA_FLOAT64).
721
- Complex / object are rejected here; CA_OBJECT goes through the
722
- axis: lift in rb_ca_sorted_view, and complex sort semantics
723
- differ enough that we do not pick a default. */
724
- if ( cat->data_type < CA_INT8 || cat->data_type > CA_FLOAT64 ) {
725
- rb_raise(rb_eCADataTypeError,
726
- "sort_copy: data_type %d not supported "
727
- "(expected one of: i8, u8, i16, u16, i32, u32, i64, u64, f32, f64)",
728
- cat->data_type);
729
- }
720
+ int axis = rb_ca_normalize_axis_for_ndim(ca_axis_integer(vaxis_use, NULL),
721
+ cat->ndim, NULL);
730
722
 
731
723
  /* Allocate output: same shape and data_type as target
732
724
  (bytes=0 = preserve native bytes). */
@@ -751,8 +743,10 @@ rb_ca_sort_copy (int argc, VALUE *argv, VALUE self)
751
743
  the fiber loop and reused across fibers (all fibers share the
752
744
  same axis length and data_type). */
753
745
  void *aux = NULL;
746
+ volatile VALUE aux_holder = 0; /* 0, not Qnil: ALLOCV_END reads a non-zero holder */
754
747
  if ( do_stable ) {
755
- aux = xmalloc((size_t) cat->dim[axis] * (size_t) bytes);
748
+ aux = ALLOCV_N(char, aux_holder,
749
+ (size_t) cat->dim[axis] * (size_t) bytes);
756
750
  }
757
751
  CA_FOR_EACH_FIBER_INOUT(st_in, st_out, cat, cao, (int8_t) axis,
758
752
  CA_KERNEL_NO_MASK, pi, po, n) {
@@ -808,8 +802,9 @@ rb_ca_sort_copy (int argc, VALUE *argv, VALUE self)
808
802
  }
809
803
  }
810
804
  }
811
- if ( aux ) xfree(aux);
805
+ ALLOCV_END(aux_holder);
812
806
 
807
+ RB_GC_GUARD(target);
813
808
  return vout;
814
809
  }
815
810