carray-jit 0.1.0
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 +7 -0
- data/.yardopts +10 -0
- data/CHANGELOG.md +84 -0
- data/LICENSE +21 -0
- data/README.md +88 -0
- data/bin/carray-jit +194 -0
- data/carray-jit.gemspec +41 -0
- data/docs/00_Introduction.md +40 -0
- data/docs/01_GettingStarted.md +80 -0
- data/docs/02_KernelShapes.md +397 -0
- data/docs/03_SupportedFeatures.md +595 -0
- data/docs/04_Compiling.md +234 -0
- data/docs/05_DesignNotes.md +136 -0
- data/docs/06_Cheatsheet.md +177 -0
- data/examples/README.md +56 -0
- data/examples/applications/game_of_life.rb +161 -0
- data/examples/applications/heat_equation.rb +117 -0
- data/examples/applications/kepler.rb +178 -0
- data/examples/applications/mandelbrot.rb +151 -0
- data/examples/applications/moving_average.rb +124 -0
- data/examples/applications/partial_sums.rb +141 -0
- data/examples/applications/point_cloud.rb +110 -0
- data/examples/applications/quicksort.rb +118 -0
- data/examples/applications/recursion.rb +121 -0
- data/examples/applications/relaxation.rb +115 -0
- data/examples/applications/sensor_gaps.rb +118 -0
- data/examples/applications/sieve.rb +95 -0
- data/examples/applications/sobel_edges.rb +80 -0
- data/examples/features/01_element_wise.rb +69 -0
- data/examples/features/02_stencil.rb +40 -0
- data/examples/features/03_recurrence.rb +50 -0
- data/examples/features/04_thomas.rb +81 -0
- data/examples/features/05_reduction.rb +90 -0
- data/examples/features/06_jit_contract.rb +58 -0
- data/examples/features/07_masks.rb +55 -0
- data/examples/features/08_views.rb +46 -0
- data/examples/features/09_inspecting.rb +55 -0
- data/examples/features/10_complex.rb +107 -0
- data/examples/features/11_c_functions.rb +260 -0
- data/examples/features/12_sweep.rb +139 -0
- data/examples/features/13_cscalar.rb +80 -0
- data/examples/features/14_stencil_window.rb +106 -0
- data/examples/features/15_loops.rb +148 -0
- data/examples/features/16_raising.rb +69 -0
- data/ext/carray_jit_access/carray_jit_access.c +460 -0
- data/ext/carray_jit_access/extconf.rb +8 -0
- data/lib/carray/jit/analyzer.rb +1847 -0
- data/lib/carray/jit/block_reader.rb +139 -0
- data/lib/carray/jit/c_function.rb +777 -0
- data/lib/carray/jit/c_generator.rb +2305 -0
- data/lib/carray/jit/compiler.rb +468 -0
- data/lib/carray/jit/errors.rb +37 -0
- data/lib/carray/jit/expression.rb +202 -0
- data/lib/carray/jit/kernel.rb +509 -0
- data/lib/carray/jit/node.rb +573 -0
- data/lib/carray/jit/sweep.rb +97 -0
- data/lib/carray/jit/type_assignment.rb +811 -0
- data/lib/carray/jit/version.rb +5 -0
- data/lib/carray/jit.rb +1210 -0
- metadata +139 -0
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
require "digest"
|
|
2
|
+
require "fiddle"
|
|
3
|
+
require "fiddle/import"
|
|
4
|
+
|
|
5
|
+
class CArray
|
|
6
|
+
module JIT
|
|
7
|
+
|
|
8
|
+
# One parameter or return type, as the prototype spelled it.
|
|
9
|
+
#
|
|
10
|
+
# Fiddle's parser answers what the ABI needs and no more: every pointer
|
|
11
|
+
# comes back as `TYPE_VOIDP`, so `const double *`, `double *` and `void *`
|
|
12
|
+
# are one thing to it. That is enough to *call* a function and not enough
|
|
13
|
+
# to *write* one, which is why the declarator's own text is kept beside
|
|
14
|
+
# the code Fiddle assigned it.
|
|
15
|
+
CType = Struct.new(:text, :fiddle, :computation, :pointer, :array,
|
|
16
|
+
:element, :const) do
|
|
17
|
+
# True for a type that may be written into a signature but holds no
|
|
18
|
+
# value a kernel or a body can compute with.
|
|
19
|
+
def opaque?
|
|
20
|
+
computation.nil?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# A pointer to numbers, which a body may index. `void *` is not one:
|
|
24
|
+
# it points at nothing in particular, so it stays a slot.
|
|
25
|
+
def indexable?
|
|
26
|
+
pointer && !element.nil?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# C's own declarator puts an array's length where a reader can see it,
|
|
30
|
+
# so `const double coef[3]` and `const double *coef` are different
|
|
31
|
+
# declarations of the same ABI. The length is kept rather than folded
|
|
32
|
+
# away: a subscript into a parameter has no extent behind it unless the
|
|
33
|
+
# declaration carried one, and this compiler's subscripts have always
|
|
34
|
+
# had one.
|
|
35
|
+
def sized?
|
|
36
|
+
array.is_a?(Integer)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# A declarator with the name in it, which for an array is not simply
|
|
40
|
+
# the type followed by the name.
|
|
41
|
+
def declare (name)
|
|
42
|
+
return "#{text} #{name}" unless array
|
|
43
|
+
"#{text.sub(/\s*\[.*\]\z/, "")} #{name}[#{array if sized?}]"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [String] the type, as C spells it.
|
|
47
|
+
def to_s
|
|
48
|
+
text
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# A C function a kernel body can call, and that C can be handed back.
|
|
53
|
+
#
|
|
54
|
+
# The binding is borrowed from Fiddle -- `Fiddle::Handle` finds the
|
|
55
|
+
# library and the symbol, and `Fiddle::CParser` decides the ABI code of a
|
|
56
|
+
# type, which is the same path `Fiddle::Importer#extern` takes. The
|
|
57
|
+
# *call* is not borrowed: reaching a function through `Fiddle::Function`
|
|
58
|
+
# costs a few hundred nanoseconds per cell, against single digits from
|
|
59
|
+
# compiled C. Fiddle is asked where the function is; the kernel calls it.
|
|
60
|
+
#
|
|
61
|
+
# The address travels to the kernel in a buffer, the way a captured scalar
|
|
62
|
+
# already does, rather than being linked against. That means no `-l`
|
|
63
|
+
# flag, no library path at compile time, and a compiled kernel that does
|
|
64
|
+
# not depend on which library the function came from -- so `f.call(a)`
|
|
65
|
+
# compiles once and serves every function of that signature.
|
|
66
|
+
class CFunction
|
|
67
|
+
|
|
68
|
+
# @!attribute [r] name
|
|
69
|
+
# @return [String] the function's name, as the prototype gives it.
|
|
70
|
+
# @!attribute [r] prototype
|
|
71
|
+
# @return [String] the C declaration this was named by.
|
|
72
|
+
# @!attribute [r] c_source
|
|
73
|
+
# @return [String, nil] the C compiled for a body written here, or
|
|
74
|
+
# `nil` for one found elsewhere.
|
|
75
|
+
attr_reader :name, :prototype, :return_type, :parameters, :pointer,
|
|
76
|
+
:block, :c_source, :origin, :definition, :helpers,
|
|
77
|
+
# What `raise` in the body said, by the code it reports.
|
|
78
|
+
# The kernel that pastes it answers for these too.
|
|
79
|
+
:raise_messages
|
|
80
|
+
|
|
81
|
+
def initialize (name, prototype, return_type, parameters, pointer,
|
|
82
|
+
block: nil, c_source: nil, origin: nil, error: nil,
|
|
83
|
+
definition: nil, helpers: nil, takes_error: false,
|
|
84
|
+
raise_messages: {})
|
|
85
|
+
@name = name && name.to_sym
|
|
86
|
+
@prototype = prototype
|
|
87
|
+
@return_type = return_type
|
|
88
|
+
@parameters = parameters
|
|
89
|
+
@pointer = pointer
|
|
90
|
+
# A function written in Ruby keeps its block, so that what the kernel
|
|
91
|
+
# runs and what Ruby would compute can be put side by side.
|
|
92
|
+
@block = block
|
|
93
|
+
@c_source = c_source
|
|
94
|
+
# The definition on its own, without the file it was compiled in, and
|
|
95
|
+
# what it wants from a preamble -- what a kernel needs to paste it.
|
|
96
|
+
@definition = definition
|
|
97
|
+
@helpers = helpers
|
|
98
|
+
# True when that definition ends in an `int32_t *`: the body can report
|
|
99
|
+
# a failure, and pasted it reports into the caller's slot rather than
|
|
100
|
+
# into the flag in its own object.
|
|
101
|
+
@takes_error = takes_error
|
|
102
|
+
@raise_messages = raise_messages
|
|
103
|
+
@origin = origin
|
|
104
|
+
# Where the compiled body says a division had no divisor, or a
|
|
105
|
+
# subscript ran off its array. Nil when the body can do neither.
|
|
106
|
+
@error = error
|
|
107
|
+
@function = nil
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# True for one compiled from a Ruby block rather than bound from a
|
|
111
|
+
# library.
|
|
112
|
+
def compiled?
|
|
113
|
+
!@block.nil?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# True for one a kernel can paste into its own C rather than call
|
|
117
|
+
# through a pointer. The body has to be here to paste, which a borrowed
|
|
118
|
+
# function's is not: it arrives as an address and nothing else.
|
|
119
|
+
def pasted?
|
|
120
|
+
compiled? && !@definition.nil?
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# True when the pasted copy takes the caller's error slot as its last
|
|
124
|
+
# argument. Standing alone the same body reports into the flag in its
|
|
125
|
+
# own object -- `#call` reads that one -- but pasted there is no such
|
|
126
|
+
# object around it, and the failure belongs to the kernel that is
|
|
127
|
+
# running: `1 / 0` in a function called from a kernel raises the
|
|
128
|
+
# ZeroDivisionError the kernel raises for its own.
|
|
129
|
+
def pasted_takes_error?
|
|
130
|
+
pasted? && @takes_error
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# The signature, without the address.
|
|
134
|
+
def signature
|
|
135
|
+
[@return_type.text, @parameters.map(&:text)]
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# What a compiled kernel depends on. Two functions that share it share
|
|
139
|
+
# a kernel.
|
|
140
|
+
#
|
|
141
|
+
# For one bound from a library that is the signature alone: the address
|
|
142
|
+
# arrives with the call, so `j0` and `y0` are the same kernel and it is
|
|
143
|
+
# compiled once. For one compiled here it is the signature *and* the
|
|
144
|
+
# symbol, which carries the digest of the body -- the body is on its way
|
|
145
|
+
# into the kernel's own translation unit, and a kernel that has one body
|
|
146
|
+
# pasted into it cannot serve another function that is merely declared
|
|
147
|
+
# the same way. Splitting the cache costs a compile per body; sharing
|
|
148
|
+
# it would hand back the wrong answer, and would do it quietly.
|
|
149
|
+
def kernel_key
|
|
150
|
+
compiled? ? [signature, @name] : signature
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# @return [Integer] how many arguments the function takes.
|
|
154
|
+
def arity
|
|
155
|
+
@parameters.size
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# The Fiddle types, for calling it from Ruby.
|
|
159
|
+
def argument_types
|
|
160
|
+
@parameters.map(&:fiddle)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# The C spelling of the pointer type, for the typedef a kernel emits.
|
|
164
|
+
def c_declaration (typedef_name)
|
|
165
|
+
"typedef #{@return_type.text} (*#{typedef_name})" \
|
|
166
|
+
"(#{@parameters.map(&:text).join(', ')});"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# What a kernel computes the result in.
|
|
170
|
+
def result_type
|
|
171
|
+
computation_of(@return_type, "returns")
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# What it computes in where the value is dropped. `void` is a return
|
|
175
|
+
# type a call may have and a cell may not, so the question only has an
|
|
176
|
+
# answer in statement position -- which is the one place that asks.
|
|
177
|
+
def discarded_result_type
|
|
178
|
+
@return_type.opaque? ? :void : @return_type.computation
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# @private
|
|
182
|
+
def argument_result_types
|
|
183
|
+
@parameters.map { |type| computation_of(type, "takes") }
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Calling it from Ruby, so that a body means the same thing run either
|
|
187
|
+
# way. Slow -- this is the several-hundred-nanosecond path -- and here
|
|
188
|
+
# for testing and for the odd cell, not for sweeping an array.
|
|
189
|
+
#
|
|
190
|
+
# A pointer to numbers takes a CArray, which is the thing in this
|
|
191
|
+
# library that is a run of numbers with a type. The block sees the
|
|
192
|
+
# array itself and reaches it with `#[]`, the compiled C sees its
|
|
193
|
+
# address and reaches it with a subscript, and `coef[0]` means the same
|
|
194
|
+
# in both -- so the body agrees with itself whichever way it is run.
|
|
195
|
+
def call (*arguments)
|
|
196
|
+
unless arguments.size == @parameters.size
|
|
197
|
+
raise ArgumentError,
|
|
198
|
+
"wrong number of arguments (given #{arguments.size}, " \
|
|
199
|
+
"expected #{@parameters.size})"
|
|
200
|
+
end
|
|
201
|
+
@function ||= Fiddle::Function.new(@pointer, argument_types,
|
|
202
|
+
@return_type.fiddle,
|
|
203
|
+
name: @name.to_s)
|
|
204
|
+
arrays = []
|
|
205
|
+
prepared = arguments.zip(@parameters).map { |argument, type|
|
|
206
|
+
next argument unless type.indexable? && argument.is_a?(CArray)
|
|
207
|
+
buffer = check_array(argument, type)
|
|
208
|
+
arrays << [argument, buffer, type]
|
|
209
|
+
buffer
|
|
210
|
+
}
|
|
211
|
+
clear_error
|
|
212
|
+
Access.open(arrays.map { |_, buffer, _| buffer },
|
|
213
|
+
arrays.map { |_, _, type| !type.const },
|
|
214
|
+
arrays.map { nil }, arrays.map { nil }) do |bases|
|
|
215
|
+
slot = -1
|
|
216
|
+
prepared = prepared.map { |value|
|
|
217
|
+
next value unless arrays.any? { |_, buffer, _| buffer.equal?(value) }
|
|
218
|
+
Fiddle::Pointer.new(bases[slot += 1][:pointer])
|
|
219
|
+
}
|
|
220
|
+
@result = @function.call(*prepared)
|
|
221
|
+
end
|
|
222
|
+
report_error
|
|
223
|
+
# A view was copied to be made contiguous; a writable one is copied
|
|
224
|
+
# back, because the C wrote into the copy.
|
|
225
|
+
arrays.each do |array, buffer, type|
|
|
226
|
+
array[] = buffer unless type.const || array.equal?(buffer)
|
|
227
|
+
end
|
|
228
|
+
@result
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
alias [] call
|
|
232
|
+
|
|
233
|
+
# @return [String] the function's name.
|
|
234
|
+
def to_s
|
|
235
|
+
text = "#{@return_type.text} #{@name}" \
|
|
236
|
+
"(#{@parameters.map(&:text).join(', ')})"
|
|
237
|
+
@origin ? "#{text} at #{@origin}" : text
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# @return [String] the prototype this was named by.
|
|
241
|
+
def inspect
|
|
242
|
+
"#<CArray::JIT::CFunction #{self}>"
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
private
|
|
246
|
+
|
|
247
|
+
# What a pointer parameter will accept, and what has to be true of it.
|
|
248
|
+
# The length is checked only where the declaration carried one: C's own
|
|
249
|
+
# rule is that an unsized pointer is the caller's responsibility, and
|
|
250
|
+
# writing `coef[3]` is how the caller asks to be checked.
|
|
251
|
+
def clear_error
|
|
252
|
+
@error[0, 4] = [0].pack("l") if @error
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# What the kernel raises for the same code, since it is the same thing
|
|
256
|
+
# that happened: `6 % 0` is a ZeroDivisionError wherever it is written,
|
|
257
|
+
# and the compiled body cannot raise it itself. A caller reaching the
|
|
258
|
+
# address from C sees the number the helper returned and the flag
|
|
259
|
+
# standing, which is C's own arrangement for a function that has to
|
|
260
|
+
# return something whatever happened.
|
|
261
|
+
def report_error
|
|
262
|
+
return unless @error
|
|
263
|
+
code = @error[0, 4].unpack1("l")
|
|
264
|
+
case code
|
|
265
|
+
when 0 then nil
|
|
266
|
+
when 1 then raise ZeroDivisionError, "divided by 0"
|
|
267
|
+
else
|
|
268
|
+
# `raise "..."` in the body. The message did not come back through
|
|
269
|
+
# the C -- it was registered when this was compiled -- so it is
|
|
270
|
+
# looked up here, and a kernel that pasted the same body looks the
|
|
271
|
+
# same message up under the same code.
|
|
272
|
+
message = @raise_messages[code]
|
|
273
|
+
raise Error, "#{@name} reported #{code}, which is no failure it " \
|
|
274
|
+
"was compiled to report" unless message
|
|
275
|
+
raise RuntimeError, message
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def check_array (array, type)
|
|
280
|
+
wanted = CDeclaration::DATA_TYPES.fetch(type.element.fiddle)
|
|
281
|
+
unless array.data_type_name == wanted.to_s
|
|
282
|
+
raise Unsupported,
|
|
283
|
+
"`#{type.text}` takes a #{wanted} array, and this one is " \
|
|
284
|
+
"#{array.data_type_name}"
|
|
285
|
+
end
|
|
286
|
+
if type.sized? && array.elements < type.array
|
|
287
|
+
raise Unsupported,
|
|
288
|
+
"`#{type.text}` reads #{type.array} " \
|
|
289
|
+
"#{type.array == 1 ? 'element' : 'elements'}, and this array " \
|
|
290
|
+
"has #{array.elements}"
|
|
291
|
+
end
|
|
292
|
+
if array.has_mask?
|
|
293
|
+
# The same thing the kernel refuses when it hands one of its arrays
|
|
294
|
+
# over: a masked cell's bytes are out of contract, and a C function
|
|
295
|
+
# has no mask to consult, so it would read whatever is underneath.
|
|
296
|
+
# Refusing it here as well is what keeps `f.call` and `f.block.call`
|
|
297
|
+
# the same body run two ways -- the block reaches an UNDEF and says
|
|
298
|
+
# so, and the C would have quietly used the number beneath it.
|
|
299
|
+
raise Unsupported,
|
|
300
|
+
"`#{type.text}` is handed an array carrying a mask, and a " \
|
|
301
|
+
"C function has no mask to read; the values under a mask are " \
|
|
302
|
+
"not values, so `#strip_mask(fill)` is what says what the C " \
|
|
303
|
+
"should see there"
|
|
304
|
+
end
|
|
305
|
+
# A pointer is reached contiguously -- `p[i]` with no stride -- and
|
|
306
|
+
# only an entity is laid out that way, so a view is packed into one
|
|
307
|
+
# for the call and copied back afterwards if the C may write to it.
|
|
308
|
+
#
|
|
309
|
+
# `#to_ca` is not the way to pack one: it answers self for a view as
|
|
310
|
+
# well as for an entity, so reaching for it here handed the C a
|
|
311
|
+
# view's base pointer to walk contiguously, which wrote over its
|
|
312
|
+
# neighbours without a word. `#copy` is the one that always makes an
|
|
313
|
+
# entity.
|
|
314
|
+
Access.classify(array)[:entity] ? array : array.copy
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def computation_of (type, role)
|
|
318
|
+
return type.computation unless type.opaque?
|
|
319
|
+
raise Unsupported,
|
|
320
|
+
"`#{@name}` #{role} `#{type.text}`, which is a slot in the " \
|
|
321
|
+
"signature rather than a value a kernel can compute with"
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Reads the subset of C declarations a signature may be written in.
|
|
327
|
+
#
|
|
328
|
+
# Fiddle's parser is used for what it is good at -- deciding the ABI code
|
|
329
|
+
# of a type -- and this decides what that parser throws away: whether a
|
|
330
|
+
# parameter is a pointer, and what it was a pointer to. Between them a
|
|
331
|
+
# prototype yields both what is needed to call a function and what is
|
|
332
|
+
# needed to write one.
|
|
333
|
+
# @private
|
|
334
|
+
module CDeclaration
|
|
335
|
+
|
|
336
|
+
PARSER = ::Object.new.extend(Fiddle::CParser)
|
|
337
|
+
private_constant :PARSER
|
|
338
|
+
|
|
339
|
+
# The words a type may be spelled with. `<stdint.h>`'s exact-width
|
|
340
|
+
# names are here because Fiddle knows them; there is no `float64_t`
|
|
341
|
+
# because C has no such type -- its integer types have exact-width
|
|
342
|
+
# aliases and its floating types are `float` and `double`.
|
|
343
|
+
KEYWORDS = %w[
|
|
344
|
+
const unsigned signed void char short int long float double
|
|
345
|
+
int8_t int16_t int32_t int64_t
|
|
346
|
+
uint8_t uint16_t uint32_t uint64_t
|
|
347
|
+
size_t ssize_t ptrdiff_t intptr_t uintptr_t
|
|
348
|
+
].freeze
|
|
349
|
+
|
|
350
|
+
# Fiddle answers `long double` with the code for `long`, silently, so it
|
|
351
|
+
# is refused by name rather than trusted.
|
|
352
|
+
REFUSED = {
|
|
353
|
+
"long double" => "`long double` is not a type this reads: Fiddle " \
|
|
354
|
+
"reports it as `long`, which would be the wrong " \
|
|
355
|
+
"width without saying so",
|
|
356
|
+
}.freeze
|
|
357
|
+
|
|
358
|
+
# The CArray data type a pointer parameter takes, by the code Fiddle
|
|
359
|
+
# gave its element. This is the table CGenerator::STORAGE_C_TYPES
|
|
360
|
+
# already holds, read the other way round -- nothing new is decided
|
|
361
|
+
# here about how a C type and a CArray type correspond.
|
|
362
|
+
DATA_TYPES = {
|
|
363
|
+
Fiddle::TYPE_DOUBLE => :float64,
|
|
364
|
+
Fiddle::TYPE_FLOAT => :float32,
|
|
365
|
+
Fiddle::TYPE_CHAR => :int8,
|
|
366
|
+
Fiddle::TYPE_UCHAR => :uint8,
|
|
367
|
+
Fiddle::TYPE_SHORT => :int16,
|
|
368
|
+
Fiddle::TYPE_USHORT => :uint16,
|
|
369
|
+
Fiddle::TYPE_INT => :int32,
|
|
370
|
+
Fiddle::TYPE_UINT => :uint32,
|
|
371
|
+
Fiddle::TYPE_LONG => :int64,
|
|
372
|
+
Fiddle::TYPE_LONG_LONG => :int64,
|
|
373
|
+
}.freeze
|
|
374
|
+
|
|
375
|
+
# Codes Fiddle may return, mapped to what a kernel computes them in.
|
|
376
|
+
# Absent means the type may be written down but holds no value a body
|
|
377
|
+
# can compute with. `uint64_t` is absent for the reason CArray has no
|
|
378
|
+
# uint64 array: no computation type holds it without losing a bit.
|
|
379
|
+
COMPUTATION = {
|
|
380
|
+
Fiddle::TYPE_DOUBLE => :double,
|
|
381
|
+
Fiddle::TYPE_FLOAT => :double,
|
|
382
|
+
Fiddle::TYPE_CHAR => :int64,
|
|
383
|
+
Fiddle::TYPE_UCHAR => :int64,
|
|
384
|
+
Fiddle::TYPE_SHORT => :int64,
|
|
385
|
+
Fiddle::TYPE_USHORT => :int64,
|
|
386
|
+
Fiddle::TYPE_INT => :int64,
|
|
387
|
+
Fiddle::TYPE_UINT => :int64,
|
|
388
|
+
Fiddle::TYPE_LONG => :int64,
|
|
389
|
+
Fiddle::TYPE_LONG_LONG => :int64,
|
|
390
|
+
}.freeze
|
|
391
|
+
|
|
392
|
+
module_function
|
|
393
|
+
|
|
394
|
+
# Splits a prototype into [name, return CType, parameter CTypes]. The
|
|
395
|
+
# name is nil for the anonymous form, `double (*)(double)` -- the
|
|
396
|
+
# spelling C already has for the type of a function pointer, which is
|
|
397
|
+
# what this hands out.
|
|
398
|
+
def parse (prototype)
|
|
399
|
+
unless prototype.is_a?(String)
|
|
400
|
+
raise Unsupported,
|
|
401
|
+
"a C prototype is expected, as in `\"double j0(double)\"`; " \
|
|
402
|
+
"got #{prototype.class}"
|
|
403
|
+
end
|
|
404
|
+
text = prototype.strip.sub(/;\z/, "")
|
|
405
|
+
name, return_text, parameter_text = split(text, prototype)
|
|
406
|
+
parameters = split_parameters(parameter_text).map { |part|
|
|
407
|
+
read_type(part, prototype)
|
|
408
|
+
}
|
|
409
|
+
# `f(void)` takes nothing, which is not the same as taking a void.
|
|
410
|
+
parameters = [] if parameters.size == 1 &&
|
|
411
|
+
parameters.first.text == "void"
|
|
412
|
+
[name, read_type(return_text, prototype), parameters]
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def split (text, prototype)
|
|
416
|
+
if (match = /\A(.+?)\(\s*\*\s*\)\s*\((.*)\)\z/m.match(text))
|
|
417
|
+
[nil, match[1], match[2]]
|
|
418
|
+
elsif (match = /\A(.+?[\s\*])([A-Za-z_]\w*)\s*\((.*)\)\z/m.match(text))
|
|
419
|
+
[match[2], match[1], match[3]]
|
|
420
|
+
else
|
|
421
|
+
raise Unsupported,
|
|
422
|
+
"`#{prototype}` does not read as a C prototype; write it as " \
|
|
423
|
+
"`double j0(double)` to bind one, or `double (*)(double)` " \
|
|
424
|
+
"for one with no name"
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# Top-level commas only. Nothing in the supported subset nests, so this
|
|
429
|
+
# is a split -- written out so that admitting something that does nest
|
|
430
|
+
# is a change in one place.
|
|
431
|
+
def split_parameters (text)
|
|
432
|
+
return [] if text.strip.empty?
|
|
433
|
+
text.split(",").map(&:strip)
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# `[const] <keywords> [*] [name] [[]]` is the whole grammar there is.
|
|
437
|
+
# A parameter's own name is dropped: nothing reads it for a bound
|
|
438
|
+
# function, and a compiled one uses the block's parameter names.
|
|
439
|
+
def read_type (text, prototype)
|
|
440
|
+
array = nil
|
|
441
|
+
stripped = text.strip.sub(/\[\s*(\d*)\s*\]\s*\z/) {
|
|
442
|
+
array = $1.empty? ? :unsized : $1.to_i
|
|
443
|
+
""
|
|
444
|
+
}
|
|
445
|
+
words = stripped.split(/\s+|(?=\*)|(?<=\*)/).reject(&:empty?)
|
|
446
|
+
keywords = []
|
|
447
|
+
keywords << words.shift while words.first && KEYWORDS.include?(words.first)
|
|
448
|
+
pointer = false
|
|
449
|
+
while words.first == "*"
|
|
450
|
+
words.shift
|
|
451
|
+
pointer = true
|
|
452
|
+
end
|
|
453
|
+
# Whatever is left can only be the parameter's own name.
|
|
454
|
+
words.shift if words.first&.match?(/\A[A-Za-z_]\w*\z/)
|
|
455
|
+
if keywords.empty? || !words.empty?
|
|
456
|
+
raise Unsupported,
|
|
457
|
+
"`#{text.strip}` in `#{prototype}` is not a type this reads; " \
|
|
458
|
+
"it takes C's own spellings -- `double`, `int32_t`, " \
|
|
459
|
+
"`const double *` and the like"
|
|
460
|
+
end
|
|
461
|
+
build_type(keywords, pointer, array, text, prototype)
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def build_type (keywords, pointer, array, text, prototype)
|
|
465
|
+
spelling = keywords.reject { |word| word == "const" }.join(" ")
|
|
466
|
+
if (reason = REFUSED[spelling])
|
|
467
|
+
raise Unsupported, "#{reason} (in `#{prototype}`)"
|
|
468
|
+
end
|
|
469
|
+
# A parameter written as an array is a pointer at the ABI, whatever
|
|
470
|
+
# its declarator says.
|
|
471
|
+
pointer ||= !array.nil?
|
|
472
|
+
written = keywords.join(" ")
|
|
473
|
+
written += if array
|
|
474
|
+
" [#{array if array.is_a?(Integer)}]"
|
|
475
|
+
elsif pointer
|
|
476
|
+
" *"
|
|
477
|
+
else
|
|
478
|
+
""
|
|
479
|
+
end
|
|
480
|
+
code = fiddle_code(pointer ? "void *" : spelling, text, prototype)
|
|
481
|
+
# What it points at, for a pointer that points at numbers. `void *`
|
|
482
|
+
# has no element, which is what keeps it a slot.
|
|
483
|
+
element = nil
|
|
484
|
+
if pointer && spelling != "void"
|
|
485
|
+
element_code = fiddle_code(spelling, text, prototype)
|
|
486
|
+
if COMPUTATION[element_code]
|
|
487
|
+
element = CType.new(spelling, element_code,
|
|
488
|
+
COMPUTATION[element_code], false, nil, nil,
|
|
489
|
+
false)
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
CType.new(written, code, pointer ? nil : COMPUTATION[code], pointer,
|
|
493
|
+
array, element, keywords.include?("const"))
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
def fiddle_code (spelling, text, prototype)
|
|
497
|
+
PARSER.parse_ctype(spelling)
|
|
498
|
+
rescue StandardError
|
|
499
|
+
raise Unsupported,
|
|
500
|
+
"`#{text.strip}` in `#{prototype}` is not a type Fiddle knows. " \
|
|
501
|
+
"C's own spellings are what this takes, and `float64_t` is not " \
|
|
502
|
+
"one of them -- C's integer types have exact-width names, its " \
|
|
503
|
+
"floating types are `float` and `double`"
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
class << self
|
|
509
|
+
|
|
510
|
+
# A C function someone else compiled, reached by quoting its
|
|
511
|
+
# declaration:
|
|
512
|
+
#
|
|
513
|
+
# j0 = CArray.jit_extern("double j0(double)", from: "libgsl")
|
|
514
|
+
#
|
|
515
|
+
# `from` is where to look -- a path or library name, a Fiddle::Handle,
|
|
516
|
+
# or nothing at all, which searches what the process has already loaded.
|
|
517
|
+
#
|
|
518
|
+
# `extern` is C's own word for a body that lives elsewhere, and that is
|
|
519
|
+
# all this does: it asks Fiddle where the function is. Nothing is
|
|
520
|
+
# compiled here, which is the difference from `jit_function` and the
|
|
521
|
+
# reason the two are not one method with a branch in it.
|
|
522
|
+
#
|
|
523
|
+
# The declaration is C rather than a vocabulary of this compiler's own,
|
|
524
|
+
# because what is declared is a C function and the types it has to meet
|
|
525
|
+
# belong to whatever will call it. `void *params` is the point of the
|
|
526
|
+
# exercise, not an edge of it.
|
|
527
|
+
def extern (prototype, from: nil, &block)
|
|
528
|
+
if block
|
|
529
|
+
raise Unsupported,
|
|
530
|
+
"`jit_extern` finds a function already compiled, so a body " \
|
|
531
|
+
"here would be dropped; compile one with `CArray.jit_function`"
|
|
532
|
+
end
|
|
533
|
+
name, return_type, parameters = CDeclaration.parse(prototype)
|
|
534
|
+
unless name
|
|
535
|
+
raise Unsupported,
|
|
536
|
+
"`#{prototype}` names no function to find; give the name, as " \
|
|
537
|
+
"in `double j0(double)` -- or write the body and compile it " \
|
|
538
|
+
"with `CArray.jit_function`"
|
|
539
|
+
end
|
|
540
|
+
bind_c_function(prototype, name, return_type, parameters, from)
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
# A C function of your own, written in Ruby and compiled here:
|
|
544
|
+
#
|
|
545
|
+
# square = CArray.jit_function("double (*)(double)") { |x| x * x + 1 }
|
|
546
|
+
#
|
|
547
|
+
# `double (*)(double)` is the spelling C already has for the type of a
|
|
548
|
+
# function pointer, which is what this hands out. There is no name
|
|
549
|
+
# because nothing links by name -- the address is what travels -- so a
|
|
550
|
+
# name would have been invented to be looked at once. Writing one
|
|
551
|
+
# anyway is allowed, and becomes the symbol in the compiled object.
|
|
552
|
+
#
|
|
553
|
+
# What comes back is the same object `jit_extern` hands out, so a kernel
|
|
554
|
+
# calls either without knowing which it has; `compiled?` is where the
|
|
555
|
+
# difference is still visible, along with the block, which it keeps.
|
|
556
|
+
def function (prototype, &block)
|
|
557
|
+
unless block
|
|
558
|
+
raise Unsupported,
|
|
559
|
+
"a function is compiled from a block, and none was given -- " \
|
|
560
|
+
"or find one already compiled with " \
|
|
561
|
+
"`CArray.jit_extern(#{prototype.inspect})`"
|
|
562
|
+
end
|
|
563
|
+
name, return_type, parameters = CDeclaration.parse(prototype)
|
|
564
|
+
compile_c_function(prototype, name, return_type, parameters, block)
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
private
|
|
568
|
+
|
|
569
|
+
def bind_c_function (prototype, name, return_type, parameters, from)
|
|
570
|
+
handle = library_handle(from)
|
|
571
|
+
begin
|
|
572
|
+
pointer = handle[name.to_s]
|
|
573
|
+
rescue Fiddle::DLError => error
|
|
574
|
+
raise Unsupported,
|
|
575
|
+
"`#{name}` was not found in " \
|
|
576
|
+
"#{from ? from.inspect : "the loaded libraries"} " \
|
|
577
|
+
"(#{error.message}); pass `from:` to say which library it is in"
|
|
578
|
+
end
|
|
579
|
+
CFunction.new(name, prototype, return_type, parameters, pointer)
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
# The one function a compiled object holds. Nothing looks it up by name
|
|
583
|
+
# -- the address is what travels -- but a profiler and a backtrace print
|
|
584
|
+
# it, and a program handing several of these to a solver would otherwise
|
|
585
|
+
# see one name for all of them. So an anonymous one carries its own
|
|
586
|
+
# digest, and a named one carries the name it was given.
|
|
587
|
+
#
|
|
588
|
+
# Always behind a prefix, though, and that is not tidiness. A name the
|
|
589
|
+
# C already knows is the dangerous case, and the dangerous case is the
|
|
590
|
+
# one that does *not* fail: `double sin(double)` matches math.h's
|
|
591
|
+
# declaration, so the generated file defines libm's `sin` and the
|
|
592
|
+
# shared object exports it -- where symbols are interposable, that
|
|
593
|
+
# replaces sine for whatever loads it later. A mismatched signature is
|
|
594
|
+
# a compile error and would have been noticed; this one would not.
|
|
595
|
+
PREFIX = "carray_jit_"
|
|
596
|
+
|
|
597
|
+
def function_symbol (name, key)
|
|
598
|
+
digest = Digest::SHA256.hexdigest(key.inspect)[0, 12]
|
|
599
|
+
"#{PREFIX}#{name || "function"}_#{digest}"
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
def compile_c_function (prototype, name, return_type, parameters, block)
|
|
603
|
+
node, source, origin = read_block(block)
|
|
604
|
+
key = [source, return_type.text, parameters.map(&:text), name]
|
|
605
|
+
found = function_registry[key]
|
|
606
|
+
return found if found
|
|
607
|
+
function_registry[key] =
|
|
608
|
+
build_c_function(prototype, name, return_type, parameters,
|
|
609
|
+
source, node, origin, block, function_symbol(name, key))
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
def function_registry
|
|
613
|
+
@function_registry ||= {}
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
def build_c_function (prototype, name, return_type, parameters,
|
|
617
|
+
source, node, origin, block, symbol)
|
|
618
|
+
# A function is a function of its parameters. Whatever else the block
|
|
619
|
+
# reaches for is refused, and the reason differs by what it is -- so
|
|
620
|
+
# the captures are looked at before the body is walked, or the body
|
|
621
|
+
# would raise first and say something less useful.
|
|
622
|
+
names = block.parameters.map(&:last)
|
|
623
|
+
unless names.size == parameters.size
|
|
624
|
+
raise Unsupported,
|
|
625
|
+
"`#{prototype}` names #{parameters.size} " \
|
|
626
|
+
"#{parameters.size == 1 ? 'parameter' : 'parameters'}, and " \
|
|
627
|
+
"the block takes #{names.size}"
|
|
628
|
+
end
|
|
629
|
+
refuse_captures(source, node, block, names, name && name.to_sym)
|
|
630
|
+
|
|
631
|
+
# `void` is a return type a body may have: a function whose work is
|
|
632
|
+
# through its pointer parameters has nothing to hand back, and C says
|
|
633
|
+
# so with the word. What it cannot be called in is expression
|
|
634
|
+
# position, which is where the value would have been wanted -- that
|
|
635
|
+
# is the same refusal a borrowed `void` function already gets.
|
|
636
|
+
#
|
|
637
|
+
# Every other return type with no computation behind it is a pointer,
|
|
638
|
+
# which a body cannot produce: there is nothing here to take an
|
|
639
|
+
# address of that would outlive the call.
|
|
640
|
+
returns_nothing = return_type.fiddle == Fiddle::TYPE_VOID &&
|
|
641
|
+
!return_type.pointer
|
|
642
|
+
unless return_type.computation || returns_nothing
|
|
643
|
+
raise Unsupported,
|
|
644
|
+
"`#{prototype}` returns `#{return_type.text}`, which is no " \
|
|
645
|
+
"value a compiled body can produce"
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
# A pointer to numbers is indexable, and `const` says whether it may
|
|
649
|
+
# be written through -- which is the whole of the read/write
|
|
650
|
+
# distinction, spelled the way C spells it.
|
|
651
|
+
pointers = names.zip(parameters).select { |_, type| type.pointer }
|
|
652
|
+
.to_h { |name, type|
|
|
653
|
+
[name, type.indexable? ? !type.const : nil]
|
|
654
|
+
}
|
|
655
|
+
pointer_types = names.zip(parameters).select { |_, type| type.indexable? }
|
|
656
|
+
.to_h { |name, type| [name, type.element.computation] }
|
|
657
|
+
|
|
658
|
+
# A declaration that gave a name puts that name in scope inside its
|
|
659
|
+
# own body, as C does, so the body can call itself. An anonymous one
|
|
660
|
+
# has nothing to call itself by, and gets no recursion.
|
|
661
|
+
analyzer = Analyzer.new(source, node: node, function: true,
|
|
662
|
+
returns: !returns_nothing,
|
|
663
|
+
pointers: pointers,
|
|
664
|
+
recursion: (name && [name.to_sym, parameters,
|
|
665
|
+
return_type.computation]))
|
|
666
|
+
names = analyzer.parameter_names
|
|
667
|
+
# A pointer is not a value however it is spelled: `void *` cannot be
|
|
668
|
+
# reached at all, and one that points at numbers has to be indexed.
|
|
669
|
+
by_name = names.zip(parameters).to_h
|
|
670
|
+
loose = analyzer.scalar_names.find { |name|
|
|
671
|
+
by_name[name] && by_name[name].pointer
|
|
672
|
+
}
|
|
673
|
+
if loose
|
|
674
|
+
type = by_name.fetch(loose)
|
|
675
|
+
raise Unsupported,
|
|
676
|
+
(if type.indexable?
|
|
677
|
+
"`#{loose}` is declared `#{type.text}`, which is a pointer; " \
|
|
678
|
+
"index it, as in `#{loose}[0]`"
|
|
679
|
+
else
|
|
680
|
+
"`#{loose}` is declared `#{type.text}`, which is a slot in " \
|
|
681
|
+
"the signature rather than a value; the body cannot read it"
|
|
682
|
+
end)
|
|
683
|
+
end
|
|
684
|
+
types = names.zip(parameters).reject { |_, type| type.pointer }
|
|
685
|
+
.to_h { |parameter, type| [parameter, type.computation] }
|
|
686
|
+
|
|
687
|
+
assignment = TypeAssignment.new(analyzer.body, {}, {}, {},
|
|
688
|
+
scalar_types: types,
|
|
689
|
+
pointer_types: pointer_types)
|
|
690
|
+
generator = CGenerator.new(analyzer, {}, assignment.scalar_types,
|
|
691
|
+
origin: origin, block_source: source)
|
|
692
|
+
c_source = generator.generate_function(
|
|
693
|
+
symbol, names, parameters, return_type.text, return_type.computation)
|
|
694
|
+
handle, = Compiler.build(c_source, symbol, header: generator.provenance)
|
|
695
|
+
# Where the body can divide by zero or reach outside an array, the
|
|
696
|
+
# object carries a place to say so. Reading it is what lets a call
|
|
697
|
+
# from Ruby raise what the same expression raises in Ruby.
|
|
698
|
+
error = if generator.uses_error_flag?
|
|
699
|
+
Fiddle::Pointer.new(handle[CGenerator::ERROR_FLAG], 4)
|
|
700
|
+
end
|
|
701
|
+
# A body that reports failures is generated a second time for pasting,
|
|
702
|
+
# with the flag as a parameter. A second generator rather than the
|
|
703
|
+
# same one twice: what it emitted is what it holds, and the two forms
|
|
704
|
+
# differ from the first statement that can fail onwards.
|
|
705
|
+
pasted = generator
|
|
706
|
+
if generator.uses_error_flag?
|
|
707
|
+
pasted = CGenerator.new(analyzer, {}, assignment.scalar_types,
|
|
708
|
+
origin: origin, block_source: source)
|
|
709
|
+
pasted.generate_function(symbol, names, parameters, return_type.text,
|
|
710
|
+
return_type.computation,
|
|
711
|
+
error_parameter: true)
|
|
712
|
+
end
|
|
713
|
+
CFunction.new(symbol, prototype, return_type, parameters, handle[symbol],
|
|
714
|
+
block: block, c_source: generator.provenance + c_source,
|
|
715
|
+
origin: origin, error: error,
|
|
716
|
+
definition: pasted.function_definition,
|
|
717
|
+
helpers: pasted.helper_needs,
|
|
718
|
+
takes_error: generator.uses_error_flag?,
|
|
719
|
+
raise_messages: generator.raise_messages)
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
# Nothing outside the parameter list may be reached. A number could in
|
|
723
|
+
# principle be written into the C as a literal, and another c_function's
|
|
724
|
+
# address as a constant -- but both would put something in the compiled
|
|
725
|
+
# object that no key covers, so an object built for one capture would be
|
|
726
|
+
# handed back for another. The kernel path avoids that by passing
|
|
727
|
+
# captures in buffers at call time; a C function has no buffers, so its
|
|
728
|
+
# parameters are its whole surface.
|
|
729
|
+
#
|
|
730
|
+
# Read the other way round, this is what makes the body's text and the
|
|
731
|
+
# signature a complete key: with nothing captured, they settle which
|
|
732
|
+
# function it is. And it is what makes the compiled object pure C -- it
|
|
733
|
+
# touches no Ruby value and references no Ruby symbol, so the address is
|
|
734
|
+
# safe to call from a thread that holds no GVL, which is more than a
|
|
735
|
+
# Ruby-defined callback usually manages.
|
|
736
|
+
def refuse_captures (source, node, block, parameters, own_name = nil)
|
|
737
|
+
captured = capture_names(source, node) - parameters
|
|
738
|
+
# The function's own name is not a capture: it is the declarator's,
|
|
739
|
+
# in scope inside the body it heads, exactly as C has it. What the
|
|
740
|
+
# local holds while the body is being compiled is nothing -- the
|
|
741
|
+
# assignment has not happened yet -- and the compiled call reaches
|
|
742
|
+
# the symbol, not the local.
|
|
743
|
+
captured -= [own_name] if own_name
|
|
744
|
+
return if captured.empty?
|
|
745
|
+
name = captured.first
|
|
746
|
+
value = begin
|
|
747
|
+
binding_of(block).local_variable_get(name)
|
|
748
|
+
rescue NameError
|
|
749
|
+
nil
|
|
750
|
+
end
|
|
751
|
+
kind = case value
|
|
752
|
+
when CArray then "an array"
|
|
753
|
+
when CFunction then "another C function"
|
|
754
|
+
else "a value"
|
|
755
|
+
end
|
|
756
|
+
raise Unsupported,
|
|
757
|
+
"this function reaches `#{name}`, which is #{kind} outside it; " \
|
|
758
|
+
"a compiled function takes everything it needs through its " \
|
|
759
|
+
"parameters, so name it as one"
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
def library_handle (from)
|
|
763
|
+
case from
|
|
764
|
+
when nil then Fiddle::Handle::DEFAULT
|
|
765
|
+
when Fiddle::Handle then from
|
|
766
|
+
when String then Fiddle::Handle.new(from)
|
|
767
|
+
else
|
|
768
|
+
raise Unsupported,
|
|
769
|
+
"`from:` takes a library name or a Fiddle::Handle, " \
|
|
770
|
+
"got #{from.class}"
|
|
771
|
+
end
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
end
|
|
777
|
+
end
|