carray 3.0.2 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +568 -0
  3. data/README.md +6 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_descriptor.h +27 -24
  6. data/ext/ca_axis_dispatch.c +220 -0
  7. data/ext/ca_axis_group.c +201 -123
  8. data/ext/ca_binop_dispatch.c +46 -52
  9. data/ext/ca_binop_dispatch.h +1 -1
  10. data/ext/ca_categorical_iterator.c +249 -183
  11. data/ext/ca_compare.c +7 -12
  12. data/ext/ca_for_buffer.h +21 -68
  13. data/ext/ca_group_iter.c +38 -23
  14. data/ext/ca_kernel_iterator.c +1765 -2076
  15. data/ext/ca_kernel_iterator.h +584 -566
  16. data/ext/ca_monop_dispatch.c +86 -63
  17. data/ext/ca_monop_dispatch.h +5 -0
  18. data/ext/ca_obj_array.c +223 -56
  19. data/ext/ca_obj_bincmp.c +172 -138
  20. data/ext/ca_obj_binop.c +116 -230
  21. data/ext/ca_obj_bitarray.c +40 -26
  22. data/ext/ca_obj_bitfield.c +100 -68
  23. data/ext/ca_obj_block.c +19 -9
  24. data/ext/ca_obj_byte_swap.c +45 -46
  25. data/ext/ca_obj_const_string.c +269 -44
  26. data/ext/ca_obj_face.c +612 -89
  27. data/ext/ca_obj_face.h +111 -47
  28. data/ext/ca_obj_fake.c +36 -79
  29. data/ext/ca_obj_farray.c +7 -3
  30. data/ext/ca_obj_field.c +33 -8
  31. data/ext/ca_obj_fixlen_string.c +23 -24
  32. data/ext/ca_obj_grid.c +541 -210
  33. data/ext/ca_obj_meld.c +331 -111
  34. data/ext/ca_obj_moncmp.c +80 -65
  35. data/ext/ca_obj_monop.c +177 -149
  36. data/ext/ca_obj_object.c +664 -248
  37. data/ext/ca_obj_record.c +63 -23
  38. data/ext/ca_obj_reduce.c +18 -16
  39. data/ext/ca_obj_refer.c +140 -58
  40. data/ext/ca_obj_remap.c +85 -24
  41. data/ext/ca_obj_repeat.c +24 -9
  42. data/ext/ca_obj_roll.c +121 -16
  43. data/ext/ca_obj_select.c +61 -58
  44. data/ext/ca_obj_select_axis.c +73 -107
  45. data/ext/ca_obj_shift.c +32 -102
  46. data/ext/ca_obj_stack.c +372 -122
  47. data/ext/ca_obj_stride.c +348 -140
  48. data/ext/ca_obj_string.c +88 -22
  49. data/ext/ca_obj_tile.c +38 -21
  50. data/ext/ca_obj_time.c +45 -31
  51. data/ext/ca_obj_timedelta.c +43 -29
  52. data/ext/ca_obj_transpose.c +17 -6
  53. data/ext/ca_obj_triop.c +125 -141
  54. data/ext/ca_obj_window.c +330 -44
  55. data/ext/ca_op_ipower.c +24 -11
  56. data/ext/ca_op_powi.h +57 -30
  57. data/ext/ca_pow5_table.h +656 -0
  58. data/ext/ca_sort_kernels.h +70 -0
  59. data/ext/ca_sweep_engine.c +104 -81
  60. data/ext/ca_sweep_engine.h +7 -20
  61. data/ext/ca_triop_dispatch.c +4 -1
  62. data/ext/ca_triop_dispatch.h +5 -2
  63. data/ext/caframe_csv_reader.c +542 -0
  64. data/ext/carray.h +241 -92
  65. data/ext/carray_access.c +608 -276
  66. data/ext/carray_address_basis.c +494 -97
  67. data/ext/carray_attribute.c +129 -4
  68. data/ext/carray_bincount.c +39 -7
  69. data/ext/carray_broadcast.c +56 -19
  70. data/ext/carray_build_flags.h +1 -1
  71. data/ext/carray_call_cfunc.c +108 -28
  72. data/ext/carray_cast.c +1576 -223
  73. data/ext/carray_cast_func.rb +77 -24
  74. data/ext/carray_class.c +4 -3
  75. data/ext/carray_conversion.c +208 -75
  76. data/ext/carray_copy.c +82 -24
  77. data/ext/carray_core.c +646 -162
  78. data/ext/carray_count.c +171 -88
  79. data/ext/carray_element.c +94 -60
  80. data/ext/carray_factorize.c +670 -324
  81. data/ext/carray_generate.c +260 -83
  82. data/ext/carray_histogram.c +209 -106
  83. data/ext/carray_hold.c +14 -16
  84. data/ext/carray_index_classifier.c +422 -243
  85. data/ext/carray_internal.h +692 -3
  86. data/ext/carray_kernels_bincmp.c +7 -0
  87. data/ext/carray_kernels_binop.c +487 -120
  88. data/ext/carray_kernels_init.c +653 -50
  89. data/ext/carray_kernels_moncmp.c +15 -8
  90. data/ext/carray_kernels_monop.c +99 -43
  91. data/ext/carray_kernels_reduce_aggregate.c +3925 -6112
  92. data/ext/carray_kernels_reduce_boolean.c +112 -36
  93. data/ext/carray_kernels_reduce_cumulative.c +2127 -6903
  94. data/ext/carray_kernels_reduce_extreme.c +3032 -2892
  95. data/ext/carray_kernels_reduce_variance.c +439 -500
  96. data/ext/carray_kernels_scan.c +1097 -488
  97. data/ext/carray_kernels_search.c +3787 -2176
  98. data/ext/carray_kernels_sort.c +1200 -920
  99. data/ext/carray_kernels_triop.c +479 -7
  100. data/ext/carray_key_windows.c +216 -0
  101. data/ext/carray_lazy.c +967 -153
  102. data/ext/carray_loop.c +81 -65
  103. data/ext/carray_mask.c +910 -324
  104. data/ext/carray_math_kernel.h +1 -0
  105. data/ext/carray_median_percentile.c +299 -100
  106. data/ext/carray_memory_view.c +872 -758
  107. data/ext/carray_operator.c +1171 -364
  108. data/ext/carray_order.c +505 -133
  109. data/ext/carray_partition.c +112 -65
  110. data/ext/carray_random.c +327 -239
  111. data/ext/carray_round.c +273 -0
  112. data/ext/carray_scatter.c +346 -78
  113. data/ext/carray_segment.c +257 -0
  114. data/ext/carray_slab.c +249 -86
  115. data/ext/carray_sort.c +224 -227
  116. data/ext/carray_sort_kernel.c +8 -1
  117. data/ext/carray_struct.c +118 -39
  118. data/ext/carray_test.c +193 -69
  119. data/ext/carray_time_text.c +1182 -0
  120. data/ext/carray_undef.c +4 -0
  121. data/ext/carray_utils.c +579 -113
  122. data/ext/extconf.rb +27 -2
  123. data/ext/mk_call_cfunc.rb +19 -4
  124. data/ext/mk_pow5_table.rb +35 -0
  125. data/ext/mkkernel.rb +1202 -1014
  126. data/ext/ruby_carray.c +17 -5
  127. data/ext/version.h +4 -4
  128. data/ext/version.rb +4 -2
  129. data/lib/carray/attribute.rb +160 -17
  130. data/lib/carray/autoload_carray.rb +23 -27
  131. data/lib/carray/autoload_method_extension.rb +20 -5
  132. data/lib/carray/axis_group.rb +360 -63
  133. data/lib/carray/basics.rb +162 -28
  134. data/lib/carray/bincount_nd.rb +18 -2
  135. data/lib/carray/block_iterator.rb +231 -58
  136. data/lib/carray/boolean_reduce.rb +34 -42
  137. data/lib/carray/categorical.rb +242 -52
  138. data/lib/carray/categorical_iterator.rb +437 -827
  139. data/lib/carray/complex.rb +41 -34
  140. data/lib/carray/conditional.rb +150 -14
  141. data/lib/carray/const_string.rb +59 -4
  142. data/lib/carray/construct.rb +15 -11
  143. data/lib/carray/data_type_extension.rb +1 -0
  144. data/lib/carray/distance_limit.rb +77 -0
  145. data/lib/carray/fixlen_string.rb +7 -1
  146. data/lib/carray/frame/column_verbs.rb +70 -0
  147. data/lib/carray/frame/concat.rb +157 -6
  148. data/lib/carray/frame/convert.rb +82 -7
  149. data/lib/carray/frame/csv_parser.rb +688 -87
  150. data/lib/carray/frame/describe.rb +80 -0
  151. data/lib/carray/frame/frame.rb +369 -45
  152. data/lib/carray/frame/group.rb +631 -90
  153. data/lib/carray/frame/io.rb +506 -79
  154. data/lib/carray/frame/join.rb +411 -62
  155. data/lib/carray/frame/records.rb +116 -19
  156. data/lib/carray/frame/reshape.rb +455 -0
  157. data/lib/carray/frame/sort.rb +30 -18
  158. data/lib/carray/frame/verbs.rb +868 -61
  159. data/lib/carray/frame/windows.rb +117 -0
  160. data/lib/carray/frame.rb +4 -0
  161. data/lib/carray/fuse_source.rb +118 -2
  162. data/lib/carray/fusion.rb +257 -42
  163. data/lib/carray/histogram.rb +6 -1
  164. data/lib/carray/inspect.rb +54 -13
  165. data/lib/carray/iterator.rb +99 -9
  166. data/lib/carray/key_windows.rb +620 -0
  167. data/lib/carray/lazy.rb +216 -193
  168. data/lib/carray/mask_gap_fill.rb +112 -92
  169. data/lib/carray/math.rb +32 -19
  170. data/lib/carray/meld_reduce.rb +52 -21
  171. data/lib/carray/methods/align_addr.rb +8 -4
  172. data/lib/carray/methods/bin.rb +26 -18
  173. data/lib/carray/methods/bincount.rb +18 -2
  174. data/lib/carray/methods/bit_string.rb +15 -4
  175. data/lib/carray/methods/choose.rb +16 -3
  176. data/lib/carray/methods/composition.rb +18 -10
  177. data/lib/carray/methods/factorize.rb +2 -2
  178. data/lib/carray/methods/gather_nd.rb +25 -5
  179. data/lib/carray/methods/insert_block.rb +9 -14
  180. data/lib/carray/methods/is_between.rb +59 -0
  181. data/lib/carray/methods/is_in.rb +140 -10
  182. data/lib/carray/methods/join.rb +2 -7
  183. data/lib/carray/methods/lag.rb +49 -0
  184. data/lib/carray/methods/locate_addr.rb +182 -45
  185. data/lib/carray/methods/mode.rb +18 -1
  186. data/lib/carray/methods/nunique.rb +11 -6
  187. data/lib/carray/methods/repeat.rb +1 -20
  188. data/lib/carray/methods/resize.rb +21 -23
  189. data/lib/carray/methods/snap.rb +34 -12
  190. data/lib/carray/methods/unique.rb +11 -2
  191. data/lib/carray/methods/value_counts.rb +4 -11
  192. data/lib/carray/record_order.rb +208 -0
  193. data/lib/carray/rng.rb +3 -0
  194. data/lib/carray/runtime.rb +65 -17
  195. data/lib/carray/segment_iterator.rb +1221 -0
  196. data/lib/carray/serialize.rb +214 -30
  197. data/lib/carray/slab_iterator.rb +101 -27
  198. data/lib/carray/stack.rb +10 -4
  199. data/lib/carray/string.rb +105 -5
  200. data/lib/carray/string_operation_extension.rb +12 -4
  201. data/lib/carray/struct.rb +137 -58
  202. data/lib/carray/struct_builder.rb +124 -17
  203. data/lib/carray/time.rb +1222 -186
  204. data/lib/carray/window_iterator.rb +242 -81
  205. data/lib/carray.rb +9 -2
  206. data/yard-stubs/ca_obj_array.rb +4 -4
  207. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  208. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  209. data/yard-stubs/ca_obj_fake.rb +1 -1
  210. data/yard-stubs/ca_obj_field.rb +5 -1
  211. data/yard-stubs/ca_obj_grid.rb +21 -10
  212. data/yard-stubs/ca_obj_refer.rb +5 -4
  213. data/yard-stubs/ca_obj_roll.rb +2 -3
  214. data/yard-stubs/ca_obj_shift.rb +5 -1
  215. data/yard-stubs/ca_obj_stride.rb +10 -7
  216. data/yard-stubs/ca_obj_tile.rb +1 -1
  217. data/yard-stubs/ca_obj_transpose.rb +1 -1
  218. data/yard-stubs/ca_obj_window.rb +9 -5
  219. data/yard-stubs/carray_access.rb +11 -5
  220. data/yard-stubs/carray_attribute.rb +0 -5
  221. data/yard-stubs/carray_broadcast.rb +1 -1
  222. data/yard-stubs/carray_cast.rb +88 -43
  223. data/yard-stubs/carray_conversion.rb +15 -2
  224. data/yard-stubs/carray_copy.rb +12 -5
  225. data/yard-stubs/carray_core.rb +3 -3
  226. data/yard-stubs/carray_count.rb +9 -0
  227. data/yard-stubs/carray_element.rb +10 -4
  228. data/yard-stubs/carray_generate.rb +21 -1
  229. data/yard-stubs/carray_lazy.rb +5 -1
  230. data/yard-stubs/carray_loop.rb +4 -1
  231. data/yard-stubs/carray_mask.rb +60 -12
  232. data/yard-stubs/carray_math.rb +52 -14
  233. data/yard-stubs/carray_median_percentile.rb +15 -8
  234. data/yard-stubs/carray_memory_view.rb +40 -23
  235. data/yard-stubs/carray_order.rb +61 -29
  236. data/yard-stubs/carray_random.rb +13 -4
  237. data/yard-stubs/carray_scatter.rb +21 -9
  238. data/yard-stubs/carray_segment.rb +72 -0
  239. data/yard-stubs/carray_slab.rb +10 -2
  240. data/yard-stubs/carray_sort.rb +49 -8
  241. data/yard-stubs/carray_test.rb +15 -3
  242. data/yard-stubs/carray_utils.rb +0 -26
  243. metadata +19 -5
  244. data/ext/ca_for_each_element.h +0 -239
  245. data/ext/carray_kernels_map.c +0 -3467
  246. data/lib/carray/arrow_tensor.rb +0 -401
  247. data/lib/carray/table.rb +0 -108
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,47 @@ 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. */
155
175
  TypedData_Get_Struct(argv[j], CArray, &carray_data_type, ca_j);
