carray 3.0.6 → 3.0.7

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 (139) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +110 -0
  3. data/README.md +24 -3
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_group.c +6 -6
  6. data/ext/ca_binop_dispatch.c +8 -0
  7. data/ext/ca_binop_dispatch.h +1 -0
  8. data/ext/ca_categorical_iterator.c +7 -7
  9. data/ext/ca_kernel_iterator.h +6 -4
  10. data/ext/ca_obj_array.c +4 -4
  11. data/ext/ca_obj_bincmp.c +19 -3
  12. data/ext/ca_obj_binop.c +36 -11
  13. data/ext/ca_obj_bitarray.c +2 -4
  14. data/ext/ca_obj_bitfield.c +86 -4
  15. data/ext/ca_obj_byte_swap.c +2 -4
  16. data/ext/ca_obj_const_string.c +422 -257
  17. data/ext/ca_obj_face.c +71 -33
  18. data/ext/ca_obj_fake.c +112 -13
  19. data/ext/ca_obj_farray.c +7 -1
  20. data/ext/ca_obj_fixlen_string.c +51 -17
  21. data/ext/ca_obj_grid.c +12 -7
  22. data/ext/ca_obj_meld.c +5 -5
  23. data/ext/ca_obj_monop.c +65 -39
  24. data/ext/ca_obj_object.c +9 -9
  25. data/ext/ca_obj_reduce.c +2 -4
  26. data/ext/ca_obj_refer.c +104 -10
  27. data/ext/ca_obj_repeat.c +1 -1
  28. data/ext/ca_obj_roll.c +2 -4
  29. data/ext/ca_obj_select.c +8 -6
  30. data/ext/ca_obj_select_axis.c +8 -6
  31. data/ext/ca_obj_stack.c +5 -5
  32. data/ext/ca_obj_stride.c +46 -8
  33. data/ext/ca_obj_string.c +2 -2
  34. data/ext/ca_obj_tile.c +2 -4
  35. data/ext/ca_obj_triop.c +15 -12
  36. data/ext/ca_obj_window.c +22 -15
  37. data/ext/ca_op_ipower.c +63 -15
  38. data/ext/ca_op_powi.h +6 -29
  39. data/ext/ca_sort_kernels.h +3 -0
  40. data/ext/ca_sweep_engine.c +90 -23
  41. data/ext/ca_sweep_engine.h +5 -6
  42. data/ext/caframe_csv_reader.c +1 -1
  43. data/ext/carray.h +3 -1
  44. data/ext/carray_access.c +98 -39
  45. data/ext/carray_address_basis.c +98 -24
  46. data/ext/carray_broadcast.c +2 -2
  47. data/ext/carray_call_cfunc.c +5 -8
  48. data/ext/carray_cast.c +311 -37
  49. data/ext/carray_cast_func.rb +37 -5
  50. data/ext/carray_conversion.c +56 -2
  51. data/ext/carray_copy.c +24 -0
  52. data/ext/carray_core.c +69 -6
  53. data/ext/carray_count.c +28 -9
  54. data/ext/carray_element.c +9 -3
  55. data/ext/carray_factorize.c +11 -11
  56. data/ext/carray_histogram.c +2 -2
  57. data/ext/carray_index_classifier.c +7 -5
  58. data/ext/carray_internal.h +94 -0
  59. data/ext/carray_kernels_binop.c +55 -55
  60. data/ext/carray_kernels_init.c +14 -14
  61. data/ext/carray_kernels_monop.c +90 -140
  62. data/ext/carray_lazy.c +16 -1
  63. data/ext/carray_loop.c +9 -2
  64. data/ext/carray_mask.c +118 -11
  65. data/ext/carray_median_percentile.c +13 -8
  66. data/ext/carray_memory_view.c +41 -22
  67. data/ext/carray_operator.c +72 -11
  68. data/ext/carray_order.c +52 -9
  69. data/ext/carray_partition.c +1 -1
  70. data/ext/carray_random.c +1 -1
  71. data/ext/carray_round.c +24 -1
  72. data/ext/carray_scatter.c +4 -0
  73. data/ext/carray_segment.c +1 -1
  74. data/ext/carray_slab.c +13 -4
  75. data/ext/carray_sort.c +6 -3
  76. data/ext/carray_sort_kernel.c +24 -0
  77. data/ext/carray_struct.c +21 -6
  78. data/ext/carray_test.c +85 -39
  79. data/ext/carray_time_text.c +6 -0
  80. data/ext/carray_utils.c +124 -54
  81. data/ext/mk_call_cfunc.rb +6 -11
  82. data/ext/mkkernel.rb +37 -27
  83. data/ext/version.h +4 -4
  84. data/lib/carray/axis_group.rb +9 -6
  85. data/lib/carray/basics.rb +26 -8
  86. data/lib/carray/bincount_nd.rb +8 -2
  87. data/lib/carray/block_iterator.rb +48 -21
  88. data/lib/carray/categorical.rb +21 -8
  89. data/lib/carray/categorical_iterator.rb +2 -1
  90. data/lib/carray/conditional.rb +52 -4
  91. data/lib/carray/const_string.rb +74 -0
  92. data/lib/carray/construct.rb +2 -2
  93. data/lib/carray/core_extensions.rb +19 -68
  94. data/lib/carray/data_type_extension.rb +1 -0
  95. data/lib/carray/fixlen_string.rb +83 -1
  96. data/lib/carray/frame/column_verbs.rb +61 -6
  97. data/lib/carray/frame/concat.rb +50 -6
  98. data/lib/carray/frame/csv_parser.rb +13 -4
  99. data/lib/carray/frame/frame.rb +38 -6
  100. data/lib/carray/frame/group.rb +67 -40
  101. data/lib/carray/frame/io.rb +10 -5
  102. data/lib/carray/frame/join.rb +20 -6
  103. data/lib/carray/frame/records.rb +6 -4
  104. data/lib/carray/frame/reshape.rb +25 -5
  105. data/lib/carray/frame/verbs.rb +78 -25
  106. data/lib/carray/frame/windows.rb +30 -6
  107. data/lib/carray/fuse_source.rb +4 -2
  108. data/lib/carray/iterator.rb +8 -0
  109. data/lib/carray/key_windows.rb +10 -4
  110. data/lib/carray/lazy.rb +12 -17
  111. data/lib/carray/methods/bin.rb +3 -0
  112. data/lib/carray/methods/composition.rb +1 -1
  113. data/lib/carray/methods/factorize.rb +2 -0
  114. data/lib/carray/methods/gather_nd.rb +4 -1
  115. data/lib/carray/methods/is_in.rb +23 -3
  116. data/lib/carray/methods/locate_addr.rb +63 -12
  117. data/lib/carray/methods/repeat.rb +11 -1
  118. data/lib/carray/record_order.rb +35 -6
  119. data/lib/carray/runtime.rb +10 -0
  120. data/lib/carray/segment_iterator.rb +19 -16
  121. data/lib/carray/serialize.rb +22 -0
  122. data/lib/carray/slab_iterator.rb +1 -1
  123. data/lib/carray/stack.rb +3 -3
  124. data/lib/carray/string.rb +6 -6
  125. data/lib/carray/string_operation_extension.rb +25 -4
  126. data/lib/carray/struct_builder.rb +4 -1
  127. data/lib/carray/time.rb +190 -46
  128. data/lib/carray/window_iterator.rb +48 -12
  129. data/yard-stubs/ca_obj_bitfield.rb +4 -1
  130. data/yard-stubs/ca_obj_refer.rb +3 -0
  131. data/yard-stubs/ca_obj_window.rb +2 -1
  132. data/yard-stubs/carray_access.rb +4 -2
  133. data/yard-stubs/carray_cast.rb +6 -0
  134. data/yard-stubs/carray_count.rb +6 -3
  135. data/yard-stubs/carray_math.rb +5 -0
  136. data/yard-stubs/carray_order.rb +6 -0
  137. data/yard-stubs/carray_scatter.rb +2 -1
  138. data/yard-stubs/carray_test.rb +18 -5
  139. metadata +2 -2
