carray 3.0.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +2 -1
- data/CHANGELOG.md +845 -3
- data/{NEWS.md → CHANGELOG.v1.md} +1 -1
- data/README.md +33 -33
- data/carray.gemspec +12 -8
- data/ext/ca_axis_dispatch.c +33 -4
- data/ext/ca_axis_group.c +256 -114
- data/ext/ca_binop_dispatch.c +5 -6
- data/ext/ca_binop_dispatch.h +0 -7
- data/ext/ca_categorical_iterator.c +121 -67
- data/ext/ca_for_each_element.h +6 -8
- data/ext/ca_group_iter.c +1 -1
- data/ext/ca_kernel_iterator.c +386 -74
- data/ext/ca_kernel_iterator.h +145 -45
- data/ext/ca_obj_array.c +62 -20
- data/ext/ca_obj_bincmp.c +57 -27
- data/ext/ca_obj_binop.c +86 -30
- data/ext/ca_obj_block.c +4 -4
- data/ext/ca_obj_const_string.c +88 -27
- data/ext/ca_obj_face.c +104 -0
- data/ext/ca_obj_face.h +48 -1
- data/ext/ca_obj_fake.c +11 -0
- data/ext/ca_obj_fixlen_string.c +21 -6
- data/ext/ca_obj_grid.c +11 -3
- data/ext/ca_obj_meld.c +125 -22
- data/ext/ca_obj_moncmp.c +33 -14
- data/ext/ca_obj_monop.c +19 -7
- data/ext/ca_obj_object.c +107 -4
- data/ext/ca_obj_record.c +3 -1
- data/ext/ca_obj_refer.c +30 -6
- data/ext/ca_obj_roll.c +13 -5
- data/ext/ca_obj_select.c +49 -34
- data/ext/ca_obj_select_axis.c +16 -6
- data/ext/ca_obj_shift.c +3 -3
- data/ext/ca_obj_stack.c +3 -8
- data/ext/ca_obj_stride.c +178 -24
- data/ext/ca_obj_string.c +11 -5
- data/ext/ca_obj_tile.c +13 -5
- data/ext/ca_obj_time.c +3 -1
- data/ext/ca_obj_timedelta.c +3 -1
- data/ext/ca_obj_transpose.c +1 -1
- data/ext/ca_obj_triop.c +62 -23
- data/ext/ca_obj_window.c +94 -19
- data/ext/ca_op_cmplx64.h +123 -0
- data/ext/ca_op_ipower.c +1 -5
- data/ext/ca_rng_normal.h +42 -0
- data/ext/ca_rng_xoshiro256pp.h +105 -0
- data/ext/ca_sort_kernels.h +5 -5
- data/ext/ca_sweep_engine.c +362 -155
- data/ext/ca_sweep_engine.h +39 -9
- data/ext/ca_transform_common.c +7 -0
- data/ext/carray.h +148 -132
- data/ext/carray_access.c +83 -49
- data/ext/carray_address_basis.c +590 -0
- data/ext/carray_bincount.c +8 -8
- data/ext/carray_broadcast.c +102 -9
- data/ext/carray_build_flags.h +3 -0
- data/ext/carray_call_cfunc.c +2010 -335
- data/ext/carray_call_cfunc.h +153 -0
- data/ext/carray_cast.c +184 -72
- data/ext/carray_conversion.c +22 -22
- data/ext/carray_copy.c +67 -32
- data/ext/carray_core.c +140 -13
- data/ext/carray_count.c +10 -11
- data/ext/carray_element.c +7 -7
- data/ext/carray_factorize.c +72 -51
- data/ext/carray_hold.c +1 -1
- data/ext/carray_index_classifier.c +7 -21
- data/ext/carray_internal.h +36 -3
- data/ext/carray_kernels_bincmp.c +1 -0
- data/ext/carray_kernels_binop.c +320 -298
- data/ext/carray_kernels_init.c +1095 -0
- data/ext/carray_kernels_map.c +1 -0
- data/ext/carray_kernels_moncmp.c +1 -0
- data/ext/carray_kernels_monop.c +412 -411
- data/ext/carray_kernels_reduce_aggregate.c +256 -87
- data/ext/carray_kernels_reduce_boolean.c +1 -0
- data/ext/carray_kernels_reduce_cumulative.c +324 -54
- data/ext/carray_kernels_reduce_extreme.c +605 -58
- data/ext/carray_kernels_reduce_variance.c +1 -0
- data/ext/carray_kernels_scan.c +5 -4
- data/ext/carray_kernels_search.c +187 -106
- data/ext/carray_kernels_sort.c +1 -0
- data/ext/carray_kernels_triop.c +1 -0
- data/ext/carray_lazy.c +175 -5
- data/ext/carray_loop.c +7 -1
- data/ext/carray_mask.c +29 -13
- data/ext/carray_median_percentile.c +56 -1
- data/ext/carray_memory_view.c +46 -21
- data/ext/carray_operator.c +50 -53
- data/ext/carray_order.c +1 -1
- data/ext/carray_partition.c +4 -4
- data/ext/carray_random.c +391 -47
- data/ext/carray_scatter.c +1 -1
- data/ext/carray_slab.c +13 -0
- data/ext/carray_sort.c +22 -24
- data/ext/carray_sort_kernel.c +10 -10
- data/ext/carray_test.c +1 -1
- data/ext/extconf.rb +18 -0
- data/ext/mk_call_cfunc.rb +323 -145
- data/ext/mkkernel.rb +651 -129
- data/ext/ruby_carray.c +28 -17
- data/ext/version.h +4 -4
- data/lib/carray/autoload_carray.rb +24 -3
- data/lib/carray/autoload_method_extension.rb +14 -1
- data/lib/carray/axis_group.rb +84 -7
- data/lib/carray/basics.rb +63 -59
- data/lib/carray/bincount_nd.rb +12 -12
- data/lib/carray/block_iterator.rb +111 -25
- data/lib/carray/categorical.rb +154 -37
- data/lib/carray/categorical_iterator.rb +300 -151
- data/lib/carray/conditional.rb +14 -14
- data/lib/carray/const_string.rb +131 -27
- data/lib/carray/construct.rb +61 -0
- data/lib/carray/core_extensions.rb +62 -5
- data/lib/carray/data_type_extension.rb +21 -1
- data/lib/carray/data_type_limits.rb +91 -0
- data/lib/carray/fixlen_string.rb +1 -1
- data/lib/carray/frame/concat.rb +3 -3
- data/lib/carray/frame/convert.rb +1 -1
- data/lib/carray/frame/csv_parser.rb +11 -4
- data/lib/carray/frame/frame.rb +85 -21
- data/lib/carray/frame/group.rb +36 -3
- data/lib/carray/frame/io.rb +224 -15
- data/lib/carray/frame/records.rb +18 -4
- data/lib/carray/frame/sort.rb +1 -1
- data/lib/carray/frame/verbs.rb +32 -12
- data/lib/carray/fuse_source.rb +123 -0
- data/lib/carray/fusion.rb +218 -0
- data/lib/carray/histogram.rb +16 -16
- data/lib/carray/inspect.rb +43 -16
- data/lib/carray/iterator.rb +147 -3
- data/lib/carray/lazy.rb +88 -73
- data/lib/carray/mask_gap_fill.rb +3 -1
- data/lib/carray/meld_reduce.rb +2 -2
- data/lib/carray/methods/align_addr.rb +1 -1
- data/lib/carray/methods/composition.rb +1 -1
- data/lib/carray/methods/discovery_along.rb +74 -0
- data/lib/carray/methods/factorize.rb +50 -0
- data/lib/carray/methods/is_in.rb +25 -14
- data/lib/carray/methods/locate_addr.rb +81 -3
- data/lib/carray/methods/mask_duplicates.rb +36 -2
- data/lib/carray/methods/meshgrid.rb +4 -5
- data/lib/carray/methods/mode.rb +2 -2
- data/lib/carray/methods/nunique.rb +23 -2
- data/lib/carray/methods/repeat.rb +110 -0
- data/lib/carray/methods/snap.rb +7 -2
- data/lib/carray/methods/unique.rb +44 -4
- data/lib/carray/methods/value_counts.rb +2 -2
- data/lib/carray/rng.rb +86 -0
- data/lib/carray/runtime.rb +0 -19
- data/lib/carray/slab_iterator.rb +77 -19
- data/lib/carray/string_operation_extension.rb +10 -6
- data/lib/carray/time.rb +906 -491
- data/lib/carray/window_iterator.rb +421 -27
- data/lib/carray.rb +7 -5
- data/yard-stubs/ca_obj_array.rb +385 -0
- data/yard-stubs/ca_obj_bitarray.rb +38 -0
- data/yard-stubs/ca_obj_bitfield.rb +43 -0
- data/yard-stubs/ca_obj_block.rb +68 -0
- data/yard-stubs/ca_obj_byte_swap.rb +56 -0
- data/yard-stubs/ca_obj_fake.rb +31 -0
- data/yard-stubs/ca_obj_farray.rb +32 -0
- data/yard-stubs/ca_obj_field.rb +45 -0
- data/yard-stubs/ca_obj_grid.rb +35 -0
- data/yard-stubs/ca_obj_refer.rb +72 -0
- data/yard-stubs/ca_obj_roll.rb +45 -0
- data/yard-stubs/ca_obj_shift.rb +43 -0
- data/yard-stubs/ca_obj_stride.rb +181 -0
- data/yard-stubs/ca_obj_tile.rb +29 -0
- data/yard-stubs/ca_obj_transpose.rb +40 -0
- data/yard-stubs/ca_obj_window.rb +57 -0
- data/yard-stubs/carray_access.rb +131 -0
- data/yard-stubs/carray_attribute.rb +246 -0
- data/yard-stubs/carray_broadcast.rb +37 -0
- data/yard-stubs/carray_cast.rb +489 -0
- data/yard-stubs/carray_class.rb +65 -0
- data/yard-stubs/carray_conversion.rb +76 -0
- data/yard-stubs/carray_copy.rb +79 -0
- data/yard-stubs/carray_core.rb +34 -0
- data/yard-stubs/carray_count.rb +84 -0
- data/yard-stubs/carray_element.rb +108 -0
- data/yard-stubs/carray_generate.rb +66 -0
- data/yard-stubs/carray_lazy.rb +228 -0
- data/yard-stubs/carray_loop.rb +140 -0
- data/yard-stubs/carray_mask.rb +259 -0
- data/yard-stubs/carray_math.rb +1615 -0
- data/yard-stubs/carray_mathfunc.rb +45 -0
- data/yard-stubs/carray_median_percentile.rb +103 -0
- data/yard-stubs/carray_memory_view.rb +163 -0
- data/yard-stubs/carray_order.rb +252 -0
- data/yard-stubs/carray_random.rb +89 -0
- data/yard-stubs/carray_scatter.rb +106 -0
- data/yard-stubs/carray_slab.rb +59 -0
- data/yard-stubs/carray_sort.rb +165 -0
- data/yard-stubs/carray_test.rb +85 -0
- data/yard-stubs/carray_undef.rb +64 -0
- data/yard-stubs/carray_utils.rb +97 -0
- data/yard-stubs/ruby_carray.rb +193 -0
- metadata +67 -10
- data/ext/ca_obj_unbound_repeat.c +0 -496
data/ext/mk_call_cfunc.rb
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
# --- spec -------------------------------------------------------------------
|
|
33
33
|
|
|
34
34
|
RAW_ARITIES = (1..7).to_a # ca_call_cfunc_1 .. _7
|
|
35
|
+
SLAB_ARITIES = (1..7).to_a # ca_call_cslab_1 .. _7
|
|
35
36
|
TYPED_PAIRS = [ # ca_call_cfunc_<m>_<n> (m outputs, n inputs)
|
|
36
37
|
[1, 1], [1, 2], [1, 3], [1, 4], [1, 5], [1, 6],
|
|
37
38
|
[2, 1], [2, 2], [2, 3], [2, 4],
|
|
@@ -97,6 +98,28 @@ def sig_typed_r(m, n)
|
|
|
97
98
|
"VALUE\nca_call_cfunc_#{m}_#{n}_r (#{typed_params(m, n, r: true)})"
|
|
98
99
|
end
|
|
99
100
|
|
|
101
|
+
# The typed slab family declares its callback as ca_cslab_t rather than an
|
|
102
|
+
# arity-shaped function pointer: a slab callback takes its operands through
|
|
103
|
+
# `base`, so its signature does not grow with M + N.
|
|
104
|
+
def typed_slab_params(m, n, r: false)
|
|
105
|
+
dty = ->(k) { m == 1 ? "dty" : "dty#{k + 1}" }
|
|
106
|
+
out_data_type_params = (0...m).map { |k| "int8_t #{dty.(k)}" }
|
|
107
|
+
in_data_type_params = (0...n).map { |k| "int8_t dtx#{k + 1}" }
|
|
108
|
+
in_value_params = (0...n).map { |k| "volatile VALUE rx#{k + 1}" }
|
|
109
|
+
func_sig = r ? "ca_cslab_r_t slabfunc" : "ca_cslab_t slabfunc"
|
|
110
|
+
params = out_data_type_params + in_data_type_params + [func_sig] + in_value_params
|
|
111
|
+
params << "void *userdata" if r
|
|
112
|
+
params.join(", ")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def sig_typed_slab(m, n)
|
|
116
|
+
"VALUE\nca_call_cslab_#{m}_#{n} (#{typed_slab_params(m, n)})"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def sig_typed_slab_r(m, n)
|
|
120
|
+
"VALUE\nca_call_cslab_#{m}_#{n}_r (#{typed_slab_params(m, n, r: true)})"
|
|
121
|
+
end
|
|
122
|
+
|
|
100
123
|
# Re-indent each non-blank line of `s` with `prefix`. `<<~` strips the
|
|
101
124
|
# common leading whitespace from a heredoc, which would leave our body
|
|
102
125
|
# at column 0; we want a 2-space body indent inside the function brace.
|
|
@@ -105,83 +128,61 @@ def indent(s, prefix = " ")
|
|
|
105
128
|
end
|
|
106
129
|
|
|
107
130
|
# --- raw ca_call_cfunc_N ----------------------------------------------------
|
|
131
|
+
#
|
|
132
|
+
# The per-cell loop runs in a walker function of its own, handed to
|
|
133
|
+
# ca_sweep_run: a callback that raises part way through gives back the
|
|
134
|
+
# operands and scratch the engine holds, rather than leaving them behind.
|
|
135
|
+
#
|
|
136
|
+
# The `_r` variant (reentrant: + void *userdata at signature tail) passes a
|
|
137
|
+
# trailing `void *userdata` through to every per-cell `func(...)`
|
|
138
|
+
# invocation as its last argument. Naming follows POSIX convention
|
|
139
|
+
# (qsort_r / bsearch_r / strtok_r), where `_r` marks a reentrant form that
|
|
140
|
+
# takes a thunk so the callback no longer depends on file-static / global
|
|
141
|
+
# state. Use when the callback needs to share state with the caller (e.g.
|
|
142
|
+
# accumulators, configuration flags, library plan handles) without
|
|
143
|
+
# resorting to file-static plumbing.
|
|
144
|
+
def emit_raw_common(n, r:)
|
|
145
|
+
name = r ? "ca_call_cfunc_#{n}_r" : "ca_call_cfunc_#{n}"
|
|
146
|
+
fsig = r ? void_p_list_r(n) : void_p_list(n)
|
|
147
|
+
call = "c->func(#{args_p(n)}#{r ? ", c->userdata" : ""})"
|
|
108
148
|
|
|
109
|
-
def emit_raw(n)
|
|
110
|
-
$src.puts sig_raw(n)
|
|
111
|
-
$src.puts "{"
|
|
112
|
-
$src.puts indent(<<~END_C)
|
|
113
|
-
CArray *cx[#{n}];
|
|
114
|
-
char *base[#{n}];
|
|
115
|
-
ca_size_t stride[#{n}];
|
|
116
|
-
char *owned_buf[#{n}];
|
|
117
|
-
int attached[#{n}];
|
|
118
|
-
ca_sweep_state_t state;
|
|
119
|
-
int k_op;
|
|
120
|
-
|
|
121
|
-
END_C
|
|
122
|
-
# extract CArray* from VALUE
|
|
123
|
-
(0...n).each do |k|
|
|
124
|
-
$src.puts " TypedData_Get_Struct(rcx#{k}, CArray, &carray_data_type, cx[#{k}]);"
|
|
125
|
-
end
|
|
126
|
-
$src.puts ""
|
|
127
149
|
$src.puts <<~END_C
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
char *p[#{n}];
|
|
146
|
-
ca_size_t k;
|
|
147
|
-
if ( state.m0 ) {
|
|
148
|
-
for ( k = 0; k < state.n_kernel; k++ ) {
|
|
149
|
-
if ( ! state.m0[k] ) {
|
|
150
|
-
for ( k_op = 0; k_op < #{n}; k_op++ ) {
|
|
151
|
-
p[k_op] = base[k_op] + k * stride[k_op];
|
|
152
|
-
}
|
|
153
|
-
func(#{args_p(n)});
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
} else {
|
|
157
|
-
for ( k = 0; k < state.n_kernel; k++ ) {
|
|
150
|
+
typedef struct {
|
|
151
|
+
ca_sweep_state_t *st;
|
|
152
|
+
void (*func)(#{fsig});
|
|
153
|
+
void *userdata;
|
|
154
|
+
} #{name}_ctx_t;
|
|
155
|
+
|
|
156
|
+
static VALUE
|
|
157
|
+
#{name}_walk (VALUE arg)
|
|
158
|
+
{
|
|
159
|
+
#{name}_ctx_t *c = (#{name}_ctx_t *) arg;
|
|
160
|
+
ca_sweep_state_t *st = c->st;
|
|
161
|
+
char *p[#{n}];
|
|
162
|
+
ca_size_t k;
|
|
163
|
+
int k_op;
|
|
164
|
+
if ( st->m0 ) {
|
|
165
|
+
for ( k = 0; k < st->n_kernel; k++ ) {
|
|
166
|
+
if ( ! st->m0[k] ) {
|
|
158
167
|
for ( k_op = 0; k_op < #{n}; k_op++ ) {
|
|
159
|
-
p[k_op] = base[k_op] + k * stride[k_op];
|
|
168
|
+
p[k_op] = st->base[k_op] + k * st->stride[k_op];
|
|
160
169
|
}
|
|
161
|
-
|
|
170
|
+
#{call};
|
|
162
171
|
}
|
|
163
172
|
}
|
|
173
|
+
} else {
|
|
174
|
+
for ( k = 0; k < st->n_kernel; k++ ) {
|
|
175
|
+
for ( k_op = 0; k_op < #{n}; k_op++ ) {
|
|
176
|
+
p[k_op] = st->base[k_op] + k * st->stride[k_op];
|
|
177
|
+
}
|
|
178
|
+
#{call};
|
|
179
|
+
}
|
|
164
180
|
}
|
|
165
|
-
|
|
166
|
-
ca_sweep_release(&state);
|
|
167
|
-
|
|
168
|
-
return rcx0;
|
|
181
|
+
return Qnil;
|
|
169
182
|
}
|
|
170
183
|
|
|
171
184
|
END_C
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
# --- raw ca_call_cfunc_N_r (reentrant: + void *userdata at signature tail) --
|
|
175
|
-
#
|
|
176
|
-
# Variant of emit_raw with a trailing `void *userdata` parameter passed
|
|
177
|
-
# through to every per-cell `func(...)` invocation as its last argument.
|
|
178
|
-
# Naming follows POSIX convention (qsort_r / bsearch_r / strtok_r), where
|
|
179
|
-
# `_r` marks a reentrant form that takes a thunk so the callback no longer
|
|
180
|
-
# depends on file-static / global state. Use when the callback needs to
|
|
181
|
-
# share state with the caller (e.g. accumulators, configuration flags,
|
|
182
|
-
# library plan handles) without resorting to file-static plumbing.
|
|
183
|
-
def emit_raw_r(n)
|
|
184
|
-
$src.puts sig_raw_r(n)
|
|
185
|
+
$src.puts(r ? sig_raw_r(n) : sig_raw(n))
|
|
185
186
|
$src.puts "{"
|
|
186
187
|
$src.puts indent(<<~END_C)
|
|
187
188
|
CArray *cx[#{n}];
|
|
@@ -190,7 +191,7 @@ def emit_raw_r(n)
|
|
|
190
191
|
char *owned_buf[#{n}];
|
|
191
192
|
int attached[#{n}];
|
|
192
193
|
ca_sweep_state_t state;
|
|
193
|
-
|
|
194
|
+
#{name}_ctx_t ctx;
|
|
194
195
|
|
|
195
196
|
END_C
|
|
196
197
|
(0...n).each do |k|
|
|
@@ -198,8 +199,9 @@ def emit_raw_r(n)
|
|
|
198
199
|
end
|
|
199
200
|
$src.puts ""
|
|
200
201
|
$src.puts <<~END_C
|
|
201
|
-
/* sweep engine:
|
|
202
|
-
|
|
202
|
+
/* sweep engine: per-operand acquire (alias / xmalloc + ca_xfer_all),
|
|
203
|
+
operand pairing, mask OR across INPUTs, mask propagate to OUTPUTs.
|
|
204
|
+
Lifecycle template lives in ext/ca_sweep_engine.{c,h}. */
|
|
203
205
|
state.n_ops = #{n};
|
|
204
206
|
state.fsync = fsync;
|
|
205
207
|
state.cx = cx;
|
|
@@ -208,33 +210,14 @@ def emit_raw_r(n)
|
|
|
208
210
|
state.owned_buf = owned_buf;
|
|
209
211
|
state.attached = attached;
|
|
210
212
|
state.no_mask = 0;
|
|
211
|
-
state.src_label = "
|
|
213
|
+
state.src_label = "#{name}";
|
|
212
214
|
|
|
213
215
|
ca_sweep_acquire(&state);
|
|
214
216
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
for ( k = 0; k < state.n_kernel; k++ ) {
|
|
220
|
-
if ( ! state.m0[k] ) {
|
|
221
|
-
for ( k_op = 0; k_op < #{n}; k_op++ ) {
|
|
222
|
-
p[k_op] = base[k_op] + k * stride[k_op];
|
|
223
|
-
}
|
|
224
|
-
func(#{args_p_r(n)});
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
} else {
|
|
228
|
-
for ( k = 0; k < state.n_kernel; k++ ) {
|
|
229
|
-
for ( k_op = 0; k_op < #{n}; k_op++ ) {
|
|
230
|
-
p[k_op] = base[k_op] + k * stride[k_op];
|
|
231
|
-
}
|
|
232
|
-
func(#{args_p_r(n)});
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
ca_sweep_release(&state);
|
|
217
|
+
ctx.st = &state;
|
|
218
|
+
ctx.func = func;
|
|
219
|
+
ctx.userdata = #{r ? "userdata" : "NULL"};
|
|
220
|
+
ca_sweep_run(&state, #{name}_walk, (VALUE) &ctx);
|
|
238
221
|
|
|
239
222
|
return rcx0;
|
|
240
223
|
}
|
|
@@ -242,21 +225,25 @@ def emit_raw_r(n)
|
|
|
242
225
|
END_C
|
|
243
226
|
end
|
|
244
227
|
|
|
245
|
-
|
|
228
|
+
def emit_raw(n) ; emit_raw_common(n, r: false); end
|
|
229
|
+
def emit_raw_r(n) ; emit_raw_common(n, r: true); end
|
|
246
230
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
231
|
+
# --- typed dispatchers: M outputs, N inputs ---------------------------------
|
|
232
|
+
#
|
|
233
|
+
# The four typed emitters (cfunc / cfunc_r / cslab / cslab_r) differ in one
|
|
234
|
+
# line -- which lower-level family they delegate to -- and are identical in
|
|
235
|
+
# the wrapping, template allocation and return handling around it. That
|
|
236
|
+
# shared part lives in emit_typed_body so a change to the coercion rules is
|
|
237
|
+
# made once rather than four times, which is the same reason this file is a
|
|
238
|
+
# generator at all.
|
|
239
|
+
#
|
|
240
|
+
# Naming convention (matches legacy ext/carray_call_cfunc.c byte-for-byte):
|
|
241
|
+
# M==1 : output data_type = `dty`, output var = `ry`
|
|
242
|
+
# M >1 : output data_types = `dty1..dtyM`, output vars = `ry1..ryM`
|
|
243
|
+
def emit_typed_body(m, n)
|
|
252
244
|
dty = ->(k) { m == 1 ? "dty" : "dty#{k + 1}" }
|
|
253
245
|
ry = ->(k) { m == 1 ? "ry" : "ry#{k + 1}" }
|
|
254
246
|
|
|
255
|
-
fsync_str = "1" * m + "0" * n
|
|
256
|
-
|
|
257
|
-
$src.puts sig_typed(m, n)
|
|
258
|
-
$src.puts "{"
|
|
259
|
-
|
|
260
247
|
# output VALUE declarations
|
|
261
248
|
out_decl = (0...m).map { |k| "#{ry.(k)} = Qnil" }.join(", ")
|
|
262
249
|
$src.puts " volatile VALUE #{out_decl};"
|
|
@@ -284,9 +271,9 @@ def emit_typed(m, n)
|
|
|
284
271
|
end
|
|
285
272
|
$src.puts ""
|
|
286
273
|
|
|
287
|
-
# delegate to lower-level
|
|
274
|
+
# delegate to the lower-level family
|
|
288
275
|
args = (0...m).map { |k| ry.(k) } + (0...n).map { |k| "rx#{k + 1}" }
|
|
289
|
-
|
|
276
|
+
yield args.join(", ")
|
|
290
277
|
$src.puts ""
|
|
291
278
|
|
|
292
279
|
# return: scalar-fetch each output if rank-0, then assemble
|
|
@@ -305,62 +292,156 @@ def emit_typed(m, n)
|
|
|
305
292
|
$src.puts ""
|
|
306
293
|
end
|
|
307
294
|
|
|
295
|
+
def fsync_literal(m, n)
|
|
296
|
+
"1" * m + "0" * n
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def emit_typed(m, n)
|
|
300
|
+
$src.puts sig_typed(m, n)
|
|
301
|
+
$src.puts "{"
|
|
302
|
+
emit_typed_body(m, n) do |args|
|
|
303
|
+
$src.puts " ca_call_cfunc_#{m + n}(mathfunc, \"#{fsync_literal(m, n)}\", #{args});"
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
308
307
|
# --- typed ca_call_cfunc_M_N_r (reentrant variant) -------------------------
|
|
309
308
|
#
|
|
310
309
|
# Same as emit_typed but the callback signature and outer function gain a
|
|
311
310
|
# trailing `void *userdata`; the inner delegate calls ca_call_cfunc_(M+N)_r
|
|
312
311
|
# and forwards `userdata` through.
|
|
313
312
|
def emit_typed_r(m, n)
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
313
|
+
$src.puts sig_typed_r(m, n)
|
|
314
|
+
$src.puts "{"
|
|
315
|
+
emit_typed_body(m, n) do |args|
|
|
316
|
+
$src.puts " ca_call_cfunc_#{m + n}_r(mathfunc, \"#{fsync_literal(m, n)}\", #{args}, userdata);"
|
|
317
|
+
end
|
|
318
|
+
end
|
|
317
319
|
|
|
318
|
-
|
|
320
|
+
# --- typed ca_call_cslab_M_N (chunked variant) -----------------------------
|
|
321
|
+
#
|
|
322
|
+
# The same convenience over the slab family: the caller declares the data
|
|
323
|
+
# types its callback works in and gets back a freshly allocated output,
|
|
324
|
+
# rather than allocating one and matching dtypes by hand.
|
|
325
|
+
#
|
|
326
|
+
# This is where chunking pays most, and not by coincidence. Coercion is
|
|
327
|
+
# what this layer is for, and rb_ca_wrap_readonly implements it as a lazy
|
|
328
|
+
# readonly cast view -- which is never attach-alias, so it is exactly the
|
|
329
|
+
# operand kind the whole-buffer path materialises with
|
|
330
|
+
# xmalloc(elements * bytes). Declaring CA_DOUBLE over an int32 array
|
|
331
|
+
# therefore costs a full converted copy through ca_call_cfunc_M_N and one
|
|
332
|
+
# chunk of arena scratch through ca_call_cslab_M_N. The typical use of the
|
|
333
|
+
# typed layer is the case chunking was built for.
|
|
334
|
+
def emit_typed_slab(m, n)
|
|
335
|
+
$src.puts sig_typed_slab(m, n)
|
|
336
|
+
$src.puts "{"
|
|
337
|
+
emit_typed_body(m, n) do |args|
|
|
338
|
+
$src.puts " ca_call_cslab_#{m + n}(slabfunc, \"#{fsync_literal(m, n)}\", #{args});"
|
|
339
|
+
end
|
|
340
|
+
end
|
|
319
341
|
|
|
320
|
-
|
|
342
|
+
def emit_typed_slab_r(m, n)
|
|
343
|
+
$src.puts sig_typed_slab_r(m, n)
|
|
321
344
|
$src.puts "{"
|
|
345
|
+
emit_typed_body(m, n) do |args|
|
|
346
|
+
$src.puts " ca_call_cslab_#{m + n}_r(slabfunc, \"#{fsync_literal(m, n)}\", #{args}, userdata);"
|
|
347
|
+
end
|
|
348
|
+
end
|
|
322
349
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
350
|
+
# --- slab ca_call_cslab_N ---------------------------------------------------
|
|
351
|
+
#
|
|
352
|
+
# The chunked counterpart of ca_call_cfunc_N. Two things differ, and they
|
|
353
|
+
# are the same thing seen from two sides:
|
|
354
|
+
#
|
|
355
|
+
# memory -- ca_sweep_acquire_chunked holds one arena scratch of ~32KB per
|
|
356
|
+
# non-alias INPUT for the whole walk and re-gathers it per chunk, where
|
|
357
|
+
# ca_sweep_acquire materialises each such operand whole. Peak input
|
|
358
|
+
# memory stops scaling with the operand.
|
|
359
|
+
#
|
|
360
|
+
# speed -- the callback is handed a whole chunk rather than one cell, so
|
|
361
|
+
# the indirect call is paid once per few thousand cells instead of once
|
|
362
|
+
# per cell. Measured on `y = a + b*2.0` over 10M doubles: 1.48 ns per
|
|
363
|
+
# element through a per-cell callback against 0.34 ns for the same
|
|
364
|
+
# arithmetic in a loop the compiler can see. A slab callback keeps the
|
|
365
|
+
# loop, so it keeps the 0.34, and it is the shape a vectoriser can work
|
|
366
|
+
# with -- a per-cell callback defeats one by construction.
|
|
367
|
+
#
|
|
368
|
+
# The callback signature does not depend on the arity, because the operands
|
|
369
|
+
# reach it through `base` / `stride` rather than as separate parameters.
|
|
370
|
+
# One typedef therefore serves every ca_call_cslab_N.
|
|
371
|
+
#
|
|
372
|
+
# Masked cells are NOT skipped for the author, as the per-cell form skips
|
|
373
|
+
# them: a slab has no way to leave a hole. The chunk's slice of the mask
|
|
374
|
+
# arrives as `m0` instead -- one byte per cell, indexed 0..n-1 alongside the
|
|
375
|
+
# data, or NULL when no INPUT carried a mask.
|
|
376
|
+
def sig_slab(n)
|
|
377
|
+
"VALUE\nca_call_cslab_#{n} (ca_cslab_t func, const char *fsync,\n #{value_param_list(n)})"
|
|
378
|
+
end
|
|
326
379
|
|
|
380
|
+
def sig_slab_r(n)
|
|
381
|
+
"VALUE\nca_call_cslab_#{n}_r (ca_cslab_r_t func, const char *fsync,\n #{value_param_list(n)},\n void *userdata)"
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def emit_slab_body(n, name, walker, userdata)
|
|
385
|
+
$src.puts indent(<<~END_C)
|
|
386
|
+
CArray *cx[#{n}];
|
|
387
|
+
char *base[#{n}];
|
|
388
|
+
char *base_orig[#{n}];
|
|
389
|
+
ca_size_t stride[#{n}];
|
|
390
|
+
char *owned_buf[#{n}];
|
|
391
|
+
int attached[#{n}];
|
|
392
|
+
ca_sweep_state_t state;
|
|
393
|
+
ca_cslab_ctx_t ctx;
|
|
394
|
+
|
|
395
|
+
END_C
|
|
327
396
|
(0...n).each do |k|
|
|
328
|
-
$src.puts "
|
|
397
|
+
$src.puts " TypedData_Get_Struct(rcx#{k}, CArray, &carray_data_type, cx[#{k}]);"
|
|
329
398
|
end
|
|
330
399
|
$src.puts ""
|
|
400
|
+
$src.puts <<~END_C
|
|
401
|
+
/* sweep engine, chunked path: per-operand acquire (attach for OUTPUT
|
|
402
|
+
and alias INPUT, arena chunk scratch for non-alias INPUT), broadcast
|
|
403
|
+
shape check, mask OR across INPUTs, mask propagate to OUTPUTs at
|
|
404
|
+
release. Lifecycle template lives in ext/ca_sweep_engine.{c,h}.
|
|
405
|
+
base_orig is the field the whole-buffer path leaves zero; the
|
|
406
|
+
chunked path walks it, so it has to be given here. */
|
|
407
|
+
state.n_ops = #{n};
|
|
408
|
+
state.fsync = fsync;
|
|
409
|
+
state.cx = cx;
|
|
410
|
+
state.base = base;
|
|
411
|
+
state.base_orig = base_orig;
|
|
412
|
+
state.stride = stride;
|
|
413
|
+
state.owned_buf = owned_buf;
|
|
414
|
+
state.attached = attached;
|
|
415
|
+
state.no_mask = 0;
|
|
416
|
+
state.src_label = "#{name}";
|
|
331
417
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
418
|
+
ca_sweep_acquire_chunked(&state);
|
|
419
|
+
|
|
420
|
+
ctx.st = &state;
|
|
421
|
+
ctx.func = #{userdata == "NULL" ? "func" : "NULL"};
|
|
422
|
+
ctx.func_r = #{userdata == "NULL" ? "NULL" : "func"};
|
|
423
|
+
ctx.userdata = #{userdata};
|
|
424
|
+
ca_sweep_run_chunked(&state, #{walker}, (VALUE) &ctx);
|
|
425
|
+
|
|
426
|
+
return rcx0;
|
|
337
427
|
}
|
|
338
|
-
plain = (0...n).map { |in_k| "rx#{in_k + 1}" }
|
|
339
|
-
$src.puts " if ( #{conds.join(" || ")} ) {"
|
|
340
|
-
$src.puts " #{ry.(out_k)} = rb_ca_template_n(#{n}, #{wrapped.join(", ")});"
|
|
341
|
-
$src.puts " } else {"
|
|
342
|
-
$src.puts " #{ry.(out_k)} = rb_ca_template_n(#{n}, #{plain.join(", ")});"
|
|
343
|
-
$src.puts " }"
|
|
344
|
-
end
|
|
345
|
-
$src.puts ""
|
|
346
428
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
$src.puts ""
|
|
429
|
+
END_C
|
|
430
|
+
end
|
|
350
431
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
$src.puts "
|
|
363
|
-
|
|
432
|
+
def emit_slab(n)
|
|
433
|
+
$src.puts sig_slab(n)
|
|
434
|
+
$src.puts "{"
|
|
435
|
+
emit_slab_body(n, "ca_call_cslab_#{n}", "ca_cslab_walk", "NULL")
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
# Variant of emit_slab with a trailing `void *userdata` parameter passed
|
|
439
|
+
# through to every chunk invocation as its last argument, following the same
|
|
440
|
+
# POSIX `_r` convention as ca_call_cfunc_N_r.
|
|
441
|
+
def emit_slab_r(n)
|
|
442
|
+
$src.puts sig_slab_r(n)
|
|
443
|
+
$src.puts "{"
|
|
444
|
+
emit_slab_body(n, "ca_call_cslab_#{n}_r", "ca_cslab_r_walk", "userdata")
|
|
364
445
|
end
|
|
365
446
|
|
|
366
447
|
# --- header declaration emitters --------------------------------------------
|
|
@@ -369,6 +450,10 @@ def decl_raw(n) ; $hdr.puts sig_raw(n) + ";"; $hdr.puts ""; end
|
|
|
369
450
|
def decl_raw_r(n) ; $hdr.puts sig_raw_r(n) + ";"; $hdr.puts ""; end
|
|
370
451
|
def decl_typed(m, n) ; $hdr.puts sig_typed(m, n) + ";"; $hdr.puts ""; end
|
|
371
452
|
def decl_typed_r(m, n) ; $hdr.puts sig_typed_r(m, n) + ";"; $hdr.puts ""; end
|
|
453
|
+
def decl_typed_slab(m, n) ; $hdr.puts sig_typed_slab(m, n) + ";"; $hdr.puts ""; end
|
|
454
|
+
def decl_typed_slab_r(m, n) ; $hdr.puts sig_typed_slab_r(m, n) + ";"; $hdr.puts ""; end
|
|
455
|
+
def decl_slab(n) ; $hdr.puts sig_slab(n) + ";"; $hdr.puts ""; end
|
|
456
|
+
def decl_slab_r(n) ; $hdr.puts sig_slab_r(n) + ";"; $hdr.puts ""; end
|
|
372
457
|
|
|
373
458
|
# --- main -------------------------------------------------------------------
|
|
374
459
|
|
|
@@ -406,6 +491,18 @@ $src.puts <<~END_C
|
|
|
406
491
|
* operand invariant established in PROPOSAL_EAGER_ELEMENTWISE_NO_ATTACH
|
|
407
492
|
* and extended in PROPOSAL_EAGER_SLOWPATH_CHUNKING_ARENA).
|
|
408
493
|
*
|
|
494
|
+
* Slab variants `ca_call_cslab_N` / `ca_call_cslab_N_r` take the chunked
|
|
495
|
+
* path instead (ca_sweep_acquire_chunked / ca_sweep_next_chunk /
|
|
496
|
+
* ca_sweep_release_chunked) and hand the callback a whole chunk -- base /
|
|
497
|
+
* stride per operand, a cell count, and the chunk's slice of the mask --
|
|
498
|
+
* rather than one cell. A non-alias INPUT is then re-gathered into a
|
|
499
|
+
* ~32KB arena scratch per chunk rather than materialised whole, so input
|
|
500
|
+
* memory peak stops scaling with the operand; and the indirect call is
|
|
501
|
+
* paid once per chunk, so the author's inner loop is one the compiler can
|
|
502
|
+
* vectorise. Use cfunc when the per-cell body is what you have (a math
|
|
503
|
+
* function to wrap); use cslab when the operand is large, virtual, or the
|
|
504
|
+
* body is worth vectorising.
|
|
505
|
+
*
|
|
409
506
|
* L0.1 (PROPOSAL_L0_AUTHOR_SURFACE, 2026-06-11): the per-operand acquire
|
|
410
507
|
* + broadcast check + mask OR + release lifecycle is now factored out
|
|
411
508
|
* into ext/ca_sweep_engine.{c,h} (ca_sweep_acquire / ca_sweep_release). This
|
|
@@ -417,6 +514,46 @@ $src.puts <<~END_C
|
|
|
417
514
|
#include "ca_sweep_engine.h"
|
|
418
515
|
#include <string.h>
|
|
419
516
|
|
|
517
|
+
/* Chunk walk for the ca_call_cslab_N family, run by ca_sweep_run_chunked
|
|
518
|
+
so that a callback raising part way through gives back what the engine
|
|
519
|
+
holds. Hands the author one chunk at a time. base[] is rewritten per
|
|
520
|
+
chunk by ca_sweep_next_chunk -- for a non-alias INPUT it points at the
|
|
521
|
+
arena scratch the chunk was just gathered into, which is packed, so
|
|
522
|
+
stride[] is the element size and the author's inner loop sees
|
|
523
|
+
contiguous data. m0 is the chunk's iteration mask, or NULL when no
|
|
524
|
+
INPUT operand carried one: chunk-sized and re-gathered per chunk, so it
|
|
525
|
+
is already the slice -- one byte per cell, indexed 0..chunk_n-1
|
|
526
|
+
alongside base[] and stride[]. The arity does not appear here: the
|
|
527
|
+
operands reach the callback through base[] / stride[]. */
|
|
528
|
+
typedef struct {
|
|
529
|
+
ca_sweep_state_t *st;
|
|
530
|
+
ca_cslab_t func; /* ca_call_cslab_N */
|
|
531
|
+
ca_cslab_r_t func_r; /* ca_call_cslab_N_r */
|
|
532
|
+
void *userdata;
|
|
533
|
+
} ca_cslab_ctx_t;
|
|
534
|
+
|
|
535
|
+
static VALUE
|
|
536
|
+
ca_cslab_walk (VALUE arg)
|
|
537
|
+
{
|
|
538
|
+
ca_cslab_ctx_t *c = (ca_cslab_ctx_t *) arg;
|
|
539
|
+
ca_sweep_state_t *st = c->st;
|
|
540
|
+
while ( ca_sweep_next_chunk(st) ) {
|
|
541
|
+
c->func(st->base, st->stride, st->chunk_n, st->m0);
|
|
542
|
+
}
|
|
543
|
+
return Qnil;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
static VALUE
|
|
547
|
+
ca_cslab_r_walk (VALUE arg)
|
|
548
|
+
{
|
|
549
|
+
ca_cslab_ctx_t *c = (ca_cslab_ctx_t *) arg;
|
|
550
|
+
ca_sweep_state_t *st = c->st;
|
|
551
|
+
while ( ca_sweep_next_chunk(st) ) {
|
|
552
|
+
c->func_r(st->base, st->stride, st->chunk_n, st->m0, c->userdata);
|
|
553
|
+
}
|
|
554
|
+
return Qnil;
|
|
555
|
+
}
|
|
556
|
+
|
|
420
557
|
END_C
|
|
421
558
|
|
|
422
559
|
$hdr.puts <<~END_H
|
|
@@ -438,6 +575,18 @@ $hdr.puts <<~END_H
|
|
|
438
575
|
#ifndef CARRAY_CALL_CFUNC_H
|
|
439
576
|
#define CARRAY_CALL_CFUNC_H
|
|
440
577
|
|
|
578
|
+
/* Chunk callback for the ca_call_cslab_N family. `base` and `stride` are
|
|
579
|
+
one entry per operand, in the order the operands were passed; `n` is the
|
|
580
|
+
number of cells in this chunk; `m0` is the chunk's slice of the mask,
|
|
581
|
+
one byte per cell, or NULL when no INPUT carried a mask. The arity does
|
|
582
|
+
not appear in the signature -- the operands arrive through `base` -- so
|
|
583
|
+
one typedef serves every arity. */
|
|
584
|
+
typedef void (*ca_cslab_t) (char **base, ca_size_t *stride, ca_size_t n,
|
|
585
|
+
const boolean8_t *m0);
|
|
586
|
+
|
|
587
|
+
typedef void (*ca_cslab_r_t) (char **base, ca_size_t *stride, ca_size_t n,
|
|
588
|
+
const boolean8_t *m0, void *userdata);
|
|
589
|
+
|
|
441
590
|
END_H
|
|
442
591
|
|
|
443
592
|
RAW_ARITIES.each do |n|
|
|
@@ -474,6 +623,35 @@ TYPED_PAIRS.each do |m, n|
|
|
|
474
623
|
decl_typed_r(m, n)
|
|
475
624
|
end
|
|
476
625
|
|
|
626
|
+
$src.puts "/* -------------------------------------------------------------------- */"
|
|
627
|
+
$src.puts "/* Slab variants: the chunked counterpart of ca_call_cfunc_N. The */"
|
|
628
|
+
$src.puts "/* callback is handed a whole chunk (base / stride / count / mask */"
|
|
629
|
+
$src.puts "/* slice) rather than one cell, and non-alias INPUTs are gathered */"
|
|
630
|
+
$src.puts "/* into a ~32KB arena scratch per chunk instead of materialised */"
|
|
631
|
+
$src.puts "/* whole, so input memory peak stops scaling with the operand. */"
|
|
632
|
+
$src.puts "/* -------------------------------------------------------------------- */"
|
|
633
|
+
$src.puts ""
|
|
634
|
+
|
|
635
|
+
SLAB_ARITIES.each do |n|
|
|
636
|
+
emit_slab(n)
|
|
637
|
+
decl_slab(n)
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
SLAB_ARITIES.each do |n|
|
|
641
|
+
emit_slab_r(n)
|
|
642
|
+
decl_slab_r(n)
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
TYPED_PAIRS.each do |m, n|
|
|
646
|
+
emit_typed_slab(m, n)
|
|
647
|
+
decl_typed_slab(m, n)
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
TYPED_PAIRS.each do |m, n|
|
|
651
|
+
emit_typed_slab_r(m, n)
|
|
652
|
+
decl_typed_slab_r(m, n)
|
|
653
|
+
end
|
|
654
|
+
|
|
477
655
|
$hdr.puts "#endif /* CARRAY_CALL_CFUNC_H */"
|
|
478
656
|
|
|
479
657
|
$src.close
|