carray 3.0.0 → 3.0.1
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 +325 -3
- data/{NEWS.md → CHANGELOG.v1.md} +1 -1
- data/README.md +32 -32
- data/carray.gemspec +12 -8
- data/ext/ca_axis_group.c +55 -19
- data/ext/ca_binop_dispatch.c +5 -6
- data/ext/ca_binop_dispatch.h +0 -7
- data/ext/ca_categorical_iterator.c +13 -13
- data/ext/ca_for_each_element.h +6 -8
- data/ext/ca_group_iter.c +1 -1
- data/ext/ca_kernel_iterator.c +69 -23
- data/ext/ca_kernel_iterator.h +3 -10
- data/ext/ca_obj_bincmp.c +57 -27
- data/ext/ca_obj_binop.c +86 -30
- data/ext/ca_obj_const_string.c +3 -1
- data/ext/ca_obj_face.c +80 -0
- data/ext/ca_obj_face.h +33 -1
- data/ext/ca_obj_fake.c +11 -0
- data/ext/ca_obj_fixlen_string.c +3 -1
- data/ext/ca_obj_grid.c +11 -3
- data/ext/ca_obj_meld.c +13 -8
- data/ext/ca_obj_moncmp.c +33 -14
- data/ext/ca_obj_monop.c +19 -7
- data/ext/ca_obj_object.c +99 -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_axis.c +16 -6
- data/ext/ca_obj_shift.c +3 -3
- data/ext/ca_obj_stride.c +106 -23
- data/ext/ca_obj_string.c +3 -1
- 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 +86 -17
- data/ext/ca_op_cmplx64.h +123 -0
- data/ext/ca_op_ipower.c +0 -3
- data/ext/ca_sort_kernels.h +5 -5
- data/ext/ca_sweep_engine.c +78 -35
- data/ext/ca_sweep_engine.h +13 -4
- data/ext/ca_transform_common.c +7 -0
- data/ext/carray.h +127 -130
- data/ext/carray_access.c +56 -34
- data/ext/carray_bincount.c +8 -8
- data/ext/carray_broadcast.c +99 -6
- data/ext/carray_build_flags.h +3 -0
- data/ext/carray_call_cfunc.c +1491 -0
- data/ext/carray_call_cfunc.h +153 -0
- data/ext/carray_cast.c +69 -31
- data/ext/carray_conversion.c +22 -22
- data/ext/carray_copy.c +12 -2
- data/ext/carray_core.c +57 -10
- data/ext/carray_count.c +1 -1
- data/ext/carray_element.c +7 -7
- data/ext/carray_factorize.c +28 -28
- data/ext/carray_hold.c +1 -1
- data/ext/carray_index_classifier.c +7 -21
- data/ext/carray_internal.h +19 -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 +88 -87
- data/ext/carray_kernels_reduce_boolean.c +1 -0
- data/ext/carray_kernels_reduce_cumulative.c +54 -53
- data/ext/carray_kernels_reduce_extreme.c +51 -50
- data/ext/carray_kernels_reduce_variance.c +1 -0
- data/ext/carray_kernels_scan.c +1 -0
- data/ext/carray_kernels_search.c +93 -92
- 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_mask.c +6 -5
- data/ext/carray_median_percentile.c +1 -1
- data/ext/carray_memory_view.c +46 -21
- data/ext/carray_operator.c +46 -49
- data/ext/carray_partition.c +4 -4
- data/ext/carray_random.c +7 -7
- data/ext/carray_scatter.c +1 -1
- data/ext/carray_sort.c +3 -3
- 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 +243 -52
- data/ext/mkkernel.rb +354 -100
- data/ext/ruby_carray.c +18 -16
- data/ext/version.h +4 -4
- data/lib/carray/autoload_carray.rb +19 -0
- data/lib/carray/autoload_method_extension.rb +2 -1
- data/lib/carray/axis_group.rb +7 -7
- data/lib/carray/basics.rb +59 -59
- data/lib/carray/bincount_nd.rb +12 -12
- data/lib/carray/block_iterator.rb +24 -14
- data/lib/carray/categorical.rb +4 -4
- data/lib/carray/categorical_iterator.rb +98 -76
- data/lib/carray/conditional.rb +14 -14
- data/lib/carray/construct.rb +21 -0
- data/lib/carray/core_extensions.rb +62 -5
- data/lib/carray/data_type_extension.rb +18 -1
- data/lib/carray/frame/concat.rb +3 -3
- data/lib/carray/frame/convert.rb +1 -1
- data/lib/carray/frame/frame.rb +4 -11
- data/lib/carray/frame/io.rb +159 -2
- data/lib/carray/frame/sort.rb +1 -1
- data/lib/carray/frame/verbs.rb +18 -1
- 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 +1 -7
- data/lib/carray/iterator.rb +4 -3
- data/lib/carray/lazy.rb +125 -73
- 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/is_in.rb +12 -12
- data/lib/carray/methods/locate_addr.rb +6 -1
- data/lib/carray/methods/mask_duplicates.rb +1 -1
- data/lib/carray/methods/meshgrid.rb +4 -5
- data/lib/carray/methods/mode.rb +2 -2
- data/lib/carray/methods/nunique.rb +1 -1
- data/lib/carray/methods/snap.rb +7 -2
- data/lib/carray/methods/unique.rb +3 -3
- data/lib/carray/methods/value_counts.rb +2 -2
- data/lib/carray/runtime.rb +0 -19
- data/lib/carray/slab_iterator.rb +20 -7
- data/lib/carray/string_operation_extension.rb +5 -5
- data/lib/carray/time.rb +890 -491
- data/lib/carray/window_iterator.rb +285 -13
- data/lib/carray.rb +5 -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 +73 -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 +49 -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 +114 -0
- data/yard-stubs/carray_count.rb +79 -0
- data/yard-stubs/carray_element.rb +108 -0
- data/yard-stubs/carray_generate.rb +66 -0
- data/yard-stubs/carray_lazy.rb +23 -0
- data/yard-stubs/carray_loop.rb +140 -0
- data/yard-stubs/carray_mask.rb +259 -0
- data/yard-stubs/carray_math.rb +132 -0
- data/yard-stubs/carray_mathfunc.rb +45 -0
- data/yard-stubs/carray_median_percentile.rb +89 -0
- data/yard-stubs/carray_memory_view.rb +163 -0
- data/yard-stubs/carray_order.rb +312 -0
- data/yard-stubs/carray_random.rb +89 -0
- data/yard-stubs/carray_scatter.rb +106 -0
- data/yard-stubs/carray_slab.rb +57 -0
- data/yard-stubs/carray_sort.rb +163 -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 +59 -10
- data/ext/ca_obj_unbound_repeat.c +0 -496
data/ext/mkkernel.rb
CHANGED
|
@@ -144,6 +144,55 @@ module MkKernel
|
|
|
144
144
|
SNIPPET
|
|
145
145
|
end
|
|
146
146
|
|
|
147
|
+
# Complex libm comes in two widths: `csqrt` takes a `double _Complex`,
|
|
148
|
+
# `csqrtf` a `float _Complex`. A body written with only the double-
|
|
149
|
+
# taking name still compiles for a cmplx64 cell, because the cell
|
|
150
|
+
# widens on the way in and rounds on the way out -- so the kernel
|
|
151
|
+
# computes at cmplx128 whatever the array said it was, and the `+ - *`
|
|
152
|
+
# kernels beside it stay narrow. This builds the two expr entries that
|
|
153
|
+
# hold each complex data_type at its own width.
|
|
154
|
+
#
|
|
155
|
+
# In `body`, `<f>` marks each spot the `f` suffix belongs and `<t>` a
|
|
156
|
+
# real scalar of the matching width:
|
|
157
|
+
#
|
|
158
|
+
# cmplx_widths("(#2) = csqrt<f>(#1);")
|
|
159
|
+
# cmplx_widths("{ <t> _r = creal<f>(#1); ... }")
|
|
160
|
+
#
|
|
161
|
+
# Returns array-keyed entries, so merge it into an expr Hash that
|
|
162
|
+
# carries the other families:
|
|
163
|
+
#
|
|
164
|
+
# expr: { float: "...", object: "..." }.merge(cmplx_widths("..."))
|
|
165
|
+
def self.cmplx_widths(body)
|
|
166
|
+
{
|
|
167
|
+
[:cmplx64] => at_width(body, "f", "float"),
|
|
168
|
+
[:cmplx128] => at_width(body, "", "double"),
|
|
169
|
+
}
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# The same split for the real families, and for the same reason: `sin`
|
|
173
|
+
# takes a double, so an f32 cell widens on the way in and rounds on the
|
|
174
|
+
# way out. Use it in place of a `float:` entry whenever the body calls
|
|
175
|
+
# a libm function that has an `f` variant.
|
|
176
|
+
#
|
|
177
|
+
# expr: { object: "..." }.merge(float_widths("(#2) = sin<f>(#1);"))
|
|
178
|
+
#
|
|
179
|
+
# Not every double-taking call wants this. `fabs`, `fmin` / `fmax`,
|
|
180
|
+
# `ceil` / `floor` / `trunc` and the `isnan` family are exact on a
|
|
181
|
+
# float either way, and `round` (`floor(x + 0.5)`) is exact only in
|
|
182
|
+
# double -- narrowing it would round `x + 0.5f` first and step the
|
|
183
|
+
# answer at the boundary.
|
|
184
|
+
def self.float_widths(body)
|
|
185
|
+
{
|
|
186
|
+
[:f32] => at_width(body, "f", "float"),
|
|
187
|
+
[:f64] => at_width(body, "", "double"),
|
|
188
|
+
}
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Substitute the width markers in a kernel body.
|
|
192
|
+
def self.at_width(body, suffix, real_type)
|
|
193
|
+
body.gsub("<f>", suffix).gsub("<t>", real_type)
|
|
194
|
+
end
|
|
195
|
+
|
|
147
196
|
# CA_NTYPE order from ext/carray.h. Drives the per-data_type table layout
|
|
148
197
|
# for eager-style monop/binop tables `ca_<form>_<name>[CA_NTYPE]`.
|
|
149
198
|
# `:reserved` slots emit `ca_<form>_not_implement` (= retired holes
|
|
@@ -757,7 +806,7 @@ module MkKernel
|
|
|
757
806
|
# output cell is UNDEF).
|
|
758
807
|
# :sentinel -> masked cells are treated as an incomparable sentinel,
|
|
759
808
|
# the same role NaN plays for :end nan_policy but runtime-
|
|
760
|
-
# selectable and
|
|
809
|
+
# selectable and type-agnostic. Per fiber, unmasked
|
|
761
810
|
# cells are compacted into a contiguous sub-range and only
|
|
762
811
|
# that sub-range is sorted/quickselected; masked cells are
|
|
763
812
|
# compacted into the complementary sub-range at the head
|
|
@@ -1005,7 +1054,9 @@ module MkKernel
|
|
|
1005
1054
|
# demotes to real f64). Hash form requires the kernel author to
|
|
1006
1055
|
# write expr that produces output data_type values for each source family
|
|
1007
1056
|
# (e.g. cabs() for complex returning double).
|
|
1008
|
-
raise "#{name}: unknown output #{output}"
|
|
1057
|
+
raise "#{name}: unknown output #{output}" \
|
|
1058
|
+
unless output == :preserve || output == :real_of_source ||
|
|
1059
|
+
DTYPES.key?(output) || output.is_a?(Hash)
|
|
1009
1060
|
KERNELS << {
|
|
1010
1061
|
kind: :monop,
|
|
1011
1062
|
name: name,
|
|
@@ -1188,6 +1239,13 @@ module MkKernel
|
|
|
1188
1239
|
# fallback when no family matches. Uses the same family aliases
|
|
1189
1240
|
# as monop_expr_family_match? (:numeric / :int / :float / :complex
|
|
1190
1241
|
# / :bool / :object).
|
|
1242
|
+
#
|
|
1243
|
+
# A Hash value (or a bare output form) may also be :real_of_source,
|
|
1244
|
+
# meaning "the real component width of the source complex data_type"
|
|
1245
|
+
# (cmplx64 -> f32, cmplx128 -> f64). This is what an op like abs
|
|
1246
|
+
# wants for its complex family: the magnitude of a cmplx64 is a
|
|
1247
|
+
# float32, the same width `.real` and `.imag` already return. Naming
|
|
1248
|
+
# :f64 there would hand cmplx64 the real width of cmplx128.
|
|
1191
1249
|
# SL.1.1: Resolve the reduce macro suffix for a given kernel entry,
|
|
1192
1250
|
# driven by reduction_kind. Returns "" for :none (= legacy
|
|
1193
1251
|
# CA_SLAB_REDUCE_T_EX), or "_PLUS" / "_MIN" / "_MAX" / "_STAR" to
|
|
@@ -1208,7 +1266,7 @@ module MkKernel
|
|
|
1208
1266
|
#
|
|
1209
1267
|
# Ineligible:
|
|
1210
1268
|
# - suffix == "" (:object src / no_simd_src override — reduce body
|
|
1211
|
-
# is Ruby callback or
|
|
1269
|
+
# is Ruby callback or type-specific, single-accumulator required)
|
|
1212
1270
|
# - array_arg (weighted reductions use the ARRAY_T_EX macro family,
|
|
1213
1271
|
# which has its own emit path)
|
|
1214
1272
|
def self.reduce_8way_eligible?(k, src, suffix)
|
|
@@ -1325,6 +1383,24 @@ module MkKernel
|
|
|
1325
1383
|
end
|
|
1326
1384
|
end
|
|
1327
1385
|
|
|
1386
|
+
# Real component width of a complex data_type: cmplx64 -> f32,
|
|
1387
|
+
# cmplx128 -> f64. Raises for anything else, since :real_of_source
|
|
1388
|
+
# only has a meaning for a complex source.
|
|
1389
|
+
REAL_OF_CMPLX = { cmplx64: :f32, cmplx128: :f64 }.freeze
|
|
1390
|
+
|
|
1391
|
+
def self.resolve_output_dtype(kernel, src, dt)
|
|
1392
|
+
case dt
|
|
1393
|
+
when :preserve then DTYPES[src]
|
|
1394
|
+
when :real_of_source
|
|
1395
|
+
real = REAL_OF_CMPLX[src]
|
|
1396
|
+
raise "#{kernel[:name]}: output :real_of_source needs a complex src (got #{src})" \
|
|
1397
|
+
unless real
|
|
1398
|
+
DTYPES[real]
|
|
1399
|
+
else
|
|
1400
|
+
DTYPES[dt]
|
|
1401
|
+
end
|
|
1402
|
+
end
|
|
1403
|
+
|
|
1328
1404
|
def self.output_info(kernel, src)
|
|
1329
1405
|
out = kernel[:output]
|
|
1330
1406
|
case out
|
|
@@ -1334,16 +1410,15 @@ module MkKernel
|
|
|
1334
1410
|
next if family == :default
|
|
1335
1411
|
if monop_expr_family_match?(family, src)
|
|
1336
1412
|
# Hash value may itself be :preserve (= "same as source for this
|
|
1337
|
-
# family") or a data_type symbol like :f64.
|
|
1338
|
-
return (
|
|
1413
|
+
# family"), :real_of_source, or a data_type symbol like :f64.
|
|
1414
|
+
return resolve_output_dtype(kernel, src, dt)
|
|
1339
1415
|
end
|
|
1340
1416
|
end
|
|
1341
1417
|
raise "#{kernel[:name]}: output Hash has no match for src #{src} and no :default" \
|
|
1342
1418
|
unless out.key?(:default)
|
|
1343
|
-
|
|
1344
|
-
(dt == :preserve) ? DTYPES[src] : DTYPES[dt]
|
|
1419
|
+
resolve_output_dtype(kernel, src, out[:default])
|
|
1345
1420
|
else
|
|
1346
|
-
|
|
1421
|
+
resolve_output_dtype(kernel, src, out)
|
|
1347
1422
|
end
|
|
1348
1423
|
end
|
|
1349
1424
|
|
|
@@ -2643,8 +2718,8 @@ module MkKernel
|
|
|
2643
2718
|
write stride 1 (= contig output tail) both preserved -> SIMD
|
|
2644
2719
|
tile reduce + write maintained.
|
|
2645
2720
|
|
|
2646
|
-
Performance characteristic (deliver-via-view
|
|
2647
|
-
|
|
2721
|
+
Performance characteristic (deliver-via-view -- the surface
|
|
2722
|
+
prioritises delivering the cells over avoiding a copy): bench (b2 pattern, M=200, K=5, eff_INNER
|
|
2648
2723
|
=360, INNER_pre_K=16) yields 3674 us vs eager-entity 1545 us
|
|
2649
2724
|
= 2.38x slow. Root cause is the multi-parent data layout
|
|
2650
2725
|
(= 5 separate 9 MB regions instead of one contig 46 MB),
|
|
@@ -2800,8 +2875,8 @@ module MkKernel
|
|
|
2800
2875
|
# reductions consume the same slab)
|
|
2801
2876
|
# - no streaming / view_flat / array_arg / value_arg
|
|
2802
2877
|
# - reduction_kind: :none only
|
|
2803
|
-
# The if-form discipline
|
|
2804
|
-
#
|
|
2878
|
+
# The if-form discipline for multi-reduction fused kernels (see
|
|
2879
|
+
# emit_min_max below) is enforced by author, not by the generator.
|
|
2805
2880
|
def self.emit_reduce_native_multi(io, k, src)
|
|
2806
2881
|
si = DTYPES[src]
|
|
2807
2882
|
oi = output_info(k, src)
|
|
@@ -2976,7 +3051,15 @@ module MkKernel
|
|
|
2976
3051
|
io.puts " (void) masked_cnt;"
|
|
2977
3052
|
end
|
|
2978
3053
|
io.puts " ca_lazy_arena_enter();"
|
|
2979
|
-
|
|
3054
|
+
if si[:c] == "VALUE"
|
|
3055
|
+
# Object lane: the chunk holds VALUEs pulled from the source, and
|
|
3056
|
+
# the fold below calls rb_funcall per cell. A collection there
|
|
3057
|
+
# would free cells a lazy source produced into the chunk, so the
|
|
3058
|
+
# slot has to stay marked for as long as it is held.
|
|
3059
|
+
io.puts " VALUE *__chunk = (VALUE *) ca_lazy_arena_acquire_object(__chunk_elems);"
|
|
3060
|
+
else
|
|
3061
|
+
io.puts " #{si[:c]} *__chunk = (#{si[:c]} *) ca_lazy_arena_acquire(__chunk_elems * sizeof(#{si[:c]}));"
|
|
3062
|
+
end
|
|
2980
3063
|
io.puts " while ( __outer_off < __outer ) {"
|
|
2981
3064
|
io.puts " ca_size_t __r = (__outer - __outer_off < __rows) ? (__outer - __outer_off) : __rows;"
|
|
2982
3065
|
io.puts " ca_size_t __n = __r * __inner;"
|
|
@@ -3794,7 +3877,7 @@ module MkKernel
|
|
|
3794
3877
|
# `masked_last` parameter. Sets `sort_lo` / `sort_n` to the unmasked
|
|
3795
3878
|
# sub-range so the downstream sort/quickselect call only ever compares
|
|
3796
3879
|
# unmasked pairs -- masked cells are an incomparable sentinel, the same
|
|
3797
|
-
# role NaN plays for nan_policy: :end, but
|
|
3880
|
+
# role NaN plays for nan_policy: :end, but type-agnostic and runtime-
|
|
3798
3881
|
# selectable. `payload_expr(k)` computes the `.i` payload (fiber-local
|
|
3799
3882
|
# index for :fiber_local semantics, view-flat address for :view_flat)
|
|
3800
3883
|
# given the Ruby string `k` naming the C loop variable.
|
|
@@ -4829,7 +4912,7 @@ module MkKernel
|
|
|
4829
4912
|
{
|
|
4830
4913
|
/* rev4 A1 via single-element CArray: convert CScalar / [1] 1-D /
|
|
4831
4914
|
all dim==1 etc. to a Ruby Float and route it through the Case A
|
|
4832
|
-
scalar path. The
|
|
4915
|
+
scalar path. The data type matches ca (= rb_ca_obj2ptr coerces it
|
|
4833
4916
|
downstream). */
|
|
4834
4917
|
if ( rb_obj_is_carray(rval) ) {
|
|
4835
4918
|
CArray *cv_pre_;
|
|
@@ -6055,6 +6138,90 @@ module MkKernel
|
|
|
6055
6138
|
# The aggregator init.c, where Init_carray_kernels() calls each per-tag
|
|
6056
6139
|
# Init_<suffix>() in file_tags order. No tag depends on another: an alias
|
|
6057
6140
|
# already sits after its target within its own file.
|
|
6141
|
+
# ---------------------------------------------------------------------
|
|
6142
|
+
# The kernel bodies, as text, for a caller that has to compute the same
|
|
6143
|
+
# thing somewhere other than in these kernels -- a compiler handed an
|
|
6144
|
+
# expression tree, which must produce what the eager kernel produces.
|
|
6145
|
+
# Emitting the table here rather than reading this generator at runtime
|
|
6146
|
+
# keeps a build-time tool out of the running process.
|
|
6147
|
+
#
|
|
6148
|
+
# The bodies keep their `#1` / `#2` / `#3` and `<type>` placeholders: the
|
|
6149
|
+
# caller substitutes its own operands. Object-lane bodies are left out,
|
|
6150
|
+
# since they call back into the interpreter and cannot be compiled apart
|
|
6151
|
+
# from it.
|
|
6152
|
+
# ---------------------------------------------------------------------
|
|
6153
|
+
|
|
6154
|
+
BODY_TABLE_DTYPES = %i[i8 u8 i16 u16 i32 u32 i64 u64 f32 f64
|
|
6155
|
+
bool cmplx64 cmplx128].freeze
|
|
6156
|
+
|
|
6157
|
+
def self.body_table_rows
|
|
6158
|
+
rows = []
|
|
6159
|
+
KERNELS.each do |k|
|
|
6160
|
+
next unless %i[monop binop triop].include?(k[:kind])
|
|
6161
|
+
BODY_TABLE_DTYPES.each do |src|
|
|
6162
|
+
body = monop_expr_for(k, src)
|
|
6163
|
+
next unless body.is_a?(String)
|
|
6164
|
+
next if body.include?("rb_funcall")
|
|
6165
|
+
rows << [k[:kind].to_s, k[:name].to_s,
|
|
6166
|
+
DTYPES[src][:ca].sub(/\ACA_/, "").downcase, body]
|
|
6167
|
+
end
|
|
6168
|
+
end
|
|
6169
|
+
rows
|
|
6170
|
+
end
|
|
6171
|
+
|
|
6172
|
+
def self.c_string_literal(text)
|
|
6173
|
+
'"' + text.gsub("\\", "\\\\").gsub('"', '\\"').gsub("\n", '\\n') + '"'
|
|
6174
|
+
end
|
|
6175
|
+
|
|
6176
|
+
def self.emit_kernel_bodies(io)
|
|
6177
|
+
rows = body_table_rows
|
|
6178
|
+
io.puts
|
|
6179
|
+
io.puts "/* The text of every kernel body, for a caller that compiles the same"
|
|
6180
|
+
io.puts " operation elsewhere. Placeholders are left in place. */"
|
|
6181
|
+
io.puts
|
|
6182
|
+
io.puts "typedef struct {"
|
|
6183
|
+
io.puts " const char *kind;"
|
|
6184
|
+
io.puts " const char *name;"
|
|
6185
|
+
io.puts " const char *data_type;"
|
|
6186
|
+
io.puts " const char *body;"
|
|
6187
|
+
io.puts "} ca_kernel_body_t;"
|
|
6188
|
+
io.puts
|
|
6189
|
+
io.puts "static const ca_kernel_body_t ca_kernel_bodies[] = {"
|
|
6190
|
+
rows.each do |kind, name, dtype, body|
|
|
6191
|
+
io.puts " { #{c_string_literal(kind)}, #{c_string_literal(name)}, " \
|
|
6192
|
+
"#{c_string_literal(dtype)},"
|
|
6193
|
+
io.puts " #{c_string_literal(body)} },"
|
|
6194
|
+
end
|
|
6195
|
+
io.puts "};"
|
|
6196
|
+
io.puts
|
|
6197
|
+
io.puts "static const int ca_kernel_bodies_count = #{rows.size};"
|
|
6198
|
+
io.puts
|
|
6199
|
+
io.puts <<~C
|
|
6200
|
+
/* CArray.__kernel_body__(kind, name, data_type) -> String, or nil where
|
|
6201
|
+
this operation has no body at that data type. */
|
|
6202
|
+
static VALUE
|
|
6203
|
+
rb_ca_s_kernel_body (VALUE klass, VALUE rkind, VALUE rname, VALUE rtype)
|
|
6204
|
+
{
|
|
6205
|
+
VALUE kind = rb_obj_as_string(rkind);
|
|
6206
|
+
VALUE name = rb_obj_as_string(rname);
|
|
6207
|
+
VALUE type = rb_obj_as_string(rtype);
|
|
6208
|
+
const char *k = StringValueCStr(kind);
|
|
6209
|
+
const char *n = StringValueCStr(name);
|
|
6210
|
+
const char *t = StringValueCStr(type);
|
|
6211
|
+
int i;
|
|
6212
|
+
for ( i = 0; i < ca_kernel_bodies_count; i++ ) {
|
|
6213
|
+
const ca_kernel_body_t *e = &ca_kernel_bodies[i];
|
|
6214
|
+
if ( strcmp(e->kind, k) == 0 &&
|
|
6215
|
+
strcmp(e->name, n) == 0 &&
|
|
6216
|
+
strcmp(e->data_type, t) == 0 ) {
|
|
6217
|
+
return rb_str_new_cstr(e->body);
|
|
6218
|
+
}
|
|
6219
|
+
}
|
|
6220
|
+
return Qnil;
|
|
6221
|
+
}
|
|
6222
|
+
C
|
|
6223
|
+
end
|
|
6224
|
+
|
|
6058
6225
|
def self.emit_aggregator_init(io, tags)
|
|
6059
6226
|
io.puts "/* GENERATED aggregator: dispatches to per-tag Init_carray_kernels_<tag>() */"
|
|
6060
6227
|
io.puts "#include \"carray.h\""
|
|
@@ -6063,23 +6230,30 @@ module MkKernel
|
|
|
6063
6230
|
io.puts "void Init_carray_kernels_#{file_suffix(kind, sub)} (void);"
|
|
6064
6231
|
end
|
|
6065
6232
|
io.puts
|
|
6233
|
+
emit_kernel_bodies(io)
|
|
6234
|
+
io.puts
|
|
6066
6235
|
io.puts "void"
|
|
6067
6236
|
io.puts "Init_carray_kernels (void)"
|
|
6068
6237
|
io.puts "{"
|
|
6069
6238
|
tags.each do |kind, sub|
|
|
6070
6239
|
io.puts " Init_carray_kernels_#{file_suffix(kind, sub)}();"
|
|
6071
6240
|
end
|
|
6241
|
+
io.puts " rb_define_singleton_method(rb_cCArray, \"__kernel_body__\","
|
|
6242
|
+
io.puts " rb_ca_s_kernel_body, 3);"
|
|
6072
6243
|
io.puts "}"
|
|
6073
6244
|
end
|
|
6074
6245
|
|
|
6075
6246
|
# Single-stream Init_carray_kernels(): every kind emitted in order inside
|
|
6076
6247
|
# one function.
|
|
6077
6248
|
def self.emit_init(io)
|
|
6249
|
+
emit_kernel_bodies(io)
|
|
6078
6250
|
io.puts
|
|
6079
6251
|
io.puts "void"
|
|
6080
6252
|
io.puts "Init_carray_kernels (void)"
|
|
6081
6253
|
io.puts "{"
|
|
6082
6254
|
KERNELS.each { |k| emit_init_line(io, k) }
|
|
6255
|
+
io.puts " rb_define_singleton_method(rb_cCArray, \"__kernel_body__\","
|
|
6256
|
+
io.puts " rb_ca_s_kernel_body, 3);"
|
|
6083
6257
|
io.puts "}"
|
|
6084
6258
|
end
|
|
6085
6259
|
|
|
@@ -6155,7 +6329,7 @@ module MkKernel
|
|
|
6155
6329
|
# bench A/B comparison during Phase E (E.1-E.5); after E.7 they
|
|
6156
6330
|
# are retired. Maps and scans keep their `_ki` names because
|
|
6157
6331
|
# they have no user-facing equivalent yet (cumsum etc. pending
|
|
6158
|
-
# rewire
|
|
6332
|
+
# rewire).
|
|
6159
6333
|
#
|
|
6160
6334
|
# SO.2 rev6 (2026-06-04): sort kernels can opt out of the _ki
|
|
6161
6335
|
# binding via bind_ruby: false (= internal-only kernels consumed
|
|
@@ -6500,11 +6674,11 @@ MkKernel.reduce :stddev,
|
|
|
6500
6674
|
# -fopenmp-simd). The 2x f64 speedup survives SL.1.2's reduction(min/max:)
|
|
6501
6675
|
# vectorizer reject because fminnm + fmaxnm dual-issue on M2's two FP pipes.
|
|
6502
6676
|
#
|
|
6503
|
-
# if-form discipline
|
|
6677
|
+
# if-form discipline for multi-reduction fused kernels:
|
|
6504
6678
|
# the body uses `if (v < lo) lo = v;` etc, NOT ternary `lo = (v < lo) ? v : lo;`.
|
|
6505
6679
|
# DO NOT change to ternary — pragma-less ILP path depends on if-form.
|
|
6506
6680
|
#
|
|
6507
|
-
# FM.1.0 scope: mask not yet propagated (FM.1.5). Numeric
|
|
6681
|
+
# FM.1.0 scope: mask not yet propagated (FM.1.5). Numeric data types only.
|
|
6508
6682
|
|
|
6509
6683
|
MkKernel.reduce :minmax,
|
|
6510
6684
|
state: { lo: :load_type, hi: :load_type },
|
|
@@ -6541,9 +6715,9 @@ MkKernel.reduce :minmax,
|
|
|
6541
6715
|
# actual name (`best_v` here) through to the macro -- the macro accepts
|
|
6542
6716
|
# any identifier as its accumulator argument.
|
|
6543
6717
|
#
|
|
6544
|
-
# Exposed as Ruby `min_index` / `max_index` (= naming
|
|
6545
|
-
#
|
|
6546
|
-
#
|
|
6718
|
+
# Exposed as Ruby `min_index` / `max_index` (= the naming rule that a
|
|
6719
|
+
# method returning a position uses the `_index` suffix; see the rules
|
|
6720
|
+
# table in guides/devel/00_glossary.md). These
|
|
6547
6721
|
# replace the legacy `min_addr` / `max_addr` retired in E.7 stat_proc
|
|
6548
6722
|
# retire (commit f5c7ecd). 3.0 breaking: name change from `*_addr` to
|
|
6549
6723
|
# `*_index` is intentional.
|
|
@@ -6595,10 +6769,10 @@ MkKernel.reduce :argmax,
|
|
|
6595
6769
|
# axis-local-to-flat-addr round-trip).
|
|
6596
6770
|
#
|
|
6597
6771
|
# Paired with the sort family's sort_addr(axis:) (= already public)
|
|
6598
|
-
# and partition family's partition_addr_ki (= internal).
|
|
6599
|
-
#
|
|
6600
|
-
#
|
|
6601
|
-
#
|
|
6772
|
+
# and partition family's partition_addr_ki (= internal). `_addr`
|
|
6773
|
+
# is OK to expose for a per-axis primitive that returns a real flat
|
|
6774
|
+
# address: the dual API is `_index` for an axis-local position, `_addr`
|
|
6775
|
+
# for a view-flat one.
|
|
6602
6776
|
#
|
|
6603
6777
|
# Runtime constraints:
|
|
6604
6778
|
# - naxes == 1 (single-axis reduce): view-flat transform applied
|
|
@@ -6806,7 +6980,8 @@ MkKernel.reduce :count_equal,
|
|
|
6806
6980
|
# paths removed (= ALL_NUMERIC + :raise fallback). Re-add via demand-
|
|
6807
6981
|
# driven complex specialization or CA_OBJECT bridge phase.
|
|
6808
6982
|
# - **per-axis support gained**: `a.wsum(w, 0)`, `a.wsum(w, 0, 1)`, etc.
|
|
6809
|
-
# (= original "open per-axis" goal
|
|
6983
|
+
# (= the original "open per-axis" goal: every reduction gains an
|
|
6984
|
+
# axis form).
|
|
6810
6985
|
#
|
|
6811
6986
|
# public_method: true rebinds `wsum` from legacy rb_ca_wsum to
|
|
6812
6987
|
# rb_ca_wsum_ki at Init time.
|
|
@@ -7015,7 +7190,7 @@ MkKernel.scan :cumcount,
|
|
|
7015
7190
|
# acc_type: :load_type -> acc carries T_LOAD (= last seen input value).
|
|
7016
7191
|
# STEP additionally sees `first` (int) marking the first live cell of
|
|
7017
7192
|
# the fiber. Output data_type = :bool. No production consumer remains:
|
|
7018
|
-
# every
|
|
7193
|
+
# every data type's mask_duplicates now uses the O(distinct) seen-set hash
|
|
7019
7194
|
# lane (__mask_duplicates__, with boolean riding its uint8 lane), which
|
|
7020
7195
|
# has no sort buffers. The numeric widths are kept as a standalone scan
|
|
7021
7196
|
# kernel (a sort-path reference oracle in the mask_duplicates tests).
|
|
@@ -7392,8 +7567,8 @@ MkKernel.search :search_nearest,
|
|
|
7392
7567
|
# (= axis-local position) is transformed to a view-flat (row-major) address
|
|
7393
7568
|
# into self by the dispatcher. Paired with the *_index family
|
|
7394
7569
|
# (= bsearch / search / search_nearest already returning axis-local
|
|
7395
|
-
# positions per the dual `_index` / `_addr` API
|
|
7396
|
-
#
|
|
7570
|
+
# positions per the dual `_index` / `_addr` API: `_index` for an
|
|
7571
|
+
# axis-local position, `_addr` for a view-flat one).
|
|
7397
7572
|
#
|
|
7398
7573
|
# Scope: scalar val path only (= case A). CArray val + broadcast
|
|
7399
7574
|
# path (case B/C) raises NotImpError until extended (= the per-element
|
|
@@ -7805,13 +7980,16 @@ MkKernel.monop :abs_i,
|
|
|
7805
7980
|
MkKernel::SINT64_DTYPES => "(#2) = llabs(#1);",
|
|
7806
7981
|
MkKernel::UINT_DTYPES => "(#2) = (#1);",
|
|
7807
7982
|
MkKernel::FLOAT_DTYPES => "(#2) = fabs((float64_t)#1);",
|
|
7808
|
-
|
|
7983
|
+
[:cmplx64] => "(#2) = cabsf(#1);",
|
|
7984
|
+
[:cmplx128] => "(#2) = cabs(#1);",
|
|
7809
7985
|
[:object] => '(#2) = rb_funcall((#1), rb_intern("abs"), 0);',
|
|
7810
7986
|
}
|
|
7811
7987
|
|
|
7812
7988
|
# abs: data_type-changing monop (the framework-piece test customer for monop
|
|
7813
7989
|
# Hash output form). numeric input -> preserve data_type (= int/float abs),
|
|
7814
|
-
# complex input ->
|
|
7990
|
+
# complex input -> the real component width of that complex data_type
|
|
7991
|
+
# (= magnitude is real, and a cmplx64 magnitude is a float32 just as
|
|
7992
|
+
# `.real` and `.imag` are). Replaces the hand-
|
|
7815
7993
|
# written rb_ca_abs / rb_ca_abs_bang in ext/carray_math.c. Object data_type
|
|
7816
7994
|
# kept on abs_i (= bind: false on object would need a different output
|
|
7817
7995
|
# rule; deferred). The abs_i monop above remains the primary kernel for
|
|
@@ -7820,13 +7998,15 @@ MkKernel.monop :abs_i,
|
|
|
7820
7998
|
# returns the real-valued magnitude entity.
|
|
7821
7999
|
MkKernel.monop :abs,
|
|
7822
8000
|
source: MkKernel::ALL_NUMERIC + MkKernel::CMPLX_DTYPES,
|
|
7823
|
-
output: { numeric: :preserve, complex: :
|
|
8001
|
+
output: { numeric: :preserve, complex: :real_of_source },
|
|
7824
8002
|
expr: {
|
|
7825
8003
|
MkKernel::SINT_SMALL_DTYPES => "(#2) = abs(#1);",
|
|
7826
8004
|
MkKernel::SINT64_DTYPES => "(#2) = llabs(#1);",
|
|
7827
8005
|
MkKernel::UINT_DTYPES => "(#2) = (#1);",
|
|
7828
8006
|
MkKernel::FLOAT_DTYPES => "(#2) = fabs(#1);",
|
|
7829
|
-
|
|
8007
|
+
# complex -> the real magnitude, at that complex type's own width
|
|
8008
|
+
[:cmplx64] => "(#2) = cabsf(#1);",
|
|
8009
|
+
[:cmplx128] => "(#2) = cabs(#1);",
|
|
7830
8010
|
}
|
|
7831
8011
|
|
|
7832
8012
|
# abs2: squared magnitude. For real x this is x*x (identical to :square
|
|
@@ -7837,22 +8017,22 @@ MkKernel.monop :abs,
|
|
|
7837
8017
|
# optics, signal processing) do not pay for a sqrt they immediately
|
|
7838
8018
|
# square away.
|
|
7839
8019
|
#
|
|
7840
|
-
# Output data_type follows :abs: numeric preserved, complex
|
|
8020
|
+
# Output data_type follows :abs: numeric preserved, complex demoted to
|
|
8021
|
+
# its own real component width.
|
|
7841
8022
|
MkKernel.monop :abs2,
|
|
7842
8023
|
source: MkKernel::ALL_NUMERIC + MkKernel::CMPLX_DTYPES,
|
|
7843
|
-
output: { numeric: :preserve, complex: :
|
|
8024
|
+
output: { numeric: :preserve, complex: :real_of_source },
|
|
7844
8025
|
expr: {
|
|
7845
8026
|
numeric: "(#2) = (#1) * (#1);",
|
|
7846
|
-
|
|
7847
|
-
|
|
8027
|
+
}.merge(MkKernel.cmplx_widths(
|
|
8028
|
+
"{ <t> _r = creal<f>(#1); <t> _i = cimag<f>(#1); (#2) = _r * _r + _i * _i; }"))
|
|
7848
8029
|
|
|
7849
8030
|
MkKernel.monop :conj,
|
|
7850
8031
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
7851
8032
|
expr: {
|
|
7852
8033
|
numeric: "(#2) = (#1);",
|
|
7853
|
-
complex: "(#2) = conj(#1);",
|
|
7854
8034
|
object: '(#2) = rb_funcall((#1), rb_intern("conj"), 0);',
|
|
7855
|
-
}
|
|
8035
|
+
}.merge(MkKernel.cmplx_widths("(#2) = conj<f>(#1);"))
|
|
7856
8036
|
|
|
7857
8037
|
# arg: data_type-changing monop — phase angle of the complex plane.
|
|
7858
8038
|
# Mathematically `arg(z)` for z = re + im*i is `atan2(im, re)` in
|
|
@@ -7863,11 +8043,11 @@ MkKernel.monop :conj,
|
|
|
7863
8043
|
# Replaces the hand-written rb_ca_arg in ext/carray_numeric.c (which
|
|
7864
8044
|
# was f64-only, float-or-complex parent, also computed `carg`).
|
|
7865
8045
|
#
|
|
7866
|
-
# Output data_type
|
|
7867
|
-
#
|
|
7868
|
-
#
|
|
7869
|
-
#
|
|
7870
|
-
#
|
|
8046
|
+
# Output data_type keeps the width the input carries its real values in:
|
|
8047
|
+
# a float stays that float, a complex demotes to its own real component
|
|
8048
|
+
# width (cmplx64 -> f32, cmplx128 -> f64). Integers are the exception
|
|
8049
|
+
# and go to f64, because pi does not fit any integer slot — preserving
|
|
8050
|
+
# the int data_type would silently truncate `arg(-1) = pi` to 3.
|
|
7871
8051
|
#
|
|
7872
8052
|
# 3.0 breaking (vs hand-written rb_ca_arg):
|
|
7873
8053
|
# - integer input is now accepted (was a raise). Returns f64
|
|
@@ -7897,16 +8077,15 @@ MkKernel.monop :sign,
|
|
|
7897
8077
|
MkKernel::UINT_DTYPES => "(#2) = ((#1) > 0) ? 1 : 0;",
|
|
7898
8078
|
MkKernel::SINT_DTYPES => "(#2) = ((#1) > 0) - ((#1) < 0);",
|
|
7899
8079
|
MkKernel::FLOAT_DTYPES => "(#2) = isnan(#1) ? (#1) : (((#1) > 0) - ((#1) < 0));",
|
|
7900
|
-
|
|
7901
|
-
|
|
8080
|
+
}.merge(MkKernel.cmplx_widths(
|
|
8081
|
+
"{ <t> _m = cabs<f>(#1); (#2) = (_m == 0) ? 0 : ((#1) / _m); }"))
|
|
7902
8082
|
|
|
7903
8083
|
MkKernel.monop :arg,
|
|
7904
8084
|
source: MkKernel::ALL_NUMERIC + MkKernel::CMPLX_DTYPES,
|
|
7905
|
-
output: {
|
|
8085
|
+
output: { int: :f64, float: :preserve, complex: :real_of_source },
|
|
7906
8086
|
expr: {
|
|
7907
8087
|
numeric: "(#2) = carg((cmplx128_t)(#1));",
|
|
7908
|
-
|
|
7909
|
-
}
|
|
8088
|
+
}.merge(MkKernel.cmplx_widths("(#2) = carg<f>(#1);"))
|
|
7910
8089
|
|
|
7911
8090
|
# imag_i: data_type-preserving kernel that places the imag part in the
|
|
7912
8091
|
# slot (= cimag for complex stores into the real component since cmplx
|
|
@@ -7920,9 +8099,8 @@ MkKernel.monop :imag_i,
|
|
|
7920
8099
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
7921
8100
|
expr: {
|
|
7922
8101
|
numeric: "(#2) = 0;",
|
|
7923
|
-
complex: "(#2) = cimag(#1);",
|
|
7924
8102
|
object: '(#2) = rb_funcall((#1), rb_intern("imaginary"), 0);',
|
|
7925
|
-
}
|
|
8103
|
+
}.merge(MkKernel.cmplx_widths("(#2) = cimag<f>(#1);"))
|
|
7926
8104
|
|
|
7927
8105
|
# arg_i: data_type-preserving kernel that writes the complex argument
|
|
7928
8106
|
# (phase angle) into the slot. For complex input, cassignment from a
|
|
@@ -7937,9 +8115,9 @@ MkKernel.monop :imag_i,
|
|
|
7937
8115
|
MkKernel.monop :arg_i,
|
|
7938
8116
|
source: MkKernel::FLOAT_DTYPES + MkKernel::CMPLX_DTYPES,
|
|
7939
8117
|
expr: {
|
|
7940
|
-
[:f32] => "(#2) = (
|
|
8118
|
+
[:f32] => "(#2) = cargf((cmplx64_t)(#1));",
|
|
7941
8119
|
[:f64] => "(#2) = carg((cmplx128_t)(#1));",
|
|
7942
|
-
[:cmplx64] => "(#2) = (
|
|
8120
|
+
[:cmplx64] => "(#2) = cargf(#1);",
|
|
7943
8121
|
[:cmplx128] => "(#2) = carg(#1);",
|
|
7944
8122
|
}
|
|
7945
8123
|
|
|
@@ -7995,7 +8173,8 @@ MkKernel.monfunc :rcp,
|
|
|
7995
8173
|
expr: {
|
|
7996
8174
|
int: "if ((#1)==0) {ca_zerodiv();}; (#2) = 1/(#1);",
|
|
7997
8175
|
float: "(#2) = 1/(#1);",
|
|
7998
|
-
|
|
8176
|
+
[:cmplx64] => "(#2) = op_crcp_cmplx64(#1);",
|
|
8177
|
+
[:cmplx128] => "(#2) = 1/(#1);",
|
|
7999
8178
|
object: '(#2) = rb_funcall(INT2NUM(1), rb_intern("/"), 1, (#1));',
|
|
8000
8179
|
}
|
|
8001
8180
|
|
|
@@ -8004,7 +8183,6 @@ MkKernel.monfunc :rcp,
|
|
|
8004
8183
|
{
|
|
8005
8184
|
sqrt: "sqrt",
|
|
8006
8185
|
exp: "exp",
|
|
8007
|
-
log: "log",
|
|
8008
8186
|
sin: "sin",
|
|
8009
8187
|
cos: "cos",
|
|
8010
8188
|
tan: "tan",
|
|
@@ -8015,21 +8193,37 @@ MkKernel.monfunc :rcp,
|
|
|
8015
8193
|
MkKernel.monfunc op,
|
|
8016
8194
|
source: MkKernel::FLOAT_DTYPES + MkKernel::CMPLX_DTYPES + [:object],
|
|
8017
8195
|
expr: {
|
|
8018
|
-
float: "(#2) = #{c_fn}(#1);",
|
|
8019
|
-
complex: "(#2) = c#{c_fn}(#1);",
|
|
8020
8196
|
object: MkKernel.obj_float_math("#{c_fn}(<v>)", c_fn),
|
|
8021
|
-
}
|
|
8197
|
+
}.merge(MkKernel.float_widths("(#2) = #{c_fn}<f>(#1);"))
|
|
8198
|
+
.merge(MkKernel.cmplx_widths("(#2) = c#{c_fn}<f>(#1);"))
|
|
8022
8199
|
end
|
|
8023
8200
|
|
|
8201
|
+
# log is the one member of the family whose complex form cancels, so it
|
|
8202
|
+
# does not follow the others onto `clogf`. The real part of `clog(z)` is
|
|
8203
|
+
# `log|z|`, which vanishes on the unit circle; computing |z| at the
|
|
8204
|
+
# operand's width rounds away everything the logarithm then needs, and
|
|
8205
|
+
# the answer comes back with a relative error near 400 instead of 1e-7.
|
|
8206
|
+
# The complex branch therefore stays on the double-taking `clog`, the way
|
|
8207
|
+
# `*` and `/` do -- see ca_op_cmplx64.h. Real `log` has no such step:
|
|
8208
|
+
# its operand is the argument itself, and `logf` measures at one float
|
|
8209
|
+
# epsilon right through x = 1.
|
|
8210
|
+
MkKernel.monfunc :log,
|
|
8211
|
+
source: MkKernel::FLOAT_DTYPES + MkKernel::CMPLX_DTYPES + [:object],
|
|
8212
|
+
expr: {
|
|
8213
|
+
complex: "(#2) = clog(#1);",
|
|
8214
|
+
object: MkKernel.obj_float_math("log(<v>)", "log"),
|
|
8215
|
+
}.merge(MkKernel.float_widths("(#2) = log<f>(#1);"))
|
|
8216
|
+
|
|
8024
8217
|
# exp2 special case: complex variant uses cpow(2, x), not cexp2 (which
|
|
8025
8218
|
# isn't standardized in C99/POSIX).
|
|
8026
8219
|
MkKernel.monfunc :exp2,
|
|
8027
8220
|
source: MkKernel::FLOAT_DTYPES + MkKernel::CMPLX_DTYPES + [:object],
|
|
8028
8221
|
expr: {
|
|
8029
|
-
|
|
8222
|
+
# cpow(a, z) is cexp(z * clog(a)), so it inherits clog's cancellation
|
|
8223
|
+
# and stays on the double-taking form for both complex data_types.
|
|
8030
8224
|
complex: "(#2) = cpow(2, (#1));",
|
|
8031
8225
|
object: MkKernel.obj_float_math("exp2(<v>)", "exp2"),
|
|
8032
|
-
}
|
|
8226
|
+
}.merge(MkKernel.float_widths("(#2) = exp2<f>(#1);"))
|
|
8033
8227
|
|
|
8034
8228
|
# log10, log2, logb: no complex variant in the original mkmath emit
|
|
8035
8229
|
{
|
|
@@ -8040,9 +8234,8 @@ MkKernel.monfunc :exp2,
|
|
|
8040
8234
|
MkKernel.monfunc op,
|
|
8041
8235
|
source: MkKernel::FLOAT_DTYPES + [:object],
|
|
8042
8236
|
expr: {
|
|
8043
|
-
float: "(#2) = #{c_fn}(#1);",
|
|
8044
8237
|
object: MkKernel.obj_float_math("#{c_fn}(<v>)", c_fn),
|
|
8045
|
-
}
|
|
8238
|
+
}.merge(MkKernel.float_widths("(#2) = #{c_fn}<f>(#1);"))
|
|
8046
8239
|
end
|
|
8047
8240
|
|
|
8048
8241
|
# exp10: special object expr (= bypass OBJ_FLOAT_MATH for the
|
|
@@ -8050,8 +8243,6 @@ end
|
|
|
8050
8243
|
MkKernel.monfunc :exp10,
|
|
8051
8244
|
source: MkKernel::FLOAT_DTYPES + MkKernel::CMPLX_DTYPES + [:object],
|
|
8052
8245
|
expr: {
|
|
8053
|
-
float: "(#2) = pow(10, (#1));",
|
|
8054
|
-
complex: "(#2) = cpow(10, (#1));",
|
|
8055
8246
|
object: <<~SNIPPET,
|
|
8056
8247
|
{
|
|
8057
8248
|
VALUE _obj_arg = (#1);
|
|
@@ -8062,10 +8253,13 @@ MkKernel.monfunc :exp10,
|
|
|
8062
8253
|
}
|
|
8063
8254
|
}
|
|
8064
8255
|
SNIPPET
|
|
8065
|
-
|
|
8256
|
+
complex: "(#2) = cpow(10, (#1));", # see exp2 on why cpow stays wide
|
|
8257
|
+
}.merge(MkKernel.float_widths("(#2) = pow<f>(10, (#1));"))
|
|
8066
8258
|
|
|
8067
|
-
# Hyperbolic family:
|
|
8068
|
-
#
|
|
8259
|
+
# Hyperbolic family: float uses the real-typed C function, complex uses
|
|
8260
|
+
# the C99 `c`-prefixed one. Passing a `double _Complex` to `sinh(double)`
|
|
8261
|
+
# discards the imaginary part, so the complex branch must not share the
|
|
8262
|
+
# real-typed name.
|
|
8069
8263
|
{
|
|
8070
8264
|
sinh: "sinh",
|
|
8071
8265
|
cosh: "cosh",
|
|
@@ -8077,10 +8271,9 @@ MkKernel.monfunc :exp10,
|
|
|
8077
8271
|
MkKernel.monfunc op,
|
|
8078
8272
|
source: MkKernel::FLOAT_DTYPES + MkKernel::CMPLX_DTYPES + [:object],
|
|
8079
8273
|
expr: {
|
|
8080
|
-
float: "(#2) = #{c_fn}(#1);",
|
|
8081
|
-
complex: "(#2) = #{c_fn}(#1);",
|
|
8082
8274
|
object: MkKernel.obj_float_math("#{c_fn}(<v>)", c_fn),
|
|
8083
|
-
}
|
|
8275
|
+
}.merge(MkKernel.float_widths("(#2) = #{c_fn}<f>(#1);"))
|
|
8276
|
+
.merge(MkKernel.cmplx_widths("(#2) = c#{c_fn}<f>(#1);"))
|
|
8084
8277
|
end
|
|
8085
8278
|
|
|
8086
8279
|
# ---- M.1 (PyTorch alignment): additional monfunc / monop ------------------
|
|
@@ -8094,19 +8287,17 @@ end
|
|
|
8094
8287
|
MkKernel.monfunc op,
|
|
8095
8288
|
source: MkKernel::FLOAT_DTYPES + [:object],
|
|
8096
8289
|
expr: {
|
|
8097
|
-
float: "(#2) = #{c_fn}(#1);",
|
|
8098
8290
|
object: MkKernel.obj_float_math("#{c_fn}(<v>)", c_fn),
|
|
8099
|
-
}
|
|
8291
|
+
}.merge(MkKernel.float_widths("(#2) = #{c_fn}<f>(#1);"))
|
|
8100
8292
|
end
|
|
8101
8293
|
|
|
8102
8294
|
# rsqrt: 1 / sqrt(x). float + complex + object (complex via 1.0 / csqrt).
|
|
8103
8295
|
MkKernel.monfunc :rsqrt,
|
|
8104
8296
|
source: MkKernel::FLOAT_DTYPES + MkKernel::CMPLX_DTYPES + [:object],
|
|
8105
8297
|
expr: {
|
|
8106
|
-
float: "(#2) = 1.0 / sqrt(#1);",
|
|
8107
|
-
complex: "(#2) = 1.0 / csqrt(#1);",
|
|
8108
8298
|
object: MkKernel.obj_float_math("1.0 / sqrt(<v>)", "rsqrt"),
|
|
8109
|
-
}
|
|
8299
|
+
}.merge(MkKernel.float_widths("(#2) = ((<t>)1.0) / sqrt<f>(#1);"))
|
|
8300
|
+
.merge(MkKernel.cmplx_widths("(#2) = ((<t>)1.0) / csqrt<f>(#1);"))
|
|
8110
8301
|
|
|
8111
8302
|
# trunc: toward-zero rounding. Preserve-data_type form like ceil / floor /
|
|
8112
8303
|
# round — int branch is identity, float branch uses C99 trunc, object
|
|
@@ -8136,7 +8327,7 @@ MkKernel.monop :square,
|
|
|
8136
8327
|
# with f64-forced input/output via ca_call_cfunc_1_1; the mkkernel form
|
|
8137
8328
|
# preserves the same numeric behaviour but rides the lazy substrate +
|
|
8138
8329
|
# kernel_iterator engine. Widening monfunc: integer input auto-casts
|
|
8139
|
-
# to f64, float input preserves
|
|
8330
|
+
# to f64, float input preserves data type.
|
|
8140
8331
|
|
|
8141
8332
|
# deg_360: fold into [0, 360). Use double-typed local for the fold
|
|
8142
8333
|
# computation regardless of input precision (matches legacy hand-written
|
|
@@ -8315,12 +8506,12 @@ MkKernel.binop :minimum,
|
|
|
8315
8506
|
object: '(#3) = rb_funcall(rb_assoc_new((#1),(#2)), rb_intern("min"), 0);',
|
|
8316
8507
|
}
|
|
8317
8508
|
|
|
8318
|
-
#
|
|
8509
|
+
# + and - work on the parts independently, so one generic expression
|
|
8510
|
+
# covers every numeric data_type at any width.
|
|
8319
8511
|
{
|
|
8320
|
-
add: ["+", "+"
|
|
8321
|
-
sub: ["-", "-"
|
|
8322
|
-
|
|
8323
|
-
}.each do |name, (op, c_op, _ruby_op)|
|
|
8512
|
+
add: ["+", "+"],
|
|
8513
|
+
sub: ["-", "-"],
|
|
8514
|
+
}.each do |name, (op, c_op)|
|
|
8324
8515
|
MkKernel.binop name,
|
|
8325
8516
|
op: op,
|
|
8326
8517
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
@@ -8331,13 +8522,48 @@ MkKernel.binop :minimum,
|
|
|
8331
8522
|
}
|
|
8332
8523
|
end
|
|
8333
8524
|
|
|
8525
|
+
# * does not: a complex product subtracts two products of the parts, and
|
|
8526
|
+
# that subtraction cancels. A cmplx64 gets it in double, where there are
|
|
8527
|
+
# bits left underneath -- see ca_op_cmplx64.h. Unlike the divide this
|
|
8528
|
+
# costs speed rather than saving it, and is paid for the accuracy alone.
|
|
8529
|
+
MkKernel.binop :mul,
|
|
8530
|
+
op: "*",
|
|
8531
|
+
source: MkKernel::MATH_NUMERIC + [:object],
|
|
8532
|
+
expr: {
|
|
8533
|
+
numeric: "(#3) = (#1) * (#2);",
|
|
8534
|
+
[:cmplx64] => "(#3) = op_cmul_cmplx64((#1), (#2));",
|
|
8535
|
+
[:cmplx128] => "(#3) = (#1) * (#2);",
|
|
8536
|
+
object: '(#3) = rb_funcall((#1), rb_intern("*"), 1, (#2));',
|
|
8537
|
+
}
|
|
8538
|
+
|
|
8539
|
+
# div: `/`. Integer division floors toward -inf (= Ruby `Integer#/` and
|
|
8540
|
+
# NumPy `floor_divide`), so that `(a / b) * b + a % b == a` holds for
|
|
8541
|
+
# every sign combination. C's `/` truncates toward zero, so the signed
|
|
8542
|
+
# lanes correct the quotient by one when the division is inexact and the
|
|
8543
|
+
# operands have opposite signs. Unsigned operands can never disagree in
|
|
8544
|
+
# sign, so they keep the bare C form. Float `/` is true division and is
|
|
8545
|
+
# unchanged (matching Ruby `Float#/`); the identity above therefore holds
|
|
8546
|
+
# for integers only, exactly as in Ruby.
|
|
8334
8547
|
MkKernel.binop :div,
|
|
8335
8548
|
op: "/",
|
|
8336
8549
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
8337
8550
|
expr: {
|
|
8338
|
-
|
|
8551
|
+
MkKernel::UINT_DTYPES => "if ((#2)==0) {ca_zerodiv();}; (#3) = (#1) / (#2);",
|
|
8552
|
+
int: %{
|
|
8553
|
+
if ((#2)==0) {ca_zerodiv();};
|
|
8554
|
+
{
|
|
8555
|
+
<type> _q = (#1) / (#2);
|
|
8556
|
+
if ( (#1) % (#2) != 0 && (((#1) < 0) != ((#2) < 0)) ) { _q -= 1; }
|
|
8557
|
+
(#3) = _q;
|
|
8558
|
+
}
|
|
8559
|
+
},
|
|
8339
8560
|
float: "(#3) = (#1) / (#2);",
|
|
8340
|
-
|
|
8561
|
+
# A cmplx64 divide is computed in double and rounded once: see
|
|
8562
|
+
# ca_op_cmplx64.h for why that is both faster and correctly rounded,
|
|
8563
|
+
# and how Annex G survives it. cmplx128 has no wider type to
|
|
8564
|
+
# borrow, so it stays on the compiler's helper.
|
|
8565
|
+
[:cmplx64] => "(#3) = op_cdiv_cmplx64((#1), (#2));",
|
|
8566
|
+
[:cmplx128] => "(#3) = (#1) / (#2);",
|
|
8341
8567
|
object: '(#3) = rb_funcall((#1), rb_intern("/"), 1, (#2));',
|
|
8342
8568
|
}
|
|
8343
8569
|
|
|
@@ -8357,27 +8583,57 @@ MkKernel.binop :rcp_mul,
|
|
|
8357
8583
|
expr: {
|
|
8358
8584
|
int: "if ((#1)==0) {ca_zerodiv();}; (#3) = (#2) / (#1);",
|
|
8359
8585
|
float: "(#3) = (#2) / (#1);",
|
|
8360
|
-
|
|
8586
|
+
[:cmplx64] => "(#3) = op_cdiv_cmplx64((#2), (#1));",
|
|
8587
|
+
[:cmplx128] => "(#3) = (#2) / (#1);",
|
|
8361
8588
|
object: '(#3) = rb_funcall((#2), rb_intern("/"), 1, (#1));',
|
|
8362
8589
|
}
|
|
8363
8590
|
|
|
8591
|
+
# mod: `%`. Floored modulo -- the result carries the sign of the divisor
|
|
8592
|
+
# (= Ruby `%` and NumPy `np.mod`), and pairs with the floored `/` above.
|
|
8593
|
+
# C's `%` and `fmod` carry the sign of the dividend instead, so the signed
|
|
8594
|
+
# lanes add the divisor back when the remainder is non-zero and disagrees
|
|
8595
|
+
# in sign with it. A zero remainder is given the divisor's sign so the
|
|
8596
|
+
# rule holds without exception (float only; integers have no signed zero).
|
|
8597
|
+
# Unsigned operands already satisfy the rule. The truncating form stays
|
|
8598
|
+
# available as `fmod`.
|
|
8364
8599
|
MkKernel.binop :mod,
|
|
8365
8600
|
op: "%",
|
|
8366
8601
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8367
8602
|
expr: {
|
|
8368
|
-
|
|
8369
|
-
|
|
8603
|
+
MkKernel::UINT_DTYPES => "if ((#2)==0) {ca_zerodiv();}; (#3) = (#1) % (#2);",
|
|
8604
|
+
int: %{
|
|
8605
|
+
if ((#2)==0) {ca_zerodiv();};
|
|
8606
|
+
{
|
|
8607
|
+
<type> _r = (#1) % (#2);
|
|
8608
|
+
if ( _r != 0 && ((_r < 0) != ((#2) < 0)) ) { _r += (#2); }
|
|
8609
|
+
(#3) = _r;
|
|
8610
|
+
}
|
|
8611
|
+
},
|
|
8612
|
+
float: %{
|
|
8613
|
+
{
|
|
8614
|
+
<type> _r = fmod((#1), (#2));
|
|
8615
|
+
if ( _r != 0 ) {
|
|
8616
|
+
if ( (_r < 0) != ((#2) < 0) ) { _r += (#2); }
|
|
8617
|
+
}
|
|
8618
|
+
else { _r = copysign((<type>) 0, (#2)); }
|
|
8619
|
+
(#3) = _r;
|
|
8620
|
+
}
|
|
8621
|
+
},
|
|
8370
8622
|
object: '(#3) = rb_funcall((#1), rb_intern("%"), 1, (#2));',
|
|
8371
8623
|
}
|
|
8372
8624
|
|
|
8373
|
-
|
|
8374
|
-
|
|
8625
|
+
# fmod: the truncating counterpart of `%` -- the remainder carries the
|
|
8626
|
+
# sign of the dividend, as C's `fmod` and `%` do. Defined over integers
|
|
8627
|
+
# too (`%` no longer reaches that form since it floors), where it is C's
|
|
8628
|
+
# `%` with the same zero-divisor guard. Ruby's `Numeric#remainder` is
|
|
8629
|
+
# this same operation, so the object lane delegates to it and keeps the
|
|
8630
|
+
# element's class (an Integer stays an Integer).
|
|
8631
|
+
MkKernel.binop :fmod,
|
|
8375
8632
|
source: MkKernel::ALL_NUMERIC + [:object],
|
|
8376
8633
|
expr: {
|
|
8377
8634
|
int: "if ((#2)==0) {ca_zerodiv();}; (#3) = (#1) % (#2);",
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
}
|
|
8635
|
+
object: '(#3) = rb_funcall((#1), rb_intern("remainder"), 1, (#2));',
|
|
8636
|
+
}.merge(MkKernel.float_widths("(#3) = fmod<f>((#1), (#2));"))
|
|
8381
8637
|
|
|
8382
8638
|
MkKernel.binop :bit_and_i,
|
|
8383
8639
|
op: "&",
|
|
@@ -8482,6 +8738,7 @@ MkKernel.alias_binop :bit_rshift, :">>"
|
|
|
8482
8738
|
# pow / cpow. Object uses Ruby's `**`.
|
|
8483
8739
|
MkKernel.header_block <<~C
|
|
8484
8740
|
#include "ca_op_powi.h"
|
|
8741
|
+
#include "ca_op_cmplx64.h"
|
|
8485
8742
|
C
|
|
8486
8743
|
|
|
8487
8744
|
# ---- triop family ---------------------------------------------------------
|
|
@@ -8554,10 +8811,9 @@ MkKernel.binop :power,
|
|
|
8554
8811
|
source: MkKernel::MATH_NUMERIC + [:object],
|
|
8555
8812
|
expr: {
|
|
8556
8813
|
int: "(#3) = op_powi_<type>((#1), (#2));",
|
|
8557
|
-
|
|
8558
|
-
complex: "(#3) = cpow((#1), (#2));",
|
|
8814
|
+
complex: "(#3) = cpow((#1), (#2));", # see exp2 on why cpow stays wide
|
|
8559
8815
|
object: '(#3) = rb_funcall((#1), rb_intern("**"), 1, (#2));',
|
|
8560
|
-
}
|
|
8816
|
+
}.merge(MkKernel.float_widths("(#3) = pow<f>((#1), (#2));"))
|
|
8561
8817
|
|
|
8562
8818
|
# ---- M.2 + M.3 (PyTorch alignment): float-only binop family --------------
|
|
8563
8819
|
#
|
|
@@ -8573,7 +8829,6 @@ MkKernel.binop :power,
|
|
|
8573
8829
|
copysign: ["copysign", "copysign"],
|
|
8574
8830
|
logaddexp: [nil, nil], # custom expr — see below
|
|
8575
8831
|
nextafter: ["nextafter", "nextafter"],
|
|
8576
|
-
fmod: ["fmod", "fmod"],
|
|
8577
8832
|
atan2: ["atan2", "atan2"],
|
|
8578
8833
|
hypot: ["hypot", "hypot"],
|
|
8579
8834
|
}.each do |op_name, (c_fn, ruby_fb)|
|
|
@@ -8581,7 +8836,6 @@ MkKernel.binop :power,
|
|
|
8581
8836
|
MkKernel.binop op_name,
|
|
8582
8837
|
source: MkKernel::FLOAT_DTYPES + [:object],
|
|
8583
8838
|
expr: {
|
|
8584
|
-
float: "(#3) = #{c_fn}((#1), (#2));",
|
|
8585
8839
|
object: <<~SNIPPET,
|
|
8586
8840
|
{
|
|
8587
8841
|
VALUE _l = (#1);
|
|
@@ -8594,7 +8848,7 @@ MkKernel.binop :power,
|
|
|
8594
8848
|
}
|
|
8595
8849
|
}
|
|
8596
8850
|
SNIPPET
|
|
8597
|
-
}
|
|
8851
|
+
}.merge(MkKernel.float_widths("(#3) = #{c_fn}<f>((#1), (#2));"))
|
|
8598
8852
|
end
|
|
8599
8853
|
|
|
8600
8854
|
# logaddexp: log(exp(x) + exp(y)). Numerically stable form:
|
|
@@ -8603,7 +8857,6 @@ end
|
|
|
8603
8857
|
MkKernel.binop :logaddexp,
|
|
8604
8858
|
source: MkKernel::FLOAT_DTYPES + [:object],
|
|
8605
8859
|
expr: {
|
|
8606
|
-
float: "(#3) = fmax((#1), (#2)) + log1p(exp(-fabs((#1) - (#2))));",
|
|
8607
8860
|
object: <<~SNIPPET,
|
|
8608
8861
|
{
|
|
8609
8862
|
VALUE _l = (#1);
|
|
@@ -8619,7 +8872,8 @@ MkKernel.binop :logaddexp,
|
|
|
8619
8872
|
}
|
|
8620
8873
|
}
|
|
8621
8874
|
SNIPPET
|
|
8622
|
-
}
|
|
8875
|
+
}.merge(MkKernel.float_widths(
|
|
8876
|
+
"(#3) = fmax<f>((#1), (#2)) + log1p<f>(exp<f>(-fabs<f>((#1) - (#2))));"))
|
|
8623
8877
|
|
|
8624
8878
|
# ---- P.5b.4: moncmp family (predicates returning bool) ----------------
|
|
8625
8879
|
|
|
@@ -8669,7 +8923,7 @@ MkKernel.moncmp :is_invalid,
|
|
|
8669
8923
|
# -0.0). Integer branch: sint = (#1) < 0, uint = always 0. Float branch
|
|
8670
8924
|
# uses C99 signbit (handles -0.0 / NaN sign correctly). No complex
|
|
8671
8925
|
# variant (signbit on a complex is ambiguous; rejected at this layer).
|
|
8672
|
-
# `:sint` / `:uint` aren't family aliases — use array-of-
|
|
8926
|
+
# `:sint` / `:uint` aren't family aliases — use the array-of-types form.
|
|
8673
8927
|
MkKernel.moncmp :signbit,
|
|
8674
8928
|
source: MkKernel::SINT_DTYPES + MkKernel::UINT_DTYPES +
|
|
8675
8929
|
MkKernel::FLOAT_DTYPES + [:object],
|