data/ext/carray_utils.c CHANGED
@@ -11,12 +11,14 @@
11
11
  ---------------------------------------------------------------------------- */
12
12
 
13
13
  #include <stdarg.h>
14
+ #include <time.h>
14
15
  #include "ruby.h"
15
16
  #include "carray.h"
16
17
  #include "carray_internal.h" /* ca_calling_method_name */
17
18
 
18
19
  #include "ruby/st.h"
19
20
  #include "ruby/thread_native.h"
21
+ #include "ruby/thread.h" /* rb_thread_call_without_gvl */
20
22
  #include "ruby/ractor.h"
21
23
  #include "ruby/debug.h" /* rb_profile_frames: the method the user called */
22
24
 
@@ -103,6 +105,14 @@ ca_get_loop_count (int n, ...)
103
105
  return elements;
104
106
  }
105
107
 
108
+ /* A bound of a range that is not a key of `[]`: a window or a bit range says
109
+ how many cells, so a Float is refused rather than cut down. */
110
+ static ca_size_t
111
+ ca_range_bound (VALUE v)
112
+ {
113
+ return ca_integer_arg(v, "a range bound", NULL);
114
+ }
115
+
106
116
  /*
107
117
  ca_parse_range and ca_parse_range_without_check parse the following
108
118
  types of range specifications
@@ -138,7 +148,7 @@ ca_parse_range (VALUE arg, ca_size_t size,
138
148
  }
139
149
  else if ( rb_obj_is_kind_of(arg, rb_cInteger) ) {
140
150
  /* i */
141
- start = NUM2SIZE(arg);
151
+ start = ca_range_bound(arg);
142
152
  CA_CHECK_INDEX(start, size);
143
153
  *poffset = start;
144
154
  *pcount = 1;
@@ -146,8 +156,8 @@ ca_parse_range (VALUE arg, ca_size_t size,
146
156
  }
