Class: CArray::JIT::CFunction

Inherits:
Object
  • Object
show all
Defined in:
lib/carray/jit/c_function.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

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 = 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.

Returns:

  • (String, nil) —

    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.

Returns:

  • (String) —

    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.

Returns:

  • (String) —

    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
  @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.

Returns:

  • (Integer) —

    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.

Returns:

  • (Boolean)


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.

Returns:

  • (Boolean)


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.

Returns:

  • (String) —

    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.

Returns:

  • (Boolean)


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.

Returns:

  • (Boolean)


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.

Returns:

  • (String) —

    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