carray-jit 0.1.3 → 0.1.4

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.
@@ -111,9 +111,10 @@ class CArray
111
111
 
112
112
  def initialize (name, prototype, return_type, parameters, pointer,
113
113
  symbol: nil,
114
- block: nil, c_source: nil, origin: nil, error: nil,
114
+ block: nil, c_source: nil, origin: nil,
115
115
  definition: nil, helpers: nil, takes_error: false,
116
- raise_messages: {}, dependencies: [], shim: nil)
116
+ raise_messages: {}, dependencies: [], shim: nil,
117
+ error_state: nil)
117
118
  # The name the declaration gave, which is what a reader wrote and
118
119
  # what an error should say. It is nil where the declaration gave
119
120
  # none -- `double (*)(double)` names no function.
@@ -142,9 +143,10 @@ class CArray
142
143
  @takes_error = takes_error
143
144
  @raise_messages = raise_messages
144
145
  @origin = origin
145
- # Where the compiled body says a division had no divisor, or a
146
- # subscript ran off its array. Nil when the body can do neither.
147
- @error = error
146
+ # What the object answers when asked where this thread's flag, hook
147
+ # and stand-in stand. Nil when the body can neither divide by zero
148
+ # nor raise, and so has none of them.
149
+ @error_state = error_state
148
150
  # The address of the entry point a call from Ruby takes where the
149
151
  # signature carries a complex by value; nil where it does not, which
150
152
  # is every other signature and every borrowed function.
@@ -324,6 +326,7 @@ class CArray
324
326
  # and so never touch the flag at all.
325
327
  outer = error_code
326
328
  write_error(0)
329
+ hook = disarm_hook
327
330
  begin
328
331
  Access.open(arrays.map { |_, buffer, _| buffer },
329
332
  arrays.map { |_, _, type| !type.const },
@@ -338,6 +341,7 @@ class CArray
338
341
  code = error_code
339
342
  ensure
340
343
  write_error(outer)
344
+ rearm_hook(hook)
341
345
  end
342
346
  raise_for(code)
343
347
  # A view was copied to be made contiguous; a writable one is copied
@@ -384,6 +388,7 @@ class CArray
384
388
  }
385
389
  outer = error_code
386
390
  write_error(0)
391
+ hook = disarm_hook
387
392
  begin