156
176
  if ( ca_is_face(ca_j) ) {
157
- int orderable = ca_test_flag(ca_j, CA_FLAG_FACE_ORDERABLE_STORAGE);
158
- VALUE stripped = rb_ca_strip_face_value(argv[j]);
159
- CArray *sc;
160
- 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]));
177
+ argv[j] = ca_order_key(argv[j], "sort_addr");
178
+ continue;
179
+ }
180
+ {
181
+ CArray *k;
182
+ TypedData_Get_Struct(argv[j], CArray, &carray_data_type, k);
183
+ if ( ca_is_complex_type(k) ) {
184
+ rb_raise(rb_eCADataTypeError,
185
+ "sort_addr: a %s key has no order (complex numbers are not "
186
+ "ordered); sort by its real part, imaginary part or abs",
187
+ ca_type_name[k->data_type]);
166
188
  }
167
- argv[j] = stripped;
168
189
  }
169
190
  }
170
191
 
171
- base = xmalloc(sizeof(struct cmp_base));
192
+ /* Scratch the unwind collects: the keys are read inside an attach
193
+ window, and a comparison of object keys can raise. */
172
194
  base->n = argc;
173
195
  base->masked_last = masked_last;
174
- base->ca = xmalloc(sizeof(CArray *)*base->n);
196
+ base->ca = ALLOCV_N(CArray *, hlist, base->n);
197
+ modes = ALLOCV_N(char, hmodes, argc + 1);
175
198
 
