carray 3.0.5 → 3.0.6

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +233 -0
  3. data/README.md +1 -1
  4. data/carray.gemspec +3 -2
  5. data/ext/ca_axis_dispatch.c +6 -0
  6. data/ext/ca_axis_group.c +1 -1
  7. data/ext/ca_categorical_iterator.c +7 -7
  8. data/ext/ca_kernel_iterator.c +116 -231
  9. data/ext/ca_kernel_iterator.h +410 -254
  10. data/ext/ca_obj_bincmp.c +9 -1
  11. data/ext/ca_obj_binop.c +10 -1
  12. data/ext/ca_obj_bitfield.c +2 -0
  13. data/ext/ca_obj_byte_swap.c +2 -0
  14. data/ext/ca_obj_face.c +2 -0
  15. data/ext/ca_obj_fake.c +3 -1
  16. data/ext/ca_obj_grid.c +167 -12
  17. data/ext/ca_obj_meld.c +69 -113
  18. data/ext/ca_obj_moncmp.c +9 -1
  19. data/ext/ca_obj_monop.c +40 -2
  20. data/ext/ca_obj_object.c +54 -20
  21. data/ext/ca_obj_reduce.c +15 -1
  22. data/ext/ca_obj_refer.c +65 -20
  23. data/ext/ca_obj_remap.c +69 -10
  24. data/ext/ca_obj_roll.c +1 -0
  25. data/ext/ca_obj_select.c +4 -1
  26. data/ext/ca_obj_select_axis.c +1 -0
  27. data/ext/ca_obj_shift.c +1 -0
  28. data/ext/ca_obj_stack.c +310 -111
  29. data/ext/ca_obj_stride.c +131 -45
  30. data/ext/ca_obj_string.c +18 -2
  31. data/ext/ca_obj_tile.c +1 -0
  32. data/ext/ca_obj_triop.c +9 -1
  33. data/ext/ca_obj_window.c +1 -0
  34. data/ext/ca_op_ipower.c +8 -8
  35. data/ext/caframe_csv_reader.c +1 -1
  36. data/ext/carray.h +2 -1
  37. data/ext/carray_access.c +27 -64
  38. data/ext/carray_address_basis.c +134 -0
  39. data/ext/carray_attribute.c +8 -0
  40. data/ext/carray_broadcast.c +3 -4
  41. data/ext/carray_build_flags.h +1 -1
  42. data/ext/carray_cast.c +8 -11
  43. data/ext/carray_conversion.c +9 -5
  44. data/ext/carray_core.c +811 -72
  45. data/ext/carray_element.c +13 -9
  46. data/ext/carray_histogram.c +25 -3
  47. data/ext/carray_internal.h +147 -12
  48. data/ext/carray_kernels_bincmp.c +2007 -3
  49. data/ext/carray_kernels_binop.c +25365 -5199
  50. data/ext/carray_kernels_init.c +8 -8
  51. data/ext/carray_kernels_monop.c +10696 -3678
  52. data/ext/carray_kernels_reduce_aggregate.c +4429 -702
  53. data/ext/carray_kernels_reduce_boolean.c +1533 -15
  54. data/ext/carray_kernels_reduce_cumulative.c +1808 -248
  55. data/ext/carray_kernels_reduce_extreme.c +4508 -511
  56. data/ext/carray_kernels_reduce_variance.c +2284 -152
  57. data/ext/carray_kernels_scan.c +240 -240
  58. data/ext/carray_kernels_search.c +4211 -264
  59. data/ext/carray_kernels_sort.c +367 -450
  60. data/ext/carray_lazy.c +47 -0
  61. data/ext/carray_loop.c +1 -1
  62. data/ext/carray_mask.c +130 -139
  63. data/ext/carray_median_percentile.c +4 -4
  64. data/ext/carray_memory_view.c +13 -7
  65. data/ext/carray_operator.c +147 -44
  66. data/ext/carray_order.c +3 -2
  67. data/ext/carray_random.c +4 -1
  68. data/ext/carray_slab.c +97 -21
  69. data/ext/carray_sort.c +1 -1
  70. data/ext/carray_test.c +74 -19
  71. data/ext/carray_time_text.c +2 -2
  72. data/ext/extconf.rb +11 -0
  73. data/ext/mkkernel.rb +346 -94
  74. data/ext/ruby_carray.c +4 -1
  75. data/ext/version.h +4 -4
  76. data/lib/carray/attribute.rb +25 -1
  77. data/lib/carray/autoload_carray.rb +3 -3
  78. data/lib/carray/autoload_method_extension.rb +5 -3
  79. data/lib/carray/axis_group.rb +13 -7
  80. data/lib/carray/basics.rb +1 -1
  81. data/lib/carray/block_iterator.rb +17 -3
  82. data/lib/carray/categorical.rb +6 -8
  83. data/lib/carray/categorical_iterator.rb +2 -2
  84. data/lib/carray/complex.rb +2 -2
  85. data/lib/carray/conditional.rb +3 -2
  86. data/lib/carray/frame/column_verbs.rb +45 -14
  87. data/lib/carray/frame/convert.rb +19 -0
  88. data/lib/carray/frame/csv_parser.rb +32 -32
  89. data/lib/carray/frame/describe.rb +4 -2
  90. data/lib/carray/frame/frame.rb +399 -78
  91. data/lib/carray/frame/group.rb +679 -53
  92. data/lib/carray/frame/io.rb +209 -71
  93. data/lib/carray/frame/join.rb +146 -37
  94. data/lib/carray/frame/records.rb +3 -3
  95. data/lib/carray/frame/reshape.rb +89 -11
  96. data/lib/carray/frame/sort.rb +57 -9
  97. data/lib/carray/frame/verbs.rb +384 -49
  98. data/lib/carray/frame/windows.rb +44 -6
  99. data/lib/carray/inspect.rb +7 -2
  100. data/lib/carray/iterator.rb +32 -0
  101. data/lib/carray/lazy.rb +17 -8
  102. data/lib/carray/mask_gap_fill.rb +37 -17
  103. data/lib/carray/meld_reduce.rb +1 -1
  104. data/lib/carray/methods/choose.rb +3 -2
  105. data/lib/carray/methods/meshgrid.rb +3 -1
  106. data/lib/carray/runtime.rb +47 -8
  107. data/lib/carray/segment_iterator.rb +23 -8
  108. data/lib/carray/serialize.rb +34 -17
  109. data/lib/carray/stack.rb +18 -6
  110. data/lib/carray/time.rb +5 -0
  111. data/lib/carray/window_iterator.rb +16 -4
  112. data/yard-stubs/ca_obj_array.rb +26 -1
  113. data/yard-stubs/carray_cast.rb +8 -5
  114. data/yard-stubs/carray_conversion.rb +8 -2
  115. data/yard-stubs/carray_mask.rb +20 -1
  116. data/yard-stubs/carray_math.rb +31 -16
  117. data/yard-stubs/carray_memory_view.rb +7 -0
  118. data/yard-stubs/carray_test.rb +7 -4
  119. metadata +18 -4
data/ext/carray_core.c CHANGED
@@ -10,6 +10,7 @@
10
10
  #include "carray.h"
11
11
  #include "carray_internal.h" /* ca_lazy_arena_enter / _exit */
12
12
  #include "ca_iter_substrate.h"
13
+ #include "ca_obj_face.h"
13
14
  #include <stdarg.h>
14
15
 
15
16
  /* definition of ca_endian */
@@ -29,6 +30,10 @@ const rb_data_type_t *ca_mask_typeddata[CA_OBJ_TYPE_MAX];
29
30
  ca_operation_function_t ca_func[CA_OBJ_TYPE_MAX];
30
31
  int ca_obj_num = 0;