388
393
  Access.open(arrays.map { |_, buffer, _| buffer },
389
394
  arrays.map { |_, _, type| !type.const },
@@ -399,6 +404,7 @@ class CArray
399
404
  code = error_code
400
405
  ensure
401
406
  write_error(outer)
407
+ rearm_hook(hook)
402
408
  end
403
409
  raise_for(code)
404
410
  arrays.each do |array, buffer, type|
@@ -458,18 +464,28 @@ class CArray
458
464
  # Windows nest, and a call made inside one leaves it armed: both borrow
459
465
  # the flag and put it back as they found it, so an inner window answers
460
466
  # for its own block and no other.
461
- def watching
462
- outer = error_code
463
- write_error(0)
467
+ #
468
+ # `stand_in:` is the number a call answers C with once the flag stands,
469
+ # for a library that reads the return value as a status rather than as
470
+ # a value -- GSL's callbacks return `int`, and 0 is `GSL_SUCCESS`
471
+ # there, so a failed body reporting 0 tells GSL the step went well. It
472
+ # is cast to whatever the body returns and is 0 until a window says
473
+ # otherwise, which is what a failed call has always answered. Set for
474
+ # the length of the window, and put back afterwards.
475
+ def watching (stand_in: nil)
476
+ opened = open_window(nil, JIT.send(:window_stand_in, stand_in))
464
477
  code = 0
465
478
  begin
466
479
  result = yield
467
- code = error_code
468
480
  rescue StandardError
469
- raise_for(error_code)
481
+ # The library is complaining about what a stand-in did. The body's
482
+ # own failure is the cause, so it is the one that comes out.
483
+ code = close_window(opened)
484
+ opened = nil
485
+ raise_for(code)
470
486
  raise
471
487
  ensure
472
- write_error(outer)
488
+ code = close_window(opened) if opened
473
489
  end
474
490
  raise_for(code)
475
491
  result
@@ -500,17 +516,173 @@ class CArray
500
516
  raise_for(error_code)
501
517
  end
502
518
 
519
+ # A C function to call when the body fails, so that whoever holds the
520
+ # address can be told to stop rather than go on being handed stand-ins:
521
+ #
522
+ # f.on_error(nlopt["nlopt_force_stop"], opt)
523
+ # f.watching { nlopt_optimize.call(opt, x, minf) }
524
+ #
525
+ # `hook` is the address of a `void (*)(void *)` -- a Fiddle::Pointer,
526
+ # an Integer, or anything answering #to_i -- and `data` is the one
527
+ # argument it is called with. It is called once, from the call whose
528
+ # body put the flag up, and not again until the flag has been put down
529
+ # and has gone up again; nothing about it reaches Ruby, so it may be
530
+ # called from a thread that holds no GVL. `nil` takes it away.
531
+ #
532
+ # `#call` does not call it: that is one call made from Ruby, and it
533
+ # answers for itself by raising, as it does inside a window. A body
534
+ # that cannot fail has no flag to go up, so for it this is accepted
535
+ # and never called.
536
+ #
537
+ # Like the flag, it belongs to the thread that set it: a body lent from
538
+ # two threads at once has a hook and a flag in each of them, and this
539
+ # sets the one belonging to the thread that calls it. So it is set on
540
+ # the thread that will lend the address, which is where `#watching` is
541
+ # written anyway.
542
+ #
543
+ # Returns self.
544
+ def on_error (hook, data = nil)
545
+ state = error_state
546
+ return self unless state
547
+ address = hook.nil? ? 0 : hook.to_i
548
+ state.data[0, Fiddle::SIZEOF_VOIDP] =
549
+ [data.nil? ? 0 : data.to_i].pack("J")
550
+ state.hook[0, Fiddle::SIZEOF_VOIDP] = [address].pack("J")
551
+ state.held = hook.nil? ? nil : [hook, data]
552
+ self
553
+ end
554
+
503
555
  private
504
556
 
505
- # 0 where the body cannot fail at all: one that neither divides nor
506
- # raises is compiled without a flag to read, and has no failure to
557
+ # One thread's copy of the four the compiled object carries -- the
558
+ # flag, the hook's address, the pointer it is handed, and what a failed
559
+ # call answers C with -- with the Ruby objects `#on_error` was given
560
+ # held beside them, so that nothing the C may still call is collected.
561
+ ErrorState = Struct.new(:flag, :hook, :data, :standin, :held)
562
+ private_constant :ErrorState
563
+
564
+ # Where those copies are kept, per thread. A thread variable rather
565
+ # than a fiber-local one, because what is being tracked is the C's own
566
+ # storage: every fiber running on a thread reaches that thread's copy.
567
+ THREAD_STATES = :carray_jit_error_states
568
+ private_constant :THREAD_STATES
569
+
570
+ # This thread's, resolved by asking the object where they stand and
571
+ # kept for as long as the thread lives -- a thread's copies do not
572
+ # move. Nil where the body cannot fail at all: one that neither
573
+ # divides nor raises is compiled without a flag, and has no failure to
507
574
  # report rather than an unread one.
575
+ def error_state
576
+ return nil unless @error_state
577
+ thread = Thread.current
578
+ kept = thread.thread_variable_get(THREAD_STATES)
579
+ unless kept
580
+ kept = {}
581
+ thread.thread_variable_set(THREAD_STATES, kept)
582
+ end
583
+ # By symbol, which carries a digest of the body: two functions with
584
+ # the same symbol are one compiled object and one set of copies.
585
+ kept[@symbol] ||= resolve_error_state
586
+ end
587
+
588
+ def resolve_error_state
589
+ out = String.new("\0" * (4 * Fiddle::SIZEOF_VOIDP))
590
+ @error_state.call(out)
591
+ flag, hook, data, standin = out.unpack("J4")
592
+ ErrorState.new(Fiddle::Pointer.new(flag, 4),
593
+ Fiddle::Pointer.new(hook, Fiddle::SIZEOF_VOIDP),
594
+ Fiddle::Pointer.new(data, Fiddle::SIZEOF_VOIDP),
595
+ Fiddle::Pointer.new(standin, 8),
596
+ nil)
597
+ end
598
+
599
+ # 0 where the body cannot fail at all.
508
600
  def error_code
509
- @error ? @error[0, 4].unpack1("l") : 0
601
+ state = error_state
602
+ state ? state.flag[0, 4].unpack1("l") : 0
510
603
  end
511
604
 
512
605
  def write_error (code)
513
- @error[0, 4] = [code].pack("l") if @error
606
+ state = error_state
607
+ state.flag[0, 4] = [code].pack("l") if state
608
+ end
609
+
610
+ # The hook's address, cleared for the length of one call from Ruby,
611
+ # and put back afterwards.
612
+ def disarm_hook
613
+ state = error_state
614
+ return nil unless state
615
+ held = state.hook[0, Fiddle::SIZEOF_VOIDP]
616
+ state.hook[0, Fiddle::SIZEOF_VOIDP] = "\0" * Fiddle::SIZEOF_VOIDP
617
+ held
618
+ end
619
+
620
+ def rearm_hook (held)
621
+ state = error_state
622
+ state.hook[0, Fiddle::SIZEOF_VOIDP] = held if state && held
623
+ end
624
+
625
+ # What a window does at its two ends. `#watching` is these with a
626
+ # block between them, and `CArray::JIT.watching` is these over several
627
+ # functions at once -- which is why they are named rather than written
628
+ # inline in the one method that had them.
629
+ #
630
+ # `hook` is the `[address, data]` a window wants a hook of its own for,
631
+ # and `standin` what it wants a failed call to answer C with; nil for
632
+ # either leaves whatever is set alone.
633
+ def open_window (hook = nil, standin = nil)
634
+ outer = error_code
635
+ write_error(0)
636
+ [outer, hook && hold_hook(*hook), standin && hold_standin(standin)]
637
+ end
638
+
639
+ # What this window's calls reported, with the flag, the hook and the
640
+ # stand-in put back as they were found.
641
+ def close_window (opened)
642
+ outer, held, standin = opened
643
+ code = error_code
644
+ write_error(outer)
645
+ restore_hook(held)
646
+ restore_standin(standin)
647
+ code
648
+ end
649
+
650
+ # This window's hook set and the one it displaced handed back, so that
651
+ # the window can put it there again. `#on_error` writes these two
652
+ # pointers; what it has no way to do is read them, and a window that
653
+ # brings a hook of its own has to -- otherwise it leaves with the
654
+ # owner's gone and nothing said about it.
655
+ def hold_hook (address, data)
656
+ state = error_state
657
+ return nil unless state
658
+ held = [state.hook[0, Fiddle::SIZEOF_VOIDP],
659
+ state.data[0, Fiddle::SIZEOF_VOIDP], state.held]
660
+ on_error(address, data)
661
+ held
662
+ end
663
+
664
+ def restore_hook (held)
665
+ state = error_state
666
+ return unless state && held
667
+ state.hook[0, Fiddle::SIZEOF_VOIDP] = held[0]
668
+ state.data[0, Fiddle::SIZEOF_VOIDP] = held[1]
669
+ state.held = held[2]
670
+ end
671
+
672
+ # This window's stand-in written, and the one it displaced handed back.
673
+ # Held as the eight bytes rather than as a number, so that whatever was
674
+ # there goes back exactly as it stood.
675
+ def hold_standin (value)
676
+ state = error_state
677
+ return nil unless state
678
+ held = state.standin[0, 8]
679
+ state.standin[0, 8] = [value].pack("q")
680
+ held
681
+ end
682
+
683
+ def restore_standin (held)
684
+ state = error_state
685
+ state.standin[0, 8] = held if state && held
514
686
  end
515
687
 
516
688
  def raise_for (code)
@@ -641,6 +813,14 @@ class CArray
641
813
  "long double here to make one of",
642
814
  }.freeze