176
199
  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);
200
+ TypedData_Get_Struct(argv[j], CArray, &carray_data_type, base->ca[j]);
201
+ has_object |= ( base->ca[j]->data_type == CA_OBJECT );
202
+ modes[j] = 'r';
181
203
  }
204
+ modes[argc] = '\0';
182
205
 
183
- data = xmalloc(sizeof(struct sort_addr_key)*elements);
206
+ data = ALLOCV_N(struct sort_addr_key, hdata, elements);
184
207
  for (i=0; i<elements; i++) {
185
208
  data[i].i = i;
186
209
  data[i].base = base;
187
210
  }
188
211
 
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
212
+ run.data = data;
213
+ run.aux = has_object ? ALLOCV_N(struct sort_addr_key, haux, elements) : NULL;
214
+ run.elements = elements;
215
+ ca_attach_window(argc, base->ca, modes, sort_addr_run, (VALUE) &run);
196
216
 
197
217
  out = rb_ca_template_with_type(argv[0], INT2NUM(CA_SIZE), INT2NUM(0));
198
218
  TypedData_Get_Struct(out, CArray, &carray_data_type, co);
@@ -203,27 +223,26 @@ rb_ca_s_sort_addr (int argc, VALUE *argv, VALUE self)
203
223
  q++;
