Class: CArray::JIT::CFunction
- Inherits:
-
Object
- Object
- CArray::JIT::CFunction
- Defined in:
- lib/carray/jit/c_function.rb
Instance Attribute Summary collapse
-
#block ⇒ Object
readonly
Returns the value of attribute block.
-
#c_source ⇒ String?
readonly
The C compiled for a body written here, or
nilfor one found elsewhere. -
#definition ⇒ Object
readonly
Returns the value of attribute definition.
-
#dependencies ⇒ Object
readonly
Returns the value of attribute dependencies.
-
#helpers ⇒ Object
readonly
Returns the value of attribute helpers.
-
#name ⇒ String
readonly
The function's name, as the prototype gives it.
-
#origin ⇒ Object
readonly
Returns the value of attribute origin.
-
#parameters ⇒ Object
readonly
Returns the value of attribute parameters.
-
#pointer ⇒ Object
readonly
Returns the value of attribute pointer.
-
#prototype ⇒ String
readonly
The C declaration this was named by.
-
#raise_messages ⇒ Object
readonly
Returns the value of attribute raise_messages.
-
#return_type ⇒ Object
readonly
Returns the value of attribute return_type.
-
#symbol ⇒ Object
readonly
Returns the value of attribute symbol.
Instance Method Summary collapse
-
#argument_types ⇒ Object
The Fiddle types, for calling it from Ruby.
-
#arity ⇒ Integer
How many arguments the function takes.
-
#c_declaration(typedef_name) ⇒ Object
The C spelling of the pointer type, for the typedef a kernel emits.
-
#c_source_as(symbol) ⇒ Object
The C this was compiled from, under a symbol of the caller's choosing.
-
#call(*arguments) ⇒ Object
(also: #[])
Calling it from Ruby, so that a body means the same thing run either way.
-
#call_through_shim(arguments) ⇒ Object
Fiddle carries no complex, so the shim takes each complex argument as a pair of doubles and writes a complex result back the same way.
-
#carries_a_complex? ⇒ Boolean
Whether a call from Ruby has to go round through the shim.
-
#clear_error ⇒ Object
Put the flag down, before lending the address to something that will call it more than once.
-
#compiled? ⇒ Boolean
True for one compiled from a Ruby block rather than bound from a library.
-
#declaration_as(name) ⇒ Object
The declaration under the name the block reached it by.
-
#discarded_result_type ⇒ Object
What it computes in where the value is dropped.
-
#initialize(name, prototype, return_type, parameters, pointer, symbol: nil, block: nil, c_source: nil, origin: nil, error: nil, definition: nil, helpers: nil, takes_error: false, raise_messages: {}, dependencies: [], shim: nil) ⇒ CFunction
constructor
A new instance of CFunction.
-
#inspect ⇒ String
The prototype this was named by.
-
#kernel_key ⇒ Object
What a compiled kernel depends on.
-
#label ⇒ Object
What to call this in a message: the name its declaration gave, and the symbol where it gave none.
-
#pack_complex(value, type) ⇒ Object
A Ruby number of any kind arrives as the two doubles the shim reads.
-
#pasted? ⇒ Boolean
True for one a kernel can paste into its own C rather than call through a pointer.
-
#pasted_takes_error? ⇒ Boolean
True when the pasted copy takes the caller's error slot as its last argument.
-
#report_error ⇒ Object
What the kernel raises for the same code, since it is the same thing that happened:
6 % 0is a ZeroDivisionError wherever it is written, and the compiled body cannot raise it itself. -
#result_type ⇒ Object
What a kernel computes the result in.
-
#signature ⇒ Object
The signature, without the address.
-
#to_s ⇒ String
The function's name.
-
#watching ⇒ Object
The window a caller opens when it hands the address out.
Constructor Details
#initialize(name, prototype, return_type, parameters, pointer, symbol: nil, block: nil, c_source: nil, origin: nil, error: nil, definition: nil, helpers: nil, takes_error: false, raise_messages: {}, dependencies: [], shim: nil) ⇒ CFunction
Returns a new instance of CFunction.
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/carray/jit/c_function.rb', line 112 def initialize (name, prototype, return_type, parameters, pointer, symbol: nil, block: nil, c_source: nil, origin: nil, error: nil, definition: nil, helpers: nil, takes_error: false, raise_messages: {}, dependencies: [], shim: nil) # The name the declaration gave, which is what a reader wrote and # what an error should say. It is nil where the declaration gave # none -- `double (*)(double)` names no function. @name = name && name.to_sym # The name in the object, which is what a call reaches and what two # functions have to differ by. For one bound from a library they # are the same; for one compiled here the symbol carries a digest of # the body, so that two bodies declared alike stay apart. @symbol = (symbol || name)&.to_sym @prototype = prototype @return_type = return_type @parameters = parameters @pointer = pointer # A function written in Ruby keeps its block, so that what the kernel # runs and what Ruby would compute can be put side by side. @block = block @c_source = c_source # The definition on its own, without the file it was compiled in, and # what it wants from a preamble -- what a kernel needs to paste it. @definition = definition @helpers = helpers @dependencies = dependencies # True when that definition ends in an `int32_t *`: the body can report # a failure, and pasted it reports into the caller's slot rather than # into the flag in its own object. @takes_error = takes_error @raise_messages = @origin = origin # Where the compiled body says a division had no divisor, or a # subscript ran off its array. Nil when the body can do neither. @error = error # The address of the entry point a call from Ruby takes where the # signature carries a complex by value; nil where it does not, which # is every other signature and every borrowed function. @shim = shim @function = nil end |
Instance Attribute Details
#block ⇒ Object (readonly)
Returns the value of attribute block.
101 102 103 |
# File 'lib/carray/jit/c_function.rb', line 101 def block @block end |
#c_source ⇒ String? (readonly)
Returns the C compiled for a body written here, or
nil for one found elsewhere.
101 102 103 104 105 106 107 108 109 110 |
# File 'lib/carray/jit/c_function.rb', line 101 attr_reader :name, :symbol, :prototype, :return_type, :parameters, :pointer, :block, :c_source, :origin, :definition, :helpers, # The compiled functions this body calls. Whoever pastes # the definition has to paste these beside it: it reaches # them by symbol, and the symbol is only there if the # definition is. :dependencies, # What `raise` in the body said, by the code it reports. # The kernel that pastes it answers for these too. :raise_messages |
#definition ⇒ Object (readonly)
Returns the value of attribute definition.
101 102 103 |
# File 'lib/carray/jit/c_function.rb', line 101 def definition @definition end |
#dependencies ⇒ Object (readonly)
Returns the value of attribute dependencies.
101 102 103 |
# File 'lib/carray/jit/c_function.rb', line 101 def dependencies @dependencies end |
#helpers ⇒ Object (readonly)
Returns the value of attribute helpers.
101 102 103 |
# File 'lib/carray/jit/c_function.rb', line 101 def helpers @helpers end |
#name ⇒ String (readonly)
Returns the function's name, as the prototype gives it.
101 102 103 |
# File 'lib/carray/jit/c_function.rb', line 101 def name @name end |
#origin ⇒ Object (readonly)
Returns the value of attribute origin.
101 102 103 |
# File 'lib/carray/jit/c_function.rb', line 101 def origin @origin end |
#parameters ⇒ Object (readonly)
Returns the value of attribute parameters.
101 102 103 |
# File 'lib/carray/jit/c_function.rb', line 101 def parameters @parameters end |
#pointer ⇒ Object (readonly)
Returns the value of attribute pointer.
101 102 103 |
# File 'lib/carray/jit/c_function.rb', line 101 def pointer @pointer end |
#prototype ⇒ String (readonly)
Returns the C declaration this was named by.
101 102 103 104 105 106 107 108 109 110 |
# File 'lib/carray/jit/c_function.rb', line 101 attr_reader :name, :symbol, :prototype, :return_type, :parameters, :pointer, :block, :c_source, :origin, :definition, :helpers, # The compiled functions this body calls. Whoever pastes # the definition has to paste these beside it: it reaches # them by symbol, and the symbol is only there if the # definition is. :dependencies, # What `raise` in the body said, by the code it reports. # The kernel that pastes it answers for these too. :raise_messages |
#raise_messages ⇒ Object (readonly)
Returns the value of attribute raise_messages.
101 102 103 |
# File 'lib/carray/jit/c_function.rb', line 101 def @raise_messages end |
#return_type ⇒ Object (readonly)
Returns the value of attribute return_type.
101 102 103 |
# File 'lib/carray/jit/c_function.rb', line 101 def return_type @return_type end |
#symbol ⇒ Object (readonly)
Returns the value of attribute symbol.
101 102 103 |
# File 'lib/carray/jit/c_function.rb', line 101 def symbol @symbol end |
Instance Method Details
#argument_types ⇒ Object
The Fiddle types, for calling it from Ruby.
261 262 263 |
# File 'lib/carray/jit/c_function.rb', line 261 def argument_types @parameters.map(&:fiddle) end |
#arity ⇒ Integer
Returns how many arguments the function takes.
256 257 258 |
# File 'lib/carray/jit/c_function.rb', line 256 def arity @parameters.size end |
#c_declaration(typedef_name) ⇒ Object
The C spelling of the pointer type, for the typedef a kernel emits.
266 267 268 269 |
# File 'lib/carray/jit/c_function.rb', line 266 def c_declaration (typedef_name) "typedef #{@return_type.text} (*#{typedef_name})" \ "(#{@parameters.map(&:text).join(', ')});" end |
#c_source_as(symbol) ⇒ Object
The C this was compiled from, under a symbol of the caller's choosing.
For a caller writing the C into a file of its own rather than letting this compile it: the symbol here carries a digest of the body, which is right for an object in a cache and wrong for one in a repository, where the same build has to give the same name every time.
It is a rename rather than a second run of the generator, and that is exact rather than close enough: the symbol is the only thing in the generated file that the choice of symbol decides. Two compilations of one block under two declared names differ in nothing else once both symbols are levelled, which the suite checks.
The caller owns the name it picks, and with it the one hazard the
generator's own prefix exists to close: a body declared sin
emitted as sin defines libm's, and the object exports it. What is
refused here is a name C cannot spell, and the generator's own
namespace -- an object built under carray_jit_... would answer to
a symbol a cached kernel is entitled to.
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/carray/jit/c_function.rb', line 234 def c_source_as (symbol) symbol = symbol.to_s unless @c_source raise Unsupported, "`#{label}` was bound from a library rather than compiled " \ "here, so there is no C of its own to write out" end unless symbol =~ /\A[A-Za-z_][A-Za-z0-9_]*\z/ raise Unsupported, "`#{symbol}` is not a C identifier, so nothing could be " \ "defined under it" end if symbol.start_with?(PREFIX) raise Unsupported, "`#{symbol}` is in this compiler's own namespace " \ "(`#{PREFIX}`), where a cached kernel is entitled to the " \ "name; pick one of your own" end @c_source.gsub(@symbol.to_s, symbol) end |
#call(*arguments) ⇒ Object Also known as: []
Calling it from Ruby, so that a body means the same thing run either way. Slow -- this is the several-hundred-nanosecond path -- and here for testing and for the odd cell, not for sweeping an array.
A pointer to numbers takes a CArray, which is the thing in this
library that is a run of numbers with a type. The block sees the
array itself and reaches it with #[], the compiled C sees its
address and reaches it with a subscript, and coef[0] means the same
in both -- so the body agrees with itself whichever way it is run.
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
# File 'lib/carray/jit/c_function.rb', line 297 def call (*arguments) unless arguments.size == @parameters.size raise ArgumentError, "wrong number of arguments (given #{arguments.size}, " \ "expected #{@parameters.size})" end return call_through_shim(arguments) if @shim if carries_a_complex? raise Unsupported, "`#{self}` carries a C99 complex by value, which Fiddle has " \ "no type for, so it cannot be called from Ruby; a kernel " \ "calls it as C calls it" end @function ||= Fiddle::Function.new(@pointer, argument_types, @return_type.fiddle, name: label.to_s) arrays = [] prepared = arguments.zip(@parameters).map { |argument, type| next argument unless type.indexable? && argument.is_a?(CArray) buffer = check_array(argument, type) arrays << [argument, buffer, type] buffer } # Borrowed rather than cleared, and borrowed here rather than on the # way in: a call made inside a window -- someone else is holding this # function's address and watching the same flag -- must answer for # itself without disarming them. The checks above never reach the C # and so never touch the flag at all. outer = error_code write_error(0) begin Access.open(arrays.map { |_, buffer, _| buffer }, arrays.map { |_, _, type| !type.const }, arrays.map { nil }, arrays.map { nil }) do |bases| slot = -1 prepared = prepared.map { |value| next value unless arrays.any? { |_, buffer, _| buffer.equal?(value) } Fiddle::Pointer.new(bases[slot += 1][:pointer]) } @result = @function.call(*prepared) end code = error_code ensure write_error(outer) end raise_for(code) # A view was copied to be made contiguous; a writable one is copied # back, because the C wrote into the copy. arrays.each do |array, buffer, type| array[] = buffer unless type.const || array.equal?(buffer) end @result end |
#call_through_shim(arguments) ⇒ Object
Fiddle carries no complex, so the shim takes each complex argument as a pair of doubles and writes a complex result back the same way. Everything else keeps the type the declaration gave it and goes through Fiddle as before -- including a pointer parameter, which is still a CArray on this side.
It is the same compiled body either way: the shim calls the function, it does not reimplement it.
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 |
# File 'lib/carray/jit/c_function.rb', line 366 def call_through_shim (arguments) returns_complex = @return_type.complex? types = @parameters.map { |type| type.complex? ? Fiddle::TYPE_VOIDP : type.fiddle } types << Fiddle::TYPE_VOIDP if returns_complex @shim_function ||= Fiddle::Function.new(@shim, types, returns_complex ? Fiddle::TYPE_VOID : @return_type.fiddle) result = returns_complex ? String.new("\0" * 16) : nil arrays = [] prepared = arguments.zip(@parameters).map { |argument, type| next pack_complex(argument, type) if type.complex? next argument unless type.indexable? && argument.is_a?(CArray) buffer = check_array(argument, type) arrays << [argument, buffer, type] buffer } outer = error_code write_error(0) begin Access.open(arrays.map { |_, buffer, _| buffer }, arrays.map { |_, _, type| !type.const }, arrays.map { nil }, arrays.map { nil }) do |bases| slot = -1 passed = prepared.map { |value| next value unless arrays.any? { |_, buffer, _| buffer.equal?(value) } Fiddle::Pointer.new(bases[slot += 1][:pointer]) } passed << result if returns_complex @result = @shim_function.call(*passed) end code = error_code ensure write_error(outer) end raise_for(code) arrays.each do |array, buffer, type| array[] = buffer unless type.const || array.equal?(buffer) end @result = Complex(*result.unpack("dd")) if returns_complex @result end |
#carries_a_complex? ⇒ Boolean
Whether a call from Ruby has to go round through the shim.
354 355 356 |
# File 'lib/carray/jit/c_function.rb', line 354 def carries_a_complex? @return_type.complex? || @parameters.any?(&:complex?) end |
#clear_error ⇒ Object
Put the flag down, before lending the address to something that will
call it more than once. #watching is this and #report_error with
the lending in between, and is what to reach for where the window is a
block; these two are here for a window that is not -- one opened in
one method and closed in another, or one whose block belongs to
somebody else.
484 485 486 |
# File 'lib/carray/jit/c_function.rb', line 484 def clear_error write_error(0) end |
#compiled? ⇒ Boolean
True for one compiled from a Ruby block rather than bound from a library.
157 158 159 |
# File 'lib/carray/jit/c_function.rb', line 157 def compiled? !@block.nil? end |
#declaration_as(name) ⇒ Object
The declaration under the name the block reached it by. What a
message about a call should say: the caller wrote f, and the symbol
a body compiled here carries -- carray_jit_two_<digest> -- along
with the file it was written in are answers to a question nobody
asked there. Those stay in to_s, which names the object rather
than the call.
184 185 186 |
# File 'lib/carray/jit/c_function.rb', line 184 def declaration_as (name) "#{@return_type.text} #{name}(#{@parameters.map(&:text).join(', ')})" end |
#discarded_result_type ⇒ Object
What it computes in where the value is dropped. void is a return
type a call may have and a cell may not, so the question only has an
answer in statement position -- which is the one place that asks.
279 280 281 |
# File 'lib/carray/jit/c_function.rb', line 279 def discarded_result_type @return_type.opaque? ? :void : @return_type.computation end |
#inspect ⇒ String
Returns the prototype this was named by.
432 433 434 |
# File 'lib/carray/jit/c_function.rb', line 432 def inspect "#<CArray::JIT::CFunction #{self}>" end |
#kernel_key ⇒ Object
What a compiled kernel depends on. Two functions that share it share a kernel.
For one bound from a library that is the signature alone: the address
arrives with the call, so j0 and y0 are the same kernel and it is
compiled once. For one compiled here it is the signature and the
symbol, which carries the digest of the body -- the body is on its way
into the kernel's own translation unit, and a kernel that has one body
pasted into it cannot serve another function that is merely declared
the same way. Splitting the cache costs a compile per body; sharing
it would hand back the wrong answer, and would do it quietly.
204 205 206 |
# File 'lib/carray/jit/c_function.rb', line 204 def kernel_key compiled? ? [signature, @symbol] : signature end |
#label ⇒ Object
What to call this in a message: the name its declaration gave, and the symbol where it gave none.
210 211 212 |
# File 'lib/carray/jit/c_function.rb', line 210 def label @name || @symbol end |
#pack_complex(value, type) ⇒ Object
A Ruby number of any kind arrives as the two doubles the shim reads.
Complex() is what Ruby itself converts with, so an Integer and a
Float are taken where a Complex is asked for, as they are in Ruby.
414 415 416 417 418 419 420 421 422 |
# File 'lib/carray/jit/c_function.rb', line 414 def pack_complex (value, type) number = begin Complex(value) rescue TypeError, ArgumentError raise Unsupported, "`#{type.text}` takes a number, got #{value.class}" end String.new([number.real.to_f, number.imaginary.to_f].pack("dd")) end |
#pasted? ⇒ Boolean
True for one a kernel can paste into its own C rather than call through a pointer. The body has to be here to paste, which a borrowed function's is not: it arrives as an address and nothing else.
164 165 166 |
# File 'lib/carray/jit/c_function.rb', line 164 def pasted? compiled? && !@definition.nil? end |
#pasted_takes_error? ⇒ Boolean
True when the pasted copy takes the caller's error slot as its last
argument. Standing alone the same body reports into the flag in its
own object -- #call reads that one -- but pasted there is no such
object around it, and the failure belongs to the kernel that is
running: 1 / 0 in a function called from a kernel raises the
ZeroDivisionError the kernel raises for its own.
174 175 176 |
# File 'lib/carray/jit/c_function.rb', line 174 def pasted_takes_error? pasted? && @takes_error end |
#report_error ⇒ Object
What the kernel raises for the same code, since it is the same thing
that happened: 6 % 0 is a ZeroDivisionError wherever it is written,
and the compiled body cannot raise it itself. A caller reaching the
address from C sees the stand-in the helper returned and the flag
standing, which is C's own arrangement for a function that has to
return something whatever happened.
Quiet where nothing stands, so that a caller may ask having no idea whether anything failed -- which is the position a caller is in after handing the address to a library. Asking does not put the flag down: it reads, and the window that put it down is what picks it up.
499 500 501 |
# File 'lib/carray/jit/c_function.rb', line 499 def report_error raise_for(error_code) end |
#result_type ⇒ Object
What a kernel computes the result in.
272 273 274 |
# File 'lib/carray/jit/c_function.rb', line 272 def result_type computation_of(@return_type, "returns") end |
#signature ⇒ Object
The signature, without the address.
189 190 191 |
# File 'lib/carray/jit/c_function.rb', line 189 def signature [@return_type.text, @parameters.map(&:text)] end |
#to_s ⇒ String
Returns the function's name.
425 426 427 428 429 |
# File 'lib/carray/jit/c_function.rb', line 425 def to_s text = "#{@return_type.text} #{@name}" \ "(#{@parameters.map(&:text).join(', ')})" @origin ? "#{text} at #{@origin}" : text end |
#watching ⇒ Object
The window a caller opens when it hands the address out.
#call is one call, and answers for it before it returns. A library
given #pointer calls whenever it likes, as often as it likes, and
what wants an answer is the whole of that -- so the flag is put down
once, the address is lent for as long as the block runs, and what
happened is asked for once at the end. It is the arrangement a kernel
already keeps with its own slot, which is cleared before a sweep and
read after it, never per cell.
f.watching do
Integration.qags(f.pointer, 0.0, 1.0)
end
A failure inside the block outranks whatever the library made of it. A body that fails returns a stand-in, so the library is the first to complain -- that the endpoints do not straddle, that the iteration did not converge -- and those complaints are the failure's consequences, not what happened. So the flag is read before that exception is let through, and only where nothing stands does the library's own story get to be the story.
Windows nest, and a call made inside one leaves it armed: both borrow the flag and put it back as they found it, so an inner window answers for its own block and no other.
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 |
# File 'lib/carray/jit/c_function.rb', line 461 def watching outer = error_code write_error(0) code = 0 begin result = yield code = error_code rescue StandardError raise_for(error_code) raise ensure write_error(outer) end raise_for(code) result end |