643
815
 
816
+ # The words C builds its integer types from. They may come in any
817
+ # order and `int` may be left out -- `unsigned`, `long unsigned int`
818
+ # and `int unsigned` are all C -- while Fiddle reads only some of those
819
+ # orders, fails on a bare `unsigned`, and reads `int unsigned` as a
820
+ # signed int without saying so. So a spelling made only of these is
821
+ # counted here and handed to Fiddle in the one order it reads.
822
+ INTEGER_WORDS = %w[signed unsigned char short int long].freeze
823
+
644
824
  # The CArray data type a pointer parameter takes, by the code Fiddle
645
825
  # gave its element. This is the table CGenerator::STORAGE_C_TYPES
646
826
  # already holds, read the other way round -- nothing new is decided
@@ -797,6 +977,7 @@ class CArray
797
977
  if (reason = REFUSED[spelling])
798
978
  raise Unsupported, "#{reason} (in `#{prototype}`)"
799
979
  end
980
+ spelling = integer_spelling(spelling, text, prototype)
800
981
  # A parameter written as an array is a pointer at the ABI, whatever
801
982
  # its declarator says.
802
983
  pointer ||= !array.nil?
@@ -829,6 +1010,33 @@ class CArray
829
1010
  array, element, keywords.include?("const"))