204
224
  }
205
225
 
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);
226
+ ALLOCV_END(haux);
227
+ ALLOCV_END(hdata);
228
+ ALLOCV_END(hmodes);
229
+ ALLOCV_END(hlist);
213
230
 
214
231
  return out;
215
232
  }
216
233
 
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);
234
+ static VALUE
235
+ rb_ca_s_sort_addr (int argc, VALUE *argv, VALUE self)
236
+ {
237
+ VALUE ropt = rb_pop_options(&argc, &argv);
238
+ return rb_ca_s_sort_addr_kw(argc, argv, ropt, self);
239
+ }
240
+
241
+ /* Internal sort_addr kernel entries (C-level only, no Ruby binding;
242
+ * defined in the generated carray_kernels_sort.c). The _quick and
243
+ * _stable variants implement the kind: dispatch; both take an explicit
244
+ * masked_last so `sort` / `sort_copy` can pass the masked_position:
245
+ * kwarg through (see MkKernel.sort in mkkernel.rb). */
227
246
  extern VALUE rb_ca_sort_addr_ki_quick_mp (VALUE self, VALUE vaxis, int masked_last);
228
247
  extern VALUE rb_ca_sort_addr_ki_stable_mp (VALUE self, VALUE vaxis, int masked_last);
229
248
  extern VALUE rb_ca_remap_new (VALUE cary, VALUE rmapper); /* ca_obj_remap.c (sort view) */
