carray 3.0.1 → 3.0.2

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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +520 -0
  3. data/README.md +2 -2
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +33 -4
  6. data/ext/ca_axis_group.c +202 -96
  7. data/ext/ca_categorical_iterator.c +108 -54
  8. data/ext/ca_kernel_iterator.c +317 -51
  9. data/ext/ca_kernel_iterator.h +142 -35
  10. data/ext/ca_obj_array.c +62 -20
  11. data/ext/ca_obj_block.c +4 -4
  12. data/ext/ca_obj_const_string.c +85 -26
  13. data/ext/ca_obj_face.c +24 -0
  14. data/ext/ca_obj_face.h +15 -0
  15. data/ext/ca_obj_fixlen_string.c +18 -5
  16. data/ext/ca_obj_meld.c +123 -25
  17. data/ext/ca_obj_object.c +8 -0
  18. data/ext/ca_obj_select.c +49 -34
  19. data/ext/ca_obj_stack.c +3 -8
  20. data/ext/ca_obj_stride.c +72 -1
  21. data/ext/ca_obj_string.c +8 -4
  22. data/ext/ca_obj_window.c +8 -2
  23. data/ext/ca_op_ipower.c +1 -2
  24. data/ext/ca_rng_normal.h +42 -0
  25. data/ext/ca_rng_xoshiro256pp.h +105 -0
  26. data/ext/ca_sweep_engine.c +307 -143
  27. data/ext/ca_sweep_engine.h +26 -5
  28. data/ext/carray.h +21 -2
  29. data/ext/carray_access.c +32 -20
  30. data/ext/carray_address_basis.c +590 -0
  31. data/ext/carray_broadcast.c +3 -3
  32. data/ext/carray_call_cfunc.c +667 -483
  33. data/ext/carray_cast.c +115 -41
  34. data/ext/carray_copy.c +55 -30
  35. data/ext/carray_core.c +83 -3
  36. data/ext/carray_count.c +9 -10
  37. data/ext/carray_factorize.c +46 -25
  38. data/ext/carray_internal.h +17 -0
  39. data/ext/carray_kernels_reduce_aggregate.c +168 -0
  40. data/ext/carray_kernels_reduce_cumulative.c +270 -1
  41. data/ext/carray_kernels_reduce_extreme.c +554 -8
  42. data/ext/carray_kernels_scan.c +4 -4
  43. data/ext/carray_kernels_search.c +94 -14
  44. data/ext/carray_loop.c +7 -1
  45. data/ext/carray_mask.c +23 -8
  46. data/ext/carray_median_percentile.c +55 -0
  47. data/ext/carray_operator.c +4 -4
  48. data/ext/carray_order.c +1 -1
  49. data/ext/carray_random.c +384 -40
  50. data/ext/carray_slab.c +13 -0
  51. data/ext/carray_sort.c +20 -22
  52. data/ext/mk_call_cfunc.rb +103 -116
  53. data/ext/mkkernel.rb +297 -29
  54. data/ext/ruby_carray.c +10 -1
  55. data/ext/version.h +4 -4
  56. data/lib/carray/autoload_carray.rb +5 -3
  57. data/lib/carray/autoload_method_extension.rb +12 -0
  58. data/lib/carray/axis_group.rb +77 -0
  59. data/lib/carray/basics.rb +4 -0
  60. data/lib/carray/block_iterator.rb +92 -16
  61. data/lib/carray/categorical.rb +150 -33
  62. data/lib/carray/categorical_iterator.rb +207 -80
  63. data/lib/carray/const_string.rb +131 -27
  64. data/lib/carray/construct.rb +40 -0
  65. data/lib/carray/data_type_extension.rb +3 -0
  66. data/lib/carray/data_type_limits.rb +91 -0
  67. data/lib/carray/fixlen_string.rb +1 -1
  68. data/lib/carray/frame/csv_parser.rb +11 -4
  69. data/lib/carray/frame/frame.rb +81 -10
  70. data/lib/carray/frame/group.rb +36 -3
  71. data/lib/carray/frame/io.rb +67 -15
  72. data/lib/carray/frame/records.rb +18 -4
  73. data/lib/carray/frame/verbs.rb +14 -11
  74. data/lib/carray/inspect.rb +42 -9
  75. data/lib/carray/iterator.rb +143 -0
  76. data/lib/carray/lazy.rb +0 -37
  77. data/lib/carray/mask_gap_fill.rb +3 -1
  78. data/lib/carray/methods/discovery_along.rb +74 -0
  79. data/lib/carray/methods/factorize.rb +50 -0
  80. data/lib/carray/methods/is_in.rb +13 -2
  81. data/lib/carray/methods/locate_addr.rb +75 -2
  82. data/lib/carray/methods/mask_duplicates.rb +35 -1
  83. data/lib/carray/methods/nunique.rb +22 -1
  84. data/lib/carray/methods/repeat.rb +110 -0
  85. data/lib/carray/methods/unique.rb +41 -1
  86. data/lib/carray/rng.rb +86 -0
  87. data/lib/carray/slab_iterator.rb +58 -13
  88. data/lib/carray/string_operation_extension.rb +5 -1
  89. data/lib/carray/time.rb +18 -2
  90. data/lib/carray/window_iterator.rb +142 -20
  91. data/lib/carray.rb +2 -0
  92. data/yard-stubs/ca_obj_block.rb +2 -7
  93. data/yard-stubs/ca_obj_window.rb +10 -2
  94. data/yard-stubs/carray_access.rb +1 -1
  95. data/yard-stubs/carray_broadcast.rb +1 -1
  96. data/yard-stubs/carray_core.rb +0 -80
  97. data/yard-stubs/carray_count.rb +7 -2
  98. data/yard-stubs/carray_lazy.rb +205 -0
  99. data/yard-stubs/carray_math.rb +1486 -3
  100. data/yard-stubs/carray_median_percentile.rb +16 -2
  101. data/yard-stubs/carray_order.rb +9 -69
  102. data/yard-stubs/carray_slab.rb +9 -7
  103. data/yard-stubs/carray_sort.rb +7 -5
  104. metadata +9 -1