830
1011
  end
831
1012
 
1013
+ # `long unsigned int` as `unsigned long`, `unsigned` as `unsigned int`:
1014
+ # the order Fiddle reads, for a spelling made only of INTEGER_WORDS.
1015
+ # Anything else is handed back untouched. A combination C does not
1016
+ # have -- `signed unsigned`, `short long`, three `long`s -- is refused
1017
+ # here rather than left to a parser that has been seen to guess.
1018
+ def integer_spelling (spelling, text, prototype)
1019
+ words = spelling.split(/\s+/)
1020
+ return spelling unless words.all? { |word| INTEGER_WORDS.include?(word) }
1021
+ count = words.tally
1022
+ longs = count.fetch("long", 0)
1023
+ base = count.keys & %w[char short]
1024
+ if count.values_at("signed", "unsigned", "char", "short", "int")
1025
+ .any? { |n| n.to_i > 1 } ||
1026
+ (count.key?("signed") && count.key?("unsigned")) ||
1027
+ base.size > 1 || (base.any? && longs > 0) || longs > 2 ||
1028
+ (count.key?("char") && count.key?("int"))
1029
+ raise Unsupported,
1030
+ "`#{text.strip}` in `#{prototype}` is not a C integer type"
1031
+ end
1032
+ sign = count.key?("unsigned") ? "unsigned " : ""
1033
+ width = base.first || { 0 => "int", 1 => "long", 2 => "long long" }.fetch(longs)
1034
+ # `signed char` is a type of its own in C, apart from plain `char`,
1035
+ # and Fiddle reads both as the one byte they are.
1036
+ sign = "signed " if width == "char" && count.key?("signed")
1037
+ "#{sign}#{width}"
1038
+ end
1039
+
832
1040
  # The code for a complex spelling, or nil for anything else. `float
833
1041
  # _Complex`, `complex float`, `float complex` -- the word may sit on
834
1042
  # either side, as C allows.
@@ -980,8 +1188,127 @@ class CArray
980
1188
  declared_parameters)
981
1189
  end
982
1190
 