@@ -274,16 +293,12 @@ rb_ca_sort_addr_c (VALUE self, VALUE axis, int stable, int masked_last)
274
293
  if ( NIL_P(axis) ) {
275
294
  /* Legacy flat: equivalent to CArray.sort_addr(self, masked_position:).
276
295
  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();
296
+ internally). */
297
+ volatile VALUE kw = rb_hash_new();
283
298
  rb_hash_aset(kw, ID2SYM(rb_intern("masked_position")),
284
299
  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);
300
+ VALUE flat_argv[1] = { self };
301
+ return rb_ca_s_sort_addr_kw(1, flat_argv, kw, rb_cCArray);
287
302
  }
288
303
  return stable ? rb_ca_sort_addr_ki_stable_mp(self, axis, masked_last)
289
304
  : rb_ca_sort_addr_ki_quick_mp (self, axis, masked_last);
@@ -302,35 +317,11 @@ rb_ca_sort_addr (int argc, VALUE *argv, VALUE self)
302
317
  rb_scan_args(argc, argv, "0:", &opts);
303
318
  rb_scan_options(opts, "axis,kind,masked_position", &axis, &kind, &vmasked_position);
304
319
 
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
- }
320
+ int do_stable = NIL_P(kind) ? 0
321
+ : ca_symbol_choice(kind, "kind", "quick", "stable", NULL);
319
322
 
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
- }
323
+ int masked_last = NIL_P(vmasked_position) ? 1
324
+ : ca_symbol_choice(vmasked_position, "masked_position", "first", "last", NULL);
334
325
 
335
326
  return rb_ca_sort_addr_c(self, axis, do_stable, masked_last);
336
327
  }
@@ -362,12 +353,58 @@ rb_ca_sort_addr (int argc, VALUE *argv, VALUE self)
362
353
  *
363
354
  * indices data_type: any integer kind (zero-copy if already CA_SIZE).
364
355
  * Negative indices: Python-style (-1 = last), normalized internally.
365
- * OOB indices: raises RangeError. Default axis: 0; negative axis
356
+ * OOB indices: raises IndexError. Default axis: 0; negative axis
366
357
  * Python-style.
367
358
  *
368
359
  * Returns: CArray of CA_SIZE, same shape as indices.
369
360
  */
370
361
 