@@ -528,7 +528,24 @@ int ca_iter_state_init_l2 (ca_iter_state *st,
528
528
  source carries a mask (= ca_has_mask(src)), or NULL otherwise.
529
529
  The mask layout matches the value layout (= same iteration order
530
530
  and same n). Step 6+: kernels use the CA_FOR_EACH_UNMASKED macro
531
- family (carray.h) to skip masked cells. */
531
+ family (carray.h) to skip masked cells.
532
+
533
+ The mask is an input. What it points at is a copy taken when the
534
+ walk began, so writing through it changes nothing -- not even under
535
+ CA_KERNEL_WRITE, where the value half of the same yield is often a
536
+ live alias. It also cannot express the thing an author would most
537
+ want it for: an unmasked source yields NULL, so there is nowhere to
538
+ record that a cell has become undefined. A kernel authors its
539
+ output mask on its own output array instead:
540
+
541
+ if ( op_mask == NULL ) {
542
+ ca_create_mask(co);
543
+ op_mask = (boolean8_t *) co->mask->ptr;
544
+ }
545
+ op_mask[out_i] = 1;
546
+
547
+ which is what every kernel in carray does (carray_hold.c, and the
548
+ reduction kernels mkkernel emits). */
532
549
  int ca_iter_state_next_slab (ca_iter_state *st,
533
550
  char **out_ptr,
534
551
  boolean8_t **out_mask,
@@ -603,6 +620,13 @@ void ca_iter_state_sync_slab (ca_iter_state *st);
603
620
  exactly once after a successful init (either level). */
604
621
  void ca_iter_state_finish (ca_iter_state *st);
605
622
 
623
+ /* Raise unless rc is CA_ITER_OK. The block macros call this on the value
624
+ ca_iter_state_init_l1 / _l2 returned, so an author who never looks at a
625
+ return code still hears about a request the iterator would not serve.
626
+ Returns rc when there is nothing to report, so it composes inside the
627
+ macros' comma expressions. */
628
+ int ca_iter_check_init (int rc);
629
+
606
630
  /* ---- Phase C C.3: kernel author block macros ------------------------
607
631
  (PROPOSAL_CAPSTONE_PHASE_C.md D3.1 (A) do/while/for + D3.2 (C) 2 kinds)
608
632
 
@@ -617,17 +641,24 @@ void ca_iter_state_finish (ca_iter_state *st);
617
641
  surrounding scope.
618
642
  - `flags` arg propagates to init_l2 (= CA_KERNEL_WRITE supported).
619
643
  `sync_slab` is called automatically after each iteration; it's a
620
- no-op when WRITE flag is absent.
621
- - Init failure (ca_iter_state_init_l2 returns CA_ITER_ERR_*) is
622
- silently discarded: the body runs zero times and finish is still
623
- called. Production kernels that need explicit error messages
624
- (e.g., sum_ki's rc=%d raise) should drop down to the raw API
625
- instead of using this macro.
644
+ no-op when WRITE flag is absent. The mask cursor is an input even
645
+ under WRITE -- see ca_iter_state_next_slab above for why, and for
646
+ where an output mask is written instead.
647
+ - Init failure (ca_iter_state_init_l2 returns CA_ITER_ERR_*) raises:
648
+ the macro passes the code to ca_iter_check_init, which reports what
649
+ the iterator declined to do. The body does not run and finish is
650
+ not reached, which is safe because a failed init allocates nothing.
651
+ Kernels that want to handle a refusal rather than propagate it
652
+ should drop down to the raw API and read the code themselves.
626
653
  - `break;` from inside the body exits the loop AND triggers finish
627
654
  correctly (= outer for's "increment" clause runs once on natural
628
655
  exit; `break` from the inner while breaks both). `return` inside
629
656
  the body LEAKS resources (scratch_ptr, parent attach) — drop to
630
- raw API if early return is needed.
657
+ raw API if early return is needed. A body that raises leaks the
658
+ same way: the engine releases what it holds when the walk itself
659
+ raises (a gather or a write-back through the source's slots), but
660
+ the body runs in the caller's frame, where it has no hold on it.
661
+ An object-lane body calling back into Ruby is the case to watch.
631
662
  - Macros are not statement-equivalent (= they expand to nested for
632
663
  constructs). Don't follow them with `else` etc. */
633
664
 
@@ -760,8 +791,9 @@ void ca_iter_state_finish (ca_iter_state *st);
760
791
  always-constant argument). CA_SLAB_AXES is still FROZEN, because
761
792
  raw-API kernels pass it to ca_iter_state_init_l2 directly. */
762
793
  #define CA_FOR_EACH_SLAB(st, ca, axes, naxes, flags, p, m) \
763
- for ( int __caf_init = (ca_iter_state_init_l2(&(st), (ca), CA_SLAB_AXES, \
764
- (axes), (naxes), (flags)), \
794
+ for ( int __caf_init = (ca_iter_check_init( \
795
+ ca_iter_state_init_l2(&(st), (ca), CA_SLAB_AXES, \
796
+ (axes), (naxes), (flags))), \
765
797
  1); \
766
798
  __caf_init; \
767
799
  __caf_init = 0, ca_iter_state_finish(&(st)) ) \
@@ -777,16 +809,18 @@ void ca_iter_state_finish (ca_iter_state *st);
777
809
  Shape mismatch between ca_in / ca_out is NOT validated by the macro
778
810
  — caller responsibility (= typically output is `rb_ca_template_with_type`
779
811
  of input, guaranteeing same shape). Init failure on either iter
780
- silently skips the body. */
812
+ raises — see CA_FOR_EACH_SLAB above. */
781
813
  /* Policy fixed to CA_SLAB_AXES internally — see CA_FOR_EACH_SLAB above. */
782
814
  #define CA_FOR_EACH_SLAB_INOUT(st_in, st_out, ca_in, ca_out, \
783
815
  axes, naxes, \
784
816
  p_in, p_out, m_in, m_out) \
785
817
  for ( int __cafi_init = ( \
786
- ca_iter_state_init_l2(&(st_in), (ca_in), CA_SLAB_AXES, \
787
- (axes), (naxes), 0), \
788
- ca_iter_state_init_l2(&(st_out), (ca_out), CA_SLAB_AXES, \
789
- (axes), (naxes), CA_KERNEL_WRITE), \
818
+ ca_iter_check_init( \
819
+ ca_iter_state_init_l2(&(st_in), (ca_in), CA_SLAB_AXES, \
820
+ (axes), (naxes), 0)), \
821
+ ca_iter_check_init( \
822
+ ca_iter_state_init_l2(&(st_out), (ca_out), CA_SLAB_AXES, \
823
+ (axes), (naxes), CA_KERNEL_WRITE)), \
790
824
  1); \
791
825
  __cafi_init; \
792
826
  __cafi_init = 0, \
@@ -827,9 +861,10 @@ void ca_iter_state_finish (ca_iter_state *st);
827
861
 
828
862
  #define CA_FOR_EACH_FIBER(st, ca, axis, flags, p, n) \
829
863
  for ( int __cff_init = ( \
830
- ca_iter_state_init_l2(&(st), (ca), CA_SLAB_AXES, \
831
- (int8_t[]){(int8_t)(axis)}, 1, \
832
- (flags) | CA_KERNEL_FIBER_CONTIG), \
864
+ ca_iter_check_init( \
865
+ ca_iter_state_init_l2(&(st), (ca), CA_SLAB_AXES, \
866
+ (int8_t[]){(int8_t)(axis)}, 1, \
867
+ (flags) | CA_KERNEL_FIBER_CONTIG)), \
833
868
  (n) = (st).slab_dims[0], \
834
869
  1); \
835
870
  __cff_init; \
@@ -839,9 +874,10 @@ void ca_iter_state_finish (ca_iter_state *st);
839
874
 
840
875
  #define CA_FOR_EACH_FIBER_MASKED(st, ca, axis, flags, p, n, m) \
841
876
  for ( int __cffm_init = ( \
842
- ca_iter_state_init_l2(&(st), (ca), CA_SLAB_AXES, \
843
- (int8_t[]){(int8_t)(axis)}, 1, \
844
- (flags) | CA_KERNEL_FIBER_CONTIG), \
877
+ ca_iter_check_init( \
878
+ ca_iter_state_init_l2(&(st), (ca), CA_SLAB_AXES, \
879
+ (int8_t[]){(int8_t)(axis)}, 1, \
880
+ (flags) | CA_KERNEL_FIBER_CONTIG)), \
845
881
  (n) = (st).slab_dims[0], \
846
882
  1); \
847
883
  __cffm_init; \
@@ -864,13 +900,15 @@ void ca_iter_state_finish (ca_iter_state *st);
864
900
  #define CA_FOR_EACH_FIBER_INOUT(st_in, st_out, ca_in, ca_out, axis, \
865
901
  flags, p_in, p_out, n) \
866
902
  for ( int __cffi_init = ( \
867
- ca_iter_state_init_l2(&(st_in), (ca_in), CA_SLAB_AXES, \
868
- (int8_t[]){(int8_t)(axis)}, 1, \
869
- (flags) | CA_KERNEL_FIBER_CONTIG), \
870
- ca_iter_state_init_l2(&(st_out), (ca_out), CA_SLAB_AXES, \
871
- (int8_t[]){(int8_t)(axis)}, 1, \
872
- ((flags) | CA_KERNEL_FIBER_CONTIG \
873
- | CA_KERNEL_WRITE)), \
903
+ ca_iter_check_init( \
904
+ ca_iter_state_init_l2(&(st_in), (ca_in), CA_SLAB_AXES, \
905
+ (int8_t[]){(int8_t)(axis)}, 1, \
906
+ (flags) | CA_KERNEL_FIBER_CONTIG)), \
907
+ ca_iter_check_init( \
908
+ ca_iter_state_init_l2(&(st_out), (ca_out), CA_SLAB_AXES, \
909
+ (int8_t[]){(int8_t)(axis)}, 1, \
910
+ ((flags) | CA_KERNEL_FIBER_CONTIG \
911
+ | CA_KERNEL_WRITE))), \
874
912
  (n) = (st_in).slab_dims[0], \
875
913
  1); \
876
914
  __cffi_init; \
@@ -888,13 +926,15 @@ void ca_iter_state_finish (ca_iter_state *st);
888
926
  #define CA_FOR_EACH_FIBER_INOUT_MASKED(st_in, st_out, ca_in, ca_out, axis, \
889
927
  flags, p_in, p_out, n, m) \
890
928
  for ( int __cffim_init = ( \
891
- ca_iter_state_init_l2(&(st_in), (ca_in), CA_SLAB_AXES, \
892
- (int8_t[]){(int8_t)(axis)}, 1, \
893
- (flags) | CA_KERNEL_FIBER_CONTIG), \
894
- ca_iter_state_init_l2(&(st_out), (ca_out), CA_SLAB_AXES, \
895
- (int8_t[]){(int8_t)(axis)}, 1, \
896
- ((flags) | CA_KERNEL_FIBER_CONTIG \
897
- | CA_KERNEL_WRITE)), \
929
+ ca_iter_check_init( \
930
+ ca_iter_state_init_l2(&(st_in), (ca_in), CA_SLAB_AXES, \
931
+ (int8_t[]){(int8_t)(axis)}, 1, \
932
+ (flags) | CA_KERNEL_FIBER_CONTIG)), \
933
+ ca_iter_check_init( \
934
+ ca_iter_state_init_l2(&(st_out), (ca_out), CA_SLAB_AXES, \
935
+ (int8_t[]){(int8_t)(axis)}, 1, \
936
+ ((flags) | CA_KERNEL_FIBER_CONTIG \
937
+ | CA_KERNEL_WRITE))), \
898
938
  (n) = (st_in).slab_dims[0], \
899
939
  1); \
900
940
  __cffim_init; \
@@ -909,6 +949,73 @@ void ca_iter_state_finish (ca_iter_state *st);
909
949
  ca_iter_state_sync_slab(&(st_in)), \
910
950
  ca_iter_state_sync_slab(&(st_out)) )
911
951
 
952
+ /* PAIR forms: two sources read together along the same axis.
953
+
954
+ The INOUT forms cover input + output. These cover input + input, which
955
+ is what a routine taking two vectors of the same length wants -- a
956
+ correlation, a dot product, a distance. Neither state gets
957
+ CA_KERNEL_WRITE, and `flags` must not carry it: use the INOUT forms to
958
+ write.
959
+
960
+ Both fibers are contig-delivered, so the pair may be handed straight to
961
+ a C routine that walks it itself. The MASKED form yields BOTH mask
962
+ cursors: which cells a pair of fibers may be used at is a question about
963
+ both of them, and the INOUT forms answer only about the input.
964
+
965
+ Shape agreement is guarded the way the INOUT forms guard it (ndim,
966
+ elements, fiber length), and the body is skipped on mismatch. The two
967
+ sources may be the same array. */
968
+
969
+ #define CA_FOR_EACH_FIBER_PAIR(st_a, st_b, ca_a, ca_b, axis, \
970
+ flags, p_a, p_b, n) \
971
+ for ( int __cffp_init = ( \
972
+ ca_iter_check_init( \
973
+ ca_iter_state_init_l2(&(st_a), (ca_a), CA_SLAB_AXES, \
974
+ (int8_t[]){(int8_t)(axis)}, 1, \
975
+ (flags) | CA_KERNEL_FIBER_CONTIG)), \
976
+ ca_iter_check_init( \
977
+ ca_iter_state_init_l2(&(st_b), (ca_b), CA_SLAB_AXES, \
978
+ (int8_t[]){(int8_t)(axis)}, 1, \
979
+ (flags) | CA_KERNEL_FIBER_CONTIG)), \
980
+ (n) = (st_a).slab_dims[0], \
981
+ 1); \
982
+ __cffp_init; \
983
+ __cffp_init = 0, \
984
+ ca_iter_state_finish(&(st_a)), \
985
+ ca_iter_state_finish(&(st_b)) ) \
986
+ for ( ; (st_a).src->ndim == (st_b).src->ndim \
987
+ && (st_a).src->elements == (st_b).src->elements \
988
+ && (st_a).slab_dims[0] == (st_b).slab_dims[0] \
989
+ && ca_iter_state_next_slab_axes(&(st_a), &(p_a), NULL) \
990
+ && ca_iter_state_next_slab_axes(&(st_b), &(p_b), NULL); \
991
+ ca_iter_state_sync_slab(&(st_a)), \
992
+ ca_iter_state_sync_slab(&(st_b)) )
993
+
994
+ #define CA_FOR_EACH_FIBER_PAIR_MASKED(st_a, st_b, ca_a, ca_b, axis, \
995
+ flags, p_a, p_b, n, m_a, m_b) \
996
+ for ( int __cffpm_init = ( \
997
+ ca_iter_check_init( \
998
+ ca_iter_state_init_l2(&(st_a), (ca_a), CA_SLAB_AXES, \
999
+ (int8_t[]){(int8_t)(axis)}, 1, \
1000
+ (flags) | CA_KERNEL_FIBER_CONTIG)), \
1001
+ ca_iter_check_init( \
1002
+ ca_iter_state_init_l2(&(st_b), (ca_b), CA_SLAB_AXES, \
1003
+ (int8_t[]){(int8_t)(axis)}, 1, \
1004
+ (flags) | CA_KERNEL_FIBER_CONTIG)), \
1005
+ (n) = (st_a).slab_dims[0], \
1006
+ 1); \
1007
+ __cffpm_init; \
1008
+ __cffpm_init = 0, \
1009
+ ca_iter_state_finish(&(st_a)), \
1010
+ ca_iter_state_finish(&(st_b)) ) \
1011
+ for ( ; (st_a).src->ndim == (st_b).src->ndim \
1012
+ && (st_a).src->elements == (st_b).src->elements \
1013
+ && (st_a).slab_dims[0] == (st_b).slab_dims[0] \
1014
+ && ca_iter_state_next_slab_axes(&(st_a), &(p_a), &(m_a)) \
1015
+ && ca_iter_state_next_slab_axes(&(st_b), &(p_b), &(m_b)); \
1016
+ ca_iter_state_sync_slab(&(st_a)), \
1017
+ ca_iter_state_sync_slab(&(st_b)) )
1018
+
912
1019
  /* ---- Phase D: per-data_type reduction macro suite ----------------------- */
913
1020
 
914
1021
  /* CA_SLAB_REDUCE_T(T, ...): generic per-data_type slab reduction. T is the
data/ext/ca_obj_array.c CHANGED
@@ -825,26 +825,17 @@ rb_ca_s_allocate (VALUE klass)
825
825
  return TypedData_Make_Struct(klass, CArray, &carray_data_type, ca);
826
826
  }
827
827
 
828
- /* @overload initialize(data_type, dim, bytes=0) { ... }
828
+ /* Reads the (data_type, dim, bytes: nil) argument list that CArray.new
829
+ and CArray.__empty__ share. The two differ only in whether the buffer
830
+ is filled, so the rule for reading their arguments -- the optional
831
+ bytes for a fixlen, the refusal of a Class, the guess of the data type,
832
+ the demand that dim be an Array -- is written once here. */
829
833
 
830
- Constructs a new CArray object of <i>data_type</i>, which has the
831
- ndim and the dimensions specified by an <code>Array</code> of
832
- <code>Integer</code> or an argument list of <code>Integer</code>.
833
- The byte size of each element for the fixed length data type
834
- (<code>data_type == CA_FIXLEN</code>) is specified optional argument
835
- <i>bytes</i>. Otherwise, this optional argument has no
836
- effect. If the block is given, the new CArray
837
- object will be initialized by the value returned from the block.
838
- */
839
-
840
- static VALUE
841
- rb_ca_initialize (int argc, VALUE *argv, VALUE self)
834
+ static void
835
+ ca_scan_construct_args (int argc, VALUE *argv, int8_t *data_type,
836
+ ca_size_t *bytes, int8_t *ndim, ca_size_t *dim)
842
837
  {
843
838
  volatile VALUE rtype, rdim, ropt, rbytes = Qnil;
844
- CArray *ca;
845
- int8_t data_type, ndim;
846
- ca_size_t dim[CA_RANK_MAX];
847
- ca_size_t bytes;
848
839
  int8_t i;
849
840
 
850
841
  rb_scan_args(argc, argv, "21", (VALUE *)&rtype, (VALUE *) &rdim, (VALUE *) &ropt);
@@ -863,13 +854,36 @@ rb_ca_initialize (int argc, VALUE *argv, VALUE self)
863
854
  rtype, rtype, rtype);
864
855
  }
865
856
 
866
- rb_ca_guess_type_and_bytes(rtype, rbytes, &data_type, &bytes);
857
+ rb_ca_guess_type_and_bytes(rtype, rbytes, data_type, bytes);
867
858
 
868
859
  Check_Type(rdim, T_ARRAY);
869
- ndim = RARRAY_LEN(rdim);
870
- for (i=0; i<ndim; i++) {
860
+ *ndim = RARRAY_LEN(rdim);
861
+ for (i=0; i<*ndim; i++) {
871
862
  dim[i] = NUM2SIZE(rb_ary_entry(rdim, i));
872
863
  }
864
+ }
865
+
866
+ /* @overload initialize(data_type, dim, bytes=0) { ... }
867
+
868
+ Constructs a new CArray object of <i>data_type</i>, which has the
869
+ ndim and the dimensions specified by an <code>Array</code> of
870
+ <code>Integer</code> or an argument list of <code>Integer</code>.
871
+ The byte size of each element for the fixed length data type
872
+ (<code>data_type == CA_FIXLEN</code>) is specified optional argument
873
+ <i>bytes</i>. Otherwise, this optional argument has no
874
+ effect. If the block is given, the new CArray
875
+ object will be initialized by the value returned from the block.
876
+ */
877
+
878
+ static VALUE
879
+ rb_ca_initialize (int argc, VALUE *argv, VALUE self)
880
+ {
881
+ CArray *ca;
882
+ int8_t data_type, ndim;
883
+ ca_size_t dim[CA_RANK_MAX];
884
+ ca_size_t bytes;
885
+
886
+ ca_scan_construct_args(argc, argv, &data_type, &bytes, &ndim, dim);
873
887
 
874
888
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
875
889
  if ( ca_func[CA_OBJ_ARRAY].pool_init ) {
@@ -1438,6 +1452,33 @@ rb_ca_s_alloc_uninit (VALUE klass, VALUE rtype, VALUE rshape)
1438
1452
  return rb_carray_new(data_type, ndim, dim, bytes, NULL);
1439
1453
  }
1440
1454
 
1455
+ /* Internal primitive behind CArray.empty(data_type, dim, bytes: nil) in
1456
+ lib/carray/construct.rb, which also routes the compatibility spelling
1457
+ CArray.empty(*shape). It is CArray.new with the fill left out: the
1458
+ arguments are read by the same function, and the buffer comes from
1459
+ rb_carray_new (= no MEMZERO) rather than rb_carray_new_safe. A block
1460
+ is refused -- filling is what CArray.new is for. CA_OBJECT still
1461
+ falls through to the zero-VALUE init inside carray_setup_i (= required
1462
+ for GC), so any data_type is safe to ask for. */
1463
+
1464
+ static VALUE
1465
+ rb_ca_s_empty (int argc, VALUE *argv, VALUE klass)
1466
+ {
1467
+ int8_t data_type, ndim;
1468
+ ca_size_t dim[CA_RANK_MAX];
1469
+ ca_size_t bytes;
1470
+
1471
+ if ( rb_block_given_p() ) {
1472
+ rb_raise(rb_eArgError,
1473
+ "CArray.empty does not take a block "
1474
+ "(its contents are left undefined); use CArray.new to fill.");
1475
+ }
1476
+
1477
+ ca_scan_construct_args(argc, argv, &data_type, &bytes, &ndim, dim);
1478
+
1479
+ return rb_carray_new(data_type, ndim, dim, bytes, NULL);
1480
+ }
1481
+
1441
1482
  void
1442
1483
  Init_ca_obj_array (void)
1443
1484
  {
@@ -1449,6 +1490,7 @@ Init_ca_obj_array (void)
1449
1490
  rb_define_method(rb_cCArray, "initialize", rb_ca_initialize, -1);
1450
1491
  rb_define_singleton_method(rb_cCArray, "__alloc_uninit__",
1451
1492
  rb_ca_s_alloc_uninit, 2);
1493
+ rb_define_singleton_method(rb_cCArray, "__empty__", rb_ca_s_empty, -1);
1452
1494
 
1453
1495
  rb_define_singleton_method(rb_cCArray, "fixlen", rb_ca_s_fixlen, -1);
1454
1496
  rb_define_singleton_method(rb_cCArray, "boolean", rb_ca_s_boolean, -1);
data/ext/ca_obj_block.c CHANGED
@@ -269,8 +269,10 @@ rb_cb_initialize_copy (VALUE self, VALUE other)
269
269
  }
270
270
 
271
271
  /* Per-axis accessor macro: defines a method that returns the named
272
- CABlock tail array (size0 / start / step / count) as a Ruby Array.
273
- `offset` is a single Integer and uses its own accessor below. */
272
+ CABlock tail array (size0 / start / step) as a Ruby Array.
273
+ `offset` is a single Integer and uses its own accessor below.
274
+ `count` has no accessor: it is what `shape` already answers, and the
275
+ name belongs to CArray#count. */
274
276
  #define rb_cb_get_attr_ary(name) \
275
277
  rb_cb_ ## name (VALUE self) \
276
278
  { \
@@ -288,7 +290,6 @@ rb_cb_ ## name (VALUE self) \
288
290
  static VALUE rb_cb_get_attr_ary(size0)
289
291
  static VALUE rb_cb_get_attr_ary(start)
290
292
  static VALUE rb_cb_get_attr_ary(step)
291
- static VALUE rb_cb_get_attr_ary(count)
292
293
 
293
294
  static VALUE
294
295
  rb_cb_offset (VALUE self)
@@ -376,7 +377,6 @@ Init_ca_obj_block (void)
376
377
  rb_define_method(rb_cCABlock, "size0", rb_cb_size0, 0);
377
378
  rb_define_method(rb_cCABlock, "start", rb_cb_start, 0);
378
379
  rb_define_method(rb_cCABlock, "step", rb_cb_step, 0);
379
- rb_define_method(rb_cCABlock, "count", rb_cb_count, 0);
380
380
  rb_define_method(rb_cCABlock, "offset", rb_cb_offset, 0);
381
381
 
382
382
  rb_define_method(rb_cCABlock, "idx2addr0", rb_cb_idx2addr0, -1);
@@ -38,10 +38,10 @@
38
38
  typedef struct {
39
39
  /* === CAView prefix === */
40
40
  int16_t obj_type;
41
- int8_t data_type; /* CA_FIXLEN surface (numeric gate); storage is int64 */
41
+ int8_t data_type; /* CA_FIXLEN surface (numeric gate); storage is fixlen-16 */
42
42
  int8_t ndim;
43
43
  int32_t flags; /* CA_FLAG_IS_FACE set */
44
- ca_size_t bytes; /* sizeof(int64_t) = 8 (one offset per element) */
44
+ ca_size_t bytes; /* 2 * sizeof(int64_t) = 16 (one (start,end) pair per element) */
45
45
  ca_size_t elements;
46
46
  ca_size_t *dim;
47
47
  char *ptr;
@@ -299,6 +299,54 @@ rb_ca_const_string_wrap (VALUE parent_val, VALUE buffer, int encoding_id)
299
299
  rb_raise(rb_eTypeError, "CAConstString.wrap: buffer: must be a String");
300
300
  }
301
301
 
302
+ /* This is the one door through which already-built offsets enter, so it
303
+ validates rather than assumes. Everything downstream -- the per-cell
304
+ decode, the native byte scans, the sort comparator -- reads `buf + start`
305
+ for `end - start` bytes on the strength of these pairs; an out-of-range
306
+ one is an out-of-bounds read of the heap, not a wrong answer. Refuse it
307
+ here, while the input is still in the caller's hands, rather than letting
308
+ it surface as an IndexError from a decode or as a crash from a scan.
309
+
310
+ A masked cell is exempt: its bytes may be anything at all, that being the
311
+ CArray mask contract, and no reader dereferences them (the scan skips on
312
+ the mask before touching an offset; `.value`, the explicit strip, raises
313
+ from the decode). */
314
+ {
315
+ ca_size_t i, n;
316
+ int64_t *pair;
317
+ boolean8_t *m;
318
+
319
+ ca_attach(parent);
320
+ ca_update_mask(parent);
321
+ n = parent->elements;
322
+ pair = (int64_t *) parent->ptr;
323
+ m = parent->mask ? (boolean8_t *) parent->mask->ptr : NULL;
324
+ for ( i = 0; i < n; i++ ) {
325
+ int64_t start = pair[2 * i], end = pair[2 * i + 1];
326
+ if ( m && m[i] ) {
327
+ continue;
328
+ }
329
+ if ( start < 0 || end < start || end > (int64_t) RSTRING_LEN(buffer) ) {
330
+ ca_detach(parent);
331
+ rb_raise(rb_eArgError,
332
+ "CAConstString.wrap: element %lld has range [%lld,%lld), "
333
+ "outside the %ld-byte buffer",
334
+ (long long) i, (long long) start, (long long) end,
335
+ (long) RSTRING_LEN(buffer));
336
+ }
337
+ }
338
+ ca_detach(parent);
339
+ }
340
+
341
+ /* Take ownership of the offsets, the way CACategorical.from_codes takes
342
+ ownership of its codes. Without this the Face is read-only but its
343
+ storage is not, so `cs.parent[i] = <anything>` walks straight past the
344
+ check above and back into an out-of-bounds read. The flag rather than
345
+ #freeze, for the reason from_codes gives: freeze would propagate through
346
+ views and Faces. A caller that wants to keep a mutable entity of its own
347
+ passes `.copy`. */
348
+ ca_set_flag(parent, CA_FLAG_READ_ONLY);
349
+
302
350
  ca = ca_const_string_new(parent, rb_str_freeze(buffer), encoding_id);
303
351
  obj = TypedData_Wrap_Struct(rb_cCAConstString, &catext_data_type, ca);
304
352
  rb_ca_set_parent(obj, parent_val); /* pin parent VALUE for GC */
@@ -397,10 +445,13 @@ rb_ca_const_string_encoding (VALUE self)
397
445
  return rb_enc_from_encoding(rb_enc_from_index(ca->encoding_id));
398
446
  }
399
447
 
400
- /* `CAConstString#buffer` — returns the frozen internal byte buffer
401
- (length-prefix format). Exposes the defining tail state for
402
- introspection and the Arrow-boundary component-buffer export
403
- path. */
448
+ /* `CAConstString#buffer` — returns the frozen internal byte buffer: a pure
449
+ concatenation of the element bytes, with no per-record length prefix (=
450
+ the Arrow values buffer). Exposes the defining tail state for
451
+ introspection and the Arrow-boundary component-buffer export path. The
452
+ ranges that index it live in the storage, one (start,end) int64 pair per
453
+ element; `wrap` is where a hand-built pair array is checked against a
454
+ buffer. */
404
455
  static VALUE
405
456
  rb_ca_const_string_buffer (VALUE self)
406
457
  {
@@ -540,7 +591,7 @@ ca_const_string_scan_end (ca_const_string_scan_t *s)
540
591
  so element i occupies s->off[2*i] (start) .. s->off[2*i+1] (end).
541
592
  The buffer is a pure concatenation; length = end - start. */
542
593
  static inline int
543
- ca_const_string_record (ca_const_string_scan_t *s, ca_size_t i, const char **pp, int32_t *plen)
594
+ ca_const_string_record (ca_const_string_scan_t *s, ca_size_t i, const char **pp, int64_t *plen)
544
595
  {
545
596
  int64_t start, end;
546
597
  if ( s->m && s->m[i] ) {
@@ -549,7 +600,7 @@ ca_const_string_record (ca_const_string_scan_t *s, ca_size_t i, const char **pp,
549
600
  start = s->off[2 * i];
550
601
  end = s->off[2 * i + 1];
551
602
  *pp = s->buf + start;
552
- *plen = (int32_t) (end - start);
603
+ *plen = (int64_t) (end - start);
553
604
  return 1;
554
605
  }
555
606
 
@@ -598,7 +649,7 @@ rb_ca_const_string_byte_length (VALUE self)
598
649
  CArray *co;
599
650
  int64_t *op;
600
651
  const char *p;
601
- int32_t len;
652
+ int64_t len;
602
653
  ca_size_t i;
603
654
 
604
655
  ca_const_string_scan_begin(self, &s);
@@ -633,7 +684,7 @@ ca_const_string_predicate (VALUE self, VALUE query, int op)
633
684
  boolean8_t *out;
634
685
  const char *qp, *p;
635
686
  long qlen;
636
- int32_t len;
687
+ int64_t len;
637
688
  ca_size_t i;
638
689
 
639
690
  if ( TYPE(query) != T_STRING ) {
@@ -686,7 +737,7 @@ rb_ca_const_string_eq (VALUE self, VALUE other)
686
737
  they are read only when both records are valid, but GCC cannot prove
687
738
  that -- initialize to keep -Wmaybe-uninitialized quiet and defensive. */
688
739
  const char *pa = NULL, *pb = NULL;
689
- int32_t la = 0, lb = 0;
740
+ int64_t la = 0, lb = 0;
690
741
  ca_size_t i;
691
742
 
692
743
  ca_const_string_scan_begin(self, &a);
@@ -717,7 +768,7 @@ rb_ca_const_string_count (VALUE self, VALUE query)
717
768
  ca_const_string_scan_t s;
718
769
  const char *qp, *p;
719
770
  long qlen;
720
- int32_t len;
771
+ int64_t len;
721
772
  ca_size_t i, c = 0;
722
773
 
723
774
  if ( TYPE(query) != T_STRING ) {
@@ -747,7 +798,7 @@ rb_ca_const_string_search (VALUE self, VALUE query)
747
798
  ca_const_string_scan_t s;
748
799
  const char *qp, *p;
749
800
  long qlen;
750
- int32_t len;
801
+ int64_t len;
751
802
  ca_size_t i;
752
803
  VALUE result = Qnil;
753
804
 
@@ -786,7 +837,7 @@ ca_const_string_byte_cmp (const void *a, const void *b)
786
837
  /* sort raises on masked input, so record() always sets these here; the
787
838
  initializers keep -Wmaybe-uninitialized quiet and stay defensive. */
788
839
  const char *pa = NULL, *pb = NULL;
789
- int32_t la = 0, lb = 0;
840
+ int64_t la = 0, lb = 0;
790
841
  int c;
791
842
  ca_const_string_record(ca_const_string_sort_ctx, ia, &pa, &la);
792
843
  ca_const_string_record(ca_const_string_sort_ctx, ib, &pb, &lb);
@@ -836,9 +887,15 @@ rb_ca_const_string_sort_index (VALUE self)
836
887
  return vout;
837
888
  }
838
889
 
839
- /* CAConstString#sort is defined in Ruby (lib/carray/const_string.rb) as `self[sort_index]`,
840
- i.e. a no-copy view over the offset source (buffer + offsets shared, only
841
- the gather order changes) — consistent with CArray#sort being a view. */
890
+ /* The ordering surface is defined in Ruby (lib/carray/const_string.rb) on
891
+ top of these three: a storage cell is a byte range, so storage order is
892
+ insertion order rather than string order, and every member has to read
893
+ the bytes. What is native here is the flat form, which is the common one
894
+ for a string column and which a byte scan does an order of magnitude
895
+ faster than decoding a Ruby String per cell; the per-axis forms go
896
+ through #to_string. These are spelled as internals because the public
897
+ names carry CArray's meanings: what the permutation below answers is
898
+ sort_addr (view-flat addresses in sorted order), not sort_index. */
842
899
 
843
900
  /* CAConstString#min / #max → the byte-min / byte-max element as a frozen String,
844
901
  skipping masked elements; nil if empty or all masked. */
@@ -847,7 +904,7 @@ ca_const_string_extremum (VALUE self, int want_max)
847
904
  {
848
905
  ca_const_string_scan_t s;
849
906
  const char *p, *bestp = NULL;
850
- int32_t len, bestlen = 0;
907
+ int64_t len, bestlen = 0;
851
908
  ca_size_t i;
852
909
  int found = 0;
853
910
 
@@ -894,9 +951,9 @@ rb_ca_const_string_initialize_copy (VALUE self, VALUE other)
894
951
  CAConstString *ca, *cs;
895
952
  TypedData_Get_Struct(self, CAConstString, &catext_data_type, ca);
896
953
  TypedData_Get_Struct(other, CAConstString, &catext_data_type, cs);
897
- /* T.0/T.1: shallow re-setup (shares buffer + offset-source). T.3 will
898
- replace this with compacting deep copy (rebased offsets + compacted
899
- buffer + detach). */
954
+ /* Shallow re-setup: shares the buffer and the offset source, which is the
955
+ documented `dup` semantics for a view. `copy` is the compacting deep
956
+ copy (rebased offsets, compacted buffer, detached). */
900
957
  if ( ca_func[CA_OBJ_CONST_STRING].pool_init ) {
901
958
  ca_array_pool_alloc(ca, CA_OBJ_CONST_STRING, cs->parent->ndim);
902
959
  }
@@ -949,11 +1006,13 @@ Init_ca_obj_const_string (void)
949
1006
  rb_define_method(rb_cCAConstString, "search", rb_ca_const_string_search, 1);
950
1007
  rb_define_method(rb_cCAConstString, "find_value_index", rb_ca_const_string_search, 1);
951
1008
 
952
- /* native sort_index / min / max (§3.7), byte-memcmp comparator.
953
- sort / sort_copy are defined in Ruby on top of sort_index. */
954
- rb_define_method(rb_cCAConstString, "sort_index", rb_ca_const_string_sort_index, 0);
955
- rb_define_method(rb_cCAConstString, "min", rb_ca_const_string_min, 0);
956
- rb_define_method(rb_cCAConstString, "max", rb_ca_const_string_max, 0);
1009
+ /* Native flat sort permutation / min / max (§3.7), byte-memcmp
1010
+ comparator. The public ordering surface is built on these in
1011
+ lib/carray/const_string.rb; see the comment above their definitions. */
1012
+ rb_define_method(rb_cCAConstString, "__sort_addr_bytes__",
1013
+ rb_ca_const_string_sort_index, 0);
1014
+ rb_define_method(rb_cCAConstString, "__min_bytes__", rb_ca_const_string_min, 0);
1015
+ rb_define_method(rb_cCAConstString, "__max_bytes__", rb_ca_const_string_max, 0);
957
1016
 
958
1017
  /* Y-pilot: Face-local C-level fast path for per-cell scalar fetch. */
959
1018
  ca_face_register_storage_to_scalar(CA_OBJ_CONST_STRING, rb_ca_const_string_storage_to_scalar);
data/ext/ca_obj_face.c CHANGED
@@ -595,6 +595,30 @@ ca_face_class_has_portable_method (VALUE klass)
595
595
  return ( owner != rb_singleton_class(rb_cCArray) ) ? 1 : 0;
596
596
  }
597
597
 
598
+ VALUE
599
+ ca_face_operand_descend (VALUE operand, const char *name)
600
+ {
601
+ CArray *op;
602
+ if ( ! RTEST(rb_obj_is_carray(operand)) ) {
603
+ return operand;
604
+ }
605
+ TypedData_Get_Struct(operand, CArray, &carray_data_type, op);
606
+ if ( ! ca_is_face(op) ) {
607
+ return operand;
608
+ }
609
+ if ( ca_test_flag(op, CA_FLAG_FACE_COMPARABLE_STORAGE) ) {
610
+ return rb_ca_strip_face_value(operand);
611
+ }
612
+ rb_raise(rb_eArgError,
613
+ "%s: cannot take a %s operand: its storage is not its surface "
614
+ "(a cell encodes the value rather than being it), so comparing "
615
+ "the storage would compare the encoding. Convert the operand to "
616
+ "the receiver's space first -- #to_string for a string Face -- "
617
+ "or pass .parent on both sides to work in storage space",
618
+ name, rb_obj_classname(operand));
619
+ return Qnil; /* not reached */
620
+ }
621
+
598
622
  int
599
623
  ca_face_state_portable (int obj_type, VALUE klass)
600
624
  {