1191
+ # One window over several functions, for a call that is handed more
1192
+ # than one address:
1193
+ #
1194
+ # CArray::JIT.watching(objective, gradient) do
1195
+ # NLopt.optimize(option, x, minimum)
1196
+ # end
1197
+ #
1198
+ # Every flag is put down before the block, and every one is read after
1199
+ # it -- because a flag nobody reads is a failure that becomes a wrong
1200
+ # answer, and then surfaces on an unrelated later window. Whichever
1201
+ # failure comes first in the argument list is the one raised, and a
1202
+ # failure raised this way outranks whatever the block raised, for the
1203
+ # reason `CFunction#watching` gives: the library is complaining about
1204
+ # a stand-in, and the stand-in is the consequence.
1205
+ #
1206
+ # `on_error:` is the `[hook, data]` pair `CFunction#on_error` takes,
1207
+ # set on every function for the length of the window and taken off
1208
+ # again afterwards -- with whatever hook was there before put back.
1209
+ # That is the difference from calling `#on_error` around the block:
1210
+ # the pair can be read back here and cannot be read back from Ruby, so
1211
+ # a window that brings its own hook is the only way to borrow one
1212
+ # without leaving the owner's gone.
1213
+ #
1214
+ # `stand_in:` is the number a failed call answers C with, as
1215
+ # `CFunction#watching` takes it, and is set on every function watched.
1216
+ # Which number is right belongs to the window and not to the body: the
1217
+ # same `int (*)(...)` may be handed to a routine that stops on a
1218
+ # non-zero status and to one that reads it as a rejected step and tries
1219
+ # again, and the body has no way of knowing which it was given to.
1220
+ #
1221
+ # `nil` in the list is skipped, so an optional gradient or jacobian may
1222
+ # be passed as it stands, and a function named twice is watched once.
1223
+ # A body that cannot fail has no flag, no hook and no stand-in, and
1224
+ # passing one changes nothing -- which is what lets a binding watch
1225
+ # what it was given without asking which kind of body it is.
1226
+ def watching (*functions, on_error: nil, stand_in: nil)
1227
+ armed = functions.compact
1228
+ armed.each do |function|
1229
+ next if function.is_a?(CFunction)
1230
+ raise TypeError,
1231
+ "`CArray::JIT.watching` watches compiled functions, and " \
1232
+ "#{function.class} is not one -- pass what `jit_function` " \
1233
+ "or `jit_extern` answered"
1234
+ end
1235
+ armed = armed.uniq
1236
+ hook = window_hook(on_error)
1237
+ standin = window_stand_in(stand_in)
1238
+ return yield if armed.empty?
1239
+ opened = armed.map { |function|
1240
+ function.send(:open_window, hook, standin)
1241
+ }
1242
+ codes = []
1243
+ begin
1244
+ result = yield
1245
+ rescue StandardError
1246
+ # Closed here rather than in the ensure below, so that the failure
1247
+ # raised next is raised from inside this rescue and carries the
1248
+ # library's own complaint as its cause.
1249
+ codes = close_windows(armed, opened)
1250
+ opened = nil
1251
+ raise_first_failure(armed, codes)
1252
+ raise
1253
+ ensure
1254
+ codes = close_windows(armed, opened) if opened
1255
+ end
1256
+ raise_first_failure(armed, codes)
1257
+ result
1258
+ end
1259
+
983
1260
  private
984
1261
 