147
157
  else if ( rb_obj_is_kind_of(arg, rb_cRange) ) {
148
158
  /* i..j */
149
- start = NUM2SIZE(RANGE_BEG(arg));
150
- last = NUM2SIZE(RANGE_END(arg));
159
+ start = ca_range_bound(RANGE_BEG(arg));
160
+ last = ca_range_bound(RANGE_END(arg));
151
161
  excl = RTEST(RANGE_EXCL(arg));
152
162
  CA_CHECK_INDEX(start, size);
153
163
  if ( last < 0 ) {
@@ -168,10 +178,10 @@ ca_parse_range (VALUE arg, ca_size_t size,
168
178
  rb_arithmetic_sequence_components_t x;
169
179
  rb_arithmetic_sequence_extract(arg, &x);
170
180
 
171
- start = NUM2SIZE(x.begin);
172
- last = NUM2SIZE(x.end);
181
+ start = ca_range_bound(x.begin);
182
+ last = ca_range_bound(x.end);
173
183
  excl = RTEST(x.exclude_end);
174
- step = NUM2SIZE(x.step);
184
+ step = ca_range_bound(x.step);
175
185
  if ( step == 0 ) {
176
186
  rb_raise(rb_eArgError, "step should not be 0");
177
187
  }
@@ -209,7 +219,7 @@ ca_parse_range (VALUE arg, ca_size_t size,
209
219
  VALUE arg0 = rb_ary_entry(arg, 0);
210
220
  VALUE arg1 = rb_ary_entry(arg, 1);
211
221
  if ( NIL_P(arg0) ) { /* [nil,k] */
212
- step = NUM2SIZE(arg1);
222
+ step = ca_range_bound(arg1);
213
223
  if ( step == 0 ) {
214
224
  rb_raise(rb_eArgError, "step should not be 0");
215
225
  }
@@ -224,10 +234,10 @@ ca_parse_range (VALUE arg, ca_size_t size,
224
234
  *pstep = step;
225
235
  }
226
236
  else if ( rb_obj_is_kind_of(arg0, rb_cRange) ) { /* [i..j,k] */
227
- start = NUM2SIZE(RANGE_BEG(arg0));
228
- last = NUM2SIZE(RANGE_END(arg0));
237
+ start = ca_range_bound(RANGE_BEG(arg0));
238
+ last = ca_range_bound(RANGE_END(arg0));
229
239
  excl = RTEST(RANGE_EXCL(arg0));
230
- step = NUM2SIZE(arg1);
240
+ step = ca_range_bound(arg1);
231
241
  if ( step == 0 ) {
232
242
  rb_raise(rb_eArgError, "step should not be 0");
233
243
  }
@@ -260,10 +270,10 @@ ca_parse_range (VALUE arg, ca_size_t size,
260
270
  rb_arithmetic_sequence_components_t x;
261
271
  rb_arithmetic_sequence_extract(arg0, &x);
262
272
 
263
- start = NUM2SIZE(x.begin);
264
- last = NUM2SIZE(x.end);
273
+ start = ca_range_bound(x.begin);
274
+ last = ca_range_bound(x.end);
265
275
  excl = RTEST(x.exclude_end);
266
- step = NUM2SIZE(x.step);
276
+ step = ca_range_bound(x.step);
267
277
  if ( step == 0 ) {
268
278
  rb_raise(rb_eArgError, "step should not be 0");
269
279
  }
@@ -293,8 +303,8 @@ ca_parse_range (VALUE arg, ca_size_t size,
293
303
  }
294
304
  #endif
295
305
  else { /* [i,n] */
296
- start = NUM2SIZE(arg0);
297
- count = NUM2SIZE(arg1);
306
+ start = ca_range_bound(arg0);
307
+ count = ca_range_bound(arg1);
298
308
  CA_CHECK_INDEX(start, size);
299
309
  bound = start + (count - 1);
300
310
  if ( count > 0 && ( bound < 0 || bound >= size ) ) {
@@ -306,9 +316,9 @@ ca_parse_range (VALUE arg, ca_size_t size,
306
316
  }
307
317
  }
308
318
  else if ( RARRAY_LEN(arg) == 3 ) { /* [i,n,k] */
309
- start = NUM2SIZE(rb_ary_entry(arg, 0));
310
- count = NUM2SIZE(rb_ary_entry(arg, 1));
311
- step = NUM2SIZE(rb_ary_entry(arg, 2));
319
+ start = ca_range_bound(rb_ary_entry(arg, 0));
320
+ count = ca_range_bound(rb_ary_entry(arg, 1));
321
+ step = ca_range_bound(rb_ary_entry(arg, 2));
312
322
  if ( step == 0 ) {
313
323
  rb_raise(rb_eArgError, "step should not be 0");
314
324
  }
@@ -345,18 +355,18 @@ ca_parse_range_without_check (VALUE arg, ca_size_t size,
345
355
  }
346
356
  else if ( rb_obj_is_kind_of(arg, rb_cInteger) ) {
347
357
  /* i */
348
- start = NUM2SIZE(arg);
358
+ start = ca_range_bound(arg);
349
359
  *poffset = start;
350
360
  *pcount = 1;
351
361
  *pstep = 1;
352
362
  }
353
363
  else if ( rb_obj_is_kind_of(arg, rb_cRange) ) {
354
364
  /* i..j */
355
- start = NUM2SIZE(RANGE_BEG(arg));
356
- last = NUM2SIZE(RANGE_END(arg));
365
+ start = ca_range_bound(RANGE_BEG(arg));
366
+ last = ca_range_bound(RANGE_END(arg));
357
367
  excl = RTEST(RANGE_EXCL(arg));
358
368
  if ( excl ) {
359
- last += ( (last>=start) ? -1 : 1 );
369
+ last -= 1;
360
370
  }
361
371
  *poffset = start;
362
372
  *pcount = last - start + 1;
@@ -367,16 +377,16 @@ ca_parse_range_without_check (VALUE arg, ca_size_t size,
367
377
  rb_arithmetic_sequence_components_t x;
368
378
  rb_arithmetic_sequence_extract(arg, &x);
369
379
 
370
- start = NUM2SIZE(x.begin);
371
- last = NUM2SIZE(x.end);
380
+ start = ca_range_bound(x.begin);
381
+ last = ca_range_bound(x.end);
372
382
  excl = RTEST(x.exclude_end);
373
- step = NUM2SIZE(x.step);
383
+ step = ca_range_bound(x.step);
374
384
  if ( step == 0 ) {
375
385
  rb_raise(rb_eArgError, "%s: step should not be 0",
376
386
  ca_calling_method_name());
377
387
  }
378
388
  if ( excl ) {
379
- last += ( (last>=start) ? -1 : 1 );
389
+ last += ( (step>0) ? -1 : 1 );
380
390
  }
381
391
  count = (last - start)/llabs(step) + 1;
382
392
  *poffset = start;
@@ -394,7 +404,7 @@ ca_parse_range_without_check (VALUE arg, ca_size_t size,
394
404
  VALUE arg1 = rb_ary_entry(arg, 1);
395
405
  if ( NIL_P(arg0) ) { /* [nil,k] */
396
406
  start = 0;
397
- step = NUM2SIZE(arg1);
407
+ step = ca_range_bound(arg1);
398
408
  if ( step == 0 ) {
399
409
  rb_raise(rb_eArgError, "%s: step should not be 0",
400
410
  ca_calling_method_name());
@@ -405,10 +415,10 @@ ca_parse_range_without_check (VALUE arg, ca_size_t size,
405
415
  *pstep = step;
406
416
  }
407
417
  else if ( rb_obj_is_kind_of(arg0, rb_cRange) ) { /* [i..j,k] */
408
- start = NUM2SIZE(RANGE_BEG(arg0));
409
- last = NUM2SIZE(RANGE_END(arg0));
418
+ start = ca_range_bound(RANGE_BEG(arg0));
419
+ last = ca_range_bound(RANGE_END(arg0));
410
420
  excl = RTEST(RANGE_EXCL(arg0));
411
- step = NUM2SIZE(arg1);
421
+ step = ca_range_bound(arg1);
412
422
  if ( step == 0 ) {
413
423
  rb_raise(rb_eArgError, "%s: step should not be 0",
414
424
  ca_calling_method_name());
@@ -426,10 +436,10 @@ ca_parse_range_without_check (VALUE arg, ca_size_t size,
426
436
  rb_arithmetic_sequence_components_t x;
427
437
  rb_arithmetic_sequence_extract(arg0, &x);
428
438
 
429
- start = NUM2SIZE(x.begin);
430
- last = NUM2SIZE(x.end);
439
+ start = ca_range_bound(x.begin);
440
+ last = ca_range_bound(x.end);
431
441
  excl = RTEST(x.exclude_end);
432
- step = NUM2SIZE(x.step);
442
+ step = ca_range_bound(x.step);
433
443
  if ( step == 0 ) {
434
444
  rb_raise(rb_eArgError, "%s: step should not be 0",
435
445
  ca_calling_method_name());
@@ -444,17 +454,17 @@ ca_parse_range_without_check (VALUE arg, ca_size_t size,
444
454
  }
445
455
  #endif
446
456
  else { /* [i,n] */
447
- start = NUM2SIZE(arg0);
448
- count = NUM2SIZE(arg1);
457
+ start = ca_range_bound(arg0);
458
+ count = ca_range_bound(arg1);
449
459
  *poffset = start;
450
460
  *pcount = count;
451
461
  *pstep = 1;
452
462
  }
453
463
  }
454
464
  else if ( RARRAY_LEN(arg) == 3 ) { /* [i,n,k] */
455
- start = NUM2SIZE(rb_ary_entry(arg, 0));
456
- count = NUM2SIZE(rb_ary_entry(arg, 1));
457
- step = NUM2SIZE(rb_ary_entry(arg, 2));
465
+ start = ca_range_bound(rb_ary_entry(arg, 0));
466
+ count = ca_range_bound(rb_ary_entry(arg, 1));
467
+ step = ca_range_bound(rb_ary_entry(arg, 2));
458
468
  *poffset = start;
459
469
  *pcount = count;
460
470
  *pstep = step;
@@ -528,7 +538,7 @@ ca_stable_id_name (ID id)
528
538
  {
529
539
  st_data_t found;
530
540
  const char *name = NULL;
531
- rb_nativethread_lock_lock(&ca_id_names_lock);
541
+ ca_native_lock_without_gvl(&ca_id_names_lock);
532
542
  if ( st_lookup(ca_id_names, (st_data_t) id, &found) ) {
533
543
  name = (const char *) found;
534
544
  }
@@ -548,7 +558,8 @@ ca_stable_id_name (ID id)
548
558
  memcpy(copy, RSTRING_PTR(str), len);
549
559
  copy[len] = '\0';
550
560
  RB_GC_GUARD(str);
551
- rb_nativethread_lock_lock(&ca_id_names_lock);
561
+ /* lock: st_insert can run a GC, which does not take this lock */
562
+ ca_native_lock_without_gvl(&ca_id_names_lock);
552
563
  if ( st_lookup(ca_id_names, (st_data_t) id, &found) ) {
553
564
  xfree(copy);
554
565
  name = (const char *) found;
@@ -786,17 +797,18 @@ ca_shape_elements (int8_t ndim, const ca_size_t *dim, ca_size_t bytes)
786
797
  }
787
798
 
788
799
  /* An index array is read as ca_size_t. A float or complex one would be
789
- cut to an integer on the way, so it is refused, as the indexer refuses
790
- it. A uint64 one holding 2**63 or more would turn negative and count
791
- from the end of the axis, so it is refused as the out-of-range index it
792
- is. */
800
+ cut to an integer on the way, as an object one would, and a boolean one
801
+ selects cells rather than naming addresses, so these are refused. A
802
+ uint64 one holding 2**63 or more would turn negative and count from the
803
+ end of the axis, so it is refused as the out-of-range index it is. */
793
804
  void
794
805
  ca_check_index_array (VALUE v)
795
806
  {
796
807
  CArray *ca;
797
808
  if ( ! rb_obj_is_carray(v) ) return;
798
809
  GetCArray(v, ca);
799
- if ( ca_is_float_type(ca) || ca_is_complex_type(ca) ) {
810
+ if ( ca_is_float_type(ca) || ca_is_complex_type(ca)
811
+ || ca->data_type == CA_OBJECT || ca->data_type == CA_BOOLEAN ) {
800
812
  rb_raise(rb_eIndexError,
801
813
  "data_type %s is invalid for an index (should be integer)",
802
814
  ca_type_name[ca->data_type]);
@@ -1156,12 +1168,12 @@ rb_ca_guess_type (VALUE obj)
1156
1168
  }
1157
1169
  else if ( TYPE(obj) == T_STRING ) {
1158
1170
  const char *name0;
1159
- char *name = StringValuePtr(obj);
1171
+ const char *name = StringValueCStr(obj);
1160
1172
  int i;
1161
1173
  i = 0;
1162
1174
  while ( ( name0 = ca_name_to_type[i].name ) ) {
1163
1175
  name0 = ca_name_to_type[i].name;
1164
- if ( ! strncmp(name, name0, strlen(name0)) ) {
1176
+ if ( ! strcmp(name, name0) ) {
1165
1177
  return ca_name_to_type[i].data_type;
1166
1178
  }
1167
1179
  i++;
@@ -1196,7 +1208,7 @@ rb_ca_guess_type_and_bytes (VALUE rtype, VALUE rbytes,
1196
1208
  *bytes = 0;
1197
1209
  }
1198
1210
  else {
1199
- *bytes = NUM2SIZE(rbytes);
1211
+ *bytes = ca_integer_arg(rbytes, "bytes", NULL);
1200
1212
  }
1201
1213
  }
1202
1214
  }
@@ -1345,13 +1357,16 @@ rb_scan_options (VALUE ropt, const char *spec_in, ...)
1345
1357
  long i, n = RARRAY_LEN(keys);
1346
1358
  for ( i = 0; i < n; i++ ) {
1347
1359
  VALUE key = rb_ary_entry(keys, i);
1348
- VALUE kname = SYMBOL_P(key) ? rb_sym2str(key) : rb_obj_as_string(key);
1349
- const char *kn = StringValueCStr(kname);
1350
- long kn_len = RSTRING_LEN(kname);
1351
1360
  int found = 0;
1352
- sp = spec_in;
1353
- while ( ( tok = option_key(&sp, &len) ) != NULL ) {
1354
- if ( len == kn_len && memcmp(tok, kn, len) == 0 ) { found = 1; break; }
1361
+ /* A value is read by Symbol above, so only a Symbol can name a key. */
1362
+ if ( SYMBOL_P(key) ) {
1363
+ VALUE kname = rb_sym2str(key);
1364
+ const char *kn = StringValueCStr(kname);
1365
+ long kn_len = RSTRING_LEN(kname);
1366
+ sp = spec_in;
1367
+ while ( ( tok = option_key(&sp, &len) ) != NULL ) {
1368
+ if ( len == kn_len && memcmp(tok, kn, len) == 0 ) { found = 1; break; }
1369
+ }
1355
1370
  }
1356
1371
  if ( ! found ) {
1357
1372
  rb_raise(rb_eArgError, "unknown keyword: %"PRIsVALUE, rb_inspect(key));
@@ -1414,6 +1429,57 @@ rb_set_options (VALUE ropt, const char *spec_in, ...)
1414
1429
  }
1415
1430
 
1416
1431
 
1432
+ /* RubyVM::InstructionSequence#to_a, one caller at a time across Ractors.
1433
+ The first to_a on an instruction sequence builds a decoded copy of its
1434
+ code and publishes the buffer before filling it, so a second Ractor
1435
+ that reads the same sequence at that moment decodes raw addresses and
1436
+ crashes the process (Ruby 3.4 and 4.0; fixed for 4.1). A block written
1437
+ inside Ractor.new is one sequence shared by every Ractor started from
1438
+ it. The wait for the lock is made without the GVL, so a Ractor that
1439
+ holds the lock and starts a GC is not left waiting on one that wants
1440
+ it. */
1441
+ static rb_nativethread_lock_t ca_iseq_to_a_lock;
1442
+
1443
+ static VALUE
1444
+ ca_iseq_to_a_call (VALUE iseq)
1445
+ {
1446
+ return rb_funcall(iseq, rb_intern("to_a"), 0);
1447
+ }
1448
+
1449
+ static VALUE
1450
+ rb_ca_s_instruction_sequence_to_a (VALUE self, VALUE iseq)
1451
+ {
1452
+ VALUE result;
1453
+ int state = 0;
1454
+ /* lock: Ruby runs under it, to serialise to_a; every waiter waits without the GVL */
1455
+ ca_native_lock_without_gvl(&ca_iseq_to_a_lock);
1456
+ /* protect: what is held is the lock, not an array or a buffer */
1457
+ result = rb_protect(ca_iseq_to_a_call, iseq, &state);
1458
+ rb_nativethread_lock_unlock(&ca_iseq_to_a_lock);
1459
+ if ( state ) {
1460
+ rb_jump_tag(state);
1461
+ }
1462
+ return result;
1463
+ }
1464
+
1465
+ /* A short pause between tries for a lock, made without the GVL. */
1466
+ static void *
1467
+ ca_native_lock_pause (void *arg)
1468
+ {
1469
+ struct timespec t = { 0, 20000 }; /* 20 us */
1470
+ nanosleep(&t, NULL);
1471
+ return NULL;
1472
+ }
1473
+
1474
+ void
1475
+ ca_native_lock_without_gvl (rb_nativethread_lock_t *lock)
1476
+ {
1477
+ /* lock: the pause below runs while the lock is not held */
1478
+ while ( rb_native_mutex_trylock(lock) != 0 ) {
1479
+ rb_thread_call_without_gvl(ca_native_lock_pause, NULL, RUBY_UBF_IO, NULL);
1480
+ }
1481
+ }
1482
+
1417
1483
  /* Whether this runs in the main Ractor: Init does, and leaves a value in
1418
1484
  its Ractor-local storage that no other Ractor has. */
1419
1485
  static rb_ractor_local_key_t ca_main_ractor_key;
@@ -1432,11 +1498,15 @@ Init_carray_utils (void)
1432
1498
  rb_ractor_local_storage_value_set(ca_main_ractor_key, Qtrue);
1433
1499
  ca_id_names = st_init_numtable();
1434
1500
  rb_nativethread_lock_initialize(&ca_id_names_lock);
1501
+ rb_nativethread_lock_initialize(&ca_iseq_to_a_lock);
1435
1502
  id_answering = rb_intern("__carray_answering_method__");
1436
1503
  id_begin = rb_intern("begin");
1437
1504
  id_end = rb_intern("end");
1438
1505
  id_excl_end = rb_intern("exclude_end?");
1439
1506
 
1507
+ rb_define_private_method(rb_singleton_class(rb_cCArray),
1508
+ "instruction_sequence_to_a",
1509
+ rb_ca_s_instruction_sequence_to_a, 1);
1440
1510
  rb_define_singleton_method(rb_cCArray, "guess_type_and_bytes",
1441
1511
  rb_ca_s_guess_type_and_bytes, -1);
1442
1512
 
data/ext/mk_call_cfunc.rb CHANGED
@@ -4,9 +4,7 @@
4
4
  # `ca_call_cfunc_M_N` (= typed dispatcher) used by external CArray gems
5
5
  # (carray-gsl, etc.) to wrap C math functions over CArray operations.
6
6
  #
7
- # Generated 2026-06-10 as part of the PROPOSAL_EAGER_SLOWPATH_CHUNKING_ARENA
8
- # attach-elimination flow extension (= D phase): the raw `ca_call_cfunc_N`
9
- # template applies C.7-style alias / non-alias bifurcation for INPUT-only
7
+ # The raw `ca_call_cfunc_N` template splits alias / non-alias for INPUT-only
10
8
  # operands (= fsync[k] == '0') and ca_xfer_all-based safe mask gather (= no
11
9
  # `ca_attach` on operand masks). OUTPUT operands (= fsync[k] == '1') still
12
10
  # use ca_attach + ca_sync (= legitimate per the refined input-only-operand
@@ -500,11 +498,9 @@ $src.puts <<~END_C
500
498
  * file-static / global plumbing.
501
499
  *
502
500
  * Attach-safe: INPUT-only operands (= fsync[k] == '0') never call
503
- * ca_attach on the operand or its mask (= alias check + ALLOCV +
501
+ * ca_attach on the operand or its mask (= alias check + xmalloc +
504
502
  * ca_xfer_all for non-alias). OUTPUT operands (= fsync[k] == '1')
505
- * use ca_attach + ca_sync (= legitimate per the refined input-only-
506
- * operand invariant established in PROPOSAL_EAGER_ELEMENTWISE_NO_ATTACH
507
- * and extended in PROPOSAL_EAGER_SLOWPATH_CHUNKING_ARENA).
503
+ * use ca_attach + ca_sync.
508
504
  *
509
505
  * Slab variants `ca_call_cslab_N` / `ca_call_cslab_N_r` take the chunked
510
506
  * path instead (ca_sweep_acquire_chunked / ca_sweep_next_chunk /
@@ -518,10 +514,9 @@ $src.puts <<~END_C
518
514
  * function to wrap); use cslab when the operand is large, virtual, or the
519
515
  * body is worth vectorising.
520
516
  *
521
- * L0.1 (PROPOSAL_L0_AUTHOR_SURFACE, 2026-06-11): the per-operand acquire
522
- * + broadcast check + mask OR + release lifecycle is now factored out
523
- * into ext/ca_sweep_engine.{c,h} (ca_sweep_acquire / ca_sweep_release). This
524
- * file keeps the arity-dependent inner loop only. ABI unchanged.
517
+ * The per-operand acquire + broadcast check + mask OR + release lifecycle
518
+ * lives in ext/ca_sweep_engine.{c,h} (ca_sweep_acquire / ca_sweep_release).
519
+ * This file keeps the arity-dependent inner loop only.
525
520
  *
526
521
  * --------------------------------------------------------------------------- */
527
522
 
data/ext/mkkernel.rb CHANGED
@@ -189,6 +189,17 @@ module MkKernel
189
189
  }
190
190
  end
191
191
 
192
+ # A power body: the exponents -2 .. 4 take at most two roundings
193
+ # (x * x, x * x * x, (x * x) * (x * x), 1 / x, 1 / (x * x)), the rest go
194
+ # to `general`. The Integer exponent kernel (ext/ca_op_ipower.c) takes
195
+ # the same ones, so an exponent answers alike as a scalar and as an array.
196
+ def self.pow_short(general)
197
+ "(#3) = (#2) == 2 ? (#1) * (#1) : (#2) == 3 ? (#1) * (#1) * (#1) : " \
198
+ "(#2) == 4 ? ((#1) * (#1)) * ((#1) * (#1)) : (#2) == 1 ? (#1) : " \
199
+ "(#2) == 0 ? 1 : (#2) == -1 ? 1 / (#1) : (#2) == -2 ? 1 / ((#1) * (#1)) : " \
200
+ "#{general};"
201
+ end
202
+
192
203
  # Substitute the width markers in a kernel body.
193
204
  def self.at_width(body, suffix, real_type)
194
205
  body.gsub("<f>", suffix).gsub("<t>", real_type)
@@ -6799,11 +6810,14 @@ module MkKernel
6799
6810
 
6800
6811
  # Kernels written outside this generator that a lazy expression can still
6801
6812
  # hold. A Float or Complex array to an Integer power is CABinOp's ipow
6802
- # (ext/ca_op_ipower.c, which applies op_powi_<type> cell by cell); its
6803
- # body is that call.
6804
- EXTRA_BODY_ROWS = %w[float32 float64 cmplx64 cmplx128].map { |t|
6805
- ["binop", "ipow", t, "(#3) = op_powi_<type>((#1), (#2));"]
6806
- }.freeze
6813
+ # (ext/ca_op_ipower.c, ca_ipow_<type>): #2 is an int64, converted as an
6814
+ # int64 cell is converted to the base's type, then the power body.
6815
+ EXTRA_BODY_ROWS = [
6816
+ ["binop", "ipow", "float32", pow_short("powf((#1), (float)(double)(#2))")],
6817
+ ["binop", "ipow", "float64", pow_short("pow((#1), (double)(#2))")],
6818
+ ["binop", "ipow", "cmplx64", pow_short("cpow((#1), (float)(double)(#2))")],
6819
+ ["binop", "ipow", "cmplx128", pow_short("cpow((#1), (double)(#2))")],
6820
+ ].freeze
6807
6821
 
6808
6822
  # The helpers the bodies call that are not in the C standard library:
6809
6823
  # the part of ca_op_powi.h between its markers, as it stands.
@@ -9068,21 +9082,18 @@ MkKernel.monfunc :deg_360,
9068
9082
  expr: {
9069
9083
  float: <<~C,
9070
9084
  {
9071
- double _a = (double)(#1);
9072
- double _fa = _a / 360.0;
9073
- (#2) = (_a >= 0)
9074
- ? (_fa - floor(_fa)) * 360.0
9075
- : (_fa - ceil(_fa) + 1.0) * 360.0;
9085
+ double _fa = (double)(#1) / 360.0;
9086
+ (#2) = (_fa - floor(_fa)) * 360.0;
9087
+ if ( (#2) >= 360.0 ) (#2) = 0.0; /* a tiny negative rounds to the top */
9076
9088
  }
9077
9089
  C
9078
9090
  object: <<~SNIPPET,
9079
9091
  {
9080
9092
  VALUE _obj_arg = (#1);
9081
9093
  if (RB_FLOAT_TYPE_P(_obj_arg) || RB_INTEGER_TYPE_P(_obj_arg) || RB_TYPE_P(_obj_arg, T_RATIONAL)) {
9082
- double _a = NUM2DBL(_obj_arg);
9083
- double _fa = _a / 360.0;
9084
- (#2) = DBL2NUM((_a >= 0) ? (_fa - floor(_fa)) * 360.0
9085
- : (_fa - ceil(_fa) + 1.0) * 360.0);
9094
+ double _fa = NUM2DBL(_obj_arg) / 360.0;
9095
+ double _r = (_fa - floor(_fa)) * 360.0;
9096
+ (#2) = DBL2NUM(_r >= 360.0 ? 0.0 : _r);
9086
9097
  } else {
9087
9098
  (#2) = rb_funcall(_obj_arg, rb_intern("deg_360"), 0);
9088
9099
  }
@@ -9090,7 +9101,7 @@ MkKernel.monfunc :deg_360,
9090
9101
  SNIPPET
9091
9102
  }
9092
9103
 
9093
- # deg_180: fold into [-180, 180). Preserves trailing `if (b <= -180)
9104
+ # deg_180: fold into (-180, 180]. Preserves trailing `if (b <= -180)
9094
9105
  # b += 360` guard from the legacy implementation.
9095
9106
  MkKernel.monfunc :deg_180,
9096
9107
  source: MkKernel::FLOAT_DTYPES + [:object],
@@ -9130,11 +9141,9 @@ MkKernel.monfunc :rad_2pi,
9130
9141
  float: <<~C,
9131
9142
  {
9132
9143
  double _two_pi = 2.0 * M_PI;
9133
- double _a = (double)(#1);
9134
- double _fa = _a / _two_pi;
9135
- (#2) = (_a >= 0)
9136
- ? (_fa - floor(_fa)) * _two_pi
9137
- : (_fa - ceil(_fa) + 1.0) * _two_pi;
9144
+ double _fa = (double)(#1) / _two_pi;
9145
+ (#2) = (_fa - floor(_fa)) * _two_pi;
9146
+ if ( (#2) >= _two_pi ) (#2) = 0.0; /* a tiny negative rounds to the top */
9138
9147
  }
9139
9148
  C
9140
9149
  object: <<~SNIPPET,
@@ -9142,10 +9151,9 @@ MkKernel.monfunc :rad_2pi,
9142
9151
  VALUE _obj_arg = (#1);
9143
9152
  if (RB_FLOAT_TYPE_P(_obj_arg) || RB_INTEGER_TYPE_P(_obj_arg) || RB_TYPE_P(_obj_arg, T_RATIONAL)) {
9144
9153
  double _two_pi = 2.0 * M_PI;
9145
- double _a = NUM2DBL(_obj_arg);
9146
- double _fa = _a / _two_pi;
9147
- (#2) = DBL2NUM((_a >= 0) ? (_fa - floor(_fa)) * _two_pi
9148
- : (_fa - ceil(_fa) + 1.0) * _two_pi);
9154
+ double _fa = NUM2DBL(_obj_arg) / _two_pi;
9155
+ double _r = (_fa - floor(_fa)) * _two_pi;
9156
+ (#2) = DBL2NUM(_r >= _two_pi ? 0.0 : _r);
9149
9157
  } else {
9150
9158
  (#2) = rb_funcall(_obj_arg, rb_intern("rad_2pi"), 0);
9151
9159
  }
@@ -9404,7 +9412,9 @@ MkKernel.binop :bit_or_i,
9404
9412
  kleene: :or,
9405
9413
  source: MkKernel::INT_DTYPES + [:bool, :object],
9406
9414
  expr: {
9407
- bool: "(#3) = (#1) | (#2);",
9415
+ # 0 / 1 even when a side is a masked cell: its byte is not defined, and
9416
+ # an OR that Kleene resolves to true keeps the value written here.
9417
+ bool: "(#3) = ((#1) | (#2)) ? 1 : 0;",
9408
9418
  int: "(#3) = (#1) | (#2);",
9409
9419
  object: '(#3) = rb_funcall((#1), rb_intern("|"), 1, (#2));',
9410
9420
  }
@@ -9607,9 +9617,9 @@ MkKernel.binop :power,
9607
9617
  source: MkKernel::MATH_NUMERIC + [:object],
9608
9618
  expr: {
9609
9619
  int: "(#3) = op_powi_<type>((#1), (#2));",
9610
- complex: "(#3) = cpow((#1), (#2));", # see exp2 on why cpow stays wide
9620
+ complex: MkKernel.pow_short("cpow((#1), (#2))"), # see exp2 on why cpow stays wide
9611
9621
  object: '(#3) = rb_funcall((#1), rb_intern("**"), 1, (#2));',
9612
- }.merge(MkKernel.float_widths("(#3) = pow<f>((#1), (#2));"))
9622
+ }.merge(MkKernel.float_widths(MkKernel.pow_short("pow<f>((#1), (#2))")))
9613
9623
 
9614
9624
  # ---- M.2 + M.3 (PyTorch alignment): float-only binop family --------------
9615
9625
  #
data/ext/version.h CHANGED
@@ -1,6 +1,6 @@
1
- #define CA_VERSION "3.0.6"
2
- #define CA_VERSION_CODE 30006
1
+ #define CA_VERSION "3.0.7"
2
+ #define CA_VERSION_CODE 30007
3
3
  #define CA_VERSION_MAJOR 3
4
4
  #define CA_VERSION_MINOR 0
5
- #define CA_VERSION_TEENY 6
6
- #define CA_VERSION_DATE "2026/10/28"
5
+ #define CA_VERSION_TEENY 7
6
+ #define CA_VERSION_DATE "2026/11/03"
@@ -846,16 +846,19 @@ class CAGroupIterator
846
846
  place = Array.new(gslots.size, 1) # place value per slot
847
847
  (gslots.size - 2).downto(0) { |i| place[i] = place[i + 1] * gslots[i + 1][:k] }
848
848
  comp = nil
849
+ groups = kdims.inject(1, :*)
849
850
  gslots.each_with_index do |m, i|
850
851
  target = Array.new(gb_rank, 1)
851
852
  m[:axes].each { |a| target[gaxes.index(a)] = shape[a] }
852
853
  term = m[:codes].reshape(*target)
854
+ # The composite code has to reach the last group in the codes' type.
855
+ term = term.int64 if groups > CACategorical::MAX_LABELS[term.data_type]
853
856
  term = term * place[i] if place[i] != 1
854
857
  comp = comp.nil? ? term : comp + term
855
858
  end
856
859
  gb_size = gaxes.map { |a| shape[a] }.inject(1, :*)
857
860
  ccat = CACategorical.from_codes(comp.reshape(gb_size),
858
- CArray.int32(kdims.inject(1, :*)).seq!)
861
+ CArray.int32(groups).seq!)
859
862
  [ccat, kdims, gslots, bslots, gaxes]
860
863
  end
861
864
 
@@ -940,7 +943,7 @@ class CAGroupIterator
940
943
  # and cumcount in int64 -- not the core's answer for a boolean's running sum.
941
944
  def kernel_scans? (op)
942
945
  return false unless CArray.__fast_paths_enabled__(:axis_group_scan_kernel)
943
- return false if value.face? || value.complex?
946
+ return false if value.face? || value.complex? || value.data_type == CA_FIXLEN
944
947
  return true if value.object? || op == :cumcount
945
948
  core = CAIterator.core_probe(value).public_send(op).data_type
946
949
  kernel = [:cumsum, :cumprod].include?(op) ? CA_FLOAT64 : value.data_type
@@ -977,10 +980,10 @@ class CAGroupIterator
977
980
  end
978
981
 
979
982
  # An array of +shape+ of the same kind as +model+ -- its data type, or its
980
- # Face -- with every cell UNDEF. A read-only Face cannot be written cell by
981
- # cell, so its values are held in an object array.
983
+ # Face -- with every cell UNDEF. A Face that holds no new value cannot be
984
+ # written cell by cell, so its values are held in an object array.
982
985
  def blank_of (model, shape)
983
- out = if model.face? && model.read_only?
986
+ out = if model.face? && !model.__send__(:holds_new_values?)
984
987
  CArray.new(CA_OBJECT, shape)
985
988
  else
986
989
  model.__send__(:blank_like, shape)
@@ -1058,7 +1061,7 @@ class CAGroupIterator
1058
1061
  gb_rank = gaxes.size
1059
1062
  gb_shape = gaxes.map { |a| shape[a] }
1060
1063
  gb_size = gb_shape.inject(1, :*)
1061
- nvalid = ccat.category_sizes.int64.sum
1064
+ nvalid = ccat.category_sizes.int64.sum.to_i
1062
1065
  nband = bslots.map { |m| m[:len] }.inject(1, :*)
1063
1066
  out = CArray.int64(nband * nvalid)
1064
1067
  cursor = 0