31
32
 
33
+ /* The number of obj_types carray installs for itself, set once
34
+ Init_carray_ext has installed them all; 0 until then. */
35
+ int ca_obj_num_builtin = 0;
36
+
32
37
  /*
33
38
  definition of validity of each data_type [1 for valid, 0 for invalid]
34
39
 
@@ -350,6 +355,14 @@ ca_install_obj_type (VALUE klass,
350
355
  sizeof(((ca_operation_function_t *) 0)->xfer_all)));
351
356
  }
352
357
 
358
+ /* ca_allocate / ca_attach / ca_sync / ca_detach call these four slots
359
+ without asking, from any path that takes a view's buffer. */
360
+ if ( ! func->allocate || ! func->attach || ! func->sync || ! func->detach ) {
361
+ rb_raise(rb_eArgError,
362
+ "ca_install_obj_type: operation table has no allocate / attach / "
363
+ "sync / detach slot");
364
+ }
365
+
353
366
  /* A refused table takes no number. */
354
367
  obj_type = ca_obj_num++;
355
368
 
@@ -811,75 +824,330 @@ ca_index2addr (void *ap, ca_size_t *idx)
811
824
 
812
825
  /* ------------------------------------------------------------------- */
813
826
 
814
- /* The cyclic reference detection should be done in
815
- + ca_fetch_addr, ca_fetch_index,
816
- + ca_store_addr, ca_store_index,
817
- to avoid the system stack error in reference of the object array.
818
- */
827
+ /* Recursion over object arrays.
819
828
 
820
- void
821
- ca_set_cyclic_check(void *ap)
829
+ A cell of an object array can hold the array itself or a view of it.
830
+ Reading such a cell, or computing over the array, would come back into
831
+ the same cells without end. Both are refused when they enter an array
832
+ that the same Ruby method is already reading or computing over, in this
833
+ thread and fiber: rb_exec_recursive keeps that record, as it does for an
834
+ Array that holds itself (Array#flatten, Array#inspect). The array is
835
+ not written to, so a frozen array, or one shared between Ractors, is
836
+ read and computed over as any other.
837
+
838
+ A read, a computation and the evaluation of a lazy expression are
839
+ recorded apart (CA_RECURSION_READ / CA_RECURSION_COMPUTE /
840
+ CA_RECURSION_EVAL): a computation reads the cells it computes over, and
841
+ a reduction over a lazy expression evaluates it. */
842
+
843
+ #define CA_RECURSION_COMPUTE 0
844
+ #define CA_RECURSION_READ 1
845
+ #define CA_RECURSION_EVAL 2
846
+
847
+ static VALUE
848
+ ca_recursion_key (CArray *ca, int kind)
822
849
  {
823
- CArray *ca = (CArray *) ap;
824
- /* ca.data_type == CA_OBJECT */
825
- if ( ca_is_object_type(ca) ) {
826
- if ( ca->flags & CA_FLAG_CYCLE_CHECK ) {
827
- rb_raise(rb_eArgError, "cyclic reference is not allowed in CArray");
850
+ /* the struct of an array is aligned, so the low two bits are free */
851
+ return ULL2NUM(((unsigned long long) (uintptr_t) ca) | (unsigned long long) kind);
852
+ }
853
+
854
+ NORETURN(static void ca_refuse_cyclic (int kind));
855
+
856
+ static void
857
+ ca_refuse_cyclic (int kind)
858
+ {
859
+ if ( kind == CA_RECURSION_COMPUTE ) {
860
+ rb_raise(rb_eArgError, "%s: cyclic reference is not allowed in CArray",
861
+ ca_calling_method_name());
862
+ }
863
+ rb_raise(rb_eArgError, "cyclic reference is not allowed in CArray");
864
+ }
865
+
866
+ typedef struct {
867
+ int n;
868
+ CArray **ops;
869
+ int kind; /* CA_RECURSION_COMPUTE or CA_RECURSION_EVAL */
870
+ int k;
871
+ VALUE (*body)(VALUE);
872
+ VALUE arg;
873
+ } ca_computing_t;
874
+
875
+ static VALUE ca_computing_next (ca_computing_t *c);
876
+
877
+ static VALUE
878
+ ca_computing_step (VALUE key, VALUE varg, int recursive)
879
+ {
880
+ (void) key;
881
+ if ( recursive ) {
882
+ ca_refuse_cyclic(CA_RECURSION_COMPUTE); /* names the method */
883
+ }
884
+ return ca_computing_next((ca_computing_t *) varg);
885
+ }
886
+
887
+ static VALUE
888
+ ca_computing_next (ca_computing_t *c)
889
+ {
890
+ while ( c->k < c->n ) {
891
+ CArray *ca = c->ops[c->k++];
892
+ int j, seen = 0;
893
+ if ( ! ca || ( c->kind == CA_RECURSION_COMPUTE && ! ca_is_object_type(ca) ) ) continue;
894
+ for ( j = 0; j < c->k - 1; j++ ) {
895
+ if ( c->ops[j] == ca ) seen = 1;
828
896
  }
829
- ca_set_flag(ca, CA_FLAG_CYCLE_CHECK);
897
+ if ( seen ) continue;
898
+ return rb_exec_recursive(ca_computing_step,
899
+ ca_recursion_key(ca, c->kind),
900
+ (VALUE) c);
830
901
  }
902
+ return c->body(c->arg);
903
+ }
904
+
905
+ static VALUE ca_recursion_probe (VALUE key, VALUE arg, int recursive);
906
+
907
+ /* Whether ca is, or reaches through its parents, a lazy node being
908
+ evaluated (recorded by ca_computing_run_nodes). A cell value that does
909
+ would come back into that evaluation when it is computed. The walk is
910
+ bounded; past the bound the value is not refused here, and is caught when
911
+ it is evaluated, as a deeper expression is. */
912
+ static int
913
+ ca_reaches_evaluating_walk (CArray *ca, int *budget)
914
+ {
915
+ if ( --(*budget) < 0 ) {
916
+ return 0;
917
+ }
918
+ if ( RTEST(rb_exec_recursive(ca_recursion_probe,
919
+ ca_recursion_key(ca, CA_RECURSION_EVAL),
920
+ Qnil)) ) {
921
+ return 1;
922
+ }
923
+ if ( ca_is_entity(ca) ) {
924
+ return 0;
925
+ }
926
+ if ( ca_test_flag(ca, CA_FLAG_MULTI_PARENTS) ) {
927
+ CAMultiParent *mp = (CAMultiParent *) ca;
928
+ int32_t k;
929
+ for ( k = 0; k < mp->n_parents; k++ ) {
930
+ if ( mp->parents[k] && ca_reaches_evaluating_walk(mp->parents[k], budget) ) {
931
+ return 1;
932
+ }
933
+ }
934
+ return 0;
935
+ }
936
+ return ((CAView *) ca)->parent
937
+ ? ca_reaches_evaluating_walk(((CAView *) ca)->parent, budget)
938
+ : 0;
831
939
  }
832
940
 
833
941
  void
834
- ca_clear_cyclic_check(void *ap)
942
+ ca_refuse_cells_reaching_evaluation (ca_size_t n, const VALUE *cells)
835
943
  {
836
- CArray *ca = (CArray *) ap;
837
- /* ca.data_type == CA_OBJECT */
838
- if ( ca_is_object_type(ca) ) {
839
- ca_unset_flag(ca, CA_FLAG_CYCLE_CHECK);
944
+ ca_size_t i;
945
+ for ( i = 0; i < n; i++ ) {
946
+ if ( rb_obj_is_carray(cells[i]) ) {
947
+ CArray *cv;
948
+ int budget = 1024;
949
+ TypedData_Get_Struct(cells[i], CArray, &carray_data_type, cv);
950
+ if ( ! ca_is_entity(cv) && ca_reaches_evaluating_walk(cv, &budget) ) {
951
+ ca_refuse_cyclic(CA_RECURSION_COMPUTE);
952
+ }
953
+ }
954
+ }
955
+ }
956
+
957
+ /* Runs body(arg), a computation over ops[0..n), with each object array
958
+ among them recorded as computed over. Raises when one of them is
959
+ already being computed over by the same method: a cell's value, or an
960
+ element's operation, led the computation back into it. */
961
+ VALUE
962
+ ca_computing_run (int n, CArray **ops, VALUE (*body)(VALUE), VALUE arg)
963
+ {
964
+ ca_computing_t c;
965
+ c.n = n; c.ops = ops; c.kind = CA_RECURSION_COMPUTE; c.k = 0; c.body = body; c.arg = arg;
966
+ return ca_computing_next(&c);
967
+ }
968
+
969
+ /* As ca_computing_run, for the nodes of a lazy expression evaluated over
970
+ object cells: every array of ops[0..n) is recorded whatever its data type
971
+ (a comparison is boolean), apart from computations, so a reduction over
972
+ the expression evaluating it is no re-entry. */
973
+ VALUE
974
+ ca_computing_run_nodes (int n, CArray **ops, VALUE (*body)(VALUE), VALUE arg)
975
+ {
976
+ ca_computing_t c;
977
+ c.n = n; c.ops = ops; c.kind = CA_RECURSION_EVAL; c.k = 0; c.body = body; c.arg = arg;
978
+ return ca_computing_next(&c);
979
+ }
980
+
981
+ static VALUE
982
+ ca_recursion_probe (VALUE key, VALUE arg, int recursive)
983
+ {
984
+ (void) key; (void) arg;
985
+ return recursive ? Qtrue : Qfalse;
986
+ }
987
+
988
+ static VALUE
989
+ ca_refuse_reentry_step (VALUE self, VALUE name, int recursive)
990
+ {
991
+ (void) self;
992
+ if ( recursive ) {
993
+ rb_raise(rb_eArgError, "%" PRIsVALUE ": cyclic reference is not allowed in CArray",
994
+ name);
840
995
  }
996
+ return rb_yield(Qnil);
997
+ }
998
+
999
+ /* refuse_reentry(name) { ... } -- runs the block, refusing as cyclic a
1000
+ second call for the same array from inside it (rb_exec_recursive, by
1001
+ the receiver). Ruby code that walks the cells of an object array and
1002
+ comes back to the array through a cell, such as the nested dump of a
1003
+ view, goes through this. */
1004
+ static VALUE
1005
+ rb_ca_refuse_reentry (VALUE self, VALUE name)
1006
+ {
1007
+ return rb_exec_recursive(ca_refuse_reentry_step, self, name);
841
1008
  }
842
1009
 
1010
+ /* A cell read that delivers an array being read refuses it: the array the
1011
+ cell was read from (an entity is not recorded, see ca_with_cyclic_check)
1012
+ or one recorded further out. */
843
1013
  void
844
- ca_test_cyclic_check(void *ap, void *ptr)
1014
+ ca_test_cyclic_check (void *ap, void *ptr)
845
1015
  {
846
1016
  CArray *ca = (CArray *) ap;
847
-
848
- /* ca.data_type == CA_OBJECT */
849
1017
  if ( ca_is_object_type(ca) ) {
850
- VALUE rval = *(VALUE*) ptr;
1018
+ VALUE rval = *(VALUE *) ptr;
851
1019
  if ( rb_obj_is_carray(rval) ) {
852
1020
  CArray *cv;
853
1021
  TypedData_Get_Struct(rval, CArray, &carray_data_type, cv);
854
- if ( ca_test_flag(cv, CA_FLAG_CYCLE_CHECK) ) {
855
- rb_raise(rb_eArgError, "cyclic reference is not allowed in CArray");
1022
+ if ( cv == ca ||
1023
+ RTEST(rb_exec_recursive(ca_recursion_probe,
1024
+ ca_recursion_key(cv, CA_RECURSION_READ),
1025
+ Qnil)) ) {
1026
+ ca_refuse_cyclic(CA_RECURSION_READ);
856
1027
  }
857
1028
  }
858
1029
  }
859
1030
  }
860
1031
 
861
- /* Runs body(arg) -- one transfer of an object cell or cells -- with ca's
862
- cycle check set, and clears the check however body leaves. Either
863
- direction can call into Ruby: a GET through a CAObject slot or the
864
- check of what it delivered, a PUT that converts the caller's value. A
865
- raise that skipped the clear would leave every later access to ca
866
- reporting a cyclic reference. A PUT into an entity only copies VALUEs
867
- and cannot raise, so it runs unprotected. */
1032
+ /* Refuse to write into an object array a value that is a CArray sharing
1033
+ its cells -- the array itself or a view of it. Reading such a cell back
1034
+ is refused as cyclic (ca_test_cyclic_check); refusing the write where it
1035
+ passes through here reports the mistake where it is made. A write that
1036
+ reaches the cells another way is still caught when the cell is read.
1037
+ data holds n VALUEs. */
868
1038
  static void
869
- ca_with_cyclic_check (CArray *ca, int dir, VALUE (*body)(VALUE), VALUE arg)
1039
+ ca_refuse_storing_self (CArray *ca, const void *data, ca_size_t n)
870
1040
  {
871
- int state = 0;
872
- ca_set_cyclic_check(ca);
873
- if ( dir == CA_XFER_PUT && ca_is_entity(ca) ) {
874
- body(arg);
1041
+ const char *d = (const char *) data;
1042
+ ca_size_t i;
1043
+ for (i=0; i<n; i++) {
1044
+ VALUE rval;
1045
+ memcpy(&rval, d + i * sizeof(VALUE), sizeof(VALUE));
1046
+ if ( rb_obj_is_carray(rval) ) {
1047
+ CArray *cv;
1048
+ TypedData_Get_Struct(rval, CArray, &carray_data_type, cv);
1049
+ if ( ca_shares_root(ca, cv) ) {
1050
+ rb_raise(rb_eArgError, "%s: can not store an object array into itself",
1051
+ ca_calling_method_name());
1052
+ }
1053
+ }
875
1054
  }
876
- else {
877
- rb_protect(body, arg, &state);
1055
+ }
1056
+
1057
+ typedef struct {
1058
+ VALUE (*body)(VALUE);
1059
+ VALUE arg;
1060
+ } ca_reading_t;
1061
+
1062
+ static VALUE
1063
+ ca_reading_step (VALUE key, VALUE varg, int recursive)
1064
+ {
1065
+ ca_reading_t *r = (ca_reading_t *) varg;
1066
+ (void) key;
1067
+ if ( recursive ) {
1068
+ ca_refuse_cyclic(CA_RECURSION_READ);
878
1069
  }
879
- ca_clear_cyclic_check(ca);
880
- if ( state ) {
881
- rb_jump_tag(state);
1070
+ return r->body(r->arg);
1071
+ }
1072
+
1073
+ static char ca_obj_moves_cells[CA_OBJ_TYPE_MAX];
1074
+
1075
+ void
1076
+ ca_register_cell_moving_view (int8_t obj_type)
1077
+ {
1078
+ ca_obj_moves_cells[(uint8_t) obj_type] = 1;
1079
+ }
1080
+
1081
+ /* Whether the transfers of a Face are the forwarders of ca_obj_face.c, which
1082
+ hand the request to the parent unchanged. A Face with transfer slots of
1083
+ its own (one defined in Ruby on CAObject, or by an extension) may run Ruby
1084
+ and answers no. */
1085
+ static int
1086
+ ca_face_forwards_transfers (CArray *ca)
1087
+ {
1088
+ ca_operation_function_t *f = &ca_func[ca->obj_type];
1089
+ return f->xfer_index == ca_face_xfer_index &&
1090
+ f->xfer_addrs == ca_face_xfer_addrs &&
1091
+ f->xfer_stride == ca_face_xfer_stride &&
1092
+ f->xfer_all == ca_face_xfer_all;
1093
+ }
1094
+
1095
+ /* Whether a transfer of ca reaches entities through cell-moving views only:
1096
+ then it calls no Ruby on the way. An unknown view, one that computes its
1097
+ cells (CAObject, a lazy expression) or converts them, answers no. */
1098
+ static int
1099
+ ca_transfer_calls_no_ruby (CArray *ca, int depth)
1100
+ {
1101
+ while ( ! ca_is_entity(ca) ) {
1102
+ if ( depth++ > 64 ) {
1103
+ return 0;
1104
+ }
1105
+ if ( ca_test_flag(ca, CA_FLAG_MULTI_PARENTS) ) {
1106
+ CAMultiParent *mp = (CAMultiParent *) ca;
1107
+ int32_t k;
1108
+ if ( ! ca_obj_moves_cells[(uint8_t) ca->obj_type] ) {
1109
+ return 0;
1110
+ }
1111
+ for ( k = 0; k < mp->n_parents; k++ ) {
1112
+ if ( ! mp->parents[k] ||
1113
+ ! ca_transfer_calls_no_ruby(mp->parents[k], depth) ) {
1114
+ return 0;
1115
+ }
1116
+ }
1117
+ return 1;
1118
+ }
1119
+ if ( ca_is_face(ca) ? ! ca_face_forwards_transfers(ca)
1120
+ : ! ( ca_is_stride_family(ca) ||
1121
+ ca_obj_moves_cells[(uint8_t) ca->obj_type] ) ) {
1122
+ return 0;
1123
+ }
1124
+ ca = ((CAView *) ca)->parent;
1125
+ if ( ! ca ) {
1126
+ return 0;
1127
+ }
882
1128
  }
1129
+ return 1;
1130
+ }
1131
+
1132
+ /* Runs body(arg) -- one transfer of an object cell or cells -- with ca
1133
+ recorded as being read. Entering a transfer of ca again from inside it
1134
+ is refused as cyclic. A transfer of an entity, or of views that only move
1135
+ cells down to entities, copies VALUEs and calls no Ruby, so it cannot be
1136
+ entered again; it is not recorded, and a cell holding the array itself is
1137
+ caught by ca_test_cyclic_check. */
1138
+ static void
1139
+ ca_with_cyclic_check (CArray *ca, int dir, VALUE (*body)(VALUE), VALUE arg)
1140
+ {
1141
+ ca_reading_t r;
1142
+ (void) dir;
1143
+ if ( ca_transfer_calls_no_ruby(ca, 0) ) {
1144
+ body(arg);
1145
+ return;
1146
+ }
1147
+ r.body = body;
1148
+ r.arg = arg;
1149
+ rb_exec_recursive(ca_reading_step, ca_recursion_key(ca, CA_RECURSION_READ),
1150
+ (VALUE) &r);
883
1151
  }
884
1152
 
885
1153
  /* ------------------------------------------------------------------- */
@@ -912,6 +1180,58 @@ ca_store_addr (void *ap, ca_size_t addr, void *pval)
912
1180
  public C-API for external ext gems). Every view supplies an
913
1181
  xfer_index slot. */
914
1182
 
1183
+ /* ---- A write through a view, tried first against a stack's stand-ins ----
1184
+ (ca_multi_parent_value_trial_below) */
1185
+
1186
+ typedef struct {
1187
+ int kind;
1188
+ CArray *ca;
1189
+ ca_size_t n;
1190
+ ca_size_t *addrs;
1191
+ ca_size_t *starts;
1192
+ ca_size_t *counts;
1193
+ ca_size_t *strides;
1194
+ void *data;
1195
+ ca_size_t base;
1196
+ int8_t ndim;
1197
+ ca_size_t *steps;
1198
+ } ca_value_trial_put_t;
1199
+
1200
+ enum { CA_TRIAL_ADDRS, CA_TRIAL_STRIDE, CA_TRIAL_ALL, CA_TRIAL_FILL,
1201
+ CA_TRIAL_FILL_STRIDE, CA_TRIAL_FILL_ADDRS, CA_TRIAL_SYNC };
1202
+
1203
+ static VALUE
1204
+ ca_value_trial_put_body (VALUE arg)
1205
+ {
1206
+ ca_value_trial_put_t *a = (ca_value_trial_put_t *) arg;
1207
+ switch ( a->kind ) {
1208
+ case CA_TRIAL_ADDRS:
1209
+ ca_xfer_addrs(a->ca, a->n, a->addrs, a->data, CA_XFER_PUT); break;
1210
+ case CA_TRIAL_STRIDE:
1211
+ ca_xfer_stride(a->ca, a->starts, a->counts, a->strides, a->data, CA_XFER_PUT); break;
1212
+ case CA_TRIAL_ALL:
1213
+ ca_xfer_all(a->ca, a->data, CA_XFER_PUT); break;
1214
+ case CA_TRIAL_FILL:
1215
+ ca_fill_data(a->ca, a->data); break;
1216
+ case CA_TRIAL_FILL_STRIDE:
1217
+ ca_fill_stride(a->ca, a->base, a->ndim, a->counts, a->steps, a->data); break;
1218
+ case CA_TRIAL_FILL_ADDRS:
1219
+ ca_fill_addrs(a->ca, a->n, a->addrs, a->data); break;
1220
+ case CA_TRIAL_SYNC:
1221
+ ca_func[a->ca->obj_type].sync(a->ca); break;
1222
+ }
1223
+ return Qnil;
1224
+ }
1225
+
1226
+ #define CA_VALUE_TRIAL_PUT(...) \
1227
+ do { \
1228
+ ca_value_trial_put_t a_ = { __VA_ARGS__ }; \
1229
+ CArray *m_ = ca_multi_parent_value_trial_below(a_.ca); \
1230
+ if ( m_ != NULL ) { \
1231
+ ca_multi_parent_value_trial_through(m_, ca_value_trial_put_body, (VALUE) &a_); \
1232
+ } \
1233
+ } while (0)
1234
+
915
1235
  static inline void
916
1236
  ca_xfer_index_dispatch (CArray *ca, ca_size_t *idx, void *data, int dir)
917
1237
  {
@@ -950,6 +1270,7 @@ ca_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
950
1270
  ca_refuse_read_only();
951
1271
  }
952
1272
 
1273
+
953
1274
  /* Fast path: non-CA_OBJECT element type needs no GC protection. */
954
1275
  if ( ca->data_type != CA_OBJECT ) {
955
1276
  ca_xfer_index_dispatch(ca, idx, data, dir);
@@ -957,6 +1278,9 @@ ca_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
957
1278
  }
958
1279
 
959
1280
  /* CA_OBJECT slow path: cyclic check. */
1281
+ if ( dir == CA_XFER_PUT ) {
1282
+ ca_refuse_storing_self(ca, data, 1);
1283
+ }
960
1284
  {
961
1285
  struct ca_xfer_index_args args;
962
1286
  args.ca = ca;
@@ -1030,7 +1354,27 @@ ca_xfer_addrs_dispatch (CArray *ca, ca_size_t n, ca_size_t *addrs,
1030
1354
  return;
1031
1355
  }
1032
1356
  /* Per-cell loop for arbitrary (non-sequential) addrs:
1033
- fancy gather/scatter from CASelect 2-D mapper, CSA sparse mask, etc. */
1357
+ fancy gather/scatter from CASelect 2-D mapper, CSA sparse mask, etc.
1358
+ Cells of 1, 2, 4 or 8 bytes are copied with that length as a
1359
+ constant, which the compiler turns into one load and store (and
1360
+ which stays legal for an unaligned buffer); a length known only at
1361
+ run time is a call per cell. */
1362
+ #define CA_XFER_ADDRS_AS(W) \
1363
+ do { \
1364
+ if ( dir == CA_XFER_GET ) { \
1365
+ for (i=0; i<n; i++) memcpy(d + i * (W), ca->ptr + (W) * addrs[i], (W)); \
1366
+ } \
1367
+ else { \
1368
+ for (i=0; i<n; i++) memcpy(ca->ptr + (W) * addrs[i], d + i * (W), (W)); \
1369
+ } \
1370
+ } while (0)
1371
+ switch ( ca->bytes ) {
1372
+ case 1: CA_XFER_ADDRS_AS(1); return;
1373
+ case 2: CA_XFER_ADDRS_AS(2); return;
1374
+ case 4: CA_XFER_ADDRS_AS(4); return;
1375
+ case 8: CA_XFER_ADDRS_AS(8); return;
1376
+ }
1377
+ #undef CA_XFER_ADDRS_AS
1034
1378
  for ( i = 0; i < n; i++ ) {
1035
1379
  char *p = ca->ptr + ca->bytes * addrs[i];
1036
1380
  if ( dir == CA_XFER_GET ) memcpy(d + i * ca->bytes, p, ca->bytes);
@@ -1078,6 +1422,11 @@ ca_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs, void *data, int dir)
1078
1422
  ca_refuse_read_only();
1079
1423
  }
1080
1424
 
1425
+ if ( dir == CA_XFER_PUT && ca->ptr == NULL ) {
1426
+ CA_VALUE_TRIAL_PUT(CA_TRIAL_ADDRS, ca, n, addrs, NULL, NULL, NULL, data);
1427
+ }
1428
+
1429
+
1081
1430
  /* Fast path: non-CA_OBJECT element type needs no GC protection. */
1082
1431
  if ( ca->data_type != CA_OBJECT ) {
1083
1432
  ca_xfer_addrs_dispatch(ca, n, addrs, data, dir);
@@ -1085,6 +1434,9 @@ ca_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs, void *data, int dir)
1085
1434
  }
1086
1435
 
1087
1436
  /* CA_OBJECT slow path: cyclic check. */
1437
+ if ( dir == CA_XFER_PUT ) {
1438
+ ca_refuse_storing_self(ca, data, n);
1439
+ }
1088
1440
  {
1089
1441
  struct ca_xfer_addrs_args args;
1090
1442
  args.ca = ca;
@@ -1440,7 +1792,6 @@ ca_xfer_stride_dispatch (CArray *ca, ca_size_t *starts, ca_size_t *counts,
1440
1792
  int8_t ndim = ca->ndim;
1441
1793
  ca_size_t native[CA_RANK_MAX];
1442
1794
  ca_size_t base = 0;
1443
- ca_size_t doff = 0;
1444
1795
  ca_size_t s;
1445
1796
  int8_t k;
1446
1797
  ca_size_t idx[CA_RANK_MAX];
@@ -1467,18 +1818,27 @@ ca_xfer_stride_dispatch (CArray *ca, ca_size_t *starts, ca_size_t *counts,
1467
1818
  return;
1468
1819
  }
1469
1820
 
1470
- /* fallback: per-cell via byte offset -> flat addr -> index -> xfer_index. */
1471
- for ( k = 0; k < ndim; k++ ) idx[k] = 0;
1472
- /* empty: its one caller, ca_xfer_stride, returns on an empty region */
1473
- while ( 1 ) {
1474
- ca_size_t soff = base, vidx[CA_RANK_MAX];
1475
- for ( k = 0; k < ndim; k++ ) soff += idx[k] * strides[k];
1476
- ca_addr2index(ca, soff / ca->bytes, vidx);
1477
- ca_xfer_index_dispatch(ca, vidx, d + doff, dir);
1478
- doff += ca->bytes;
1479
- k = ndim - 1;
1480
- while ( k >= 0 ) { if ( ++idx[k] < counts[k] ) break; idx[k] = 0; k--; }
1481
- if ( k < 0 ) break;
1821
+ /* fallback for a type without xfer_stride: the region's cells by address,
1822
+ handed to xfer_addrs in one call (data is packed over counts, as
1823
+ xfer_addrs takes it). */
1824
+ {
1825
+ volatile VALUE holder = 0;
1826
+ ca_size_t n = 1, i = 0;
1827
+ ca_size_t *addrs;
1828
+ for ( k = 0; k < ndim; k++ ) n *= counts[k];
1829
+ addrs = ALLOCV_N(ca_size_t, holder, n);
1830
+ for ( k = 0; k < ndim; k++ ) idx[k] = 0;
1831
+ /* empty: its one caller, ca_xfer_stride, returns on an empty region */
1832
+ while ( 1 ) {
1833
+ ca_size_t soff = base;
1834
+ for ( k = 0; k < ndim; k++ ) soff += idx[k] * strides[k];
1835
+ addrs[i++] = soff / ca->bytes;
1836
+ k = ndim - 1;
1837
+ while ( k >= 0 ) { if ( ++idx[k] < counts[k] ) break; idx[k] = 0; k--; }
1838
+ if ( k < 0 ) break;
1839
+ }
1840
+ ca_xfer_addrs_dispatch(ca, n, addrs, d, dir);
1841
+ ALLOCV_END(holder);
1482
1842
  }
1483
1843
  }
1484
1844
 
@@ -1523,11 +1883,22 @@ ca_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
1523
1883
 
1524
1884
  if ( ca_region_is_empty(ca->ndim, counts) ) return;
1525
1885
 
1886
+ if ( dir == CA_XFER_PUT && ca->ptr == NULL ) {
1887
+ CA_VALUE_TRIAL_PUT(CA_TRIAL_STRIDE, ca, 0, NULL, starts, counts, strides, data);
1888
+ }
1889
+
1890
+
1526
1891
  if ( ca->data_type != CA_OBJECT ) {
1527
1892
  ca_xfer_stride_dispatch(ca, starts, counts, strides, data, dir);
1528
1893
  return;
1529
1894
  }
1530
1895
 
1896
+ if ( dir == CA_XFER_PUT ) {
1897
+ ca_size_t n = 1;
1898
+ int8_t k;
1899
+ for (k=0; k<ca->ndim; k++) n *= counts[k];
1900
+ ca_refuse_storing_self(ca, data, n);
1901
+ }
1531
1902
  {
1532
1903
  struct ca_xfer_stride_args args;
1533
1904
  args.ca = ca; args.starts = starts; args.counts = counts;
@@ -1588,6 +1959,9 @@ ca_xfer_all (void *ap, void *data, int dir)
1588
1959
  if ( ca->elements == 0 ) {
1589
1960
  return;
1590
1961
  }
1962
+ if ( dir == CA_XFER_PUT && ca->ptr == NULL ) {
1963
+ CA_VALUE_TRIAL_PUT(CA_TRIAL_ALL, ca, 0, NULL, NULL, NULL, NULL, data);
1964
+ }
1591
1965
  /* Universal arena lifetime hook. ca_xfer_all is the single universal
1592
1966
  entry for materialise (to_ca -> ca_copy -> ca_copy_data ->
1593
1967
  ca_xfer_all), so wrapping here covers every outermost view type,
@@ -1679,14 +2053,111 @@ rb_ca_s_attached_views (VALUE klass)
1679
2053
  return LONG2NUM(__atomic_load_n(&ca_attached_views, __ATOMIC_RELAXED));
1680
2054
  }
1681
2055
 
1682
- /* Set once at load from CARRAY_GENERAL_PATH; see carray_internal.h. */
2056
+ /* Set once at load from CARRAY_GENERAL_PATH and CARRAY_SHORTCUTS_OFF; see
2057
+ carray_internal.h. */
1683
2058
  int ca_fast_paths_on = 1;
1684
2059
  int ca_iter_transfer_only_on = 0;
2060
+ unsigned char ca_shortcut_off[CA_SC_COUNT];
2061
+
2062
+ /* In the order of enum ca_shortcut. */
2063
+ static const char *ca_shortcut_names[CA_SC_COUNT] = {
2064
+ "shift_whole_view",
2065
+ "stack",
2066
+ "stack_outer_k",
2067
+ "fiber_fused_conversion",
2068
+ "per_slab_hoist",
2069
+ "protect_only_may_raise",
2070
+ "stack_addrs_k0",
2071
+ "stack_stride_box",
2072
+ "stack_stride_reloc",
2073
+ "stack_parents_k0",
2074
+ "stack_fold",
2075
+ "meld_stride_box",
2076
+ "meld_parents_axis0",
2077
+ "median_partition",
2078
+ "percentile_partition",
2079
+ "loop_interchange",
2080
+ "stack_loop_interchange",
2081
+ "lazy_streaming_reduce",
2082
+ "short_fiber_reduce",
2083
+ "meld_reduce",
2084
+ "segment_nunique",
2085
+ "segment_scan_kernel",
2086
+ "segment_edge_kernel",
2087
+ "segment_reduce_kernel",
2088
+ "segment_all_any_kernel",
2089
+ "categorical_group_by_index",
2090
+ "axis_group_count_kernel",
2091
+ "axis_group_whole_block",
2092
+ "axis_group_reduce_kernel",
2093
+ "axis_group_scan_kernel",
2094
+ "window_fold_by_offset",
2095
+ };
1685
2096
 
2097
+ static int
2098
+ ca_shortcut_lookup (const char *name, long len)
2099
+ {
2100
+ int i;
2101
+ for (i=0; i<CA_SC_COUNT; i++) {
2102
+ if ( (long) strlen(ca_shortcut_names[i]) == len
2103
+ && ! strncmp(ca_shortcut_names[i], name, len) ) {
2104
+ return i;
2105
+ }
2106
+ }
2107
+ return -1;
2108
+ }
2109
+
2110
+ /* CARRAY_SHORTCUTS_OFF is a comma-separated list of names. A name it does
2111
+ not know is warned about, so a misspelt one does not leave the shortcut
2112
+ on unnoticed. */
2113
+ static void
2114
+ ca_shortcuts_off_from_env (void)
2115
+ {
2116
+ const char *p = getenv("CARRAY_SHORTCUTS_OFF");
2117
+ if ( ! p ) {
2118
+ return;
2119
+ }
2120
+ while ( *p ) {
2121
+ const char *q = strchr(p, ',');
2122
+ long len = q ? (long) (q - p) : (long) strlen(p);
2123
+ if ( len > 0 ) {
2124
+ int id = ca_shortcut_lookup(p, len);
2125
+ if ( id < 0 ) {
2126
+ rb_warn("CARRAY_SHORTCUTS_OFF: unknown shortcut %.*s", (int) len, p);
2127
+ }
2128
+ else {
2129
+ ca_shortcut_off[id] = 1;
2130
+ }
2131
+ }
2132
+ if ( ! q ) {
2133
+ break;
2134
+ }
2135
+ p = q + 1;
2136
+ }
2137
+ }
2138
+
2139
+ /* CArray.__fast_paths_enabled__ answers whether the fast paths are on;
2140
+ with a name, whether that one is. */
1686
2141
  static VALUE
1687
- rb_ca_s_fast_paths_enabled (VALUE klass)
2142
+ rb_ca_s_fast_paths_enabled (int argc, VALUE *argv, VALUE klass)
1688
2143
  {
1689
- return ca_fast_paths_on ? Qtrue : Qfalse;
2144
+ VALUE rname;
2145
+ int id;
2146
+
2147
+ rb_scan_args(argc, argv, "01", &rname);
2148
+ if ( NIL_P(rname) ) {
2149
+ return ca_fast_paths_on ? Qtrue : Qfalse;
2150
+ }
2151
+ if ( SYMBOL_P(rname) ) {
2152
+ rname = rb_sym2str(rname);
2153
+ }
2154
+ StringValue(rname);
2155
+ id = ca_shortcut_lookup(RSTRING_PTR(rname), RSTRING_LEN(rname));
2156
+ if ( id < 0 ) {
2157
+ rb_raise(rb_eArgError, "__fast_paths_enabled__: unknown shortcut %"PRIsVALUE,
2158
+ rname);
2159
+ }
2160
+ return ca_fast_path(id) ? Qtrue : Qfalse;
1690
2161
  }
1691
2162
 
1692
2163
  static VALUE
@@ -1880,27 +2351,37 @@ ca_sync_all_step (VALUE arg)
1880
2351
  return Qnil;
1881
2352
  }
1882
2353
 
2354
+ /* Syncs `ca`, carrying on past a raise: the first exception is kept in
2355
+ *error (left as it is when one is already there), to be raised once
2356
+ the others have been synced. A throw or break cannot wait, and goes at
2357
+ once. */
2358
+ void
2359
+ ca_sync_carry_on (CArray *ca, volatile VALUE *error)
2360
+ {
2361
+ int tag = 0;
2362
+ rb_protect(ca_sync_all_step, (VALUE) ca, &tag);
2363
+ if ( tag ) {
2364
+ VALUE err = rb_errinfo();
2365
+ if ( ! RTEST(rb_obj_is_kind_of(err, rb_eException)) ) {
2366
+ rb_jump_tag(tag);
2367
+ }
2368
+ if ( NIL_P(*error) ) {
2369
+ *error = err;
2370
+ }
2371
+ rb_set_errinfo(Qnil);
2372
+ }
2373
+ }
2374
+
1883
2375
  /* Syncs every one of list[0..n-1] in turn, carrying on past one that
1884
2376
  raises so that the others still receive what was written, then raises
1885
- the first exception. A throw or break cannot wait, and goes at once. */
2377
+ the first exception. */
1886
2378
  void
1887
2379
  ca_sync_all (CArray **list, int32_t n)
1888
2380
  {
1889
2381
  volatile VALUE error = Qnil;
1890
2382
  int32_t k;
1891
2383
  for ( k = 0; k < n; k++ ) {
1892
- int tag = 0;
1893
- rb_protect(ca_sync_all_step, (VALUE) list[k], &tag);
1894
- if ( tag ) {
1895
- VALUE err = rb_errinfo();
1896
- if ( ! RTEST(rb_obj_is_kind_of(err, rb_eException)) ) {
1897
- rb_jump_tag(tag);
1898
- }
1899
- if ( NIL_P(error) ) {
1900
- error = err;
1901
- }
1902
- rb_set_errinfo(Qnil);
1903
- }
2384
+ ca_sync_carry_on(list[k], &error);
1904
2385
  }
1905
2386
  if ( ! NIL_P(error) ) {
1906
2387
  rb_exc_raise(error);
@@ -2092,6 +2573,234 @@ ca_attach_window (int32_t n, CArray **list, const char *modes,
2092
2573
  return result;
2093
2574
  }
2094
2575
 
2576
+ /* ---- A window whose Fiber is abandoned ------------------------------------
2577
+
2578
+ A window that yields to a block is closed by rb_ensure. Ruby does not
2579
+ run the ensure of a Fiber that is suspended and then collected -- an
2580
+ Enumerator stopped with #next and dropped is one -- so such a window
2581
+ would stay open for good: a view keeps its attach, and a source that
2582
+ publishes its ptr only while held (a CASource) keeps it published and
2583
+ stops checking its backend.
2584
+
2585
+ The guard closes it from a finalizer instead. The window's state lives
2586
+ on the heap, owned by the guard; a sentinel object that only the
2587
+ window's frame refers to carries a finalizer that holds the guard. When
2588
+ the Fiber is collected the sentinel goes with it, and the finalizer --
2589
+ which runs outside GC, where Ruby may be called -- runs `cleanup`. A
2590
+ window that closes normally disarms the guard first.
2591
+
2592
+ `mark` marks the VALUEs `arg` holds, which nothing else does once the
2593
+ frame that referred to them is gone. The guard frees `arg` with xfree. */
2594
+
2595
+ typedef struct {
2596
+ void (*cleanup) (void *arg);
2597
+ void (*mark) (void *arg);
2598
+ void *arg;
2599
+ int armed;
2600
+ } ca_window_guard_t;
2601
+
2602
+ static void
2603
+ ca_window_guard_mark (void *p)
2604
+ {
2605
+ ca_window_guard_t *g = (ca_window_guard_t *) p;
2606
+ if ( g->arg && g->mark ) {
2607
+ g->mark(g->arg);
2608
+ }
2609
+ }
2610
+
2611
+ static void
2612
+ ca_window_guard_free (void *p)
2613
+ {
2614
+ ca_window_guard_t *g = (ca_window_guard_t *) p;
2615
+ xfree(g->arg);
2616
+ xfree(g);
2617
+ }
2618
+
2619
+ static const rb_data_type_t ca_window_guard_data_type = {
2620
+ .wrap_struct_name = "CArrayWindowGuard",
2621
+ .function = {
2622
+ .dmark = ca_window_guard_mark,
2623
+ .dfree = ca_window_guard_free,
2624
+ .dsize = NULL,
2625
+ },
2626
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
2627
+ };
2628
+
2629
+ static VALUE
2630
+ ca_window_guard_cleanup_step (VALUE gv)
2631
+ {
2632
+ ca_window_guard_t *g;
2633
+ TypedData_Get_Struct(gv, ca_window_guard_t, &ca_window_guard_data_type, g);
2634
+ g->cleanup(g->arg);
2635
+ return Qnil;
2636
+ }
2637
+
2638
+ /* The finalizer is the guard itself, called with the sentinel's object id.
2639
+ A proc made here would capture the frame it is made in -- inside the
2640
+ Fiber -- and keep the Fiber alive through the finalizer table. */
2641
+ static VALUE rb_cCAWindowGuard;
2642
+
2643
+ static VALUE
2644
+ ca_window_guard_call (VALUE gv, VALUE object_id)
2645
+ {
2646
+ ca_window_guard_t *g;
2647
+ int tag = 0;
2648
+ (void) object_id;
2649
+ TypedData_Get_Struct(gv, ca_window_guard_t, &ca_window_guard_data_type, g);
2650
+ if ( g->armed && g->cleanup ) {
2651
+ g->armed = 0;
2652
+ rb_protect(ca_window_guard_cleanup_step, gv, &tag);
2653
+ if ( tag ) {
2654
+ rb_set_errinfo(Qnil);
2655
+ }
2656
+ }
2657
+ return Qnil;
2658
+ }
2659
+
2660
+ /* Takes ownership of arg (allocated with xmalloc). Returns the guard and
2661
+ stores the sentinel; the caller keeps both on its frame (RB_GC_GUARD)
2662
+ for as long as the window may be open. */
2663
+ VALUE
2664
+ ca_window_guard_arm (void (*cleanup)(void *), void (*mark)(void *), void *arg,
2665
+ VALUE *sentinel)
2666
+ {
2667
+ ca_window_guard_t *g;
2668
+ volatile VALUE gv, sv;
2669
+ gv = TypedData_Make_Struct(rb_cCAWindowGuard, ca_window_guard_t,
2670
+ &ca_window_guard_data_type, g);
2671
+ g->cleanup = cleanup;
2672
+ g->mark = mark;
2673
+ g->arg = arg;
2674
+ g->armed = 1;
2675
+ sv = rb_class_new_instance(0, NULL, rb_cObject);
2676
+ rb_define_finalizer(sv, gv);
2677
+ *sentinel = sv;
2678
+ return gv;
2679
+ }
2680
+
2681
+ /* Called first thing when the window closes normally, so that cleanup
2682
+ cannot also run from the finalizer. */
2683
+ void
2684
+ ca_window_guard_disarm (VALUE gv, VALUE sentinel)
2685
+ {
2686
+ ca_window_guard_t *g;
2687
+ TypedData_Get_Struct(gv, ca_window_guard_t, &ca_window_guard_data_type, g);
2688
+ g->armed = 0;
2689
+ /* rb_undefine_finalizer is declared in the headers but not exported by
2690
+ every libruby (3.4.1 lacks it), so go through ObjectSpace. */
2691
+ rb_funcall(rb_const_get(rb_cObject, rb_intern("ObjectSpace")),
2692
+ rb_intern("undefine_finalizer"), 1, sentinel);
2693
+ }
2694
+
2695
+ /* ca_attach_window for a window that yields to a block, closed by the
2696
+ guard above if its Fiber is abandoned. The context, the list and the
2697
+ modes are copied to the heap, since the caller's frame goes with the
2698
+ Fiber; `owners` keeps the arrays in list alive until then. */
2699
+
2700
+ typedef struct {
2701
+ ca_window_ctx_t w;
2702
+ VALUE owners;
2703
+ CArray *list[CA_RANK_MAX];
2704
+ char modes[CA_RANK_MAX + 1];
2705
+ } ca_guarded_window_t;
2706
+
2707
+ static void
2708
+ ca_guarded_window_mark (void *p)
2709
+ {
2710
+ ca_guarded_window_t *gw = (ca_guarded_window_t *) p;
2711
+ rb_gc_mark(gw->owners);
2712
+ rb_gc_mark(gw->w.masks);
2713
+ rb_gc_mark(gw->w.sync_err);
2714
+ }
2715
+
2716
+ static void
2717
+ ca_guarded_window_cleanup (void *p)
2718
+ {
2719
+ ca_guarded_window_t *gw = (ca_guarded_window_t *) p;
2720
+ ca_attach_window_close((VALUE) &gw->w);
2721
+ }
2722
+
2723
+ typedef struct {
2724
+ ca_guarded_window_t *gw;
2725
+ VALUE guard;
2726
+ VALUE sentinel;
2727
+ } ca_guarded_window_run_t;
2728
+
2729
+ static VALUE
2730
+ ca_guarded_window_close (VALUE varg)
2731
+ {
2732
+ ca_guarded_window_run_t *r = (ca_guarded_window_run_t *) varg;
2733
+ ca_window_guard_disarm(r->guard, r->sentinel);
2734
+ return ca_attach_window_close((VALUE) &r->gw->w);
2735
+ }
2736
+
2737
+ VALUE
2738
+ ca_attach_window_yielding (VALUE owners, int32_t n, CArray **list,
2739
+ const char *modes, VALUE (*body)(VALUE), VALUE arg)
2740
+ {
2741
+ ca_guarded_window_t *gw;
2742
+ ca_guarded_window_run_t r;
2743
+ volatile VALUE guard, sentinel;
2744
+ VALUE result;
2745
+ int tag = 0;
2746
+
2747
+ if ( n < 1 || n > CA_RANK_MAX ) { /* rank-check: a count of arrays, bounded by gw->list */
2748
+ rb_raise(rb_eRuntimeError, "[BUG] ca_attach_window_yielding: %d arrays", (int) n);
2749
+ }
2750
+ gw = ZALLOC(ca_guarded_window_t);
2751
+ gw->owners = owners;
2752
+ memcpy(gw->list, list, sizeof(CArray *) * (size_t) n);
2753
+ memcpy(gw->modes, modes, (size_t) n);
2754
+ gw->w.n = n;
2755
+ gw->w.list = gw->list;
2756
+ gw->w.modes = gw->modes;
2757
+ gw->w.opened = 0;
2758
+ gw->w.body_done = 0;
2759
+ gw->w.sync_tag = 0;
2760
+ gw->w.sync_err = Qnil;
2761
+ gw->w.body = body;
2762
+ gw->w.arg = arg;
2763
+ gw->w.masks = Qnil;
2764
+ guard = ca_window_guard_arm(NULL, ca_guarded_window_mark, gw, (VALUE *) &sentinel);
2765
+ ca_attach_window_save_masks(&gw->w);
2766
+
2767
+ rb_protect(ca_attach_window_open, (VALUE) &gw->w, &tag);
2768
+ if ( tag ) {
2769
+ ca_window_guard_disarm(guard, sentinel);
2770
+ while ( gw->w.opened > 0 ) {
2771
+ gw->w.opened--;
2772
+ if ( gw->w.list[gw->w.opened] ) {
2773
+ ca_detach(gw->w.list[gw->w.opened]);
2774
+ }
2775
+ }
2776
+ ca_attach_window_restore_masks(&gw->w);
2777
+ rb_jump_tag(tag);
2778
+ }
2779
+
2780
+ /* Only now is there something for the finalizer to close. */
2781
+ {
2782
+ ca_window_guard_t *g;
2783
+ TypedData_Get_Struct(guard, ca_window_guard_t, &ca_window_guard_data_type, g);
2784
+ g->cleanup = ca_guarded_window_cleanup;
2785
+ }
2786
+
2787
+ r.gw = gw;
2788
+ r.guard = guard;
2789
+ r.sentinel = sentinel;
2790
+ result = rb_ensure(ca_attach_window_body, (VALUE) &gw->w,
2791
+ ca_guarded_window_close, (VALUE) &r);
2792
+ if ( gw->w.sync_tag && RB_TYPE_P(gw->w.sync_err, T_OBJECT) &&
2793
+ RTEST(rb_obj_is_kind_of(gw->w.sync_err, rb_eException)) ) {
2794
+ VALUE err = gw->w.sync_err;
2795
+ RB_GC_GUARD(guard);
2796
+ RB_GC_GUARD(sentinel);
2797
+ rb_exc_raise(err);
2798
+ }
2799
+ RB_GC_GUARD(guard);
2800
+ RB_GC_GUARD(sentinel);
2801
+ return result;
2802
+ }
2803
+
2095
2804
  /* re-gather an attached view's buffer from its parent */
2096
2805
 
2097
2806
  void
@@ -2139,6 +2848,12 @@ ca_sync (void *ap)
2139
2848
  ca_refuse_read_only();
2140
2849
  }
2141
2850
 
2851
+ /* Before the mask is synced: a refused write leaves the mask as well. */
2852
+ if ( ca_is_view(ca) && ! CAVIEW(ca)->nosync ) {
2853
+ CA_VALUE_TRIAL_PUT(CA_TRIAL_SYNC, ca, 0, NULL, NULL, NULL, NULL, NULL);
2854
+ }
2855
+
2856
+
2142
2857
  /* Only a mask that exists can hold writes: one made inside the window
2143
2858
  is attached with the view (ca_create_mask). Making one here would
2144
2859
  read the parent's mask while the window closes, and a parent that
@@ -2370,6 +3085,11 @@ ca_fill_data (void *ap, void *aptr)
2370
3085
  ca_refuse_read_only();
2371
3086
  }
2372
3087
 
3088
+ if ( ca->ptr == NULL ) {
3089
+ CA_VALUE_TRIAL_PUT(CA_TRIAL_FILL, ca, 0, NULL, NULL, NULL, NULL, ptr);
3090
+ }
3091
+
3092
+
2373
3093
  if ( ca_is_view(ca) ) { /* view array */
2374
3094
  if ( ca_is_attached(ca) ) { /* ca is to be attached */
2375
3095
  ca_func[CA_OBJ_ARRAY].fill_data(ap, ptr);
@@ -2426,6 +3146,12 @@ ca_fill_stride (void *ap, ca_size_t base, int8_t ndim,
2426
3146
 
2427
3147
  if ( ca_region_is_empty(ndim, counts) ) return;
2428
3148
 
3149
+ if ( ca->ptr == NULL ) {
3150
+ CA_VALUE_TRIAL_PUT(CA_TRIAL_FILL_STRIDE, ca, 0, NULL, NULL, counts, NULL, ptr,
3151
+ base, ndim, steps);
3152
+ }
3153
+
3154
+
2429
3155
  if ( ca_func[ca->obj_type].fill_stride ) {
2430
3156
  ca_func[ca->obj_type].fill_stride(ap, base, ndim, counts, steps, ptr);
2431
3157
  return;
@@ -2544,6 +3270,11 @@ ca_fill_addrs (void *ap, ca_size_t n, ca_size_t *addrs, void *ptr)
2544
3270
  ca_refuse_read_only();
2545
3271
  }
2546
3272
 
3273
+ if ( ca->ptr == NULL ) {
3274
+ CA_VALUE_TRIAL_PUT(CA_TRIAL_FILL_ADDRS, ca, n, addrs, NULL, NULL, NULL, ptr);
3275
+ }
3276
+
3277
+
2547
3278
  if ( ca_func[ca->obj_type].fill_addrs ) {
2548
3279
  ca_func[ca->obj_type].fill_addrs(ap, n, addrs, ptr);
2549
3280
  return;
@@ -3067,6 +3798,13 @@ Init_carray_core (void)
3067
3798
 
3068
3799
  ca_init_obj_type();
3069
3800
 
3801
+ rb_define_private_method(rb_cCArray, "refuse_reentry", rb_ca_refuse_reentry, 1);
3802
+
3803
+ rb_cCAWindowGuard = rb_define_class_under(rb_cCArray, "WindowGuard", rb_cObject);
3804
+ rb_undef_alloc_func(rb_cCAWindowGuard);
3805
+ rb_undef_method(CLASS_OF(rb_cCAWindowGuard), "new");
3806
+ rb_define_method(rb_cCAWindowGuard, "call", ca_window_guard_call, 1);
3807
+
3070
3808
  #ifdef CARRAY_DEV_BUILD
3071
3809
  rb_define_singleton_method(rb_cCArray, "attach", rb_ca_s_attach, -1);
3072
3810
  rb_define_singleton_method(rb_cCArray, "attach!", rb_ca_s_attach_bang, -1);
@@ -3085,9 +3823,10 @@ Init_carray_core (void)
3085
3823
  const char *general = getenv("CARRAY_GENERAL_PATH");
3086
3824
  ca_fast_paths_on = !( general && general[0] && strcmp(general, "0") );
3087
3825
  ca_iter_transfer_only_on = ( general && ! strcmp(general, "2") );
3826
+ ca_shortcuts_off_from_env();
3088
3827
  }
3089
3828
  rb_define_singleton_method(rb_cCArray, "__fast_paths_enabled__",
3090
- rb_ca_s_fast_paths_enabled, 0);
3829
+ rb_ca_s_fast_paths_enabled, -1);
3091
3830
  rb_define_singleton_method(rb_cCArray, "__iter_by_transfer_only__",
3092
3831
  rb_ca_s_iter_by_transfer_only, 0);
3093
3832
  #endif