Class: CArray::JIT::CompiledKernel
- Inherits:
-
Object
- Object
- CArray::JIT::CompiledKernel
- Defined in:
- lib/carray/jit/kernel.rb
Overview
A compiled kernel, bound to one set of array data types and one set of scalar types.
Every kernel has the same C signature, so the Fiddle::Function shape is fixed and the per-kernel detail travels in the six buffers.
Constant Summary collapse
- PLAN_LIMIT =
How many prepared calls one kernel keeps. A stencil asks under its interior's bounds and one pair per axis of frame, so a two-dimensional one needs five before it repeats; a caller alternating between two model states needs two of whatever it was already using. Past this the oldest goes and the call prepares itself again, which is what every call did before.
8
Instance Attribute Summary collapse
-
#address_arrays ⇒ Object
readonly
Returns the value of attribute address_arrays.
-
#arrays ⇒ Object
readonly
Returns the value of attribute arrays.
-
#c_source ⇒ String
readonly
The C this kernel was compiled from.
-
#compiled ⇒ Boolean
readonly
Whether this call invoked the compiler, rather than reusing a cached object.
-
#complexes ⇒ Object
readonly
Returns the value of attribute complexes.
-
#contracted_names ⇒ Object
readonly
Returns the value of attribute contracted_names.
-
#directions ⇒ Object
readonly
Returns the value of attribute directions.
-
#index_axes ⇒ Object
readonly
Returns the value of attribute index_axes.
-
#index_names ⇒ Object
readonly
Returns the value of attribute index_names.
-
#integers ⇒ Object
readonly
Returns the value of attribute integers.
-
#masked ⇒ Object
readonly
Returns the value of attribute masked.
-
#raise_messages ⇒ Object
readonly
Returns the value of attribute raise_messages.
-
#rank ⇒ Object
readonly
Returns the value of attribute rank.
-
#reals ⇒ Object
readonly
Returns the value of attribute reals.
-
#source ⇒ String
readonly
The block's Ruby source, as it was read.
-
#storage_types ⇒ Object
readonly
Returns the value of attribute storage_types.
-
#unsigned_integers ⇒ Object
readonly
Returns the value of attribute unsigned_integers.
-
#window_reach ⇒ Object
readonly
Returns the value of attribute window_reach.
-
#window_reaches ⇒ Object
readonly
Returns the value of attribute window_reaches.
-
#written_arrays ⇒ Object
readonly
Returns the value of attribute written_arrays.
Instance Method Summary collapse
-
#call(array_values, scalar_values, bounds, c_function_values = {}, border: false) ⇒ Object
boundsis one [start, limit, step] per axis. -
#initialize(source:, generator:, analyzer:, storage_types:) ⇒ CompiledKernel
constructor
A new instance of CompiledKernel.
-
#slab_source ⇒ Object
The wrapper CArray's sweep calls, for the same reason #c_source is here: the generated C is the debugging surface.
-
#sweep(array_values, scalar_values, c_function_values = {}) ⇒ Object
Runs the kernel as CArray's chunked sweep rather than through the addressing here: CArray acquires the operands, broadcasts them, ORs and propagates the masks, and calls back with a chunk at a time.
-
#sweepable? ⇒ Boolean
True when this kernel could be run as a sweep at all: the body must not ask about a mask, since a chunk carries no per-cell mask the generated code can test.
Constructor Details
#initialize(source:, generator:, analyzer:, storage_types:) ⇒ CompiledKernel
Returns a new instance of CompiledKernel.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/carray/jit/kernel.rb', line 40 def initialize (source:, generator:, analyzer:, storage_types:) @source = source @c_source = generator.provenance + generator.generate @arrays = generator.arrays @reals = generator.reals @integers = generator.integers @complexes = generator.complexes @unsigned_integers = generator.unsigned_integers # Per array, the axes read at an index only the running kernel knows, # and the extents it checks them against. @extent_slots = generator.extent_slots @dynamic_arrays = @extent_slots.map(&:first).uniq @storage_types = storage_types @rank = analyzer.rank @index_names = analyzer.index_names @written_arrays = analyzer.written_arrays @array_ranks = analyzer.array_ranks @inner_ranges = analyzer.inner_ranges # An index the block wrote twice has two identifiers here; the # messages speak the name it was written with. @index_sources = analyzer.index_sources @axis_uses = @arrays.to_h { |array| [array, (0...array_rank(array)).map { |axis| analyzer.axis_use(array, axis) }] } @contracted_names = analyzer.contracted_names # Where each index appears, so that a contraction can take its extents # from the arrays and say so when they disagree. @index_axes = Hash.new { |hash, key| hash[key] = [] } @axis_uses.each do |array, uses| uses.each_with_index do |(walkers, _), axis| walkers.each { |index, _, _| @index_axes[index] << [array, axis] } end end # Which captured scalars decide where the kernel reaches, rather than # what it computes. A pinned subscript, an offset and an inner loop's # range are worked out here, in Ruby, before the first cell; every # other captured number is packed into a buffer and read by the C. So # these are the ones a prepared call has to be keyed on -- and a # coefficient or a clock that changes every step is not one of them, # which is what keeps a time-stepping caller on the prepared path. @shape_scalar_names = shape_scalar_names # Calls prepared earlier, most recently used first. @plans = [] @masked = generator.masked @window_reach = analyzer.window_reach @window_reaches = analyzer.window_reaches # What `raise` in the block said, by the code the cell that raised # writes into the error slot. The message does not travel: C has # nothing to carry it in, and it was known when this was compiled. @raise_messages = generator. # The arrays this kernel allocates at its entry, as # [name, storage, shape] -- so that a shape it could not use, or an # allocation that failed, can be named with the lengths it came to on # this call. The shapes are worked out here as they are there: from # the same expressions over the same captured integers. @heap_arrays = analyzer.local_array_declarations.filter_map { |entry| _scope, name, storage, shape, heap = entry [name, storage, shape] if heap } # Kept so the sweep entry point can be built from it if one is ever # asked for. Compiling it eagerly would pay for a road most kernels # never take. @generator = generator # The names of the C functions the block called, in the order their # addresses are packed into the `functions` buffer. Names, not the # functions themselves: a borrowed function is keyed on its signature # alone, so this kernel is shared by every function of that shape and # the address has to come from the call rather than from the build. @c_function_names = generator.address_functions.keys # Arrays handed to a C function whole, in the order their addresses # are packed into `data`, and what each was promised to be. @address_arrays = generator.address_arrays @address_parameters = generator.address_parameters # One translation unit holds both entry points -- the kernel and the # wrapper CArray's sweep calls -- and one build produces both. They # were two builds of the same body until the second was noticed: the # wrapper's source already contains the kernel verbatim, so compiling # it separately meant compiling everything twice and throwing the # first object away whenever the pass swept, which is the usual case # for jit_each. About 230 ms per call site, and an entry in the # cache that was never opened. @source_text = generator.provenance + generator.generate_slab(SLAB_NAME) @handle, @compiled = Compiler.build(generator.generate_slab(SLAB_NAME), CGenerator::FUNCTION_NAME, header: generator.provenance) @function = Fiddle::Function.new(@handle[CGenerator::FUNCTION_NAME], [Fiddle::TYPE_VOIDP] * 10, Fiddle::TYPE_VOID) end |
Instance Attribute Details
#address_arrays ⇒ Object (readonly)
Returns the value of attribute address_arrays.
21 22 23 |
# File 'lib/carray/jit/kernel.rb', line 21 def address_arrays @address_arrays end |
#arrays ⇒ Object (readonly)
Returns the value of attribute arrays.
21 22 23 |
# File 'lib/carray/jit/kernel.rb', line 21 def arrays @arrays end |
#c_source ⇒ String (readonly)
Returns the C this kernel was compiled from.
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/carray/jit/kernel.rb', line 21 attr_reader :source, :c_source, :arrays, :storage_types, :reals, :integers, :complexes, :unsigned_integers, :rank, :index_names, :written_arrays, # The arrays handed to a C function whole, by address, # rather than walked a cell at a time. A caller that lines # operands up has to leave these alone: their shape is the # C function's business, not the expression's. :address_arrays, :compiled, :masked, :directions, :contracted_names, :index_axes, # How far a stencil's windows reach on each axis, as # [lowest, highest] per axis: what the caller walks the # interior by. Empty for a kernel that has no windows. :window_reach, :window_reaches, # What a cell that stopped can have been raising about -- # the block's own `raise`s, and those of the bodies pasted # into this kernel, by the code each reports. :raise_messages |
#compiled ⇒ Boolean (readonly)
Returns whether this call invoked the compiler, rather than reusing a cached object.
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/carray/jit/kernel.rb', line 21 attr_reader :source, :c_source, :arrays, :storage_types, :reals, :integers, :complexes, :unsigned_integers, :rank, :index_names, :written_arrays, # The arrays handed to a C function whole, by address, # rather than walked a cell at a time. A caller that lines # operands up has to leave these alone: their shape is the # C function's business, not the expression's. :address_arrays, :compiled, :masked, :directions, :contracted_names, :index_axes, # How far a stencil's windows reach on each axis, as # [lowest, highest] per axis: what the caller walks the # interior by. Empty for a kernel that has no windows. :window_reach, :window_reaches, # What a cell that stopped can have been raising about -- # the block's own `raise`s, and those of the bodies pasted # into this kernel, by the code each reports. :raise_messages |
#complexes ⇒ Object (readonly)
Returns the value of attribute complexes.
21 22 23 |
# File 'lib/carray/jit/kernel.rb', line 21 def complexes @complexes end |
#contracted_names ⇒ Object (readonly)
Returns the value of attribute contracted_names.
21 22 23 |
# File 'lib/carray/jit/kernel.rb', line 21 def contracted_names @contracted_names end |
#directions ⇒ Object (readonly)
Returns the value of attribute directions.
21 22 23 |
# File 'lib/carray/jit/kernel.rb', line 21 def directions @directions end |
#index_axes ⇒ Object (readonly)
Returns the value of attribute index_axes.
21 22 23 |
# File 'lib/carray/jit/kernel.rb', line 21 def index_axes @index_axes end |
#index_names ⇒ Object (readonly)
Returns the value of attribute index_names.
21 22 23 |
# File 'lib/carray/jit/kernel.rb', line 21 def index_names @index_names end |
#integers ⇒ Object (readonly)
Returns the value of attribute integers.
21 22 23 |
# File 'lib/carray/jit/kernel.rb', line 21 def integers @integers end |
#masked ⇒ Object (readonly)
Returns the value of attribute masked.
21 22 23 |
# File 'lib/carray/jit/kernel.rb', line 21 def masked @masked end |
#raise_messages ⇒ Object (readonly)
Returns the value of attribute raise_messages.
21 22 23 |
# File 'lib/carray/jit/kernel.rb', line 21 def @raise_messages end |
#rank ⇒ Object (readonly)
Returns the value of attribute rank.
21 22 23 |
# File 'lib/carray/jit/kernel.rb', line 21 def rank @rank end |
#reals ⇒ Object (readonly)
Returns the value of attribute reals.
21 22 23 |
# File 'lib/carray/jit/kernel.rb', line 21 def reals @reals end |
#source ⇒ String (readonly)
Returns the block's Ruby source, as it was read.
21 22 23 |
# File 'lib/carray/jit/kernel.rb', line 21 def source @source end |
#storage_types ⇒ Object (readonly)
Returns the value of attribute storage_types.
21 22 23 |
# File 'lib/carray/jit/kernel.rb', line 21 def storage_types @storage_types end |
#unsigned_integers ⇒ Object (readonly)
Returns the value of attribute unsigned_integers.
21 22 23 |
# File 'lib/carray/jit/kernel.rb', line 21 def unsigned_integers @unsigned_integers end |
#window_reach ⇒ Object (readonly)
Returns the value of attribute window_reach.
21 22 23 |
# File 'lib/carray/jit/kernel.rb', line 21 def window_reach @window_reach end |
#window_reaches ⇒ Object (readonly)
Returns the value of attribute window_reaches.
21 22 23 |
# File 'lib/carray/jit/kernel.rb', line 21 def window_reaches @window_reaches end |
#written_arrays ⇒ Object (readonly)
Returns the value of attribute written_arrays.
21 22 23 |
# File 'lib/carray/jit/kernel.rb', line 21 def written_arrays @written_arrays end |
Instance Method Details
#call(array_values, scalar_values, bounds, c_function_values = {}, border: false) ⇒ Object
bounds is one [start, limit, step] per axis.
border: runs the frame's entry point instead of the interior's: the
same statements, with a window that falls off the array answered by
the rule the kernel was compiled for rather than by reading. Which is
why the reach is not checked for one -- reaching outside is what it is
for, and the C answers for it.
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/carray/jit/kernel.rb', line 139 def call (array_values, scalar_values, bounds, c_function_values = {}, border: false) arrays = @arrays.map { |name| array_values.fetch(name) } plan = prepared_call(arrays, array_values, bounds, scalar_values, border) writable = plan[:writable] error = [0].pack("l") packed_bounds = plan[:bounds] reals = packed_reals(scalar_values) # The captured integers change from call to call and are packed here; # the extents behind them are the arrays' own and travel in the plan. integers = packed_scalar_integers(scalar_values) + plan[:extents] functions = @c_function_names.map { |name| c_function_values.fetch(name).pointer.to_i }.pack("Q*") # An array handed over whole is walked contiguously by the C, so it is # packed into an entity first and copied back after if the C may have # written to it. This is the same lifecycle CFunction#call keeps, and for # the same reason. addressed = @address_arrays.map { |name| [name, array_values.fetch(name)] } packed = addressed.map { |name, array| [array, address_buffer(name, array)] } data = packed.map { |_, buffer| Access.open([buffer], [false], [nil], [nil]) { |bases| bases.first[:pointer] } }.pack("Q*") box = plan[:box] # Opened for a kernel rather than for a reader: the four buffers come # back packed, instead of a hash and two arrays per operand that this # would pack back into bytes and throw away. Access.open(arrays, writable, box[0], box[1], true) do |pointers, strides, mask_pointers, mask_strides| entry = border ? border_function : @function entry.call(buffer(pointers), buffer(strides), buffer(packed_bounds), buffer(reals), buffer(integers), buffer(functions), buffer(data), buffer(mask_pointers), buffer(mask_strides), error) end packed.each_with_index do |(array, buffer), index| next if array.equal?(buffer) next if @address_parameters.fetch(@address_arrays[index]).all?(&:const) array[] = buffer end report(error, scalar_values) end |
#slab_source ⇒ Object
The wrapper CArray's sweep calls, for the same reason #c_source is here: the generated C is the debugging surface. Built on demand, because a kernel that never sweeps never needs it.
252 253 254 |
# File 'lib/carray/jit/kernel.rb', line 252 def slab_source @source_text end |
#sweep(array_values, scalar_values, c_function_values = {}) ⇒ Object
Runs the kernel as CArray's chunked sweep rather than through the addressing here: CArray acquires the operands, broadcasts them, ORs and propagates the masks, and calls back with a chunk at a time.
array_values are the operands in the order the kernel addresses
them, which is the order it packs pointers in -- so the same three
arguments the kernel's own loop takes are the ones a chunk arrives as.
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/carray/jit/kernel.rb', line 208 def sweep (array_values, scalar_values, c_function_values = {}) arrays = @arrays.map { |name| array_values.fetch(name) } fsync = @arrays.map { |name| @written_arrays.include?(name) ? "1" : "0" }.join error = [0].pack("l") reals = packed_reals(scalar_values) integers = packed_integers(scalar_values, array_values) functions = @c_function_names.map { |name| c_function_values.fetch(name).pointer.to_i }.pack("Q*") addressed = @address_arrays.map { |name| [name, array_values.fetch(name)] } packed = addressed.map { |name, array| [array, address_buffer(name, array)] } data = packed.map { |_, item| Access.open([item], [false], [nil], [nil]) { |bases| bases.first[:pointer] } }.pack("Q*") # The kernel declares these only when it reads a mask, and a body that # reads one does not come this way; they are here because the # signature has the slots. masks = ("\0" * 8) held = [buffer(reals), buffer(integers), buffer(functions), buffer(data), masks, masks, error] context = held.map { |item| Fiddle::Pointer[item].to_i }.pack("Q*") Sweep.call(slab_pointer, fsync, arrays, Fiddle::Pointer[context]) packed.each_with_index do |(array, item), index| next if array.equal?(item) next if @address_parameters.fetch(@address_arrays[index]).all?(&:const) array[] = item end report(error, scalar_values) end |
#sweepable? ⇒ Boolean
True when this kernel could be run as a sweep at all: the body must not ask about a mask, since a chunk carries no per-cell mask the generated code can test.
259 260 261 |
# File 'lib/carray/jit/kernel.rb', line 259 def sweepable? !@masked end |