carray 3.0.1 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +824 -0
  3. data/README.md +5 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +258 -4
  6. data/ext/ca_axis_group.c +368 -184
  7. data/ext/ca_binop_dispatch.c +54 -32
  8. data/ext/ca_binop_dispatch.h +1 -0
  9. data/ext/ca_categorical_iterator.c +90 -41
  10. data/ext/ca_compare.c +8 -2
  11. data/ext/ca_for_buffer.h +21 -68
  12. data/ext/ca_group_iter.c +27 -11
  13. data/ext/ca_kernel_iterator.c +960 -245
  14. data/ext/ca_kernel_iterator.h +352 -181
  15. data/ext/ca_monop_dispatch.c +4 -4
  16. data/ext/ca_monop_dispatch.h +4 -0
  17. data/ext/ca_obj_array.c +194 -42
  18. data/ext/ca_obj_bincmp.c +152 -121
  19. data/ext/ca_obj_binop.c +107 -210
  20. data/ext/ca_obj_bitarray.c +37 -24
  21. data/ext/ca_obj_bitfield.c +95 -62
  22. data/ext/ca_obj_block.c +5 -4
  23. data/ext/ca_obj_byte_swap.c +40 -40
  24. data/ext/ca_obj_const_string.c +180 -50
  25. data/ext/ca_obj_face.c +428 -62
  26. data/ext/ca_obj_face.h +107 -47
  27. data/ext/ca_obj_fake.c +21 -50
  28. data/ext/ca_obj_farray.c +1 -0
  29. data/ext/ca_obj_field.c +16 -3
  30. data/ext/ca_obj_fixlen_string.c +39 -26
  31. data/ext/ca_obj_grid.c +181 -124
  32. data/ext/ca_obj_meld.c +235 -55
  33. data/ext/ca_obj_moncmp.c +61 -61
  34. data/ext/ca_obj_monop.c +125 -107
  35. data/ext/ca_obj_object.c +551 -233
  36. data/ext/ca_obj_record.c +51 -16
  37. data/ext/ca_obj_reduce.c +5 -8
  38. data/ext/ca_obj_refer.c +117 -33
  39. data/ext/ca_obj_remap.c +57 -17
  40. data/ext/ca_obj_repeat.c +21 -5
  41. data/ext/ca_obj_roll.c +115 -11
  42. data/ext/ca_obj_select.c +88 -75
  43. data/ext/ca_obj_select_axis.c +58 -60
  44. data/ext/ca_obj_shift.c +22 -102
  45. data/ext/ca_obj_stack.c +223 -123
  46. data/ext/ca_obj_stride.c +267 -35
  47. data/ext/ca_obj_string.c +25 -22
  48. data/ext/ca_obj_tile.c +32 -16
  49. data/ext/ca_obj_time.c +23 -25
  50. data/ext/ca_obj_timedelta.c +21 -23
  51. data/ext/ca_obj_transpose.c +12 -3
  52. data/ext/ca_obj_triop.c +110 -117
  53. data/ext/ca_obj_window.c +157 -29
  54. data/ext/ca_op_ipower.c +25 -13
  55. data/ext/ca_op_powi.h +57 -30
  56. data/ext/ca_pow5_table.h +656 -0
  57. data/ext/ca_rng_normal.h +42 -0
  58. data/ext/ca_rng_xoshiro256pp.h +105 -0
  59. data/ext/ca_sort_kernels.h +70 -0
  60. data/ext/ca_sweep_engine.c +385 -198
  61. data/ext/ca_sweep_engine.h +31 -23
  62. data/ext/ca_triop_dispatch.c +4 -1
  63. data/ext/ca_triop_dispatch.h +5 -2
  64. data/ext/caframe_csv_reader.c +463 -0
  65. data/ext/carray.h +90 -28
  66. data/ext/carray_access.c +487 -231
  67. data/ext/carray_address_basis.c +731 -0
  68. data/ext/carray_attribute.c +45 -3
  69. data/ext/carray_bincount.c +32 -2
  70. data/ext/carray_broadcast.c +49 -17
  71. data/ext/carray_call_cfunc.c +667 -511
  72. data/ext/carray_cast.c +1556 -247
  73. data/ext/carray_cast_func.rb +34 -21
  74. data/ext/carray_conversion.c +197 -73
  75. data/ext/carray_copy.c +84 -38
  76. data/ext/carray_core.c +591 -119
  77. data/ext/carray_count.c +148 -96
  78. data/ext/carray_element.c +18 -27
  79. data/ext/carray_factorize.c +604 -291
  80. data/ext/carray_generate.c +76 -40
  81. data/ext/carray_histogram.c +152 -94
  82. data/ext/carray_hold.c +2 -1
  83. data/ext/carray_index_classifier.c +63 -96
  84. data/ext/carray_internal.h +400 -1
  85. data/ext/carray_lazy.c +834 -79
  86. data/ext/carray_loop.c +88 -66
  87. data/ext/carray_mask.c +705 -324
  88. data/ext/carray_math_kernel.h +1 -0
  89. data/ext/carray_median_percentile.c +263 -63
  90. data/ext/carray_memory_view.c +254 -248
  91. data/ext/carray_operator.c +1093 -363
  92. data/ext/carray_order.c +133 -69
  93. data/ext/carray_partition.c +102 -56
  94. data/ext/carray_random.c +582 -204
  95. data/ext/carray_scatter.c +308 -65
  96. data/ext/carray_segment.c +257 -0
  97. data/ext/carray_slab.c +183 -70
  98. data/ext/carray_sort.c +235 -240
  99. data/ext/carray_sort_kernel.c +8 -1
  100. data/ext/carray_struct.c +99 -21
  101. data/ext/carray_test.c +167 -64
  102. data/ext/carray_time_text.c +1182 -0
  103. data/ext/carray_utils.c +363 -98
  104. data/ext/extconf.rb +27 -2
  105. data/ext/mk_call_cfunc.rb +103 -118
  106. data/ext/mk_pow5_table.rb +35 -0
  107. data/ext/mkkernel.rb +1349 -851
  108. data/ext/ruby_carray.c +17 -2
  109. data/ext/version.h +4 -4
  110. data/ext/version.rb +4 -2
  111. data/lib/carray/attribute.rb +39 -0
  112. data/lib/carray/autoload_carray.rb +24 -3
  113. data/lib/carray/autoload_method_extension.rb +18 -0
  114. data/lib/carray/axis_group.rb +245 -20
  115. data/lib/carray/basics.rb +95 -25
  116. data/lib/carray/bincount_nd.rb +6 -0
  117. data/lib/carray/block_iterator.rb +200 -54
  118. data/lib/carray/boolean_reduce.rb +21 -29
  119. data/lib/carray/categorical.rb +244 -59
  120. data/lib/carray/categorical_iterator.rb +391 -757
  121. data/lib/carray/complex.rb +36 -29
  122. data/lib/carray/conditional.rb +94 -7
  123. data/lib/carray/const_string.rb +177 -27
  124. data/lib/carray/construct.rb +40 -0
  125. data/lib/carray/data_type_extension.rb +3 -0
  126. data/lib/carray/data_type_limits.rb +91 -0
  127. data/lib/carray/fixlen_string.rb +1 -1
  128. data/lib/carray/frame/concat.rb +157 -6
  129. data/lib/carray/frame/convert.rb +13 -7
  130. data/lib/carray/frame/csv_parser.rb +590 -76
  131. data/lib/carray/frame/describe.rb +79 -0
  132. data/lib/carray/frame/frame.rb +205 -35
  133. data/lib/carray/frame/group.rb +199 -40
  134. data/lib/carray/frame/io.rb +452 -60
  135. data/lib/carray/frame/join.rb +134 -46
  136. data/lib/carray/frame/records.rb +54 -19
  137. data/lib/carray/frame/reshape.rb +448 -0
  138. data/lib/carray/frame/sort.rb +26 -2
  139. data/lib/carray/frame/verbs.rb +726 -56
  140. data/lib/carray/frame.rb +2 -0
  141. data/lib/carray/fuse_source.rb +111 -2
  142. data/lib/carray/fusion.rb +229 -38
  143. data/lib/carray/inspect.rb +71 -20
  144. data/lib/carray/iterator.rb +157 -2
  145. data/lib/carray/lazy.rb +54 -67
  146. data/lib/carray/mask_gap_fill.rb +66 -50
  147. data/lib/carray/math.rb +32 -19
  148. data/lib/carray/meld_reduce.rb +40 -7
  149. data/lib/carray/methods/align_addr.rb +8 -4
  150. data/lib/carray/methods/bincount.rb +2 -2
  151. data/lib/carray/methods/bit_string.rb +14 -3
  152. data/lib/carray/methods/composition.rb +16 -10
  153. data/lib/carray/methods/discovery_along.rb +74 -0
  154. data/lib/carray/methods/factorize.rb +50 -0
  155. data/lib/carray/methods/gather_nd.rb +13 -2
  156. data/lib/carray/methods/insert_block.rb +9 -14
  157. data/lib/carray/methods/is_in.rb +130 -6
  158. data/lib/carray/methods/join.rb +1 -1
  159. data/lib/carray/methods/locate_addr.rb +235 -21
  160. data/lib/carray/methods/mask_duplicates.rb +35 -1
  161. data/lib/carray/methods/mode.rb +18 -1
  162. data/lib/carray/methods/nunique.rb +22 -1
  163. data/lib/carray/methods/repeat.rb +91 -0
  164. data/lib/carray/methods/resize.rb +21 -23
  165. data/lib/carray/methods/snap.rb +34 -12
  166. data/lib/carray/methods/unique.rb +52 -3
  167. data/lib/carray/record_order.rb +208 -0
  168. data/lib/carray/rng.rb +86 -0
  169. data/lib/carray/runtime.rb +14 -11
  170. data/lib/carray/segment_iterator.rb +990 -0
  171. data/lib/carray/serialize.rb +197 -27
  172. data/lib/carray/slab_iterator.rb +109 -40
  173. data/lib/carray/stack.rb +10 -4
  174. data/lib/carray/string_operation_extension.rb +9 -4
  175. data/lib/carray/struct.rb +142 -51
  176. data/lib/carray/struct_builder.rb +110 -14
  177. data/lib/carray/table.rb +2 -2
  178. data/lib/carray/time.rb +535 -105
  179. data/lib/carray/window_iterator.rb +259 -82
  180. data/lib/carray.rb +4 -2
  181. data/yard-stubs/ca_obj_array.rb +4 -4
  182. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  183. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  184. data/yard-stubs/ca_obj_block.rb +2 -7
  185. data/yard-stubs/ca_obj_fake.rb +1 -1
  186. data/yard-stubs/ca_obj_field.rb +1 -1
  187. data/yard-stubs/ca_obj_refer.rb +3 -3
  188. data/yard-stubs/ca_obj_roll.rb +0 -1
  189. data/yard-stubs/ca_obj_stride.rb +10 -7
  190. data/yard-stubs/ca_obj_transpose.rb +1 -1
  191. data/yard-stubs/ca_obj_window.rb +11 -4
  192. data/yard-stubs/carray_access.rb +6 -5
  193. data/yard-stubs/carray_broadcast.rb +2 -2
  194. data/yard-stubs/carray_cast.rb +78 -43
  195. data/yard-stubs/carray_conversion.rb +13 -1
  196. data/yard-stubs/carray_copy.rb +8 -3
  197. data/yard-stubs/carray_core.rb +3 -83
  198. data/yard-stubs/carray_count.rb +16 -2
  199. data/yard-stubs/carray_lazy.rb +210 -1
  200. data/yard-stubs/carray_loop.rb +4 -1
  201. data/yard-stubs/carray_mask.rb +34 -10
  202. data/yard-stubs/carray_math.rb +1499 -4
  203. data/yard-stubs/carray_median_percentile.rb +26 -8
  204. data/yard-stubs/carray_order.rb +43 -82
  205. data/yard-stubs/carray_scatter.rb +10 -3
  206. data/yard-stubs/carray_segment.rb +72 -0
  207. data/yard-stubs/carray_slab.rb +11 -8
  208. data/yard-stubs/carray_sort.rb +50 -8
  209. data/yard-stubs/carray_test.rb +10 -0
  210. data/yard-stubs/carray_utils.rb +0 -26
  211. metadata +19 -19
  212. data/ext/ca_for_each_element.h +0 -239
  213. data/ext/carray_build_flags.h +0 -3
  214. data/ext/carray_config.h +0 -28
  215. data/ext/carray_kernels_bincmp.c +0 -4446
  216. data/ext/carray_kernels_binop.c +0 -11001
  217. data/ext/carray_kernels_init.c +0 -1131
  218. data/ext/carray_kernels_map.c +0 -3467
  219. data/ext/carray_kernels_moncmp.c +0 -2097
  220. data/ext/carray_kernels_monop.c +0 -18313
  221. data/ext/carray_kernels_reduce_aggregate.c +0 -25837
  222. data/ext/carray_kernels_reduce_boolean.c +0 -330
  223. data/ext/carray_kernels_reduce_cumulative.c +0 -14593
  224. data/ext/carray_kernels_reduce_extreme.c +0 -16948
  225. data/ext/carray_kernels_reduce_variance.c +0 -3910
  226. data/ext/carray_kernels_scan.c +0 -3693
  227. data/ext/carray_kernels_search.c +0 -32138
  228. data/ext/carray_kernels_sort.c +0 -10626
  229. data/ext/carray_kernels_triop.c +0 -1392