362
+ typedef struct {
363
+ CArray *idx_ca;
364
+ ca_size_t *out_ptr;
365
+ ca_size_t *strides;
366
+ int axis;
367
+ ca_size_t axis_size;
368
+ boolean8_t *mask; /* the indices' mask, or NULL */
369
+ } axis2addr_ctx_t;
370
+
371
+ /* Body of rb_ca_axis2addr_c: one flat address per index cell. */
372
+ static void
373
+ axis2addr_body (void *user_data, void *ptr, ca_size_t n)
374
+ {
375
+ axis2addr_ctx_t *c = (axis2addr_ctx_t *) user_data;
376
+ ca_size_t *idx_ptr = (ca_size_t *) ptr;
377
+ int ndim = c->idx_ca->ndim;
378
+ ca_size_t coord[CA_RANK_MAX];
379
+
380
+ for ( int8_t j = 0; j < ndim; j++ ) coord[j] = 0;
381
+ for ( ca_size_t k = 0; k < n; k++ ) {
382
+ /* A masked index has no address: the cell keeps the mask (copied from
383
+ the indices) and is skipped here, its stored value unread. */
384
+ if ( c->mask && c->mask[k] ) {
385
+ c->out_ptr[k] = 0;
386
+ goto advance;
387
+ }
388
+ ca_size_t norm = ca_index_in_range(idx_ptr[k], c->axis_size, "index");
389
+ /* Compute flat addr: sum c_j * stride_j (with c_axis = norm). */
390
+ ca_size_t addr = 0;
391
+ for ( int8_t j = 0; j < ndim; j++ ) {
392
+ if ( j == c->axis ) {
393
+ addr += norm * c->strides[j];
394
+ } else {
395
+ addr += coord[j] * c->strides[j];
396
+ }
397
+ }
398
+ c->out_ptr[k] = addr;
399
+ advance:
400
+ /* Advance coord row-major (last axis ticks fastest). */
401
+ for ( int8_t j = (int8_t)(ndim - 1); j >= 0; j-- ) {
402
+ if ( ++coord[j] < c->idx_ca->dim[j] ) break;
403
+ coord[j] = 0;
404
+ }
405
+ }
406
+ }
407
+
371
408
  /* C-callable entry: skip rb_scan_args (call-frame state dependency).
372
409
  * vaxis = Qnil treated as axis 0; Integer is taken as-is (negative
373
410
  * axis normalized internally).
@@ -384,38 +421,34 @@ rb_ca_axis2addr_c (VALUE self, VALUE vindices, VALUE vaxis)
384
421
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
385
422
  TypedData_Get_Struct(vindices, CArray, &carray_data_type, idx_ca);
386
423
 
387
- /* axis normalization + range check. */
388
- int axis_raw = NIL_P(vaxis) ? 0 : NUM2INT(vaxis);
389
- int axis = (axis_raw < 0) ? ((int) ca->ndim + axis_raw) : axis_raw;
390
- 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);
394
- }
424
+ long axis_l = ca_normalize_axis_arg(NIL_P(vaxis) ? INT2FIX(0) : vaxis,
425
+ (int) ca->ndim, NULL);
426
+ int axis = (int) axis_l;
395
427
 
396
428
  /* Shape rule: indices.ndim == self.ndim, dims match except at axis. */
397
429
  if ( idx_ca->ndim != ca->ndim ) {
398
430
  rb_raise(rb_eArgError,
399
- "axis2addr: indices.ndim (%d) must equal self.ndim (%d)",
400
- (int) idx_ca->ndim, (int) ca->ndim);
431
+ "%s: indices.ndim (%d) must equal self.ndim (%d)",
432
+ ca_calling_method_name(), (int) idx_ca->ndim, (int) ca->ndim);
401
433
  }
402
434
  for ( int8_t j = 0; j < ca->ndim; j++ ) {
403
435
  if ( j == axis ) continue;
404
436
  if ( idx_ca->dim[j] != ca->dim[j] ) {
405
437
  rb_raise(rb_eArgError,
406
- "axis2addr: indices.dim[%d] (%lld) must equal "
438
+ "%s: indices.dim[%d] (%lld) must equal "
407
439
  "self.dim[%d] (%lld)",
408
- (int) j, (long long) idx_ca->dim[j],
440
+ ca_calling_method_name(), (int) j, (long long) idx_ca->dim[j],
409
441
  (int) j, (long long) ca->dim[j]);
410
442
  }
411
443
  }
412
444
 
413
445
  /* indices data_type: integer kind accepted; cast to CA_SIZE if needed
414
446
  (zero-copy when already CA_SIZE via to_type identity). */