1262
+ # Every window closed, in the order they were opened, and what each of
1263
+ # them saw. All of them, whatever happened in the block: a flag nobody
1264
+ # reads is one that surfaces on an unrelated later window.
1265
+ def close_windows (armed, opened)
1266
+ armed.zip(opened).map { |function, state|
1267
+ function.send(:close_window, state)
1268
+ }
1269
+ end
1270
+
1271
+ # The first failure among the flags just read, raised as the function
1272
+ # that reported it would raise it. A code of 0 is no failure and is
1273
+ # passed over, so this walks the list in the order it was written and
1274
+ # stops at the first that has something to say.
1275
+ def raise_first_failure (armed, codes)
1276
+ armed.zip(codes).each do |function, code|
1277
+ function.send(:raise_for, code)
1278
+ end
1279
+ end
1280
+
1281
+ # The number a window hands the C as a failed call's answer. An
1282
+ # Integer because that is what the object carries it in -- one 64-bit
1283
+ # word, cast at the return -- and because a status is a whole number
1284
+ # wherever one is read.
1285
+ def window_stand_in (stand_in)
1286
+ return nil if stand_in.nil?
1287
+ unless stand_in.is_a?(Integer)
1288
+ raise TypeError,
1289
+ "`stand_in:` is the number a failed call answers C with, " \
1290
+ "and #{stand_in.class} is not one -- it is cast to whatever " \
1291
+ "the body returns, so what is given here is an Integer"
1292
+ end
1293
+ unless (-(2**63)...2**63).cover?(stand_in)
1294
+ raise RangeError,
1295
+ "`stand_in:` is carried in one 64-bit word, and " \
1296
+ "#{stand_in} does not fit in one"
1297
+ end
1298
+ stand_in
1299
+ end
1300
+
1301
+ def window_hook (on_error)
1302
+ return nil if on_error.nil?
1303
+ unless on_error.is_a?(Array) && (1..2).cover?(on_error.size)
1304
+ raise ArgumentError,
1305
+ "`on_error:` takes the hook and the one pointer it is " \
1306
+ "handed, as `CFunction#on_error` does -- " \
1307
+ "`on_error: [handle[\"nlopt_force_stop\"], option]`"
1308
+ end
1309
+ [on_error[0], on_error[1]]
1310
+ end
1311
+
985
1312
  def bind_c_function (prototype, name, return_type, parameters, from)
986
1313
  handle = library_handle(from)
987
1314
  begin
@@ -1154,10 +1481,12 @@ class CArray
1154
1481
  raise Unsupported,
1155
1482
  (if type.indexable?
1156
1483
  "`#{loose}` is declared `#{type.text}`, which is a pointer; " \
1157
- "index it, as in `#{loose}[0]`"
1484
+ "index it, as in `#{loose}[0]`, or ask whether one came, " \
1485
+ "as in `if #{loose}` or `#{loose}.nil?`"
1158
1486
  else
1159
1487
  "`#{loose}` is declared `#{type.text}`, which is a slot in " \
1160
- "the signature rather than a value; the body cannot read it"
1488
+ "the signature rather than a value; the body cannot read " \
1489
+ "it, only ask whether it was filled, as in `#{loose}.nil?`"
1161
1490
  end)
1162
1491
  end
1163
1492
  # A parameter of any computation type the body can work in, uint64
@@ -1193,9 +1522,18 @@ class CArray
1193
1522
  # Where the body can divide by zero or reach outside an array, the
1194
1523
  # object carries a place to say so. Reading it is what lets a call
1195
1524
  # from Ruby raise what the same expression raises in Ruby.
1196
- error = if generator.uses_error_flag?
1197
- Fiddle::Pointer.new(handle[CGenerator::ERROR_FLAG], 4)
1198
- end
1525
+ #
1526
+ # The place is the calling thread's, and a thread-local is not a name
1527
+ # the loader can hand an address for, so what is taken here is the
1528
+ # accessor beside it -- called once per thread, from that thread. It
1529
+ # is named after the body, the four it answers for being `static`, so
1530
+ # that two objects generated this way can be linked side by side.
1531
+ error_state = if generator.uses_error_flag?
1532
+ accessor = CGenerator.error_state_name(symbol)
1533
+ Fiddle::Function.new(handle[accessor],
1534
+ [Fiddle::TYPE_VOIDP],
1535
+ Fiddle::TYPE_VOID, name: accessor)
1536
+ end
1199
1537
  # A body that reports failures is generated a second time for pasting,
1200
1538
  # with the flag as a parameter. A second generator rather than the
1201
1539
  # same one twice: what it emitted is what it holds, and the two forms
@@ -1214,7 +1552,7 @@ class CArray
1214
1552
  symbol: symbol,
1215
1553
  shim: shim_symbol && handle[shim_symbol],
1216
1554
  block: block, c_source: generator.provenance + c_source,
1217
- origin: origin, error: error,
1555
+ origin: origin, error_state: error_state,
1218
1556
  definition: pasted.function_definition,
1219
1557
  helpers: pasted.helper_needs,
1220
1558
  dependencies: called.values,