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
@@ -0,0 +1,731 @@
1
+ #include "carray.h"
2
+ #include "carray_internal.h"
3
+
4
+ /* ---------------------------------------------------------------------------
5
+
6
+ Address bases -- CArray::AddressBasis.
7
+
8
+ Some code addresses cells itself. A generated kernel reads a[i-1] and
9
+ writes a[i] from its own loop, so what it needs from CArray is not element
10
+ delivery but an addressing basis: a pointer, already shifted to cell zero,
11
+ and one byte stride per axis. That is why this does not sit on the kernel
12
+ iterator (per-cell / per-slab delivery, and no N-ary form) or on the
13
+ call_cfunc families (which flatten the array and cannot recover the axis
14
+ structure a stencil needs).
15
+
16
+ This is a runtime facility at the same layer as ca_attach, not a user
17
+ surface. It hands raw addresses to Ruby, and nothing decodes them except a
18
+ consumer that already knows what to do with them -- today carray-jit, the
19
+ one companion carray knows by name, and the gem that runs its output
20
+ ahead of time. It is documented in guides/devel/, not in the user-facing
21
+ docs/ tree, and it is not advertised as API.
22
+
23
+ Arrays are classified in the order the public predicates suggest:
24
+
25
+ 1. ca_is_entity -> the buffer is already the basis
26
+ 2. ca_is_stride_family, -> ca_stride_compose_to_root folds the whole
27
+ and the fold reaches view chain into root + base + strides, so a
28
+ an entity transpose or a column slice is addressed in
29
+ place, with no gather and no scatter. A
30
+ fold that stops short of an entity is not
31
+ this tier; see folds_to_an_entity below
32
+ 3. otherwise -> ca_xfer_stride moves only the box the kernel
33
+ actually touches -- the loop range grown by
34
+ how far the kernel reaches, per array and per
35
+ axis -- into a packed buffer, and writes that
36
+ box back
37
+
38
+ Tier 3 deliberately never calls ca_attach on the view. A whole-view
39
+ materialise costs the same whether the kernel touches ten cells or ten
40
+ million: measured on a four-million-element gather view, ca_attach was
41
+ 2.7 ms regardless, while the region transfer was 0.001 ms for a hundred
42
+ cells and 2.3 ms for a million. A cost that does not scale with the work
43
+ is a cost the caller cannot reason about, and hiding one behind a JIT
44
+ would make its promise meaningless. What tier 3 does cost is proportional
45
+ to what the kernel asked to touch.
46
+
47
+ The block form (`open` with four arguments) yields one descriptive Hash per
48
+ array. The packed form (a fifth argument that is true) yields four byte
49
+ buffers instead, in a layout the consumer reads by offset; that layout is a
50
+ contract between the two and is written out as such in
51
+ guides/devel/21_address_basis.md. See packed_body below.
52
+
53
+ --------------------------------------------------------------------------- */
54
+
55
+ /* Rounded so that each section of the block below starts where the widest
56
+ thing in it may be read. */
57
+ #define ROUND_UP_8(n) (((n) + 7u) & ~(size_t) 7u)
58
+
59
+ #define TIER_ENTITY 1
60
+ #define TIER_STRIDE 2
61
+ #define TIER_XFER 3
62
+
63
+ /* Slot layout: slot i is array i, slot count + i is that array's mask.
64
+ A mask is a CArray of the same shape as its parent and, for a view, the
65
+ same kind of view -- a CABlock's mask is a CABlockMask -- so it is opened
66
+ by exactly the same tier logic as the data. */
67
+ typedef struct {
68
+ char *block; /* the one allocation the arrays below carve up */
69
+ int count;
70
+ int slots;
71
+ VALUE arrays;
72
+ CArray **carrays;
73
+ CArray **roots;
74
+ int *tier;
75
+ int *writable;
76
+ int *attached_root;
77
+ char **region; /* tier 3 packed buffer, NULL otherwise */
78
+ ca_size_t *region_start; /* count * CA_RANK_MAX */
79
+ ca_size_t *region_count;
80
+ VALUE bases;
81
+ VALUE box_starts; /* per array, per axis; nil for "all of it" */
82
+ VALUE box_counts;
83
+ int body_done; /* the block returned without raising */
84
+ volatile VALUE put_error; /* the first write-back that raised, or nil */
85
+ } open_state;
86
+
87
+ /* The view's own row-major byte layout, which is the address space
88
+ ca_xfer_stride describes a region in. */
89
+ static void
90
+ native_steps (CArray *ca, ca_size_t *steps)
91
+ {
92
+ ca_size_t step = ca->bytes;
93
+ int8_t k;
94
+ for ( k = ca->ndim - 1; k >= 0; k-- ) {
95
+ steps[k] = step;
96
+ step *= ca->dim[k];
97
+ }
98
+ }
99
+
100
+ /* Whether [start, start + count) leaves [0, dim). Written so that no sum
101
+ is formed: a start and a count near the top of the integer range add up
102
+ to a negative number, which `start + count > dim` lets through. */
103
+ static int
104
+ outside_axis (ca_size_t start, ca_size_t count, ca_size_t dim)
105
+ {
106
+ return start < 0 || count < 0 || start > dim || count > dim - start;
107
+ }
108
+
109
+ /* Checks one array's box description, all of it: that it is one start and one
110
+ count per axis, that they are numbers, and that the box they describe is
111
+ inside the array.
112
+
113
+ Run for every array before any of them is opened, whatever tier each turns
114
+ out to land in. Only tier 3 goes on to read the box -- tiers 1 and 2
115
+ address the whole array, which covers any box inside it -- so this is the
116
+ only place a caller's description is looked at at all for two of the three
117
+ tiers. Checking it there too is what keeps the same call refused the same
118
+ way whatever the arrays turn out to be, rather than a box that is wrong
119
+ about a plain array being noticed only once the same code is handed a
120
+ gather view. And it is a C extension, where a wrong type has to be a
121
+ message and not a crash. */
122
+ static void
123
+ verify_box (VALUE box_starts, VALUE box_counts, int index, CArray *ca)
124
+ {
125
+ VALUE starts, counts;
126
+ int8_t k;
127
+ if ( NIL_P(box_starts) ) return;
128
+ starts = rb_ary_entry(box_starts, index);
129
+ counts = rb_ary_entry(box_counts, index);
130
+ if ( NIL_P(starts) && NIL_P(counts) ) return;
131
+ Check_Type(starts, T_ARRAY);
132
+ Check_Type(counts, T_ARRAY);
133
+ if ( RARRAY_LEN(starts) != ca->ndim || RARRAY_LEN(counts) != ca->ndim ) {
134
+ rb_raise(rb_eArgError,
135
+ "a region is described by one start and one count per axis; "
136
+ "this array has %d", (int) ca->ndim);
137
+ }
138
+ for ( k = 0; k < ca->ndim; k++ ) {
139
+ ca_size_t start = ca_integer_arg(rb_ary_entry(starts, k), "a region start",
140
+ "CArray::AddressBasis.open");
141
+ ca_size_t count = ca_integer_arg(rb_ary_entry(counts, k), "a region count",
142
+ "CArray::AddressBasis.open");
143
+ if ( outside_axis(start, count, ca->dim[k]) ) {
144
+ rb_raise(rb_eArgError,
145
+ "the requested region falls outside the array on axis %d", (int) k);
146
+ }
147
+ }
148
+ }
149
+
150
+ /* Reads one array's box out of the Ruby-side description, defaulting to the
151
+ whole array. verify_box checked it, and checks it again here because this
152
+ is the last thing between a caller's numbers and pointer arithmetic. */
153
+ static void
154
+ read_box (open_state *state, int index, CArray *ca,
155
+ ca_size_t *starts, ca_size_t *counts)
156
+ {
157
+ VALUE per_array_start = Qnil, per_array_count = Qnil;
158
+ int8_t k;
159
+
160
+ if ( ! NIL_P(state->box_starts) ) {
161
+ per_array_start = rb_ary_entry(state->box_starts, index);
162
+ per_array_count = rb_ary_entry(state->box_counts, index);
163
+ }
164
+
165
+ for ( k = 0; k < ca->ndim; k++ ) {
166
+ if ( NIL_P(per_array_start) ) {
167
+ starts[k] = 0;
168
+ counts[k] = ca->dim[k];
169
+ } else {
170
+ starts[k] = NUM2LL(rb_ary_entry(per_array_start, k));
171
+ counts[k] = NUM2LL(rb_ary_entry(per_array_count, k));
172
+ }
173
+ if ( outside_axis(starts[k], counts[k], ca->dim[k]) ) {
174
+ rb_raise(rb_eArgError,
175
+ "the requested region falls outside the array on axis %d", (int) k);
176
+ }
177
+ }
178
+ }
179
+
180
+ static void
181
+ row_major_strides (CArray *ca, ca_size_t *strides)
182
+ {
183
+ ca_size_t step = ca->bytes;
184
+ int8_t k;
185
+ for ( k = ca->ndim - 1; k >= 0; k-- ) {
186
+ strides[k] = step;
187
+ step *= ca->dim[k];
188
+ }
189
+ }
190
+
191
+ static VALUE
192
+ size_array (ca_size_t *values, int8_t count)
193
+ {
194
+ VALUE list = rb_ary_new_capa(count);
195
+ int8_t k;
196
+ for ( k = 0; k < count; k++ ) {
197
+ rb_ary_push(list, LL2NUM((long long) values[k]));
198
+ }
199
+ return list;
200
+ }
201
+
202
+ /* Refuses what a generated kernel cannot express, rather than letting it
203
+ produce quietly wrong numbers. */
204
+ static void
205
+ verify_usable (VALUE object, CArray *ca, int writable)
206
+ {
207
+ if ( ca->data_type == CA_OBJECT ) {
208
+ rb_raise(rb_eArgError, "object arrays hold Ruby values, not numbers");
209
+ }
210
+ if ( writable && ca_is_readonly(ca) ) {
211
+ rb_raise(rb_eRuntimeError, "%"PRIsVALUE" is read-only",
212
+ rb_obj_class(object));
213
+ }
214
+ }
215
+
216
+ /* The stride tier addresses the fold's root directly, which is only sound
217
+ when that root owns its memory. ca_stride_compose_to_root stops at the
218
+ first thing it cannot fold through, and that need not be an entity: a
219
+ CARefer over a gather view (`whole[whole >= 0].reshape(4, 4)`) folds one
220
+ step and lands on the CASelect. Attaching a root like that materialises a
221
+ temporary, and detaching it throws the kernel's writes away -- silently.
222
+ So a fold that does not reach an entity is not the stride tier; the box
223
+ transfer handles it, and moves only the cells the kernel asked for. */
224
+ static int
225
+ folds_to_an_entity (CArray *ca)
226
+ {
227
+ CArray *root;
228
+ ca_size_t strides[CA_RANK_MAX];
229
+ ca_size_t base = 0;
230
+ ca_stride_compose_to_root((CAStride *) ca, &root, strides, &base);
231
+ return ca_is_entity(root);
232
+ }
233
+
234
+ /* A view that reinterprets the element size -- refer(CA_INT32, ...) over
235
+ a float64 array -- gets a mask of its own shape, but one mask cell of it
236
+ covers a fraction of a parent cell, so writing cell i's mask also marks
237
+ its neighbour. A per-cell kernel writes cells independently and cannot
238
+ express that. Checked for every tier: a box transfer moves the same
239
+ mask, and the neighbour is marked when it is written back. */
240
+ static void
241
+ verify_mask_cells (VALUE object, CArray *ca)
242
+ {
243
+ CArray *root;
244
+ ca_size_t strides[CA_RANK_MAX];
245
+ ca_size_t base = 0;
246
+ if ( ! ca->mask || ! ca_is_stride_family(ca) ) return;
247
+ ca_stride_compose_to_root((CAStride *) ca, &root, strides, &base);
248
+ if ( ca->bytes != root->bytes ) {
249
+ rb_raise(rb_eArgError,
250
+ "%"PRIsVALUE" reinterprets the element size and carries a mask; "
251
+ "its mask cells do not map one to one onto the parent's",
252
+ rb_obj_class(object));
253
+ }
254
+ }
255
+
256
+ static int
257
+ tier_for (CArray *ca)
258
+ {
259
+ if ( ca_is_entity(ca) ) return TIER_ENTITY;
260
+ if ( ca_is_stride_family(ca) && folds_to_an_entity(ca) ) return TIER_STRIDE;
261
+ return TIER_XFER;
262
+ }
263
+
264
+ /* Attaches or transfers one array and answers where its first cell is,
265
+ filling `strides` with how far apart the rest are. This is everything a
266
+ basis says that a kernel actually reads; the hash around it is for the
267
+ callers that want to look. */
268
+ static char *
269
+ acquire_basis (open_state *state, int index, ca_size_t *strides)
270
+ {
271
+ CArray *ca = state->carrays[index];
272
+ ca_size_t base = 0;
273
+ char *pointer;
274
+
275
+ switch ( state->tier[index] ) {
276
+ case TIER_ENTITY: {
277
+ CArray *root = ca;
278
+ ca_attach(root); /* window: released by open_ensure */
279
+ state->roots[index] = root;
280
+ state->attached_root[index] = 1;
281
+ row_major_strides(ca, strides);
282
+ pointer = ca->ptr;
283
+ break;
284
+ }
285
+
286
+ case TIER_STRIDE: {
287
+ CArray *root;
288
+ ca_stride_compose_to_root((CAStride *) ca, &root, strides, &base);
289
+ ca_attach(root); /* window: released by open_ensure */
290
+ state->roots[index] = root;
291
+ state->attached_root[index] = 1;
292
+ pointer = root->ptr + base;
293
+ break;
294
+ }
295
+
296
+ default: {
297
+ /* Only the requested box crosses, never the whole view. */
298
+ ca_size_t starts[CA_RANK_MAX], counts[CA_RANK_MAX], steps[CA_RANK_MAX];
299
+ ca_size_t elements = 1, shift = 0, step;
300
+ int8_t k;
301
+ char *buffer;
302
+
303
+ native_steps(ca, steps);
304
+ read_box(state, index, ca, starts, counts);
305
+ for ( k = 0; k < ca->ndim; k++ ) elements *= counts[k];
306
+
307
+ /* ca_xfer_stride packs the box row-major, so the buffer's strides come
308
+ from the box's own extents, not the view's. */
309
+ step = ca->bytes;
310
+ for ( k = ca->ndim - 1; k >= 0; k-- ) {
311
+ strides[k] = step;
312
+ step *= counts[k];
313
+ }
314
+
315
+ /* Recorded before the read: the read can raise, and open_ensure
316
+ frees what state->region holds. The counts stay zero until the
317
+ read is done, so a buffer that was never filled is not written
318
+ back. */
319
+ /* A writable box keeps a second copy of what it read, so that the
320
+ write-back can send only the cells the kernel changed (see
321
+ put_changed). */
322
+ buffer = ALLOC_N(char, (state->writable[index] ? 2 : 1)
323
+ * (elements > 0 ? elements : 1) * ca->bytes);
324
+ for ( k = 0; k < ca->ndim; k++ ) {
325
+ state->region_count[index * CA_RANK_MAX + k] = 0;
326
+ }
327
+ state->region[index] = buffer;
328
+ if ( elements > 0 ) {
329
+ ca_xfer_stride(ca, starts, counts, steps, buffer, CA_XFER_GET);
330
+ if ( state->writable[index] ) {
331
+ memcpy(buffer + elements * ca->bytes, buffer, elements * ca->bytes);
332
+ }
333
+ }
334
+
335
+ for ( k = 0; k < ca->ndim; k++ ) {
336
+ state->region_start[index * CA_RANK_MAX + k] = starts[k];
337
+ state->region_count[index * CA_RANK_MAX + k] = counts[k];
338
+ shift += starts[k] * strides[k];
339
+ }
340
+ /* Shifted so that the box's first cell lands on buffer[0], the way a
341
+ view's base_offset shifts its parent's pointer. */
342
+ pointer = buffer - shift;
343
+ break;
344
+ }
345
+ }
346
+
347
+ return pointer;
348
+ }
349
+
350
+ static VALUE
351
+ basis_for (open_state *state, int index)
352
+ {
353
+ CArray *ca = state->carrays[index];
354
+ ca_size_t strides[CA_RANK_MAX];
355
+ char *pointer = acquire_basis(state, index, strides);
356
+ VALUE result;
357
+
358
+ result = rb_hash_new();
359
+ rb_hash_aset(result, ID2SYM(rb_intern("tier")), INT2NUM(state->tier[index]));
360
+ rb_hash_aset(result, ID2SYM(rb_intern("pointer")),
361
+ ULL2NUM((unsigned long long)(uintptr_t) pointer));
362
+ rb_hash_aset(result, ID2SYM(rb_intern("strides")), size_array(strides, ca->ndim));
363
+ rb_hash_aset(result, ID2SYM(rb_intern("dim")), size_array(ca->dim, ca->ndim));
364
+ rb_hash_aset(result, ID2SYM(rb_intern("bytes")), LL2NUM((long long) ca->bytes));
365
+ rb_hash_aset(result, ID2SYM(rb_intern("data_type")), INT2NUM(ca->data_type));
366
+ rb_hash_aset(result, ID2SYM(rb_intern("writable")),
367
+ state->writable[index] ? Qtrue : Qfalse);
368
+ return result;
369
+ }
370
+
371
+ /* Each array's basis, with its mask's basis folded in under :mask_pointer
372
+ and :mask_strides (nil when the array carries no mask). */
373
+ static VALUE
374
+ open_body (VALUE argument)
375
+ {
376
+ open_state *state = (open_state *) argument;
377
+ int i;
378
+ for ( i = 0; i < state->count; i++ ) {
379
+ VALUE basis = basis_for(state, i);
380
+ if ( state->carrays[state->count + i] ) {
381
+ VALUE mask = basis_for(state, state->count + i);
382
+ rb_hash_aset(basis, ID2SYM(rb_intern("mask_pointer")),
383
+ rb_hash_aref(mask, ID2SYM(rb_intern("pointer"))));
384
+ rb_hash_aset(basis, ID2SYM(rb_intern("mask_strides")),
385
+ rb_hash_aref(mask, ID2SYM(rb_intern("strides"))));
386
+ } else {
387
+ rb_hash_aset(basis, ID2SYM(rb_intern("mask_pointer")), Qnil);
388
+ rb_hash_aset(basis, ID2SYM(rb_intern("mask_strides")), Qnil);
389
+ }
390
+ rb_ary_push(state->bases, basis);
391
+ }
392
+ {
393
+ VALUE result = rb_yield(state->bases);
394
+ state->body_done = 1;
395
+ return result;
396
+ }
397
+ }
398
+
399
+ typedef struct {
400
+ CArray *ca;
401
+ ca_size_t *start;
402
+ ca_size_t *count;
403
+ ca_size_t *steps;
404
+ char *data;
405
+ char *read; /* the box as it was read */
406
+ } write_back_args;
407
+
408
+ /* Sends back the cells whose bytes differ from what was read, one run
409
+ along the last axis at a time. Sending the whole box would also send the
410
+ cells the kernel never wrote, and those do not come back unchanged
411
+ through a view that converts -- a float64 array seen through an int32
412
+ view reads 1.75 as 1 and would be left holding 1.0. It would also
413
+ overwrite what the kernel wrote to the same cells through another basis
414
+ that addresses them in place. */
415
+ static VALUE
416
+ write_back (VALUE argument)
417
+ {
418
+ write_back_args *w = (write_back_args *) argument;
419
+ CArray *ca = w->ca;
420
+ int8_t nd = ca->ndim;
421
+ size_t bytes = ca->bytes;
422
+ ca_size_t inner = w->count[nd - 1];
423
+ ca_size_t outer = 1;
424
+ ca_size_t idx[CA_RANK_MAX], start[CA_RANK_MAX], count[CA_RANK_MAX];
425
+ ca_size_t o, j, e;
426
+ int8_t k;
427
+
428
+ for ( k = 0; k < nd - 1; k++ ) {
429
+ outer *= w->count[k];
430
+ idx[k] = 0;
431
+ count[k] = 1;
432
+ }
433
+ for ( o = 0; o < outer; o++ ) {
434
+ char *row = w->data + o * inner * bytes;
435
+ char *read = w->read + o * inner * bytes;
436
+ for ( k = 0; k < nd - 1; k++ ) start[k] = w->start[k] + idx[k];
437
+ j = 0;
438
+ while ( j < inner ) {
439
+ if ( ! memcmp(row + j * bytes, read + j * bytes, bytes) ) { j++; continue; }
440
+ e = j + 1;
441
+ while ( e < inner && memcmp(row + e * bytes, read + e * bytes, bytes) ) e++;
442
+ start[nd - 1] = w->start[nd - 1] + j;
443
+ count[nd - 1] = e - j;
444
+ ca_xfer_stride(ca, start, count, w->steps, row + j * bytes, CA_XFER_PUT);
445
+ j = e;
446
+ }
447
+ for ( k = nd - 2; k >= 0; k-- ) {
448
+ if ( ++idx[k] < w->count[k] ) break;
449
+ idx[k] = 0;
450
+ }
451
+ }
452
+ return Qnil;
453
+ }
454
+
455
+ /* Closes in reverse order, and runs whether or not the kernel raised. A
456
+ write-back that raises -- the array's own transfer slot failing -- is
457
+ set aside so that every other region is still written and freed and
458
+ every root detached; the block's own exception, if it raised, wins. */
459
+ static VALUE
460
+ open_ensure (VALUE argument)
461
+ {
462
+ open_state *state = (open_state *) argument;
463
+ int i;
464
+ for ( i = state->slots - 1; i >= 0; i-- ) {
465
+ CArray *ca = state->carrays[i];
466
+ if ( ca == NULL ) continue;
467
+ /* A tier-1 or tier-2 basis addresses the root's own memory, so a write is
468
+ already where it belongs. Only a region buffer has to be sent back. */
469
+ if ( state->region[i] ) {
470
+ ca_size_t elements = 1;
471
+ int8_t k;
472
+ for ( k = 0; k < ca->ndim; k++ ) {
473
+ elements *= state->region_count[i * CA_RANK_MAX + k];
474
+ }
475
+ if ( state->writable[i] && elements > 0 ) {
476
+ ca_size_t steps[CA_RANK_MAX];
477
+ write_back_args w;
478
+ int tag = 0;
479
+ native_steps(ca, steps);
480
+ w.ca = ca;
481
+ w.start = &state->region_start[i * CA_RANK_MAX];
482
+ w.count = &state->region_count[i * CA_RANK_MAX];
483
+ w.steps = steps;
484
+ w.data = state->region[i];
485
+ w.read = state->region[i] + elements * ca->bytes;
486
+ rb_protect(write_back, (VALUE) &w, &tag);
487
+ if ( tag ) {
488
+ if ( NIL_P(state->put_error) ) {
489
+ state->put_error = rb_errinfo();
490
+ }
491
+ rb_set_errinfo(Qnil);
492
+ }
493
+ }
494
+ xfree(state->region[i]);
495
+ }
496
+ if ( state->attached_root[i] ) {
497
+ ca_detach(state->roots[i]);
498
+ }
499
+ }
500
+ xfree(state->block);
501
+ if ( state->body_done && RTEST(rb_obj_is_kind_of(state->put_error,
502
+ rb_eException)) ) {
503
+ rb_exc_raise(state->put_error);
504
+ }
505
+ return Qnil;
506
+ }
507
+
508
+ /* What a kernel is handed, rather than what a reader wants to see.
509
+ *
510
+ * The hash form above exists so that a caller can ask an array how it was
511
+ * opened. A kernel never asks: it packs the pointers and the strides into
512
+ * four buffers and passes their addresses to the C. Building a Hash and an
513
+ * Array per array so that Ruby can immediately pack them back into bytes is
514
+ * a round trip through the object heap that nothing looks at, and it cost
515
+ * more than the opening did. So this writes the four buffers directly.
516
+ *
517
+ * An array with no mask still takes its slots in the mask strides, one per
518
+ * axis of its own, zero. Its own rank and not the kernel's: the generated C
519
+ * finds an array's mask strides at the sum of the ranks of the arrays before
520
+ * it, and an operand of lower rank than the kernel -- a row broadcast over a
521
+ * grid -- padded to the kernel's rank moved every mask after it.
522
+ */
523
+ static VALUE
524
+ packed_body (VALUE argument)
525
+ {
526
+ open_state *state = (open_state *) argument;
527
+ int count = state->count;
528
+ int i;
529
+ long stride_slots = 0, mask_stride_slots = 0;
530
+ VALUE pointers, strides, mask_pointers, mask_strides;
531
+ uint64_t *pointer_slot, *mask_pointer_slot;
532
+ int64_t *stride_slot, *mask_stride_slot;
533
+
534
+ for ( i = 0; i < count; i++ ) {
535
+ stride_slots += state->carrays[i]->ndim;
536
+ mask_stride_slots += state->carrays[count + i]
537
+ ? state->carrays[count + i]->ndim
538
+ : state->carrays[i]->ndim;
539
+ }
540
+
541
+ pointers = rb_str_new(NULL, (long) (count * sizeof(uint64_t)));
542
+ mask_pointers = rb_str_new(NULL, (long) (count * sizeof(uint64_t)));
543
+ strides = rb_str_new(NULL, stride_slots * (long) sizeof(int64_t));
544
+ mask_strides = rb_str_new(NULL, mask_stride_slots * (long) sizeof(int64_t));
545
+
546
+ pointer_slot = (uint64_t *) RSTRING_PTR(pointers);
547
+ mask_pointer_slot = (uint64_t *) RSTRING_PTR(mask_pointers);
548
+ stride_slot = (int64_t *) RSTRING_PTR(strides);
549
+ mask_stride_slot = (int64_t *) RSTRING_PTR(mask_strides);
550
+
551
+ for ( i = 0; i < count; i++ ) {
552
+ CArray *ca = state->carrays[i];
553
+ ca_size_t own[CA_RANK_MAX];
554
+ char *pointer = acquire_basis(state, i, own);
555
+ int8_t k;
556
+
557
+ *pointer_slot++ = (uint64_t)(uintptr_t) pointer;
558
+ for ( k = 0; k < ca->ndim; k++ ) *stride_slot++ = (int64_t) own[k];
559
+
560
+ if ( state->carrays[count + i] ) {
561
+ CArray *mask = state->carrays[count + i];
562
+ ca_size_t mask_own[CA_RANK_MAX];
563
+ char *mask_pointer = acquire_basis(state, count + i, mask_own);
564
+ *mask_pointer_slot++ = (uint64_t)(uintptr_t) mask_pointer;
565
+ for ( k = 0; k < mask->ndim; k++ ) *mask_stride_slot++ = (int64_t) mask_own[k];
566
+ } else {
567
+ *mask_pointer_slot++ = 0;
568
+ for ( k = 0; k < ca->ndim; k++ ) *mask_stride_slot++ = 0;
569
+ }
570
+ }
571
+
572
+ {
573
+ VALUE result = rb_yield_values(4, pointers, strides, mask_pointers,
574
+ mask_strides);
575
+ state->body_done = 1;
576
+ return result;
577
+ }
578
+ }
579
+
580
+ /*
581
+ * Opens every array, yields what it opened, and closes them all on the way
582
+ * out -- including when the block raises.
583
+ *
584
+ * With four arguments the block is handed one basis hash per array, which is
585
+ * the form to read an array's opening in. Given a fifth that is true, it is
586
+ * handed the four packed buffers a kernel passes to the C instead: pointers,
587
+ * strides, mask pointers, mask strides.
588
+ */
589
+ static VALUE
590
+ address_basis_open (int argc, VALUE *argv, VALUE module)
591
+ {
592
+ VALUE arrays, writable_flags, box_start, box_count, packed;
593
+ open_state state;
594
+ int i;
595
+
596
+ rb_scan_args(argc, argv, "23", &arrays, &writable_flags, &box_start,
597
+ &box_count, &packed);
598
+ Check_Type(arrays, T_ARRAY);
599
+ Check_Type(writable_flags, T_ARRAY);
600
+ if ( NIL_P(box_start) != NIL_P(box_count) ) {
601
+ rb_raise(rb_eArgError, "a region needs both starts and counts");
602
+ }
603
+ if ( ! NIL_P(box_start) ) {
604
+ Check_Type(box_start, T_ARRAY);
605
+ Check_Type(box_count, T_ARRAY);
606
+ if ( RARRAY_LEN(box_start) != RARRAY_LEN(arrays) ||
607
+ RARRAY_LEN(box_count) != RARRAY_LEN(arrays) ) {
608
+ rb_raise(rb_eArgError, "one region per array is required");
609
+ }
610
+ }
611
+ if ( RARRAY_LEN(arrays) != RARRAY_LEN(writable_flags) ) {
612
+ rb_raise(rb_eArgError, "one writable flag per array is required");
613
+ }
614
+
615
+ /* Every argument is checked before the block below is allocated: a
616
+ refusal raises, and it must find nothing to free. */
617
+ for ( i = 0; i < (int) RARRAY_LEN(arrays); i++ ) {
618
+ VALUE object = rb_ary_entry(arrays, i);
619
+ CArray *ca;
620
+ GetCArray(object, ca);
621
+ /* A view takes its mask from its parent when first asked for it; one
622
+ added to the parent after the view was made is not on ca->mask yet. */
623
+ ca_update_mask(ca);
624
+ verify_usable(object, ca, RTEST(rb_ary_entry(writable_flags, i)));
625
+ verify_mask_cells(object, ca);
626
+ verify_box(box_start, box_count, i, ca);
627
+ }
628
+
629
+ state.count = (int) RARRAY_LEN(arrays);
630
+ state.slots = state.count * 2;
631
+ /* Our own copy: the caller's Array can be emptied inside the block, and
632
+ the arrays it held are attached until open_ensure has run. */
633
+ state.arrays = rb_ary_dup(arrays);
634
+ state.bases = rb_ary_new_capa(state.count);
635
+ /* One allocation, carved up, rather than eight. Every array here is the
636
+ same length in slots and lives exactly as long as the open, so there is
637
+ nothing for eight separate lifetimes to buy -- and this is a per-call
638
+ cost on a path whose whole point is to be cheap enough to cross often.
639
+ Zeroed once as a block, which is also what the slots want to start as:
640
+ a NULL array, a NULL root, not writable, not attached, no region. */
641
+ {
642
+ size_t n = (size_t) state.slots + 1;
643
+ size_t pointers = ROUND_UP_8(n * sizeof(CArray *));
644
+ size_t chars = ROUND_UP_8(n * sizeof(char *));
645
+ size_t sizes = ROUND_UP_8(n * CA_RANK_MAX * sizeof(ca_size_t));
646
+ size_t ints = ROUND_UP_8(n * sizeof(int));
647
+ size_t total = 2 * pointers + chars + 2 * sizes + 3 * ints;
648
+ char *p;
649
+ state.block = ALLOC_N(char, total);
650
+ MEMZERO(state.block, char, total);
651
+ p = state.block;
652
+ state.carrays = (CArray **) p; p += pointers;
653
+ state.roots = (CArray **) p; p += pointers;
654
+ state.region = (char **) p; p += chars;
655
+ state.region_start = (ca_size_t *) p; p += sizes;
656
+ state.region_count = (ca_size_t *) p; p += sizes;
657
+ state.tier = (int *) p; p += ints;
658
+ state.writable = (int *) p; p += ints;
659
+ state.attached_root = (int *) p;
660
+ }
661
+ state.box_starts = box_start;
662
+ state.box_counts = box_count;
663
+ state.body_done = 0;
664
+ state.put_error = Qnil;
665
+
666
+ for ( i = 0; i < state.count; i++ ) {
667
+ VALUE object = rb_ary_entry(state.arrays, i);
668
+ CArray *ca;
669
+ GetCArray(object, ca);
670
+ state.carrays[i] = ca;
671
+ state.writable[i] = RTEST(rb_ary_entry(writable_flags, i));
672
+ state.tier[i] = tier_for(ca);
673
+
674
+ if ( ca->mask ) {
675
+ CArray *mask = ca->mask;
676
+ state.carrays[state.count + i] = mask;
677
+ state.writable[state.count + i] = state.writable[i];
678
+ state.tier[state.count + i] = tier_for(mask);
679
+ }
680
+ }
681
+
682
+ {
683
+ VALUE result = RTEST(packed)
684
+ ? rb_ensure(packed_body, (VALUE) &state, open_ensure, (VALUE) &state)
685
+ : rb_ensure(open_body, (VALUE) &state, open_ensure, (VALUE) &state);
686
+ RB_GC_GUARD(state.arrays);
687
+ return result;
688
+ }
689
+ }
690
+
691
+ /* Reports how an array would be opened, without opening it. */
692
+ static VALUE
693
+ address_basis_classify (VALUE module, VALUE object)
694
+ {
695
+ CArray *ca;
696
+ VALUE result;
697
+ int tier;
698
+
699
+ GetCArray(object, ca);
700
+ tier = tier_for(ca);
701
+
702
+ result = rb_hash_new();
703
+ rb_hash_aset(result, ID2SYM(rb_intern("tier")), INT2NUM(tier));
704
+ rb_hash_aset(result, ID2SYM(rb_intern("entity")), ca_is_entity(ca) ? Qtrue : Qfalse);
705
+ rb_hash_aset(result, ID2SYM(rb_intern("stride_family")),
706
+ ca_is_stride_family(ca) ? Qtrue : Qfalse);
707
+ rb_hash_aset(result, ID2SYM(rb_intern("read_only")), ca_is_readonly(ca) ? Qtrue : Qfalse);
708
+ rb_hash_aset(result, ID2SYM(rb_intern("masked")), ca_has_mask(ca) ? Qtrue : Qfalse);
709
+ rb_hash_aset(result, ID2SYM(rb_intern("dim")), size_array(ca->dim, ca->ndim));
710
+ rb_hash_aset(result, ID2SYM(rb_intern("bytes")), LL2NUM((long long) ca->bytes));
711
+ rb_hash_aset(result, ID2SYM(rb_intern("data_type")), INT2NUM(ca->data_type));
712
+ return result;
713
+ }
714
+
715
+ /* ------------------------------------------------------------------- */
716
+ /* Init_carray_address_basis */
717
+ /* ------------------------------------------------------------------- */
718
+
719
+ void
720
+ Init_carray_address_basis (void)
721
+ {
722
+ VALUE mAddressBasis = rb_define_module_under(rb_cCArray, "AddressBasis");
723
+
724
+ rb_define_singleton_method(mAddressBasis, "open", address_basis_open, -1);
725
+ rb_define_singleton_method(mAddressBasis, "classify",
726
+ address_basis_classify, 1);
727
+
728
+ rb_define_const(mAddressBasis, "TIER_ENTITY", INT2NUM(TIER_ENTITY));
729
+ rb_define_const(mAddressBasis, "TIER_STRIDE", INT2NUM(TIER_STRIDE));
730
+ rb_define_const(mAddressBasis, "TIER_XFER", INT2NUM(TIER_XFER));
731
+ }