447
+ ca_check_index_array(vindices);
415
448
  if ( ! ca_is_integer_type(idx_ca) ) {
416
449
  rb_raise(rb_eArgError,
417
- "axis2addr: indices data_type must be integer kind (got %d)",
418
- (int) idx_ca->data_type);
450
+ "%s: indices data_type must be integer kind (got %s)",
451
+ ca_calling_method_name(), ca_type_name[idx_ca->data_type]);
419
452
  }
420
453
  volatile VALUE vidx_cast = vindices;
421
454
  CArray *idx_cast = idx_ca;
@@ -439,59 +472,26 @@ rb_ca_axis2addr_c (VALUE self, VALUE vindices, VALUE vaxis)
439
472
  rb_ca_template_with_type(vidx_cast, INT2NUM(CA_SIZE), INT2NUM(0));
440
473
  TypedData_Get_Struct(vout, CArray, &carray_data_type, out_ca);
441
474
 
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);
475
+ /* out_ca is a freshly allocated entity of the indices' shape and cell
476
+ width, so the indices are copied into it (a view is gathered without
477
+ being attached) and turned into addresses in place: cell k is read
478
+ before it is written. */
479
+ axis2addr_ctx_t ctx;
480
+ ctx.idx_ca = idx_cast;
481
+ ctx.out_ptr = (ca_size_t *) out_ca->ptr;
482
+ ctx.strides = strides;
483
+ ctx.axis = axis;
484
+ ctx.axis_size = axis_size;
485
+ ctx.mask = NULL;
486
+
487
+ ca_copy_data(idx_cast, out_ca->ptr);
488
+ /* A masked index gives a masked address: readers propagate it (UNDEF in
489
+ that cell), writers skip it. */
490
+ if ( ca_has_mask(idx_cast) ) {
491
+ ca_copy_mask_overlay(out_ca, out_ca->elements, 1, idx_cast);
492
+ ctx.mask = (boolean8_t *) out_ca->mask->ptr;
494
493
  }
494
+ axis2addr_body(&ctx, out_ca->ptr, out_ca->elements);
495
495
 
496
496
  return vout;
497
497
  }
@@ -510,6 +510,31 @@ rb_ca_axis2addr (int argc, VALUE *argv, VALUE self)
510
510
  }
511
511
 
512
512
 
513
+ /* The array the order of v is read from, for a method that orders v's
514
+ cells (a sort, a selection, a ranking, an extremum by key). A plain
515
+ array is its own key. An ORDERABLE Face orders by its storage, so the
516
+ key is the storage. A Face that is not ORDERABLE but defines its own
517
+ #order (CAConstString, whose cells are offsets; CARecord, whose bytes
518
+ are no order) is keyed by that order, as a dense rank, so ties stay ties
519
+ and a masked cell stays masked. A Face that has no order (a CARecord
520
+ without order_by:) raises in that order; any other Face raises here. */
521
+ VALUE
522
+ ca_order_key (VALUE v, const char *name)
523
+ {
524
+ CArray *ca;
525
+ TypedData_Get_Struct(v, CArray, &carray_data_type, ca);
526
+ if ( ! ca_is_face(ca) ) {
527
+ return v;
528
+ }
529
+ if ( ca_face_orders_by_itself(v) ) {
530
+ VALUE opts = rb_hash_new();
531
+ rb_hash_aset(opts, ID2SYM(rb_intern("method")), ID2SYM(rb_intern("dense")));
532
+ return rb_funcallv_kw(v, rb_intern("order"), 1, &opts, RB_PASS_KEYWORDS);
533
+ }
534
+ ca_face_require_orderable(v, name, "order");
535
+ return rb_ca_strip_face_value(v);
536
+ }
537
+
513
538
  /* ===== sort / sort_copy (value & view surface, sibling of the
514
539
  sort_addr / axis2addr block above). =============== */
515
540
 
@@ -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
@@ -667,19 +674,12 @@ rb_ca_sort_copy (int argc, VALUE *argv, VALUE self)
667
674
 
668
675
  /* Resolve kind:: :quick (default) = portable textbook quicksort,
669
676
  :stable = portable textbook bottom-up mergesort. */
670
- int do_stable = 0;
671
- if ( !NIL_P(vkind) ) {
672
- static ID sym_quick = 0, sym_stable = 0;
673
- if ( !sym_quick ) sym_quick = rb_intern("quick");
674
- if ( !sym_stable ) sym_stable = rb_intern("stable");
675
- ID kind_id = SYM2ID(vkind);
676
- if ( kind_id == sym_quick ) do_stable = 0;
677
- else if ( kind_id == sym_stable ) do_stable = 1;
678
- else {
679
- rb_raise(rb_eArgError,
680
- "sort_copy: unknown kind %s (expected :quick or :stable)",
681
- rb_id2name(kind_id));
682
- }
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);
683
683
  }
