carray-jit 0.1.5 → 0.1.7
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 +51 -0
- data/README.md +2 -2
- data/carray-jit.gemspec +4 -2
- data/docs/02_KernelShapes.md +2 -2
- data/docs/03_SupportedFeatures.md +2 -2
- data/docs/04_Compiling.md +5 -5
- data/docs/08_LanguageReference.md +2 -2
- data/lib/carray/jit/analyzer.rb +1 -1
- data/lib/carray/jit/c_function.rb +140 -139
- data/lib/carray/jit/c_generator.rb +123 -24
- data/lib/carray/jit/compiler.rb +44 -26
- data/lib/carray/jit/expression.rb +79 -29
- data/lib/carray/jit/kernel.rb +23 -47
- data/lib/carray/jit/native.rb +149 -0
- data/lib/carray/jit/sweep.rb +14 -64
- data/lib/carray/jit/version.rb +1 -1
- data/lib/carray/jit.rb +96 -19
- metadata +7 -6
data/lib/carray/jit/kernel.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require "
|
|
1
|
+
require "carray/jit/native"
|
|
2
2
|
require "carray/jit/sweep"
|
|
3
3
|
|
|
4
4
|
class CArray
|
|
@@ -7,8 +7,9 @@ class CArray
|
|
|
7
7
|
# A compiled kernel, bound to one set of array data types and one set of
|
|
8
8
|
# scalar types.
|
|
9
9
|
#
|
|
10
|
-
# Every kernel has the same C signature, so the
|
|
11
|
-
# fixed and the per-kernel detail travels in the six
|
|
10
|
+
# Every kernel has the same C signature, so the shape of the function
|
|
11
|
+
# Native makes is fixed and the per-kernel detail travels in the six
|
|
12
|
+
# buffers.
|
|
12
13
|
class CompiledKernel
|
|
13
14
|
|
|
14
15
|
# @!attribute [r] source
|
|
@@ -54,6 +55,12 @@ class CArray
|
|
|
54
55
|
@rank = analyzer.rank
|
|
55
56
|
@index_names = analyzer.index_names
|
|
56
57
|
@written_arrays = analyzer.written_arrays
|
|
58
|
+
# Which operands the sweep writes, in CArray's spelling: one character
|
|
59
|
+
# per operand. Decided by the body, so spelled once rather than on
|
|
60
|
+
# every call.
|
|
61
|
+
@fsync = @arrays.map { |name|
|
|
62
|
+
@written_arrays.include?(name) ? "1" : "0"
|
|
63
|
+
}.join.freeze
|
|
57
64
|
@array_ranks = analyzer.array_ranks
|
|
58
65
|
@inner_ranges = analyzer.inner_ranges
|
|
59
66
|
@unbounded_indices = analyzer.unbounded_indices
|
|
@@ -126,9 +133,8 @@ class CArray
|
|
|
126
133
|
@handle, @compiled = Compiler.build(generator.generate_slab(SLAB_NAME),
|
|
127
134
|
CGenerator::FUNCTION_NAME,
|
|
128
135
|
header: generator.provenance)
|
|
129
|
-
@function =
|
|
130
|
-
|
|
131
|
-
Fiddle::TYPE_VOID)
|
|
136
|
+
@function = Native.function(Native.address(@handle, CGenerator::FUNCTION_NAME),
|
|
137
|
+
[:pointer] * 10, :void, release_gvl: true)
|
|
132
138
|
end
|
|
133
139
|
|
|
134
140
|
# `bounds` is one [start, limit, step] per axis.
|
|
@@ -209,9 +215,6 @@ class CArray
|
|
|
209
215
|
# arguments the kernel's own loop takes are the ones a chunk arrives as.
|
|
210
216
|
def sweep (array_values, scalar_values, c_function_values = {})
|
|
211
217
|
arrays = @arrays.map { |name| array_values.fetch(name) }
|
|
212
|
-
fsync = @arrays.map { |name|
|
|
213
|
-
@written_arrays.include?(name) ? "1" : "0"
|
|
214
|
-
}.join
|
|
215
218
|
|
|
216
219
|
error = [0].pack("l")
|
|
217
220
|
reals = packed_reals(scalar_values)
|
|
@@ -233,12 +236,10 @@ class CArray
|
|
|
233
236
|
# signature has the slots.
|
|
234
237
|
masks = ("\0" * 8)
|
|
235
238
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
slots.last[0, error.bytesize]
|
|
241
|
-
}
|
|
239
|
+
# The slab writes the error slot in `error` itself.
|
|
240
|
+
Sweep.run(slab_pointer, @fsync, arrays,
|
|
241
|
+
buffer(reals), buffer(integers), buffer(functions),
|
|
242
|
+
buffer(data), masks, masks, error)
|
|
242
243
|
|
|
243
244
|
packed.each_with_index do |(array, item), index|
|
|
244
245
|
next if array.equal?(item)
|
|
@@ -332,7 +333,7 @@ class CArray
|
|
|
332
333
|
@shape_scalar_names.map { |name| scalars[name] }]
|
|
333
334
|
end
|
|
334
335
|
|
|
335
|
-
# A kernel is shared, and
|
|
336
|
+
# A kernel is shared, and its call lets go of the GVL, so two
|
|
336
337
|
# threads can be reading one plan while a third prepares another. That
|
|
337
338
|
# is sound only while nothing in a plan is written after it is published
|
|
338
339
|
# -- and a box is arrays inside an array, so freezing the top of it
|
|
@@ -456,7 +457,7 @@ class CArray
|
|
|
456
457
|
end
|
|
457
458
|
|
|
458
459
|
def slab_pointer
|
|
459
|
-
@slab_pointer ||= @handle
|
|
460
|
+
@slab_pointer ||= Native.address(@handle, SLAB_NAME)
|
|
460
461
|
end
|
|
461
462
|
|
|
462
463
|
# Resolved when the frame is first walked rather than at build time: a
|
|
@@ -464,8 +465,8 @@ class CArray
|
|
|
464
465
|
# runs its interior first.
|
|
465
466
|
def border_function
|
|
466
467
|
@border_function ||=
|
|
467
|
-
|
|
468
|
-
|
|
468
|
+
Native.function(Native.address(@handle, CGenerator::BORDER_NAME),
|
|
469
|
+
[:pointer] * 10, :void, release_gvl: true)
|
|
469
470
|
end
|
|
470
471
|
|
|
471
472
|
# What a C function may be handed as a pointer, and what has to be true
|
|
@@ -523,7 +524,7 @@ class CArray
|
|
|
523
524
|
|
|
524
525
|
def address_buffer (name, array)
|
|
525
526
|
@address_parameters.fetch(name).each do |parameter|
|
|
526
|
-
wanted = CDeclaration::DATA_TYPES.fetch(parameter.element.
|
|
527
|
+
wanted = CDeclaration::DATA_TYPES.fetch(parameter.element.abi)
|
|
527
528
|
unless array.data_type_name == wanted.to_s
|
|
528
529
|
raise Unsupported,
|
|
529
530
|
"`#{name}` is handed to a C function as `#{parameter.text}`, " \
|
|
@@ -553,37 +554,12 @@ class CArray
|
|
|
553
554
|
Access.classify(array)[:entity] ? array : array.copy
|
|
554
555
|
end
|
|
555
556
|
|
|
556
|
-
# A pointer into an empty string is not something
|
|
557
|
-
# and a kernel with no scalars has an empty buffer.
|
|
557
|
+
# A pointer into an empty string is not something Native is asked to
|
|
558
|
+
# hand over, and a kernel with no scalars has an empty buffer.
|
|
558
559
|
def buffer (packed)
|
|
559
560
|
packed.empty? ? "\0" * 8 : packed
|
|
560
561
|
end
|
|
561
562
|
|
|
562
|
-
# Copies `parts` into one block of C memory, each part 8-byte aligned,
|
|
563
|
-
# after a table of their addresses, and yields a pointer to the table
|
|
564
|
-
# and a pointer to each part; the block is freed when this returns.
|
|
565
|
-
#
|
|
566
|
-
# For a call that is handed addresses inside other memory rather than
|
|
567
|
-
# arguments: a Ruby String's bytes may be moved by GC compaction, and
|
|
568
|
-
# an address taken from one and packed elsewhere is not one Ruby knows
|
|
569
|
-
# to keep in place. Memory from malloc is never moved.
|
|
570
|
-
def in_one_block (parts)
|
|
571
|
-
table = parts.size * Fiddle::SIZEOF_VOIDP
|
|
572
|
-
offsets = parts.each_with_object([]) { |part, list|
|
|
573
|
-
last = list.empty? ? table : list.last + parts[list.size - 1].bytesize
|
|
574
|
-
list << ((last + 7) & ~7)
|
|
575
|
-
}
|
|
576
|
-
size = offsets.last + parts.last.bytesize
|
|
577
|
-
Fiddle::Pointer.malloc(size, Fiddle::RUBY_FREE) do |block|
|
|
578
|
-
slots = parts.zip(offsets).map { |part, offset|
|
|
579
|
-
block[offset, part.bytesize] = part
|
|
580
|
-
block + offset
|
|
581
|
-
}
|
|
582
|
-
block[0, table] = slots.map(&:to_i).pack("Q*")
|
|
583
|
-
yield block, slots
|
|
584
|
-
end
|
|
585
|
-
end
|
|
586
|
-
|
|
587
563
|
# The xfer tier transfers a box rather than the whole view. The box
|
|
588
564
|
# is the loop range grown by how far the kernel reaches from the cell it
|
|
589
565
|
# is on -- per array, because two arrays in one kernel need not be read
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
require "fiddle"
|
|
2
|
+
require "fiddle/import"
|
|
3
|
+
require "carray/jit/errors"
|
|
4
|
+
|
|
5
|
+
class CArray
|
|
6
|
+
module JIT
|
|
7
|
+
|
|
8
|
+
# Everything this library asks of the layer that reaches C from Ruby, and
|
|
9
|
+
# the one place that layer is named. The rest of the library says what
|
|
10
|
+
# it wants -- open this object, find this symbol, make a function of
|
|
11
|
+
# these types, read these bytes -- in the words below, and only this
|
|
12
|
+
# file knows that Fiddle is what answers.
|
|
13
|
+
#
|
|
14
|
+
# That is so the layer can be replaced by changing one file. Fiddle does
|
|
15
|
+
# not let a call be made from a Ractor other than the main one, and a
|
|
16
|
+
# layer that does would be a second version of this module rather than a
|
|
17
|
+
# second version of the library (devel/PROPOSAL_FFI_SEAM.md).
|
|
18
|
+
#
|
|
19
|
+
# What is asked:
|
|
20
|
+
#
|
|
21
|
+
# - a shared object opened, and the libraries already loaded searched
|
|
22
|
+
# - a symbol's address found in one
|
|
23
|
+
# - a function made from an address and its types, and called
|
|
24
|
+
# - bytes read and written at an address
|
|
25
|
+
# - a Ruby object handed to C as the VALUE it is
|
|
26
|
+
#
|
|
27
|
+
# A function is called by calling what #function returns: its #call is
|
|
28
|
+
# the layer's own, so nothing stands between a kernel and the C beyond
|
|
29
|
+
# what the layer itself costs. The arguments it takes for a `:pointer`
|
|
30
|
+
# are an Integer address, a String -- whose bytes are what C is handed --
|
|
31
|
+
# or nil.
|
|
32
|
+
# @private
|
|
33
|
+
module Native
|
|
34
|
+
|
|
35
|
+
# The C types a function is made with, by this library's names for
|
|
36
|
+
# them. `:pointer` is any pointer; a function's arguments say what it
|
|
37
|
+
# points at only to the C, which is all that needs to know.
|
|
38
|
+
TYPES = {
|
|
39
|
+
void: Fiddle::TYPE_VOID,
|
|
40
|
+
pointer: Fiddle::TYPE_VOIDP,
|
|
41
|
+
char: Fiddle::TYPE_CHAR,
|
|
42
|
+
uchar: Fiddle::TYPE_UCHAR,
|
|
43
|
+
short: Fiddle::TYPE_SHORT,
|
|
44
|
+
ushort: Fiddle::TYPE_USHORT,
|
|
45
|
+
int: Fiddle::TYPE_INT,
|
|
46
|
+
uint: Fiddle::TYPE_UINT,
|
|
47
|
+
long: Fiddle::TYPE_LONG,
|
|
48
|
+
ulong: Fiddle::TYPE_ULONG,
|
|
49
|
+
long_long: Fiddle::TYPE_LONG_LONG,
|
|
50
|
+
ulong_long: Fiddle::TYPE_ULONG_LONG,
|
|
51
|
+
float: Fiddle::TYPE_FLOAT,
|
|
52
|
+
double: Fiddle::TYPE_DOUBLE,
|
|
53
|
+
}.freeze
|
|
54
|
+
|
|
55
|
+
# The other way round, for what Fiddle's parser answers. Every code it
|
|
56
|
+
# gives for a spelling CDeclaration hands it is one of the fourteen
|
|
57
|
+
# above: `size_t` and the other platform words arrive as the code of
|
|
58
|
+
# whichever of these they are on this machine.
|
|
59
|
+
NAMES = TYPES.invert.freeze
|
|
60
|
+
private_constant :NAMES
|
|
61
|
+
|
|
62
|
+
POINTER_SIZE = Fiddle::SIZEOF_VOIDP
|
|
63
|
+
|
|
64
|
+
# What a library is, for a caller that was handed one. `jit_extern`'s
|
|
65
|
+
# `from:` takes one of these as it stands.
|
|
66
|
+
Library = Fiddle::Handle
|
|
67
|
+
|
|
68
|
+
# Raised when a library will not open or a symbol is not in it.
|
|
69
|
+
LinkError = Fiddle::DLError
|
|
70
|
+
|
|
71
|
+
PARSER = ::Object.new.extend(Fiddle::CParser)
|
|
72
|
+
private_constant :PARSER
|
|
73
|
+
|
|
74
|
+
# Opens a shared object. `global:` is whether its symbols join the
|
|
75
|
+
# process-wide namespace -- RTLD_GLOBAL -- and it is a required word
|
|
76
|
+
# because layers differ on what they do unasked: Fiddle opens globally,
|
|
77
|
+
# and a layer that opened locally would change which definition a name
|
|
78
|
+
# in one generated object resolves to (see Compiler's link flags).
|
|
79
|
+
# Fiddle has no word for the other way, and nothing here asks for it.
|
|
80
|
+
def self.open_library (path, global:)
|
|
81
|
+
raise ArgumentError, "Fiddle opens a library only globally" unless global
|
|
82
|
+
Fiddle::Handle.new(path, Fiddle::RTLD_LAZY | Fiddle::RTLD_GLOBAL)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# What the process has already loaded, searched as one library.
|
|
86
|
+
def self.loaded_libraries
|
|
87
|
+
Fiddle::Handle::DEFAULT
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# The address of `name` in `library`, as an Integer.
|
|
91
|
+
def self.address (library, name)
|
|
92
|
+
library[name]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# A function at `address` taking `arguments` and returning `result`,
|
|
96
|
+
# each one of the names in TYPES.
|
|
97
|
+
#
|
|
98
|
+
# `release_gvl:` is whether the GVL is let go for the length of a call,
|
|
99
|
+
# and it is a required word because layers differ on what they do
|
|
100
|
+
# unasked -- Fiddle lets it go, FFI keeps it. Letting it go is right
|
|
101
|
+
# for C that touches no Ruby value, which is every generated kernel; it
|
|
102
|
+
# is wrong for C that does, which is CArray's own sweep.
|
|
103
|
+
def self.function (address, arguments, result, release_gvl:, name: nil)
|
|
104
|
+
types = arguments.map { |type| TYPES.fetch(type) }
|
|
105
|
+
options = name ? { name: name } : {}
|
|
106
|
+
return Fiddle::Function.new(address, types, TYPES.fetch(result), **options) if release_gvl
|
|
107
|
+
begin
|
|
108
|
+
Fiddle::Function.new(address, types, TYPES.fetch(result),
|
|
109
|
+
need_gvl: true, **options)
|
|
110
|
+
rescue ArgumentError
|
|
111
|
+
# A Fiddle too old to be told. It is also too old to have let go
|
|
112
|
+
# of the GVL in the first place -- the keyword and the releasing
|
|
113
|
+
# arrived together -- so what is left here is what that Fiddle
|
|
114
|
+
# would have done anyway.
|
|
115
|
+
Fiddle::Function.new(address, types, TYPES.fetch(result), **options)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# The type a C spelling is on this machine, as a name in TYPES, or nil
|
|
120
|
+
# where it is not one. The spelling is one CDeclaration has already
|
|
121
|
+
# put in order: `unsigned long` rather than `long unsigned`, `void *`
|
|
122
|
+
# for any pointer.
|
|
123
|
+
def self.c_type (spelling)
|
|
124
|
+
NAMES.fetch(PARSER.parse_ctype(spelling))
|
|
125
|
+
rescue StandardError
|
|
126
|
+
nil
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# `size` bytes at `address`, as a String. Fiddle::Pointer.read and
|
|
130
|
+
# .write are not in the fiddle Ruby 3.2 ships (1.1.1).
|
|
131
|
+
def self.read (address, size)
|
|
132
|
+
Fiddle::Pointer.new(address)[0, size]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# `bytes` written at `address`.
|
|
136
|
+
def self.write (address, bytes)
|
|
137
|
+
Fiddle::Pointer.new(address)[0, bytes.bytesize] = bytes
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# `object` as the VALUE C sees -- for CArray's own C, which takes the
|
|
141
|
+
# array itself rather than its memory.
|
|
142
|
+
def self.value (object)
|
|
143
|
+
Fiddle.dlwrap(object)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
end
|
|
149
|
+
end
|
data/lib/carray/jit/sweep.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require "
|
|
1
|
+
require "carray"
|
|
2
2
|
|
|
3
3
|
class CArray
|
|
4
4
|
module JIT
|
|
@@ -19,64 +19,6 @@ class CArray
|
|
|
19
19
|
# as saying it is the form a sweep can drive.
|
|
20
20
|
module Sweep
|
|
21
21
|
|
|
22
|
-
# The sweep is reached by address, the way this library reaches every
|
|
23
|
-
# other C function: CArray's extension is already loaded, so the symbol
|
|
24
|
-
# is there to be found rather than linked against.
|
|
25
|
-
def self.handle
|
|
26
|
-
return @handle if defined?(@handle)
|
|
27
|
-
path = $LOADED_FEATURES.find { |feature|
|
|
28
|
-
File.basename(feature) =~ /\Acarray_ext\.(so|bundle|dylib)\z/
|
|
29
|
-
}
|
|
30
|
-
@handle = path && begin
|
|
31
|
-
Fiddle::Handle.new(path)
|
|
32
|
-
rescue Fiddle::DLError
|
|
33
|
-
nil
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# True when this CArray is new enough to have the chunked slab family.
|
|
38
|
-
def self.available?
|
|
39
|
-
return @available if defined?(@available)
|
|
40
|
-
@available = !handle.nil? && begin
|
|
41
|
-
handle["ca_call_cslab_1_r"]
|
|
42
|
-
true
|
|
43
|
-
rescue Fiddle::DLError
|
|
44
|
-
false
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# ca_call_cslab_N_r(func, fsync, rcx0..rcxN-1, userdata)
|
|
49
|
-
#
|
|
50
|
-
# `need_gvl: true` is not a detail. Fiddle releases the GVL for a call
|
|
51
|
-
# unless it is told otherwise, which is right for every other function
|
|
52
|
-
# this library reaches -- a generated kernel is pure C, touches no Ruby
|
|
53
|
-
# value and is the better for running while other threads do. This one
|
|
54
|
-
# is not that: it is CArray's own C, and acquiring the operands attaches
|
|
55
|
-
# them, allocates with `xmalloc`, creates a mask where an output needs
|
|
56
|
-
# one and raises on a shape that will not broadcast. All of that is the
|
|
57
|
-
# Ruby runtime's, and none of it may be done without the GVL. The
|
|
58
|
-
# kernel it calls back into is still pure C, but the GVL is held for the
|
|
59
|
-
# whole call rather than the callback, because Fiddle's choice is made
|
|
60
|
-
# once at the boundary and there is nowhere inside to make it again.
|
|
61
|
-
def self.function (arity)
|
|
62
|
-
@functions ||= {}
|
|
63
|
-
@functions[arity] ||=
|
|
64
|
-
begin
|
|
65
|
-
types = [Fiddle::TYPE_VOIDP] * (arity + 3)
|
|
66
|
-
begin
|
|
67
|
-
Fiddle::Function.new(handle["ca_call_cslab_#{arity}_r"], types,
|
|
68
|
-
Fiddle::TYPE_VOIDP, need_gvl: true)
|
|
69
|
-
rescue ArgumentError
|
|
70
|
-
# A Fiddle too old to be told. It is also too old to have let
|
|
71
|
-
# go of the GVL in the first place -- the keyword and the
|
|
72
|
-
# releasing arrived together -- so what is left here is what
|
|
73
|
-
# that Fiddle would have done anyway.
|
|
74
|
-
Fiddle::Function.new(handle["ca_call_cslab_#{arity}_r"], types,
|
|
75
|
-
Fiddle::TYPE_VOIDP)
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
|
|
80
22
|
# The largest arity the family was generated for. Whether a pass can
|
|
81
23
|
# go this way at all is decided in CArray::JIT.sweepable_pass?, before
|
|
82
24
|
# there is a kernel -- because the answer changes what is compiled.
|
|
@@ -84,11 +26,19 @@ class CArray
|
|
|
84
26
|
|
|
85
27
|
# `arrays` are the operands in the order the kernel addresses them, and
|
|
86
28
|
# `fsync` says which of them it writes -- CArray's own spelling, one
|
|
87
|
-
# character per operand.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
29
|
+
# character per operand. `parts` become the table the slab is handed
|
|
30
|
+
# as `userdata`, in order; one that is not frozen is the slab's to
|
|
31
|
+
# write.
|
|
32
|
+
#
|
|
33
|
+
# CArray makes the call, and points the slab at the Strings' own bytes:
|
|
34
|
+
# they arrive as arguments, and an argument is pinned against
|
|
35
|
+
# compaction for the length of the call. It holds the GVL throughout,
|
|
36
|
+
# unlike every other call this library makes into C. Acquiring the
|
|
37
|
+
# operands attaches them, allocates and raises on a shape that will not
|
|
38
|
+
# broadcast, none of which may be done without it, and the kernel is
|
|
39
|
+
# called back from inside, where there is nowhere to let go of it.
|
|
40
|
+
def self.run (slab, fsync, arrays, *parts)
|
|
41
|
+
CArray::AddressBasis.sweep(slab, fsync, arrays, *parts)
|
|
92
42
|
end
|
|
93
43
|
|
|
94
44
|
end
|
data/lib/carray/jit/version.rb
CHANGED
data/lib/carray/jit.rb
CHANGED
|
@@ -315,9 +315,9 @@ class CArray
|
|
|
315
315
|
# CArray.jit_each { out = j0.call(x) }
|
|
316
316
|
#
|
|
317
317
|
# Nothing is compiled here: `extern` is C's word for a body that lives
|
|
318
|
-
# elsewhere, and finding it is
|
|
319
|
-
# kernel calls the address directly instead of reaching it per
|
|
320
|
-
#
|
|
318
|
+
# elsewhere, and finding it is the dynamic loader's job. What this gem adds
|
|
319
|
+
# is that the kernel calls the address directly instead of reaching it per
|
|
320
|
+
# cell through a foreign-function call from Ruby.
|
|
321
321
|
#
|
|
322
322
|
# @param prototype [String] the function's C declaration, as C writes it.
|
|
323
323
|
# @param from [String, nil] the library to open; `nil` searches the process.
|
|
@@ -905,7 +905,6 @@ class CArray
|
|
|
905
905
|
# that every operand already has the shape the pass covers, which is
|
|
906
906
|
# stricter than agreeing on the count of cells.
|
|
907
907
|
def sweepable_pass? (arrays, shape, masked)
|
|
908
|
-
return false unless Sweep.available?
|
|
909
908
|
return false if masked
|
|
910
909
|
return false if arrays.empty? || arrays.size > Sweep::MAX_ARITY
|
|
911
910
|
return false unless arrays.each_value.all? { |array|
|
|
@@ -1526,6 +1525,16 @@ class CArray
|
|
|
1526
1525
|
written = written_names.filter_map { |name| aligned[name] }
|
|
1527
1526
|
return aligned if written.empty?
|
|
1528
1527
|
roots = written.map { |array| root_of(array) }
|
|
1528
|
+
# Asked first without building anything, because the answer is nearly
|
|
1529
|
+
# always that nothing is shared, and this runs on every call. The
|
|
1530
|
+
# root is compared first since it is what nearly always says no.
|
|
1531
|
+
shared = aligned.any? { |name, array|
|
|
1532
|
+
next false if written_names.include?(name)
|
|
1533
|
+
root = root_of(array)
|
|
1534
|
+
roots.any? { |one| one.equal?(root) } &&
|
|
1535
|
+
written.none? { |one| one.equal?(array) }
|
|
1536
|
+
}
|
|
1537
|
+
return aligned unless shared
|
|
1529
1538
|
aligned.to_h do |name, array|
|
|
1530
1539
|
next [name, array] if written_names.include?(name)
|
|
1531
1540
|
next [name, array] if written.any? { |other| other.equal?(array) }
|
|
@@ -1645,21 +1654,23 @@ class CArray
|
|
|
1645
1654
|
end
|
|
1646
1655
|
pairs = bounds(extents, extents.size)
|
|
1647
1656
|
steps = pairs.map { |triple, _| triple[2] }
|
|
1657
|
+
reassociate = JIT.reassociate if reassociate.nil?
|
|
1658
|
+
|
|
1659
|
+
# Which kernel runs, and whether any operand would be transferred,
|
|
1660
|
+
# answer the same for two calls whose operands agree on name, type,
|
|
1661
|
+
# mask and tier -- so they are worked out once per such agreement,
|
|
1662
|
+
# as an element-wise pass does (#run_over_whole_arrays).
|
|
1663
|
+
key = indexed_run_key(arrays, scalars, c_functions, randoms, steps,
|
|
1664
|
+
into, reassociate)
|
|
1665
|
+
plan = key && found_run_plan(source, key)
|
|
1666
|
+
unless plan
|
|
1667
|
+
plan = plan_indexed_run(source, node, origin, arrays, scalars,
|
|
1668
|
+
c_functions, randoms, steps, into,
|
|
1669
|
+
reassociate)
|
|
1670
|
+
keep_run_plan(source, key, plan) if key
|
|
1671
|
+
end
|
|
1648
1672
|
|
|
1649
|
-
kernel =
|
|
1650
|
-
node: node,
|
|
1651
|
-
origin: origin,
|
|
1652
|
-
array_names: arrays.keys,
|
|
1653
|
-
storage_types: arrays.transform_values(&:data_type_name),
|
|
1654
|
-
scalar_values: scalars,
|
|
1655
|
-
c_functions: c_functions,
|
|
1656
|
-
randoms: randoms,
|
|
1657
|
-
masked: arrays.each_value.any? { |array| array.has_mask? },
|
|
1658
|
-
steps: steps,
|
|
1659
|
-
cell_names: cell_names(arrays),
|
|
1660
|
-
map: into ? true : false,
|
|
1661
|
-
result: into ? INIT_TARGET : nil,
|
|
1662
|
-
reassociate: reassociate.nil? ? JIT.reassociate : reassociate)
|
|
1673
|
+
kernel = plan.kernel
|
|
1663
1674
|
# The kernel decides, not the caller: mentioning UNDEF makes it a
|
|
1664
1675
|
# masked kernel even when no array carries a mask yet.
|
|
1665
1676
|
if kernel.masked
|
|
@@ -1675,11 +1686,77 @@ class CArray
|
|
|
1675
1686
|
"#{pairs.size} #{pairs.size == 1 ? 'extent was' : 'extents were'} " \
|
|
1676
1687
|
"given"
|
|
1677
1688
|
end
|
|
1678
|
-
|
|
1689
|
+
# Only an operand opened at the xfer tier can be refused, and the tier
|
|
1690
|
+
# is in the key; which arrays share a root is not, and is asked here.
|
|
1691
|
+
refuse_a_transferred_alias(kernel, arrays) if plan.transferred
|
|
1679
1692
|
kernel.call(arrays, scalars, pairs.map(&:first), c_functions)
|
|
1680
1693
|
into || kernel
|
|
1681
1694
|
end
|
|
1682
1695
|
|
|
1696
|
+
# What an indexed pass works out from its operands without their data
|
|
1697
|
+
# or their shapes: the kernel, and whether any operand is opened at the
|
|
1698
|
+
# xfer tier. The shapes are the kernel's own plan to keep, since they
|
|
1699
|
+
# are what its bounds are checked against (CompiledKernel#call).
|
|
1700
|
+
#
|
|
1701
|
+
# @private
|
|
1702
|
+
IndexedRunPlan = Data.define(:kernel, :transferred)
|
|
1703
|
+
|
|
1704
|
+
# What two calls of one indexed block have to agree on for one to run
|
|
1705
|
+
# the other's kernel: what #compile is keyed on, read off the operands
|
|
1706
|
+
# rather than built. Each operand's name and type, whether it is a
|
|
1707
|
+
# CScalar (read at its one cell rather than walked) and whether it has
|
|
1708
|
+
# a mask; and its tier, because whether a transferred operand has to be
|
|
1709
|
+
# refused depends on it. Not its shape: the kernel is the same over
|
|
1710
|
+
# any shape, and a caller that grows its arrays would miss on every
|
|
1711
|
+
# call. Not a scalar's value, for the reason #run_key gives.
|
|
1712
|
+
#
|
|
1713
|
+
# The leading symbol keeps these apart from an element-wise pass's
|
|
1714
|
+
# keys, which share the store.
|
|
1715
|
+
#
|
|
1716
|
+
# nil where a scalar is not one a kernel takes, so that the call is
|
|
1717
|
+
# refused where and as it always was rather than here.
|
|
1718
|
+
def indexed_run_key (arrays, scalars, c_functions, randoms, steps, into,
|
|
1719
|
+
reassociate)
|
|
1720
|
+
key = [:indexed, into ? true : false, reassociate, steps, arrays.size]
|
|
1721
|
+
arrays.each do |name, array|
|
|
1722
|
+
key << name << array.data_type << array.is_a?(CScalar) <<
|
|
1723
|
+
array.has_mask? << Access.tier(array)
|
|
1724
|
+
end
|
|
1725
|
+
key << scalars.size
|
|
1726
|
+
scalars.each do |name, value|
|
|
1727
|
+
key << name << TypeAssignment.scalar_type(value)
|
|
1728
|
+
end
|
|
1729
|
+
key << c_functions.size
|
|
1730
|
+
c_functions.each { |name, function| key << name << function.kernel_key }
|
|
1731
|
+
randoms.each { |name, generator| key << name << generator.generator }
|
|
1732
|
+
key
|
|
1733
|
+
rescue Unsupported
|
|
1734
|
+
nil
|
|
1735
|
+
end
|
|
1736
|
+
|
|
1737
|
+
# @private
|
|
1738
|
+
def plan_indexed_run (source, node, origin, arrays, scalars, c_functions,
|
|
1739
|
+
randoms, steps, into, reassociate)
|
|
1740
|
+
kernel = compile(source,
|
|
1741
|
+
node: node,
|
|
1742
|
+
origin: origin,
|
|
1743
|
+
array_names: arrays.keys,
|
|
1744
|
+
storage_types: arrays.transform_values(&:data_type_name),
|
|
1745
|
+
scalar_values: scalars,
|
|
1746
|
+
c_functions: c_functions,
|
|
1747
|
+
randoms: randoms,
|
|
1748
|
+
masked: arrays.each_value.any? { |array| array.has_mask? },
|
|
1749
|
+
steps: steps,
|
|
1750
|
+
cell_names: cell_names(arrays),
|
|
1751
|
+
map: into ? true : false,
|
|
1752
|
+
result: into ? INIT_TARGET : nil,
|
|
1753
|
+
reassociate: reassociate)
|
|
1754
|
+
transferred = arrays.each_value.any? { |array|
|
|
1755
|
+
Access.tier(array) == Access::TIER_XFER
|
|
1756
|
+
}
|
|
1757
|
+
IndexedRunPlan.new(kernel: kernel, transferred: transferred)
|
|
1758
|
+
end
|
|
1759
|
+
|
|
1683
1760
|
# Compiles for one set of array data types and one set of scalar types,
|
|
1684
1761
|
# and returns the same CompiledKernel for every later call with the
|
|
1685
1762
|
# same ones. Nothing on this path is cheap relative to running the
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: carray-jit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- himotoyoshi
|
|
@@ -15,7 +15,7 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 3.0.
|
|
18
|
+
version: 3.0.6
|
|
19
19
|
- - "<"
|
|
20
20
|
- !ruby/object:Gem::Version
|
|
21
21
|
version: '3.1'
|
|
@@ -25,7 +25,7 @@ dependencies:
|
|
|
25
25
|
requirements:
|
|
26
26
|
- - ">="
|
|
27
27
|
- !ruby/object:Gem::Version
|
|
28
|
-
version: 3.0.
|
|
28
|
+
version: 3.0.6
|
|
29
29
|
- - "<"
|
|
30
30
|
- !ruby/object:Gem::Version
|
|
31
31
|
version: '3.1'
|
|
@@ -35,14 +35,14 @@ dependencies:
|
|
|
35
35
|
requirements:
|
|
36
36
|
- - ">="
|
|
37
37
|
- !ruby/object:Gem::Version
|
|
38
|
-
version:
|
|
38
|
+
version: 1.1.1
|
|
39
39
|
type: :runtime
|
|
40
40
|
prerelease: false
|
|
41
41
|
version_requirements: !ruby/object:Gem::Requirement
|
|
42
42
|
requirements:
|
|
43
43
|
- - ">="
|
|
44
44
|
- !ruby/object:Gem::Version
|
|
45
|
-
version:
|
|
45
|
+
version: 1.1.1
|
|
46
46
|
description: |
|
|
47
47
|
Write the loop in Ruby, and it runs at the speed of C -- one to two orders
|
|
48
48
|
of magnitude faster than interpreted. It is for what a CArray expression
|
|
@@ -125,6 +125,7 @@ files:
|
|
|
125
125
|
- lib/carray/jit/errors.rb
|
|
126
126
|
- lib/carray/jit/expression.rb
|
|
127
127
|
- lib/carray/jit/kernel.rb
|
|
128
|
+
- lib/carray/jit/native.rb
|
|
128
129
|
- lib/carray/jit/node.rb
|
|
129
130
|
- lib/carray/jit/sorting_networks.rb
|
|
130
131
|
- lib/carray/jit/sweep.rb
|
|
@@ -148,7 +149,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
148
149
|
- !ruby/object:Gem::Version
|
|
149
150
|
version: '0'
|
|
150
151
|
requirements: []
|
|
151
|
-
rubygems_version: 4.0.
|
|
152
|
+
rubygems_version: 4.0.8
|
|
152
153
|
specification_version: 4
|
|
153
154
|
summary: JIT compilation of CArray kernels written in Ruby
|
|
154
155
|
test_files: []
|