@@ -22,45 +22,161 @@
22
22
  #include "ca_for_buffer.h"
23
23
  #include <string.h>
24
24
 
25
+ /* "[2, 3]" for an operand's shape. */
26
+ static VALUE
27
+ ca_sweep_shape_str (CArray *ca)
28
+ {
29
+ volatile VALUE s = rb_str_new_cstr("[");
30
+ int k;
31
+ for (k = 0; k < ca->ndim; k++) {
32
+ if (k > 0) rb_str_cat_cstr(s, ", ");
33
+ rb_str_catf(s, "%lld", (long long) ca->dim[k]);
34
+ }
35
+ rb_str_cat_cstr(s, "]");
36
+ return s;
37
+ }
38
+
25
39
  void
26
- ca_sweep_acquire (ca_sweep_state_t *st)
40
+ ca_sweep_refuse_shapes (CArray *a, CArray *b)
27
41
  {
42
+ volatile VALUE sa = ca_sweep_shape_str(a);
43
+ volatile VALUE sb = ca_sweep_shape_str(b);
44
+ rb_raise(rb_eArgError,
45
+ "shape mismatch between operands (%s and %s); a scalar pairs "
46
+ "with any array, two arrays only when their shapes agree",
47
+ StringValueCStr(sa), StringValueCStr(sb));
48
+ }
49
+
50
+ int
51
+ ca_sweep_same_shape (CArray *a, CArray *b)
52
+ {
53
+ int k;
54
+ if (a->ndim != b->ndim) return 0;
55
+ for (k = 0; k < a->ndim; k++) {
56
+ if (a->dim[k] != b->dim[k]) return 0;
57
+ }
58
+ return 1;
59
+ }
60
+
61
+ /* Pair the operands: a scalar walks with stride 0, every other operand
62
+ * must have the shape of the first non-scalar one, which is returned (NULL
63
+ * when all operands are scalars). Sets stride[] and n_kernel. Allocates
64
+ * nothing, so it runs before any operand is acquired. */
65
+ static CArray *
66
+ ca_sweep_pair_operands (ca_sweep_state_t *st)
67
+ {
68
+ CArray *donor = NULL;
28
69
  int k_op;
29
- int any_input_mask = 0;
70
+ st->n_kernel = 1;
71
+ for (k_op = 0; k_op < st->n_ops; k_op++) {
72
+ CArray *ca = st->cx[k_op];
73
+ if (ca_is_scalar(ca)) {
74
+ st->stride[k_op] = 0;
75
+ continue;
76
+ }
77
+ st->stride[k_op] = ca->bytes;
78
+ if (!donor) {
79
+ donor = ca;
80
+ st->n_kernel = ca->elements;
81
+ } else if (!ca_sweep_same_shape(donor, ca)) {
82
+ ca_sweep_refuse_shapes(donor, ca);
83
+ }
84
+ }
85
+ return donor;
86
+ }
30
87
 