684
684
 
685
685
  CArray *ca;
@@ -691,13 +691,11 @@ rb_ca_sort_copy (int argc, VALUE *argv, VALUE self)
691
691
  same shape/class contract as the fast path. */
692
692
  if ( ca->data_type < CA_INT8 || ca->data_type > CA_FLOAT64
693
693
  || ca_has_mask(ca) ) {
694
- VALUE sv_kw = rb_hash_new();
694
+ volatile VALUE sv_kw = rb_hash_new();
695
695
  if ( !NIL_P(vaxis) ) rb_hash_aset(sv_kw, ID2SYM(rb_intern("axis")), vaxis);
696
696
  if ( !NIL_P(vkind) ) rb_hash_aset(sv_kw, ID2SYM(rb_intern("kind")), vkind);
697
697
  if ( !NIL_P(vmasked_position) ) rb_hash_aset(sv_kw, ID2SYM(rb_intern("masked_position")), vmasked_position);
698
- VALUE sv_argv[1] = { sv_kw };
699
- int sv_argc = ( NIL_P(vaxis) && NIL_P(vkind) && NIL_P(vmasked_position) ) ? 0 : 1;
700
- 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));
701
699
  CA_FACE_LIFT_IF_FACE(obj, self, ca);
702
700
  return obj;
703
701
  }
@@ -705,7 +703,7 @@ rb_ca_sort_copy (int argc, VALUE *argv, VALUE self)
705
703
  /* Build target view (flatten for no-arg, identity for axis: kwarg).
706
704
  Unmasked past this point (guarded above), so no mask handling is
707
705
  needed here -- see the analogous block in rb_ca_sorted_view. */
708
- VALUE target;
706
+ volatile VALUE target; /* cat points into it; the walk can run a GC */
709
707
  VALUE vaxis_use;
710
708
  if ( NIL_P(vaxis) ) {
711
709
  target = rb_ca_flatten(self);
@@ -719,12 +717,7 @@ rb_ca_sort_copy (int argc, VALUE *argv, VALUE self)
719
717
  TypedData_Get_Struct(target, CArray, &carray_data_type, cat);
720
718
 
721
719
  /* Normalize axis (negative -> +ndim, range check). */
722
- int axis = NUM2INT(vaxis_use);
723
- if ( axis < 0 ) axis += cat->ndim;
724
- if ( axis < 0 || axis >= cat->ndim ) {
725
- rb_raise(rb_eArgError, "sort_copy: axis %d out of range for ndim %d",
726
- NUM2INT(vaxis_use), cat->ndim);
727
- }
720
+ int axis = ca_normalize_axis_arg(vaxis_use, cat->ndim, NULL);
728
721
 
729
722
  /* Allocate output: same shape and data_type as target
730
723
  (bytes=0 = preserve native bytes). */
@@ -749,8 +742,10 @@ rb_ca_sort_copy (int argc, VALUE *argv, VALUE self)
749
742
  the fiber loop and reused across fibers (all fibers share the
750
743
  same axis length and data_type). */
751
744
  void *aux = NULL;
745
+ volatile VALUE aux_holder = 0; /* 0, not Qnil: ALLOCV_END reads a non-zero holder */
752
746
  if ( do_stable ) {
753
- aux = xmalloc((size_t) cat->dim[axis] * (size_t) bytes);
747
+ aux = ALLOCV_N(char, aux_holder,
748
+ (size_t) cat->dim[axis] * (size_t) bytes);
754
749
  }
755
750
  CA_FOR_EACH_FIBER_INOUT(st_in, st_out, cat, cao, (int8_t) axis,
756
751
  CA_KERNEL_NO_MASK, pi, po, n) {
@@ -806,8 +801,10 @@ rb_ca_sort_copy (int argc, VALUE *argv, VALUE self)
806
801
  }
807
802
  }
808
803
  }
809
- if ( aux ) xfree(aux);
804
+ ALLOCV_END(aux_holder);
810
805
 
806
+ RB_GC_GUARD(target);
807
+ rb_ca_inherit_attr(vout, self);
811
808
  return vout;
812
809
  }
813
810