carray 3.0.2 → 3.0.4
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/CHANGELOG.md +568 -0
- data/README.md +6 -6
- data/carray.gemspec +1 -1
- data/ext/ca_axis_descriptor.h +27 -24
- data/ext/ca_axis_dispatch.c +220 -0
- data/ext/ca_axis_group.c +201 -123
- data/ext/ca_binop_dispatch.c +46 -52
- data/ext/ca_binop_dispatch.h +1 -1
- data/ext/ca_categorical_iterator.c +249 -183
- data/ext/ca_compare.c +7 -12
- data/ext/ca_for_buffer.h +21 -68
- data/ext/ca_group_iter.c +38 -23
- data/ext/ca_kernel_iterator.c +1765 -2076
- data/ext/ca_kernel_iterator.h +584 -566
- data/ext/ca_monop_dispatch.c +86 -63
- data/ext/ca_monop_dispatch.h +5 -0
- data/ext/ca_obj_array.c +223 -56
- data/ext/ca_obj_bincmp.c +172 -138
- data/ext/ca_obj_binop.c +116 -230
- data/ext/ca_obj_bitarray.c +40 -26
- data/ext/ca_obj_bitfield.c +100 -68
- data/ext/ca_obj_block.c +19 -9
- data/ext/ca_obj_byte_swap.c +45 -46
- data/ext/ca_obj_const_string.c +269 -44
- data/ext/ca_obj_face.c +612 -89
- data/ext/ca_obj_face.h +111 -47
- data/ext/ca_obj_fake.c +36 -79
- data/ext/ca_obj_farray.c +7 -3
- data/ext/ca_obj_field.c +33 -8
- data/ext/ca_obj_fixlen_string.c +23 -24
- data/ext/ca_obj_grid.c +541 -210
- data/ext/ca_obj_meld.c +331 -111
- data/ext/ca_obj_moncmp.c +80 -65
- data/ext/ca_obj_monop.c +177 -149
- data/ext/ca_obj_object.c +664 -248
- data/ext/ca_obj_record.c +63 -23
- data/ext/ca_obj_reduce.c +18 -16
- data/ext/ca_obj_refer.c +140 -58
- data/ext/ca_obj_remap.c +85 -24
- data/ext/ca_obj_repeat.c +24 -9
- data/ext/ca_obj_roll.c +121 -16
- data/ext/ca_obj_select.c +61 -58
- data/ext/ca_obj_select_axis.c +73 -107
- data/ext/ca_obj_shift.c +32 -102
- data/ext/ca_obj_stack.c +372 -122
- data/ext/ca_obj_stride.c +348 -140
- data/ext/ca_obj_string.c +88 -22
- data/ext/ca_obj_tile.c +38 -21
- data/ext/ca_obj_time.c +45 -31
- data/ext/ca_obj_timedelta.c +43 -29
- data/ext/ca_obj_transpose.c +17 -6
- data/ext/ca_obj_triop.c +125 -141
- data/ext/ca_obj_window.c +330 -44
- data/ext/ca_op_ipower.c +24 -11
- data/ext/ca_op_powi.h +57 -30
- data/ext/ca_pow5_table.h +656 -0
- data/ext/ca_sort_kernels.h +70 -0
- data/ext/ca_sweep_engine.c +104 -81
- data/ext/ca_sweep_engine.h +7 -20
- data/ext/ca_triop_dispatch.c +4 -1
- data/ext/ca_triop_dispatch.h +5 -2
- data/ext/caframe_csv_reader.c +542 -0
- data/ext/carray.h +241 -92
- data/ext/carray_access.c +608 -276
- data/ext/carray_address_basis.c +494 -97
- data/ext/carray_attribute.c +129 -4
- data/ext/carray_bincount.c +39 -7
- data/ext/carray_broadcast.c +56 -19
- data/ext/carray_build_flags.h +1 -1
- data/ext/carray_call_cfunc.c +108 -28
- data/ext/carray_cast.c +1576 -223
- data/ext/carray_cast_func.rb +77 -24
- data/ext/carray_class.c +4 -3
- data/ext/carray_conversion.c +208 -75
- data/ext/carray_copy.c +82 -24
- data/ext/carray_core.c +646 -162
- data/ext/carray_count.c +171 -88
- data/ext/carray_element.c +94 -60
- data/ext/carray_factorize.c +670 -324
- data/ext/carray_generate.c +260 -83
- data/ext/carray_histogram.c +209 -106
- data/ext/carray_hold.c +14 -16
- data/ext/carray_index_classifier.c +422 -243
- data/ext/carray_internal.h +692 -3
- data/ext/carray_kernels_bincmp.c +7 -0
- data/ext/carray_kernels_binop.c +487 -120
- data/ext/carray_kernels_init.c +653 -50
- data/ext/carray_kernels_moncmp.c +15 -8
- data/ext/carray_kernels_monop.c +99 -43
- data/ext/carray_kernels_reduce_aggregate.c +3925 -6112
- data/ext/carray_kernels_reduce_boolean.c +112 -36
- data/ext/carray_kernels_reduce_cumulative.c +2127 -6903
- data/ext/carray_kernels_reduce_extreme.c +3032 -2892
- data/ext/carray_kernels_reduce_variance.c +439 -500
- data/ext/carray_kernels_scan.c +1097 -488
- data/ext/carray_kernels_search.c +3787 -2176
- data/ext/carray_kernels_sort.c +1200 -920
- data/ext/carray_kernels_triop.c +479 -7
- data/ext/carray_key_windows.c +216 -0
- data/ext/carray_lazy.c +967 -153
- data/ext/carray_loop.c +81 -65
- data/ext/carray_mask.c +910 -324
- data/ext/carray_math_kernel.h +1 -0
- data/ext/carray_median_percentile.c +299 -100
- data/ext/carray_memory_view.c +872 -758
- data/ext/carray_operator.c +1171 -364
- data/ext/carray_order.c +505 -133
- data/ext/carray_partition.c +112 -65
- data/ext/carray_random.c +327 -239
- data/ext/carray_round.c +273 -0
- data/ext/carray_scatter.c +346 -78
- data/ext/carray_segment.c +257 -0
- data/ext/carray_slab.c +249 -86
- data/ext/carray_sort.c +224 -227
- data/ext/carray_sort_kernel.c +8 -1
- data/ext/carray_struct.c +118 -39
- data/ext/carray_test.c +193 -69
- data/ext/carray_time_text.c +1182 -0
- data/ext/carray_undef.c +4 -0
- data/ext/carray_utils.c +579 -113
- data/ext/extconf.rb +27 -2
- data/ext/mk_call_cfunc.rb +19 -4
- data/ext/mk_pow5_table.rb +35 -0
- data/ext/mkkernel.rb +1202 -1014
- data/ext/ruby_carray.c +17 -5
- data/ext/version.h +4 -4
- data/ext/version.rb +4 -2
- data/lib/carray/attribute.rb +160 -17
- data/lib/carray/autoload_carray.rb +23 -27
- data/lib/carray/autoload_method_extension.rb +20 -5
- data/lib/carray/axis_group.rb +360 -63
- data/lib/carray/basics.rb +162 -28
- data/lib/carray/bincount_nd.rb +18 -2
- data/lib/carray/block_iterator.rb +231 -58
- data/lib/carray/boolean_reduce.rb +34 -42
- data/lib/carray/categorical.rb +242 -52
- data/lib/carray/categorical_iterator.rb +437 -827
- data/lib/carray/complex.rb +41 -34
- data/lib/carray/conditional.rb +150 -14
- data/lib/carray/const_string.rb +59 -4
- data/lib/carray/construct.rb +15 -11
- data/lib/carray/data_type_extension.rb +1 -0
- data/lib/carray/distance_limit.rb +77 -0
- data/lib/carray/fixlen_string.rb +7 -1
- data/lib/carray/frame/column_verbs.rb +70 -0
- data/lib/carray/frame/concat.rb +157 -6
- data/lib/carray/frame/convert.rb +82 -7
- data/lib/carray/frame/csv_parser.rb +688 -87
- data/lib/carray/frame/describe.rb +80 -0
- data/lib/carray/frame/frame.rb +369 -45
- data/lib/carray/frame/group.rb +631 -90
- data/lib/carray/frame/io.rb +506 -79
- data/lib/carray/frame/join.rb +411 -62
- data/lib/carray/frame/records.rb +116 -19
- data/lib/carray/frame/reshape.rb +455 -0
- data/lib/carray/frame/sort.rb +30 -18
- data/lib/carray/frame/verbs.rb +868 -61
- data/lib/carray/frame/windows.rb +117 -0
- data/lib/carray/frame.rb +4 -0
- data/lib/carray/fuse_source.rb +118 -2
- data/lib/carray/fusion.rb +257 -42
- data/lib/carray/histogram.rb +6 -1
- data/lib/carray/inspect.rb +54 -13
- data/lib/carray/iterator.rb +99 -9
- data/lib/carray/key_windows.rb +620 -0
- data/lib/carray/lazy.rb +216 -193
- data/lib/carray/mask_gap_fill.rb +112 -92
- data/lib/carray/math.rb +32 -19
- data/lib/carray/meld_reduce.rb +52 -21
- data/lib/carray/methods/align_addr.rb +8 -4
- data/lib/carray/methods/bin.rb +26 -18
- data/lib/carray/methods/bincount.rb +18 -2
- data/lib/carray/methods/bit_string.rb +15 -4
- data/lib/carray/methods/choose.rb +16 -3
- data/lib/carray/methods/composition.rb +18 -10
- data/lib/carray/methods/factorize.rb +2 -2
- data/lib/carray/methods/gather_nd.rb +25 -5
- data/lib/carray/methods/insert_block.rb +9 -14
- data/lib/carray/methods/is_between.rb +59 -0
- data/lib/carray/methods/is_in.rb +140 -10
- data/lib/carray/methods/join.rb +2 -7
- data/lib/carray/methods/lag.rb +49 -0
- data/lib/carray/methods/locate_addr.rb +182 -45
- data/lib/carray/methods/mode.rb +18 -1
- data/lib/carray/methods/nunique.rb +11 -6
- data/lib/carray/methods/repeat.rb +1 -20
- data/lib/carray/methods/resize.rb +21 -23
- data/lib/carray/methods/snap.rb +34 -12
- data/lib/carray/methods/unique.rb +11 -2
- data/lib/carray/methods/value_counts.rb +4 -11
- data/lib/carray/record_order.rb +208 -0
- data/lib/carray/rng.rb +3 -0
- data/lib/carray/runtime.rb +65 -17
- data/lib/carray/segment_iterator.rb +1221 -0
- data/lib/carray/serialize.rb +214 -30
- data/lib/carray/slab_iterator.rb +101 -27
- data/lib/carray/stack.rb +10 -4
- data/lib/carray/string.rb +105 -5
- data/lib/carray/string_operation_extension.rb +12 -4
- data/lib/carray/struct.rb +137 -58
- data/lib/carray/struct_builder.rb +124 -17
- data/lib/carray/time.rb +1222 -186
- data/lib/carray/window_iterator.rb +242 -81
- data/lib/carray.rb +9 -2
- data/yard-stubs/ca_obj_array.rb +4 -4
- data/yard-stubs/ca_obj_bitarray.rb +7 -0
- data/yard-stubs/ca_obj_bitfield.rb +23 -12
- data/yard-stubs/ca_obj_fake.rb +1 -1
- data/yard-stubs/ca_obj_field.rb +5 -1
- data/yard-stubs/ca_obj_grid.rb +21 -10
- data/yard-stubs/ca_obj_refer.rb +5 -4
- data/yard-stubs/ca_obj_roll.rb +2 -3
- data/yard-stubs/ca_obj_shift.rb +5 -1
- data/yard-stubs/ca_obj_stride.rb +10 -7
- data/yard-stubs/ca_obj_tile.rb +1 -1
- data/yard-stubs/ca_obj_transpose.rb +1 -1
- data/yard-stubs/ca_obj_window.rb +9 -5
- data/yard-stubs/carray_access.rb +11 -5
- data/yard-stubs/carray_attribute.rb +0 -5
- data/yard-stubs/carray_broadcast.rb +1 -1
- data/yard-stubs/carray_cast.rb +88 -43
- data/yard-stubs/carray_conversion.rb +15 -2
- data/yard-stubs/carray_copy.rb +12 -5
- data/yard-stubs/carray_core.rb +3 -3
- data/yard-stubs/carray_count.rb +9 -0
- data/yard-stubs/carray_element.rb +10 -4
- data/yard-stubs/carray_generate.rb +21 -1
- data/yard-stubs/carray_lazy.rb +5 -1
- data/yard-stubs/carray_loop.rb +4 -1
- data/yard-stubs/carray_mask.rb +60 -12
- data/yard-stubs/carray_math.rb +52 -14
- data/yard-stubs/carray_median_percentile.rb +15 -8
- data/yard-stubs/carray_memory_view.rb +40 -23
- data/yard-stubs/carray_order.rb +61 -29
- data/yard-stubs/carray_random.rb +13 -4
- data/yard-stubs/carray_scatter.rb +21 -9
- data/yard-stubs/carray_segment.rb +72 -0
- data/yard-stubs/carray_slab.rb +10 -2
- data/yard-stubs/carray_sort.rb +49 -8
- data/yard-stubs/carray_test.rb +15 -3
- data/yard-stubs/carray_utils.rb +0 -26
- metadata +19 -5
- data/ext/ca_for_each_element.h +0 -239
- data/ext/carray_kernels_map.c +0 -3467
- data/lib/carray/arrow_tensor.rb +0 -401
- data/lib/carray/table.rb +0 -108
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# CAFrame windows bounded by a width of a key column.
|
|
2
|
+
|
|
3
|
+
class CAFrame
|
|
4
|
+
# Moving windows bounded by a width of the key column +on+ (a time, a
|
|
5
|
+
# duration or a number) instead of a count of rows: the window of a row
|
|
6
|
+
# whose key is t holds the rows whose key lies between t - before and
|
|
7
|
+
# t + after. +exclude:+ leaves out the lower end (default), the upper end,
|
|
8
|
+
# both or neither, as CArray#is_between does. +by:+ names columns that
|
|
9
|
+
# must match exactly, so each window stays within one station; +interval:+
|
|
10
|
+
# gives the key's grid step for WindowedFrame#is_complete. The rows need
|
|
11
|
+
# not be sorted; the answers come in row order. A row whose key is
|
|
12
|
+
# masked, NaN or infinite is in no window and has no window.
|
|
13
|
+
#
|
|
14
|
+
# w = df.windows(on: "time", before: "1 hour", by: "station")
|
|
15
|
+
# w.mean("temp") # => CArray, one value per row
|
|
16
|
+
# w.mean # => frame of the windowed mean of every column
|
|
17
|
+
# w.elements # => the number of rows in each window
|
|
18
|
+
#
|
|
19
|
+
# Given +at:+, the windows sit at those points of the key instead of at
|
|
20
|
+
# the rows, and every answer is one per point: a frame of the reductions
|
|
21
|
+
# has a row per point, the points as its index and the on: column's name
|
|
22
|
+
# as its row axis name. The points need not be sorted or on the rows'
|
|
23
|
+
# keys. A grid of points gives windows on a grid; a window with no row
|
|
24
|
+
# answers as an empty reduction does (+mean+ UNDEF, +count+ 0). +at:+
|
|
25
|
+
# cannot be given with +by:+.
|
|
26
|
+
#
|
|
27
|
+
# grid = CArray.time_range(t0, t1, unit: "1 hour")
|
|
28
|
+
# df.windows(on: "time", at: grid, after: "3 hours", exclude: :upper).mean
|
|
29
|
+
# # => a frame with a row per hour, the mean over [t, t + 3 hours)
|
|
30
|
+
#
|
|
31
|
+
# @return [WindowedFrame]
|
|
32
|
+
def windows (on:, before: nil, after: nil, exclude: :lower, by: nil, interval: nil, at: nil)
|
|
33
|
+
on_name = column_key(on, "windows")
|
|
34
|
+
key = key_column(on_name, "windows")
|
|
35
|
+
by_names = by.nil? ? [] : column_keys(by.is_a?(Array) ? by : [by], "windows")
|
|
36
|
+
by_cols = by_names.map { |b| key_column(b, "windows") }
|
|
37
|
+
# The points are taken as they are now: a later write to the array
|
|
38
|
+
# given changes neither the windows nor a result's index.
|
|
39
|
+
at = at.copy if at.is_a?(CArray)
|
|
40
|
+
plan = CAKeyWindowIterator.new(CArray.int64(nrow).seq, on: key,
|
|
41
|
+
before: before, after: after, exclude: exclude,
|
|
42
|
+
by: by_cols.empty? ? nil : by_cols,
|
|
43
|
+
interval: interval, at: at)
|
|
44
|
+
WindowedFrame.new(self, plan, [on_name, *by_names], at: at)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The windows of a CAFrame (CAFrame#windows): the reductions over each row's
|
|
49
|
+
# window, one answer per row, and what each window covered.
|
|
50
|
+
class WindowedFrame
|
|
51
|
+
include CAFrame::ColumnVerbs
|
|
52
|
+
|
|
53
|
+
def initialize (frame, plan, key_names, at: nil)
|
|
54
|
+
@frame = frame
|
|
55
|
+
@plan = plan
|
|
56
|
+
@key_names = key_names
|
|
57
|
+
@at = at
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @overload [](name)
|
|
61
|
+
# Returns the CAKeyWindowIterator of one column.
|
|
62
|
+
# @return [CAKeyWindowIterator]
|
|
63
|
+
def [] (name)
|
|
64
|
+
@plan.for_value(@frame[@frame.__send__(:column_key, name, "windows")])
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# What each window covered, one value per row: see CAKeyWindowIterator.
|
|
68
|
+
[:first_addr, :last_addr, :first_key, :last_key, :elements, :is_complete].each do |m|
|
|
69
|
+
define_method(m, &Ractor.make_shareable(proc { @plan.public_send(m) }))
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @overload bounds
|
|
73
|
+
# Returns the range each window was asked to cover, as two arrays of the
|
|
74
|
+
# key's kind.
|
|
75
|
+
# @return [Array(CArray, CArray)]
|
|
76
|
+
def bounds
|
|
77
|
+
@plan.bounds
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# The windowed reductions. Without names, a frame of every column whose
|
|
81
|
+
# type has the reduction (the on: and by: columns left out), on the
|
|
82
|
+
# frame's rows and index; given names, the CArray (one) or the Array of
|
|
83
|
+
# them (several), as +df[...]+ returns columns.
|
|
84
|
+
CArray.send(:define_named_methods, self,
|
|
85
|
+
[:sum, :mean, :min, :max, :prod, :accumulate, :median,
|
|
86
|
+
:variance, :stddev, :variancep, :stddevp], :window_reduction)
|
|
87
|
+
|
|
88
|
+
CArray.send(:define_named_methods, self,
|
|
89
|
+
[:first, :last, :nunique, :count, :all, :any], :window_value)
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def window_reduction (reduction, *names, min_count: nil, fill_value: nil)
|
|
94
|
+
window_reduce(reduction, names, min_count: min_count, fill_value: fill_value)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def window_value (reduction, *names)
|
|
98
|
+
window_reduce(reduction, names)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# An N-D column (each row a profile) is reduced along the rows, each
|
|
102
|
+
# window keeping the trailing shape, as a group reduces it.
|
|
103
|
+
def window_reduce (reduction, names, **keywords)
|
|
104
|
+
keywords = keywords.compact
|
|
105
|
+
out = over_named_columns(reduction, names, @key_names) do |col|
|
|
106
|
+
@plan.for_value(col).public_send(reduction, **keywords)
|
|
107
|
+
end
|
|
108
|
+
return out unless names.empty?
|
|
109
|
+
if @at
|
|
110
|
+
# Each result has an index of its own, as a group's result has.
|
|
111
|
+
key = @frame.__send__(:key_column, @key_names.first, "windows")
|
|
112
|
+
CAFrame.new(out, axis_name: @key_names.first, index: CAFrame.__send__(:key_labels, key, @at.copy))
|
|
113
|
+
else
|
|
114
|
+
CAFrame.new(out, axis_name: @frame.axis_name, index: @frame.index)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
data/lib/carray/frame.rb
CHANGED
|
@@ -6,11 +6,15 @@
|
|
|
6
6
|
# devel/MEMO_DATAFRAME_ON_CARRAY.md for the design.
|
|
7
7
|
|
|
8
8
|
require 'carray/frame/frame'
|
|
9
|
+
require 'carray/frame/column_verbs'
|
|
9
10
|
require 'carray/frame/verbs'
|
|
10
11
|
require 'carray/frame/convert'
|
|
11
12
|
require 'carray/frame/io'
|
|
12
13
|
require 'carray/frame/records'
|
|
13
14
|
require 'carray/frame/group'
|
|
14
15
|
require 'carray/frame/join'
|
|
16
|
+
require 'carray/frame/windows'
|
|
15
17
|
require 'carray/frame/concat'
|
|
16
18
|
require 'carray/frame/sort'
|
|
19
|
+
require 'carray/frame/reshape'
|
|
20
|
+
require 'carray/frame/describe'
|
data/lib/carray/fuse_source.rb
CHANGED
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
# shadows back -- `fuse(a, b) { |x, y| ... }` -- which names each of them
|
|
12
12
|
# twice. Julia writes `@.` for the same reason and does the same thing to
|
|
13
13
|
# the expression underneath.
|
|
14
|
+
#
|
|
15
|
+
# Reading and rewriting a block gives the same text every time it is
|
|
16
|
+
# called, so it is done once per block -- per place in the source -- and
|
|
17
|
+
# kept. What is kept is a lambda that takes the block's free locals as
|
|
18
|
+
# arguments and is run with the block's `self`; a call only reads the
|
|
19
|
+
# locals.
|
|
14
20
|
# ---------------------------------------------------------------------------
|
|
15
21
|
|
|
16
22
|
require "prism"
|
|
@@ -26,13 +32,117 @@ class CArray
|
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
def self.evaluate (block)
|
|
29
|
-
result =
|
|
30
|
-
*block.source_location)
|
|
35
|
+
result = compiled(block).call(block)
|
|
31
36
|
# An expression that is just an array is that array; the shadow put
|
|
32
37
|
# around it has nothing to fuse.
|
|
33
38
|
result.is_a?(CALazyMarker) ? result.parent : result
|
|
34
39
|
end
|
|
35
40
|
|
|
41
|
+
# -- one compiled form per block --------------------------------------
|
|
42
|
+
|
|
43
|
+
# Keyed by the block's instruction sequence, which is the same object
|
|
44
|
+
# every time the same block is called and a different one for each
|
|
45
|
+
# block, even two on one line. Where Ruby has weak keys, a block whose
|
|
46
|
+
# code is unloaded takes its entry with it.
|
|
47
|
+
def self.new_sites
|
|
48
|
+
defined?(ObjectSpace::WeakKeyMap) ? ObjectSpace::WeakKeyMap.new
|
|
49
|
+
: {}.compare_by_identity
|
|
50
|
+
end
|
|
51
|
+
SITES = new_sites
|
|
52
|
+
|
|
53
|
+
# A Ractor other than the main one keeps its own table, and evaluates
|
|
54
|
+
# every block in its frame: the empty scope a lambda is compiled in is
|
|
55
|
+
# the top of a file, which only the main Ractor has.
|
|
56
|
+
def self.compiled (block)
|
|
57
|
+
site = RubyVM::InstructionSequence.of(block)
|
|
58
|
+
return compile(block) unless site
|
|
59
|
+
sites = Ractor.current == Ractor.main ? SITES : (Ractor.current[:carray_fuse_sites] ||= new_sites)
|
|
60
|
+
sites[site] || (sites[site] = compile(block))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# The block's free locals, handed to a lambda that was compiled without
|
|
64
|
+
# them. `self` is the block's own, so instance variables and methods
|
|
65
|
+
# are what they were.
|
|
66
|
+
Compiled = Struct.new(:locals, :lambda) do
|
|
67
|
+
def call (block)
|
|
68
|
+
scope = block.binding
|
|
69
|
+
values = locals.map { |name| scope.local_variable_get(name) }
|
|
70
|
+
scope.receiver.instance_exec(*values, &lambda)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# A block that needs its own frame -- it assigns to a local outside it,
|
|
75
|
+
# or yields, or asks for its binding -- is evaluated there every time;
|
|
76
|
+
# only the rewrite is kept.
|
|
77
|
+
InFrame = Struct.new(:text, :file, :line) do
|
|
78
|
+
def call (block)
|
|
79
|
+
eval(text, block.binding, file, line)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Calls that mean something only in the frame they are made from.
|
|
84
|
+
FRAME_BOUND = %i[binding local_variables block_given? __method__ __dir__
|
|
85
|
+
eval].freeze
|
|
86
|
+
|
|
87
|
+
FRAME_BOUND_NODES = [Prism::YieldNode, Prism::SuperNode,
|
|
88
|
+
Prism::ForwardingSuperNode, Prism::ReturnNode,
|
|
89
|
+
Prism::BreakNode, Prism::NextNode,
|
|
90
|
+
Prism::RedoNode].freeze
|
|
91
|
+
|
|
92
|
+
def self.compile (block)
|
|
93
|
+
body = body_source(block)
|
|
94
|
+
text = rewrite(body)
|
|
95
|
+
file, line = block.source_location
|
|
96
|
+
scope = block.binding
|
|
97
|
+
locals, needs_frame = free_locals(body, scope.local_variables)
|
|
98
|
+
return InFrame.new(text, file, line) if needs_frame || Ractor.current != Ractor.main
|
|
99
|
+
Compiled.new(locals, compile_lambda(text, locals, scope, file, line))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Read on its own, a body names a local from outside as a call with no
|
|
103
|
+
# receiver and no arguments, so every one of them is found this way. A
|
|
104
|
+
# body that assigns to one has to keep its frame.
|
|
105
|
+
def self.free_locals (body, outer)
|
|
106
|
+
reads = []
|
|
107
|
+
needs_frame = false
|
|
108
|
+
Prism.parse(body).value.breadth_first_search do |node|
|
|
109
|
+
case node
|
|
110
|
+
when Prism::CallNode
|
|
111
|
+
reads << node.name if node.variable_call? && outer.include?(node.name)
|
|
112
|
+
needs_frame = true if node.receiver.nil? && FRAME_BOUND.include?(node.name)
|
|
113
|
+
when *FRAME_BOUND_NODES
|
|
114
|
+
needs_frame = true
|
|
115
|
+
else
|
|
116
|
+
if node.class.name.match?(/\APrism::LocalVariable\w*(Write|Target)Node\z/) &&
|
|
117
|
+
outer.include?(node.name)
|
|
118
|
+
needs_frame = true
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
false
|
|
122
|
+
end
|
|
123
|
+
[reads.uniq, needs_frame]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Constants resolve as they do where the block was written: each module
|
|
127
|
+
# it sits in is reopened around the lambda, outermost first. The
|
|
128
|
+
# refinements active there are activated again. None of it sees the
|
|
129
|
+
# frame of the call that compiles it.
|
|
130
|
+
def self.compile_lambda (text, locals, scope, file, line)
|
|
131
|
+
nesting = scope.eval("Module.nesting").reverse
|
|
132
|
+
refinements = scope.eval("Module.used_modules")
|
|
133
|
+
Thread.current[:__carray_fuse_scope__] = [nesting, refinements]
|
|
134
|
+
code = refinements.each_index.map { |i|
|
|
135
|
+
"using Thread.current[:__carray_fuse_scope__][1][#{i}];"
|
|
136
|
+
}.join + "lambda { |#{locals.join(", ")}| #{text}\n}"
|
|
137
|
+
(nesting.size - 1).downto(0) do |i|
|
|
138
|
+
code = "Thread.current[:__carray_fuse_scope__][0][#{i}]" \
|
|
139
|
+
".module_eval(#{code.dump}, #{file.dump}, #{line})"
|
|
140
|
+
end
|
|
141
|
+
EMPTY_SCOPE.call.eval(code, file, line)
|
|
142
|
+
ensure
|
|
143
|
+
Thread.current[:__carray_fuse_scope__] = nil
|
|
144
|
+
end
|
|
145
|
+
|
|
36
146
|
# -- the block's own text ---------------------------------------------
|
|
37
147
|
|
|
38
148
|
def self.body_source (block)
|
|
@@ -121,3 +231,9 @@ class CArray
|
|
|
121
231
|
end
|
|
122
232
|
end
|
|
123
233
|
end
|
|
234
|
+
|
|
235
|
+
# A scope with no locals and no refinements in it, for compiling a block's
|
|
236
|
+
# expression where it can neither see nor hold on to the frame of the call
|
|
237
|
+
# that happens to compile it. Both are true only of the top of a file.
|
|
238
|
+
CArray::FuseSource::EMPTY_SCOPE = -> { binding }
|
|
239
|
+
CArray::FuseSource.private_constant :EMPTY_SCOPE
|
data/lib/carray/fusion.rb
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
# Turning a lazy expression into a plan a compiler can read.
|
|
3
3
|
#
|
|
4
4
|
# A lazy view is already a typed, closed expression graph, and the kernels
|
|
5
|
-
# already carry the C that computes each operation (CArray.__kernel_body__
|
|
5
|
+
# already carry the C that computes each operation (CArray.__kernel_body__,
|
|
6
|
+
# with CArray.__kernel_helpers__ for the functions a body calls beyond the
|
|
7
|
+
# C standard library).
|
|
6
8
|
# What is missing between them is the reading: which operation each node is,
|
|
7
9
|
# what its mask does, and where the leaves are. That is what a plan holds.
|
|
8
10
|
#
|
|
@@ -17,16 +19,54 @@ class CArray
|
|
|
17
19
|
|
|
18
20
|
# One value per node, in evaluation order; the last is the result.
|
|
19
21
|
#
|
|
20
|
-
# Op
|
|
21
|
-
# Leaf
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
# Op an operation, reading the nodes named in `args`
|
|
23
|
+
# Leaf an array, the `index`-th of plan.leaves, read at the cell
|
|
24
|
+
# being computed
|
|
25
|
+
# Shifted the same, read `offset` cells away along each axis -- what
|
|
26
|
+
# CArray#shift makes. A cell that falls outside the array is
|
|
27
|
+
# `fill` where `bounds` says :fill and masked where it says
|
|
28
|
+
# :mask. `masked` says whether the array itself has a mask.
|
|
29
|
+
# Const a scalar written into the expression
|
|
30
|
+
#
|
|
31
|
+
# A Shifted node reads cells other than the one being computed, so an
|
|
32
|
+
# evaluator that writes its output over one of the plan's leaves must
|
|
33
|
+
# not compute one that is read shifted: a cell it reads may already
|
|
34
|
+
# have been written.
|
|
35
|
+
#
|
|
36
|
+
# A comparison's body is the one for the data type it compares; what it
|
|
37
|
+
# answers is boolean, which is the node's data_type.
|
|
38
|
+
# An Op's fields:
|
|
39
|
+
#
|
|
40
|
+
# kind :monop, :binop, :triop, :moncmp or :bincmp
|
|
41
|
+
# name the operation as the kernels name it (:add, :sqrt, :ipow)
|
|
42
|
+
# args indices of the nodes it reads, in operand order
|
|
43
|
+
# body its C, from CArray.__kernel_body__: `#1`, `#2`, ... are
|
|
44
|
+
# the operands and the next number is the result; `<type>`
|
|
45
|
+
# is CArray's name for the type (float64_t)
|
|
46
|
+
# mask how the result is masked: :pass (as its one operand),
|
|
47
|
+
# :union (where any operand is), :kleene_or / :kleene_and
|
|
48
|
+
# (boolean | and &, unmasked where the known side settles it),
|
|
49
|
+
# :select (where the first operand is, else where the
|
|
50
|
+
# operand it chooses is: the second where it is true, the
|
|
51
|
+
# third where it is false)
|
|
52
|
+
# trapping true where a masked cell must not be computed at all
|
|
53
|
+
#
|
|
54
|
+
# What a reader may rely on: the node classes, the kinds, the names, the
|
|
55
|
+
# mask rules and the fields above keep the meaning they have here. New
|
|
56
|
+
# ones are added -- a node class, a kind, an operation, a mask rule --
|
|
57
|
+
# and an evaluator that meets one it does not know declines the plan
|
|
58
|
+
# (returns false). It does not raise: raising takes it out of service
|
|
59
|
+
# for the rest of the process. Declining is what lets CArray add to
|
|
60
|
+
# the plan without a new evaluator being released at the same time.
|
|
61
|
+
Op = Struct.new(:kind, :name, :data_type, :args, :body, :mask, :trapping)
|
|
62
|
+
Leaf = Struct.new(:index, :data_type, :masked)
|
|
63
|
+
Shifted = Struct.new(:index, :data_type, :masked, :offset, :bounds, :fill)
|
|
64
|
+
Const = Struct.new(:value, :data_type)
|
|
26
65
|
|
|
27
66
|
Plan = Struct.new(:nodes, :leaves, :data_type, :dim, :masked, :signature)
|
|
28
67
|
|
|
29
68
|
LAZY_CLASSES = [CAMonOp, CABinOp, CATriOp, CAMonCmp, CABinCmp, CALazyMarker]
|
|
69
|
+
Ractor.make_shareable(LAZY_CLASSES)
|
|
30
70
|
|
|
31
71
|
# A lazy node names its operation by an id and the kernels name it by a
|
|
32
72
|
# symbol. These are the same operations, spelled the way each side
|
|
@@ -37,17 +77,21 @@ class CArray
|
|
|
37
77
|
:& => :bit_and_i, :| => :bit_or_i, :^ => :bit_xor_i,
|
|
38
78
|
:<< => :bit_lshift, :>> => :bit_rshift,
|
|
39
79
|
}.freeze
|
|
40
|
-
TRIOP_NAMES = { :
|
|
80
|
+
TRIOP_NAMES = { :clamp_between => :clip }.freeze
|
|
41
81
|
|
|
42
82
|
MONOP_BY_ID = CArray::LAZY_MONOP_OP_IDS.invert.freeze
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
#
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
83
|
+
# A Float or Complex array to an Integer power is its own node, ipow,
|
|
84
|
+
# which `**` makes and no table above names.
|
|
85
|
+
BINOP_BY_ID = CArray::LAZY_BINOP_OP_IDS.invert
|
|
86
|
+
.merge(CABinOp::OP_IPOWER => :ipow).freeze
|
|
87
|
+
# The lazy then_else is its own triop, select, which no method names.
|
|
88
|
+
TRIOP_BY_ID = CArray::LAZY_TRIOP_OP_IDS.invert
|
|
89
|
+
.merge(CATriOp::OP_SELECT => :select).freeze
|
|
90
|
+
MONCMP_BY_ID = CArray::LAZY_MONCMP_OP_IDS.invert.freeze
|
|
91
|
+
# The table holds both spellings of each comparison (`lt` and `<`); the
|
|
92
|
+
# kernels are named by the word.
|
|
93
|
+
BINCMP_BY_ID = CArray::LAZY_BINCMP_OP_IDS
|
|
94
|
+
.select { |name, _| name.to_s.match?(/\A[a-z]/) }.invert.freeze
|
|
51
95
|
|
|
52
96
|
class Refused < StandardError; end
|
|
53
97
|
|
|
@@ -74,29 +118,100 @@ class CArray
|
|
|
74
118
|
|
|
75
119
|
# Returns the array, or nil where nothing computed it.
|
|
76
120
|
def self.evaluate (view)
|
|
77
|
-
|
|
78
|
-
out = CArray.__alloc_uninit__(view.data_type, view.
|
|
79
|
-
|
|
121
|
+
plan = worth_asking(view) or return nil
|
|
122
|
+
out = CArray.__alloc_uninit__(view.data_type, view.shape)
|
|
123
|
+
ask(plan, out) ? out : nil
|
|
80
124
|
end
|
|
81
125
|
|
|
82
126
|
# Fills an array the caller already has. Called from the store as well,
|
|
83
127
|
# where making one and copying it over would be most of the work.
|
|
84
128
|
# Returns true when something computed it.
|
|
85
129
|
def self.evaluate_into (view, out)
|
|
130
|
+
plan = worth_asking(view) or return false
|
|
131
|
+
ask(plan, out)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# The plan to hand over, or nil. A marker over an array, or anything
|
|
135
|
+
# else with nothing to compute, is not worth handing over. Nothing here
|
|
136
|
+
# is the evaluator's doing, so nothing here retires it: an array that
|
|
137
|
+
# raises when it is looked at (a CAObject whose backing is gone) raises
|
|
138
|
+
# again in the walk, where the caller sees it.
|
|
139
|
+
def self.worth_asking (view)
|
|
140
|
+
return nil unless askable?(view)
|
|
141
|
+
plan = plan(view) or return nil
|
|
142
|
+
plan.nodes.any? { |n| n.is_a?(Op) } ? plan : nil
|
|
143
|
+
rescue StandardError
|
|
144
|
+
nil
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def self.ask (plan, out)
|
|
86
148
|
evaluator = CArray.expression_evaluator or return false
|
|
87
|
-
return false
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
149
|
+
return false if overlaps?(plan, out)
|
|
150
|
+
return false unless ready_mask(plan, out)
|
|
151
|
+
begin
|
|
152
|
+
evaluator.call(plan, out) ? true : false
|
|
153
|
+
rescue AddressBasis::Failure
|
|
154
|
+
# Raised by CArray lending the arrays or taking them back: a refusal,
|
|
155
|
+
# or an array that cannot be read. The walk meets the same array.
|
|
156
|
+
false
|
|
157
|
+
rescue StandardError => error
|
|
158
|
+
retire(error)
|
|
159
|
+
false
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# The walk leaves the destination masked where the expression is, so a
|
|
164
|
+
# destination that arrives masked loses its mask to an expression that
|
|
165
|
+
# has none. A destination that cannot hold a mask (a value array) is
|
|
166
|
+
# left to the walk, which says so.
|
|
167
|
+
def self.ready_mask (plan, out)
|
|
168
|
+
if plan.masked
|
|
169
|
+
out.mask = 0 unless out.has_mask?
|
|
170
|
+
elsif out.has_mask?
|
|
171
|
+
out.mask = 0
|
|
172
|
+
end
|
|
173
|
+
true
|
|
174
|
+
rescue StandardError
|
|
175
|
+
false
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Whether the destination shares storage with a leaf it would read. An
|
|
179
|
+
# evaluator computes cell by cell, so writing a cell another leaf reads
|
|
180
|
+
# later -- `z[] = y + y` with z and y overlapping slices of one array --
|
|
181
|
+
# reads what it has just written. The walk reads every operand before
|
|
182
|
+
# it writes. The destination itself as a leaf is fine as long as it is
|
|
183
|
+
# read at the cell being written, not shifted.
|
|
184
|
+
def self.overlaps? (plan, out)
|
|
185
|
+
mine = storages(out)
|
|
186
|
+
shifted = plan.nodes.grep(Shifted).map(&:index)
|
|
187
|
+
plan.leaves.each_with_index.any? do |leaf, index|
|
|
188
|
+
if leaf.equal?(out)
|
|
189
|
+
shifted.include?(index)
|
|
190
|
+
else
|
|
191
|
+
storages(leaf).any? { |r| mine.any? { |m| m.send(:storage_overlaps?, r) } }
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# The arrays that hold an array's cells: its root, or each parent's
|
|
197
|
+
# where a view stands over several. Two of them share storage when
|
|
198
|
+
# they are one array, or entities whose memory overlaps (an array and a
|
|
199
|
+
# wrap_memory_view of it).
|
|
200
|
+
def self.storages (array, found = [])
|
|
201
|
+
root = array.root_array
|
|
202
|
+
if ! root.entity? && root.respond_to?(:parents)
|
|
203
|
+
root.parents.each { |parent| storages(parent, found) }
|
|
204
|
+
else
|
|
205
|
+
found << root
|
|
206
|
+
end
|
|
207
|
+
found
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def self.retire (error)
|
|
95
211
|
CArray.expression_evaluator = nil
|
|
96
212
|
warn "CArray: the registered expression evaluator raised " \
|
|
97
213
|
"(#{error.class}: #{error.message}); expressions will be walked " \
|
|
98
214
|
"from here on"
|
|
99
|
-
false
|
|
100
215
|
end
|
|
101
216
|
|
|
102
217
|
def self.askable? (view)
|
|
@@ -115,8 +230,9 @@ class CArray
|
|
|
115
230
|
raise Refused, "not a lazy expression" unless lazy?(view)
|
|
116
231
|
w = Walk.new
|
|
117
232
|
w.visit(view)
|
|
118
|
-
|
|
119
|
-
w.
|
|
233
|
+
masked = w.leaves.any? { |a| a.has_mask? } ||
|
|
234
|
+
w.nodes.any? { |n| n.is_a?(Shifted) && n.bounds.include?(:mask) }
|
|
235
|
+
Plan.new(w.nodes, w.leaves, view.data_type, view.shape, masked, w.signature)
|
|
120
236
|
end
|
|
121
237
|
|
|
122
238
|
def self.lazy? (x)
|
|
@@ -131,6 +247,7 @@ class CArray
|
|
|
131
247
|
def initialize
|
|
132
248
|
@nodes = []
|
|
133
249
|
@leaves = []
|
|
250
|
+
@leaf_index = {}
|
|
134
251
|
@seen = {}
|
|
135
252
|
@signature = +""
|
|
136
253
|
end
|
|
@@ -147,6 +264,9 @@ class CArray
|
|
|
147
264
|
when CAMonOp then unary(n)
|
|
148
265
|
when CABinOp then binary(n)
|
|
149
266
|
when CATriOp then ternary(n)
|
|
267
|
+
when CAMonCmp then unary_comparison(n)
|
|
268
|
+
when CABinCmp then binary_comparison(n)
|
|
269
|
+
when CAShift then shifted(n)
|
|
150
270
|
when CScalar then constant(n)
|
|
151
271
|
when CArray then leaf(n)
|
|
152
272
|
else raise Refused, "#{n.class} in an expression"
|
|
@@ -154,11 +274,24 @@ class CArray
|
|
|
154
274
|
end
|
|
155
275
|
|
|
156
276
|
def unary (n)
|
|
277
|
+
return conversion(n) if n.__op_id__ >= CAMonOp::CAST_BASE
|
|
157
278
|
name = spell(MONOP_BY_ID, n.__op_id__, {})
|
|
158
279
|
args = [visit(n.parent)]
|
|
159
280
|
# A view over one array is masked exactly where that array is
|
|
160
281
|
# (ca_obj_monop.c).
|
|
161
|
-
op(:monop, name, n.data_type, args, :pass)
|
|
282
|
+
op(:monop, name, n.data_type, args, :pass, n.__trapping__)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# A cast node is named by the type it converts to, cast_<data_type>,
|
|
286
|
+
# and its body is keyed by the type it converts from.
|
|
287
|
+
def conversion (n)
|
|
288
|
+
from = n.parent.data_type
|
|
289
|
+
name = :"cast_#{n.data_type}"
|
|
290
|
+
body = CArray.__kernel_body__(:monop, name, from) or
|
|
291
|
+
raise Refused, "monop #{name} has no body at #{from}"
|
|
292
|
+
args = [visit(n.parent)]
|
|
293
|
+
note("c", name, from, args.join(","))
|
|
294
|
+
push Op.new(:monop, name, n.data_type, args, body, :pass, n.__trapping__)
|
|
162
295
|
end
|
|
163
296
|
|
|
164
297
|
def binary (n)
|
|
@@ -170,32 +303,112 @@ class CArray
|
|
|
170
303
|
elsif n.data_type == :boolean && name == :bit_and_i then :kleene_and
|
|
171
304
|
else :union
|
|
172
305
|
end
|
|
173
|
-
op(:binop, name, n.data_type, args, rule)
|
|
306
|
+
op(:binop, name, n.data_type, args, rule, n.__trapping__)
|
|
174
307
|
end
|
|
175
308
|
|
|
176
309
|
def ternary (n)
|
|
177
310
|
name = spell(TRIOP_BY_ID, n.__op_id__, TRIOP_NAMES)
|
|
178
311
|
args = [visit(n.parent), visit(n.__triop_op2__), visit(n.__triop_op3__)]
|
|
179
|
-
|
|
312
|
+
# select is masked where its condition is, or where the branch the
|
|
313
|
+
# condition chooses is (ca_obj_triop.c).
|
|
314
|
+
rule = name == :select ? :select : :union
|
|
315
|
+
op(:triop, name, n.data_type, args, rule, n.__trapping__)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# A comparison is masked where its operands are (ca_obj_moncmp.c,
|
|
319
|
+
# ca_obj_bincmp.c): there is no three-valued comparison.
|
|
320
|
+
def unary_comparison (n)
|
|
321
|
+
name = spell(MONCMP_BY_ID, n.__op_id__, {})
|
|
322
|
+
operand = n.parent
|
|
323
|
+
args = [visit(operand)]
|
|
324
|
+
comparison(:moncmp, name, operand.data_type, args, :pass, n.__trapping__)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def binary_comparison (n)
|
|
328
|
+
name = spell(BINCMP_BY_ID, n.__op_id__, {})
|
|
329
|
+
right = n.__bincmp_right__
|
|
330
|
+
# Both sides are already of the type compared (CABinCmp casts one
|
|
331
|
+
# to the other); a pair that is not has no single body.
|
|
332
|
+
unless n.parent.data_type == right.data_type
|
|
333
|
+
raise Refused, "#{name} between #{n.parent.data_type} and #{right.data_type}"
|
|
334
|
+
end
|
|
335
|
+
args = [visit(n.parent), visit(right)]
|
|
336
|
+
comparison(:bincmp, name, n.parent.data_type, args, :union, n.__trapping__)
|
|
180
337
|
end
|
|
181
338
|
|
|
182
|
-
|
|
339
|
+
# `trapping`: the node skips the cells its operands mask rather than
|
|
340
|
+
# computing them -- one that can raise on a cell or calls Ruby for it.
|
|
341
|
+
# Each lazy view answers it from the rule its own kernel follows.
|
|
342
|
+
def comparison (kind, name, compared, args, mask, trapping)
|
|
343
|
+
body = CArray.__kernel_body__(kind, name, compared) or
|
|
344
|
+
raise Refused, "#{kind} #{name} has no body at #{compared}"
|
|
345
|
+
# A body written against its compared type or a tolerance (`feq`)
|
|
346
|
+
# needs more than its operands substituted.
|
|
347
|
+
if body.match?(/<\w+>|\btol\b/)
|
|
348
|
+
raise Refused, "#{kind} #{name} needs more than its operands"
|
|
349
|
+
end
|
|
350
|
+
note(kind.to_s[0..3], name, compared, args.join(","))
|
|
351
|
+
push Op.new(kind, name, :boolean, args, body, mask, trapping)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def op (kind, name, type, args, mask, trapping)
|
|
183
355
|
body = CArray.__kernel_body__(kind, name, type) or
|
|
184
356
|
raise Refused, "#{kind} #{name} has no body at #{type}"
|
|
185
|
-
note(kind.to_s[0], name, type)
|
|
186
|
-
push Op.new(kind, name, type, args, body, mask,
|
|
187
|
-
TRAPPING.include?(name) && INTEGERS.include?(type))
|
|
357
|
+
note(kind.to_s[0], name, type, args.join(","))
|
|
358
|
+
push Op.new(kind, name, type, args, body, mask, trapping)
|
|
188
359
|
end
|
|
189
360
|
|
|
190
361
|
def leaf (n)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
push Leaf.new(
|
|
362
|
+
index = leaf_index(n)
|
|
363
|
+
note("a", index, n.data_type, n.has_mask? ? 1 : 0)
|
|
364
|
+
push Leaf.new(index, n.data_type, n.has_mask?)
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# A shift of an array is that array read elsewhere, so it is the
|
|
368
|
+
# array that becomes a leaf. A shift of anything else -- an
|
|
369
|
+
# expression, say -- has no array to read and is taken as a whole.
|
|
370
|
+
def shifted (n)
|
|
371
|
+
array = n.parent
|
|
372
|
+
bounds = n.__boundary_rule_of_each_axis__
|
|
373
|
+
if Fusion.lazy?(array) || array.shape != n.shape ||
|
|
374
|
+
! bounds.all? { |b| b == :fill || b == :mask }
|
|
375
|
+
return leaf(n)
|
|
376
|
+
end
|
|
377
|
+
offset = n.start
|
|
378
|
+
# The fill as a cell of the array holds it: a boolean array is
|
|
379
|
+
# filled with true or false, not with the Integer it was given.
|
|
380
|
+
fill = bounds.include?(:fill) ? as_cell(n.fill_value, array.data_type) : nil
|
|
381
|
+
index = leaf_index(array)
|
|
382
|
+
note("s", index, array.data_type, array.has_mask? ? 1 : 0,
|
|
383
|
+
offset.join(","), bounds.join(","), fill.inspect)
|
|
384
|
+
push Shifted.new(index, array.data_type, array.has_mask?,
|
|
385
|
+
offset, bounds, fill)
|
|
194
386
|
end
|
|
195
387
|
|
|
388
|
+
# A leaf the address bases would refuse to lend cannot be read by an
|
|
389
|
+
# evaluator, so the expression is not described.
|
|
390
|
+
def leaf_index (array)
|
|
391
|
+
@leaf_index.fetch(array.object_id) do
|
|
392
|
+
if (reason = AddressBasis.classify(array)[:refusal])
|
|
393
|
+
raise Refused, "a leaf: #{reason}"
|
|
394
|
+
end
|
|
395
|
+
@leaves << array
|
|
396
|
+
@leaf_index[array.object_id] = @leaves.size - 1
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# A masked scalar has no value to write into the expression.
|
|
196
401
|
def constant (n)
|
|
197
|
-
|
|
198
|
-
|
|
402
|
+
raise Refused, "a masked scalar" if n.has_mask? && n.is_masked[0]
|
|
403
|
+
value = n[0]
|
|
404
|
+
note("k", value.inspect, n.data_type)
|
|
405
|
+
push Const.new(value, n.data_type)
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
def as_cell (value, data_type)
|
|
409
|
+
cell = CScalar.new(data_type)
|
|
410
|
+
cell[0] = value
|
|
411
|
+
cell[0]
|
|
199
412
|
end
|
|
200
413
|
|
|
201
414
|
def push (node)
|
|
@@ -210,6 +423,8 @@ class CArray
|
|
|
210
423
|
|
|
211
424
|
# Two expressions of the same shape compute alike, whatever arrays
|
|
212
425
|
# they are over, so a consumer can keep one compiled kernel for both.
|
|
426
|
+
# The shape includes which node each operation reads and which leaf
|
|
427
|
+
# each read is: `(a - b) - b` and `(a - b) - a` differ only there.
|
|
213
428
|
def note (*parts)
|
|
214
429
|
@signature << parts.join(":") << ";"
|
|
215
430
|
end
|