31
- if ((int) strlen(st->fsync) != st->n_ops) {
32
- rb_raise(rb_eRuntimeError,
33
- "[BUG] invalid length of fsync arg in %s (expected %d)",
34
- st->src_label ? st->src_label : "ca_sweep_acquire",
35
- st->n_ops);
88
+ /* Non-zero when INPUT k_op lives in the same root as an OUTPUT other
89
+ * than itself. Read in place, or chunk by chunk after earlier chunks
90
+ * were written, it would show the kernel cells already written; such an
91
+ * input is read whole before the walk. An input that is the output
92
+ * itself is read in place: each cell is read before it is written. */
93
+ static int
94
+ ca_sweep_input_overlaps_output (ca_sweep_state_t *st, int k_op)
95
+ {
96
+ int j;
97
+ CArray *ca = st->cx[k_op];
98
+ if (st->fsync[k_op] != '0' || ca_is_scalar(ca)) return 0;
99
+ for (j = 0; j < st->n_ops; j++) {
100
+ if (st->fsync[j] == '1' && st->cx[j] != ca &&
101
+ ca_shares_root(ca, st->cx[j])) {
102
+ return 1;
103
+ }
36
104
  }
105
+ return 0;
106
+ }
37
107
 
108
+ /* Non-zero when any INPUT operand carries a mask. Asks, allocates
109
+ * nothing. */
110
+ static int
111
+ ca_sweep_any_input_mask (ca_sweep_state_t *st)
112
+ {
113
+ int k_op;
38
114
  for (k_op = 0; k_op < st->n_ops; k_op++) {
39
- st->base[k_op] = NULL;
115
+ if (st->fsync[k_op] == '0' && ca_has_mask(st->cx[k_op])) return 1;
116
+ }
117
+ return 0;
118
+ }
119
+
120
+ /* Give back everything acquire has taken so far, without syncing: detach
121
+ * attached operands, free owned scratch (xmalloc on the whole-buffer path,
122
+ * arena on the chunked one) and the mask buffers. Called when a read
123
+ * raises part way through, so that the raise leaves nothing attached or
124
+ * allocated behind it. */
125
+ static void
126
+ ca_sweep_abort (ca_sweep_state_t *st, int chunked)
127
+ {
128
+ int k_op;
129
+ for (k_op = st->n_ops - 1; k_op >= 0; k_op--) {
130
+ if (st->attached[k_op]) {
131
+ st->attached[k_op] = 0;
132
+ ca_detach(st->cx[k_op]);
133
+ } else if (st->owned_buf[k_op]) {
134
+ if (chunked) ca_lazy_arena_release(st->owned_buf[k_op]);
135
+ else xfree(st->owned_buf[k_op]);
136
+ }
40
137
  st->owned_buf[k_op] = NULL;
41
- st->attached[k_op] = 0;
42
138
  }
43
- st->m0 = NULL;
44
- st->n_kernel = 1;
45
- /* CAREFUL: do not reset st->no_mask here — caller sets it before
46
- * acquire and the NO_MASK guard below consumes it. */
139
+ if (st->m0) {
140
+ if (chunked) ca_lazy_arena_release(st->m0);
141
+ else xfree(st->m0);
142
+ st->m0 = NULL;
143
+ }
144
+ if (chunked && st->mask_scratch) {
145
+ ca_lazy_arena_release(st->mask_scratch);
146
+ st->mask_scratch = NULL;
147
+ }
148
+ }
149
+
150
+ typedef struct {
151
+ ca_sweep_state_t *st;
152
+ int any_input_mask;
153
+ boolean8_t *ms; /* per-operand mask staging, whole path */
154
+ } ca_sweep_acquire_ctx_t;
155
+
156
+ /* The part of acquire that takes resources and reads operands, and so can
157
+ * raise holding them. Runs under rb_protect; every buffer is recorded in
158
+ * the state (or the ctx) before the read that may raise. */
159
+ static VALUE
160
+ ca_sweep_acquire_body (VALUE arg)
161
+ {
162
+ ca_sweep_acquire_ctx_t *ctx = (ca_sweep_acquire_ctx_t *) arg;
163
+ ca_sweep_state_t *st = ctx->st;
164
+ int k_op;
47
165
 
48
166
  /* Per-operand acquire:
49
167
  * OUTPUT (fsync == '1') -> ca_attach + base = ca->ptr
50
168
  * INPUT alias -> ca_attach + base = ca->ptr
51
- * INPUT non-alias non-scalar -> xmalloc scratch + ca_xfer_all
169
+ * INPUT non-alias non-scalar,
170
+ * or sharing an OUTPUT's root -> xmalloc scratch + ca_xfer_all
52
171
  * INPUT non-alias never attaches the operand itself; the scratch
53
172
  * copy carries the values into the kernel. */
54
173
  for (k_op = 0; k_op < st->n_ops; k_op++) {
55
174
  CArray *ca = st->cx[k_op];
56
- if (st->fsync[k_op] == '1') {
175
+ if (st->fsync[k_op] == '1' ||
176
+ (ca_attach_is_alias(ca) && ! ca_sweep_input_overlaps_output(st, k_op))) {
57
177
  ca_attach(ca);
58
- st->base[k_op] = (char *) ca->ptr;
59
178
  st->attached[k_op] = 1;
60
- } else if (ca_attach_is_alias(ca)) {
61
- ca_attach(ca);
62
179
  st->base[k_op] = (char *) ca->ptr;
63
- st->attached[k_op] = 1;
64
180
  } else {
65
181
  ca_size_t bytes_total = ca->elements * ca->bytes;
66
182
  st->owned_buf[k_op] = xmalloc(bytes_total);
@@ -69,36 +185,9 @@ ca_sweep_acquire (ca_sweep_state_t *st)
69
185
  }
70
186
  }
71
187
 
72
- /* compute n_kernel (= broadcast shape) and per-cell strides */
73
- for (k_op = 0; k_op < st->n_ops; k_op++) {
74
- CArray *ca = st->cx[k_op];
75
- if (ca_is_scalar(ca)) {
76
- st->stride[k_op] = 0;
77
- } else {
78
- st->stride[k_op] = ca->bytes;
79
- if (st->n_kernel == 1) {
80
- st->n_kernel = ca->elements;
81
- } else if (ca->elements != st->n_kernel) {
82
- rb_raise(rb_eRuntimeError, "data size mismatch in operation");
83
- }
84
- }
85
- }
86
-
87
188
  /* iter mask m0 = OR of INPUT operand masks, gathered via ca_xfer_all
88
189
  so no operand mask attach happens. Stays NULL if no INPUT masks. */
89
- for (k_op = 0; k_op < st->n_ops; k_op++) {
90
- if (st->fsync[k_op] == '0' && ca_has_mask(st->cx[k_op])) {
91
- any_input_mask = 1;
92
- break;
93
- }
94
- }
95
- if (any_input_mask && st->no_mask) {
96
- rb_raise(rb_eRuntimeError,
97
- "%s: masked INPUT not allowed in NO_MASK form "
98
- "(use the *_MASKED form to handle masked cells explicitly)",
99
- st->src_label ? st->src_label : "ca_sweep_acquire");
100
- }
101
- if (any_input_mask) {
190
+ if (ctx->any_input_mask) {
102
191
  st->m0 = xmalloc(st->n_kernel);
103
192
  memset(st->m0, 0, st->n_kernel);
104
193
  for (k_op = 0; k_op < st->n_ops; k_op++) {
@@ -111,11 +200,10 @@ ca_sweep_acquire (ca_sweep_state_t *st)
111
200
  ca_xfer_all(ca->mask, &bit, CA_XFER_GET);
112
201
  if (bit) memset(st->m0, 1, st->n_kernel);
113
202
  } else {
114
- boolean8_t *ms = xmalloc(st->n_kernel);
115
203
  ca_size_t j;
116
- ca_xfer_all(ca->mask, ms, CA_XFER_GET);
117
- for (j = 0; j < st->n_kernel; j++) st->m0[j] |= ms[j];
118
- xfree(ms);
204
+ if (!ctx->ms) ctx->ms = xmalloc(st->n_kernel);
205
+ ca_xfer_all(ca->mask, ctx->ms, CA_XFER_GET);
206
+ for (j = 0; j < st->n_kernel; j++) st->m0[j] |= ctx->ms[j];
119
207
  }
120
208
  }
121
209
  }
@@ -126,21 +214,130 @@ ca_sweep_acquire (ca_sweep_state_t *st)
126
214
  for (k_op = 0; k_op < st->n_ops; k_op++) {
127
215
  CArray *ca = st->cx[k_op];
128
216
  if (st->fsync[k_op] != '1') continue;
129
- ca_update_mask(ca);
130
- if (!ca->mask) ca_create_mask(ca);
217
+ ca_create_mask(ca);
131
218
  memcpy(ca->mask->ptr, st->m0, st->n_kernel);
132
219
  }
133
220
  }
221
+ return Qnil;
134
222
  }
135
223
 
136
224
  void
137
- ca_sweep_release (ca_sweep_state_t *st)
225
+ ca_sweep_acquire (ca_sweep_state_t *st)
138
226
  {
227
+ ca_sweep_acquire_ctx_t ctx;
139
228
  int k_op;
140
- /* sync OUTPUTs (reverse order) */
229
+ int tag = 0;
230
+
231
+ if ((int) strlen(st->fsync) != st->n_ops) {
232
+ rb_raise(rb_eRuntimeError,
233
+ "[BUG] invalid length of fsync arg in %s (expected %d)",
234
+ st->src_label ? st->src_label : "ca_sweep_acquire",
235
+ st->n_ops);
236
+ }
237
+
238
+ for (k_op = 0; k_op < st->n_ops; k_op++) {
239
+ st->base[k_op] = NULL;
240
+ st->owned_buf[k_op] = NULL;
241
+ st->attached[k_op] = 0;
242
+ }
243
+ st->m0 = NULL;
244
+
245
+ /* Refusals come first, while nothing is held. */
246
+ ca_sweep_pair_operands(st);
247
+ ctx.st = st;
248
+ ctx.ms = NULL;
249
+ ctx.any_input_mask = ca_sweep_any_input_mask(st);
250
+
251
+ rb_protect(ca_sweep_acquire_body, (VALUE) &ctx, &tag);
252
+ if (ctx.ms) xfree(ctx.ms);
253
+ if (tag) {
254
+ ca_sweep_abort(st, 0);
255
+ rb_jump_tag(tag);
256
+ }
257
+ }
258
+
259
+ void
260
+ ca_sweep_run (ca_sweep_state_t *st, VALUE (*walk)(VALUE), VALUE arg)
261
+ {
262
+ int tag = 0;
263
+ rb_protect(walk, arg, &tag);
264
+ if (tag) {
265
+ ca_sweep_abort(st, 0);
266
+ rb_jump_tag(tag);
267
+ }
268
+ ca_sweep_release(st);
269
+ }
270
+
271
+ void
272
+ ca_sweep_run_chunked (ca_sweep_state_t *st, VALUE (*walk)(VALUE), VALUE arg)
273
+ {
274
+ int tag = 0;
275
+ rb_protect(walk, arg, &tag);
276
+ if (tag) {
277
+ /* a raise inside ca_sweep_next_chunk has already given back */
278
+ if (st->chunked_state != 3) {
279
+ ca_sweep_abort(st, 1);
280
+ st->chunked_state = 3;
281
+ }
282
+ rb_jump_tag(tag);
283
+ }
284
+ ca_sweep_release_chunked(st);
285
+ }
286
+
287
+ static VALUE
288
+ ca_sweep_sync_one (VALUE arg)
289
+ {
290
+ ca_sync((CArray *) arg);
291
+ return Qnil;
292
+ }
293
+
294
+ /* Sync the OUTPUTs (reverse order). Writing back through a view's slots
295
+ * can raise (a CAObject's store hook); the raise is held so that release
296
+ * still detaches and frees everything, and ca_sweep_reraise propagates
297
+ * it afterwards. An entity's sync does nothing that can raise, so it is
298
+ * called directly: the outputs this library allocates itself pay nothing. */
299
+ static int
300
+ ca_sweep_sync_outputs (ca_sweep_state_t *st, volatile VALUE *error)
301
+ {
302
+ int k_op, first = 0;
141
303
  for (k_op = st->n_ops - 1; k_op >= 0; k_op--) {
142
- if (st->fsync[k_op] == '1') ca_sync(st->cx[k_op]);
304
+ CArray *ca = st->cx[k_op];
305
+ if (st->fsync[k_op] != '1') {
306
+ continue;
307
+ }
308
+ if (ca_is_entity(ca)) {
309
+ ca_sync(ca);
310
+ }
311
+ else {
312
+ int tag = 0;
313
+ rb_protect(ca_sweep_sync_one, (VALUE) ca, &tag);
314
+ if (tag) {
315
+ if (! first) {
316
+ first = tag;
317
+ *error = rb_errinfo();
318
+ }
319
+ rb_set_errinfo(Qnil);
320
+ }
321
+ }
143
322
  }
323
+ return first;
324
+ }
325
+
326
+ static void
327
+ ca_sweep_reraise (int tag, VALUE error)
328
+ {
329
+ if (tag) {
330
+ rb_set_errinfo(error);
331
+ rb_jump_tag(tag);
332
+ }
333
+ }
334
+
335
+ void
336
+ ca_sweep_release (ca_sweep_state_t *st)
337
+ {
338
+ int k_op;
339
+ volatile VALUE error = Qnil;
340
+ int tag = ca_sweep_sync_outputs(st, &error);
144
341
  /* detach attached / xfree owned buffers (reverse order) */
145
342
  for (k_op = st->n_ops - 1; k_op >= 0; k_op--) {
146
343
  if (st->attached[k_op]) {
@@ -154,86 +351,32 @@ ca_sweep_release (ca_sweep_state_t *st)
154
351
  xfree(st->m0);
155
352
  st->m0 = NULL;
156
353
  }
354
+ ca_sweep_reraise(tag, error);
157
355
  }
158
356
 
159
357
  /* ===== Chunked path implementation ===== */
160
358
 
161
- void
162
- ca_sweep_acquire_chunked (ca_sweep_state_t *st)
359
+ static VALUE
360
+ ca_sweep_acquire_chunked_body (VALUE arg)
163
361
  {
362
+ ca_sweep_acquire_ctx_t *ctx = (ca_sweep_acquire_ctx_t *) arg;
363
+ ca_sweep_state_t *st = ctx->st;
164
364
  int k_op;
165
- int any_input_mask = 0;
166
- CArray *shape_donor = NULL;
167
-
168
- if ((int) strlen(st->fsync) != st->n_ops) {
169
- rb_raise(rb_eRuntimeError,
170
- "[BUG] invalid length of fsync arg in %s (expected %d)",
171
- st->src_label ? st->src_label : "ca_sweep_acquire_chunked",
172
- st->n_ops);
173
- }
174
-
175
- for (k_op = 0; k_op < st->n_ops; k_op++) {
176
- st->base[k_op] = NULL;
177
- st->base_orig[k_op] = NULL;
178
- st->owned_buf[k_op] = NULL;
179
- st->attached[k_op] = 0;
180
- }
181
- st->m0 = NULL;
182
- st->mask_scratch = NULL;
183
- st->n_kernel = 1;
184
- st->chunk_off = 0;
185
- st->chunk_n = 0;
186
- st->chunk_n_max = 0;
187
- st->inner = 1;
188
- st->chunked_state = 0;
189
-
190
- /* compute broadcast shape (n_kernel + strides) from operand shapes.
191
- * scalar operands collapse to stride 0; non-scalar operands must agree
192
- * on element count. shape_donor is the first non-scalar operand and
193
- * defines the chunking inner-axis size. */
194
- for (k_op = 0; k_op < st->n_ops; k_op++) {
195
- CArray *ca = st->cx[k_op];
196
- if (ca_is_scalar(ca)) {
197
- st->stride[k_op] = 0;
198
- } else {
199
- st->stride[k_op] = ca->bytes;
200
- if (st->n_kernel == 1) {
201
- st->n_kernel = ca->elements;
202
- shape_donor = ca;
203
- } else if (ca->elements != st->n_kernel) {
204
- rb_raise(rb_eRuntimeError, "data size mismatch in operation");
205
- }
206
- }
207
- }
208
-
209
- /* chunk-size policy: inner = donor's product of dims[1..]; chunk_n_max
210
- * = compute_n on donor->bytes (= type-dependent 32KB target). */
211
- if (shape_donor) {
212
- st->inner = ca_chunk_inner_size(shape_donor);
213
- st->chunk_n_max = ca_chunk_compute_n(st->n_kernel, st->inner,
214
- shape_donor->bytes);
215
- } else {
216
- /* all-scalar: single 1-cell chunk */
217
- st->inner = 1;
218
- st->chunk_n_max = 1;
219
- }
220
365
 
221
366
  /* per-operand acquire:
222
367
  * OUTPUT (fsync == '1') : ca_attach + base_orig = ca->ptr (legitimate)
223
368
  * INPUT alias : ca_attach + base_orig = ca->ptr (zero-copy)
224
- * INPUT non-alias non-scalar : arena chunk scratch of chunk_n_max bytes
369
+ * INPUT non-alias non-scalar,
370
+ * or sharing an OUTPUT's root : arena chunk scratch of chunk_n_max bytes
225
371
  * INPUT scalar : ca_attach + base_orig = ca->ptr (1 cell)
226
372
  */
227
373
  for (k_op = 0; k_op < st->n_ops; k_op++) {
228
374
  CArray *ca = st->cx[k_op];
229
- if (st->fsync[k_op] == '1') {
375
+ if (st->fsync[k_op] == '1' || ca_is_scalar(ca) ||
376
+ (ca_attach_is_alias(ca) && ! ca_sweep_input_overlaps_output(st, k_op))) {
230
377
  ca_attach(ca);
231
- st->base_orig[k_op] = (char *) ca->ptr;
232
378
  st->attached[k_op] = 1;
233
- } else if (ca_is_scalar(ca) || ca_attach_is_alias(ca)) {
234
- ca_attach(ca);
235
379
  st->base_orig[k_op] = (char *) ca->ptr;
236
- st->attached[k_op] = 1;
237
380
  } else {
238
381
  /* non-alias non-scalar INPUT: arena scratch sized for chunk_n_max */
239
382
  ca_size_t scratch_bytes = st->chunk_n_max * ca->bytes;
@@ -251,19 +394,7 @@ ca_sweep_acquire_chunked (ca_sweep_state_t *st)
251
394
  * scaling with the operand, which is the thing this path exists to avoid:
252
395
  * at 1 byte per cell it is an eighth of an f64 operand, but an eighth of
253
396
  * unbounded is still unbounded. */
254
- for (k_op = 0; k_op < st->n_ops; k_op++) {
255
- if (st->fsync[k_op] == '0' && ca_has_mask(st->cx[k_op])) {
256
- any_input_mask = 1;
257
- break;
258
- }
259
- }
260
- if (any_input_mask && st->no_mask) {
261
- rb_raise(rb_eRuntimeError,
262
- "%s: masked INPUT not allowed in NO_MASK form "
263
- "(use the *_MASKED form to handle masked cells explicitly)",
264
- st->src_label ? st->src_label : "ca_sweep_acquire_chunked");
265
- }
266
- if (any_input_mask) {
397
+ if (ctx->any_input_mask) {
267
398
  st->m0 = (boolean8_t *) ca_lazy_arena_acquire(st->chunk_n_max);
268
399
  st->mask_scratch = (boolean8_t *) ca_lazy_arena_acquire(st->chunk_n_max);
269
400
  memset(st->m0, 0, st->chunk_n_max);
@@ -273,8 +404,7 @@ ca_sweep_acquire_chunked (ca_sweep_state_t *st)
273
404
  for (k_op = 0; k_op < st->n_ops; k_op++) {
274
405
  CArray *ca = st->cx[k_op];
275
406
  if (st->fsync[k_op] != '1') continue;
276
- ca_update_mask(ca);
277
- if (!ca->mask) ca_create_mask(ca);
407
+ ca_create_mask(ca);
278
408
  }
279
409
  }
280
410
 
@@ -283,6 +413,75 @@ ca_sweep_acquire_chunked (ca_sweep_state_t *st)
283
413
  * once more in release), never at acquire. Author per-cell m_out writes
284
414
  * land in m0 during the chunk loop and must be captured after that loop
285
415
  * has run, not before it. */
416
+ return Qnil;
417
+ }
418
+
419
+ void
420
+ ca_sweep_acquire_chunked (ca_sweep_state_t *st)
421
+ {
422
+ ca_sweep_acquire_ctx_t ctx;
423
+ int k_op;
424
+ int tag = 0;
425
+ CArray *shape_donor = NULL;
426
+
427
+ if ((int) strlen(st->fsync) != st->n_ops) {
428
+ rb_raise(rb_eRuntimeError,
429
+ "[BUG] invalid length of fsync arg in %s (expected %d)",
430
+ st->src_label ? st->src_label : "ca_sweep_acquire_chunked",
431
+ st->n_ops);
432
+ }
433
+
434
+ for (k_op = 0; k_op < st->n_ops; k_op++) {
435
+ st->base[k_op] = NULL;
436
+ st->base_orig[k_op] = NULL;
437
+ st->owned_buf[k_op] = NULL;
438
+ st->attached[k_op] = 0;
439
+ }
440
+ st->m0 = NULL;
441
+ st->mask_scratch = NULL;
442
+ st->chunk_off = 0;
443
+ st->chunk_n = 0;
444
+ st->chunk_n_max = 0;
445
+ st->inner = 1;
446
+ st->chunked_state = 0;
447
+
448
+ /* shape_donor is the first non-scalar operand and defines the chunking
449
+ * inner-axis size. */
450
+ shape_donor = ca_sweep_pair_operands(st);
451
+
452
+ /* chunk-size policy: inner = donor's product of dims[1..]; chunk_n_max
453
+ * = compute_n on donor->bytes (= type-dependent 32KB target). */
454
+ if (shape_donor) {
455
+ st->inner = ca_chunk_inner_size(shape_donor);
456
+ st->chunk_n_max = ca_chunk_compute_n(st->n_kernel, st->inner,
457
+ shape_donor->bytes);
458
+ } else {
459
+ /* all-scalar: single 1-cell chunk */
460
+ st->inner = 1;
461
+ st->chunk_n_max = 1;
462
+ }
463
+
464
+ /* An INPUT sharing an OUTPUT's root is read whole before anything is
465
+ written: the walk becomes a single chunk, so its data and its mask
466
+ are gathered before the first flush. */
467
+ for (k_op = 0; k_op < st->n_ops; k_op++) {
468
+ if (ca_sweep_input_overlaps_output(st, k_op) && st->n_kernel > 0) {
469
+ st->chunk_n_max = st->n_kernel;
470
+ break;
471
+ }
472
+ }
473
+
474
+ /* Refusals come first, while nothing is held. */
475
+ ctx.st = st;
476
+ ctx.ms = NULL;
477
+ ctx.any_input_mask = ca_sweep_any_input_mask(st);
478
+
479
+ rb_protect(ca_sweep_acquire_chunked_body, (VALUE) &ctx, &tag);
480
+ if (tag) {
481
+ ca_sweep_abort(st, 1);
482
+ st->chunked_state = 3;
483
+ rb_jump_tag(tag);
484
+ }
286
485
  }
287
486
 
288
487
  /* OR one INPUT operand's mask for the current chunk into m0. Gathered via
@@ -313,9 +512,7 @@ ca_sweep_gather_chunk_mask (ca_sweep_state_t *st, ca_size_t off, ca_size_t n)
313
512
  }
314
513
  }
315
514
 
316
- /* Copy the chunk just finished out to every OUTPUT operand's mask. Runs
317
- * after the author's loop over that chunk, so m_out writes into m0 are
318
- * carried through. */
515
+ /* Copy the chunk just finished out to every OUTPUT operand's mask. */
319
516
  static void
320
517
  ca_sweep_flush_chunk_mask (ca_sweep_state_t *st)
321
518
  {
@@ -329,18 +526,46 @@ ca_sweep_flush_chunk_mask (ca_sweep_state_t *st)
329
526
  }
330
527
  }
331
528
 
529
+ /* Set up base[] for the chunk at chunk_off / chunk_n: gather each
530
+ non-alias INPUT into its arena scratch, then the chunk's mask. */
531
+ static VALUE
532
+ ca_sweep_next_chunk_body (VALUE arg)
533
+ {
534
+ ca_sweep_state_t *st = (ca_sweep_state_t *) arg;
535
+ ca_size_t off = st->chunk_off;
536
+ ca_size_t n = st->chunk_n;
537
+ int k_op;
538
+
539
+ for (k_op = 0; k_op < st->n_ops; k_op++) {
540
+ CArray *ca = st->cx[k_op];
541
+ if (st->stride[k_op] == 0) {
542
+ /* scalar: stride 0, base is the single-cell ptr (base_orig) */
543
+ st->base[k_op] = st->base_orig[k_op];
544
+ } else if (st->base_orig[k_op]) {
545
+ /* alias INPUT or OUTPUT: walk through ca->ptr by chunk_off */
546
+ st->base[k_op] = st->base_orig[k_op] + off * st->stride[k_op];
547
+ } else {
548
+ /* non-alias INPUT: per-chunk gather into owned_buf (arena) */
549
+ ca_chunked_gather(ca, off, n, st->owned_buf[k_op]);
550
+ st->base[k_op] = st->owned_buf[k_op];
551
+ }
552
+ }
553
+
554
+ if (st->m0) ca_sweep_gather_chunk_mask(st, off, n);
555
+ return Qnil;
556
+ }
557
+
332
558
  int
333
559
  ca_sweep_next_chunk (ca_sweep_state_t *st)
334
560
  {
335
- int k_op;
336
561
  ca_size_t off, n;
562
+ int tag = 0;
337
563
 
338
564
  if (st->chunked_state == 0) {
339
565
  /* first chunk */
340
566
  st->chunk_off = 0;
341
567
  st->chunked_state = 1;
342
568
  } else {
343
- /* the chunk that just finished is the author's last word on its mask */
344
569
  ca_sweep_flush_chunk_mask(st);
345
570
  /* advance */
346
571
  st->chunk_off += st->chunk_n;
@@ -356,24 +581,16 @@ ca_sweep_next_chunk (ca_sweep_state_t *st)
356
581
  if (off + n > st->n_kernel) n = st->n_kernel - off;
357
582
  st->chunk_n = n;
358
583
 
359
- /* set up base[] for the upcoming chunk */
360
- for (k_op = 0; k_op < st->n_ops; k_op++) {
361
- CArray *ca = st->cx[k_op];
362
- if (st->stride[k_op] == 0) {
363
- /* scalar: stride 0, base is the single-cell ptr (base_orig) */
364
- st->base[k_op] = st->base_orig[k_op];
365
- } else if (st->base_orig[k_op]) {
366
- /* alias INPUT or OUTPUT: walk through ca->ptr by chunk_off */
367
- st->base[k_op] = st->base_orig[k_op] + off * st->stride[k_op];
368
- } else {
369
- /* non-alias INPUT: per-chunk gather into owned_buf (arena) */
370
- ca_chunked_gather(ca, off, n, st->owned_buf[k_op]);
371
- st->base[k_op] = st->owned_buf[k_op];
372
- }
584
+ /* The gathers read the operands and so can raise; a raise gives back
585
+ the walk's resources before it propagates, since the caller's release
586
+ is never reached. */
587
+ rb_protect(ca_sweep_next_chunk_body, (VALUE) st, &tag);
588
+ if (tag) {
589
+ ca_sweep_abort(st, 1);
590
+ st->chunked_state = 3;
591
+ rb_jump_tag(tag);
373
592
  }
374
593
 
375
- if (st->m0) ca_sweep_gather_chunk_mask(st, off, n);
376
-
377
594
  return 1;
378
595
  }
379
596
 
@@ -381,14 +598,15 @@ void
381
598
  ca_sweep_release_chunked (ca_sweep_state_t *st)
382
599
  {
383
600
  int k_op;
601
+ volatile VALUE error = Qnil;
602
+ int tag;
603
+ /* a walk that raised has already given everything back */
604
+ if (st->chunked_state == 3) return;
384
605
  /* The final chunk has no next_chunk call to flush it, so it is flushed
385
- * here. For INOUT_MASKED forms this is what captures the author's
386
- * per-cell m_out writes over that last chunk. */
606
+ * here. */
387
607
  ca_sweep_flush_chunk_mask(st);
388
- /* sync OUTPUTs (reverse order, regardless of whether chunk loop ran) */
389
- for (k_op = st->n_ops - 1; k_op >= 0; k_op--) {
390
- if (st->fsync[k_op] == '1') ca_sync(st->cx[k_op]);
391
- }
608
+ /* sync OUTPUTs (regardless of whether chunk loop ran) */
609
+ tag = ca_sweep_sync_outputs(st, &error);
392
610
  /* detach attached / release arena scratch buffers (reverse order) */
393
611
  for (k_op = st->n_ops - 1; k_op >= 0; k_op--) {
394
612
  if (st->attached[k_op]) {
@@ -406,13 +624,16 @@ ca_sweep_release_chunked (ca_sweep_state_t *st)
406
624
  ca_lazy_arena_release(st->mask_scratch);
407
625
  st->mask_scratch = NULL;
408
626
  }
627
+ if (tag) {
628
+ st->chunked_state = 3; /* everything has been given back */
629
+ }
630
+ ca_sweep_reraise(tag, error);
409
631
  }
410
632
 
411
633
  /* ===== WHOLE_BUFFER function form (rb_ensure-protected) ===== */
412
634
 
413
635
  typedef struct {
414
636
  CArray *ca;
415
- int writable;
416
637
  ca_with_buffer_body_fn body_fn;
417
638
  void *user_data;
418
639
  } ca_with_buffer_ctx_t;
@@ -425,49 +646,15 @@ ca_with_buffer_body_trampoline (VALUE ctx_val)
425
646
  return Qnil;
426
647
  }
427
648
 
428
- static VALUE
429
- ca_with_buffer_ensure_trampoline (VALUE ctx_val)
430
- {
431
- ca_with_buffer_ctx_t *ctx = (ca_with_buffer_ctx_t *) ctx_val;
432
- if (ctx->writable) ca_sync(ctx->ca);
433
- ca_detach(ctx->ca);
434
- return Qnil;
435
- }
436
-
437
649
  void
438
650
  rb_ca_call_with_buffer (VALUE r_ca, int writable,
439
651
  ca_with_buffer_body_fn body, void *user_data)
440
652
  {
441
653
  ca_with_buffer_ctx_t ctx;
442
654
  TypedData_Get_Struct(r_ca, CArray, &carray_data_type, ctx.ca);
443
- /* CAREFUL: attach outside rb_ensure. If attach raises, we are not
444
- * yet inside the ensure scope, so the ensure trampoline will not
445
- * fire on a not-attached ca (which would try to detach and crash). */
446
- ca_attach(ctx.ca);
447
- ctx.writable = writable;
448
655
  ctx.body_fn = body;
449
656
  ctx.user_data = user_data;
450
- rb_ensure(ca_with_buffer_body_trampoline, (VALUE) &ctx,
451
- ca_with_buffer_ensure_trampoline, (VALUE) &ctx);
657
+ ca_attach_window(1, &ctx.ca, writable ? "w" : "r",
658
+ ca_with_buffer_body_trampoline, (VALUE) &ctx);
452
659
  }
453
660
 
454
- void
455
- ca_sweep_check_same_shape (CArray *ca_in, CArray *ca_out, const char *src_label)
456
- {
457
- int k;
458
- const char *lbl = src_label ? src_label : "ca_sweep_check_same_shape";
459
- if (ca_in->ndim != ca_out->ndim) {
460
- rb_raise(rb_eRuntimeError,
461
- "%s: shape mismatch (ndim %d vs %d)",
462
- lbl, ca_in->ndim, ca_out->ndim);
463
- }
464
- for (k = 0; k < ca_in->ndim; k++) {
465
- if (ca_in->dim[k] != ca_out->dim[k]) {
466
- rb_raise(rb_eRuntimeError,
467
- "%s: shape mismatch at dim[%d] (%lld vs %lld)",
468
- lbl, k,
469
- (long long) ca_in->dim[k],
470
- (long long) ca_out->dim[k]);
471
- }
472
- }
473
- }