carray-jit 0.1.4 → 0.1.6
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 +4 -4
- data/CHANGELOG.md +48 -0
- data/README.md +2 -2
- data/carray-jit.gemspec +1 -1
- data/docs/03_SupportedFeatures.md +1 -1
- data/docs/04_Compiling.md +1 -1
- data/docs/05_DesignNotes.md +1 -1
- data/docs/08_LanguageReference.md +3 -2
- data/lib/carray/jit/c_function.rb +7 -0
- data/lib/carray/jit/c_generator.rb +104 -14
- data/lib/carray/jit/expression.rb +11 -1
- data/lib/carray/jit/kernel.rb +31 -5
- data/lib/carray/jit/version.rb +1 -1
- data/lib/carray/jit.rb +216 -54
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e052fd85b01164a6147a30ca655b8997ba7c87ac9c7ec8354d73447d1208b5ca
|
|
4
|
+
data.tar.gz: 2a6ebb524fb6afee5c871c0a45ed13ecf2f120f9ef0194e36f486ac3b015b378
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bd5017f5251ad7b9f4263bfc25091a938d3bccd7f18ef5d83a5a68b63f57e0df6c1ee4278ccd1b903a68cb77a5a156a47c98cbc4832edabbf144593778fed1b5
|
|
7
|
+
data.tar.gz: d8d2733f8a98376561ccb183895e1e963eff8a1caea636d066e88ce4852f167fb8755c6f1225beaf0c89aea51bf18dab4482bde0a079983ff507e9cd66769b2f
|
data/CHANGELOG.md
CHANGED
|
@@ -37,6 +37,54 @@ version you have and a newer one.
|
|
|
37
37
|
a release needs is said in the gemspec, and an entry says so only
|
|
38
38
|
when the answer changes. -->
|
|
39
39
|
|
|
40
|
+
## 0.1.6
|
|
41
|
+
|
|
42
|
+
- Change: needs CArray 3.0.5 or later in the 3.0 series; install it
|
|
43
|
+
alongside this release. A Float stored into an integer array in a block
|
|
44
|
+
is refused as CArray 3.0.5's own store refuses it, and under 3.0.4 the
|
|
45
|
+
block would raise where CArray's store does not.
|
|
46
|
+
|
|
47
|
+
- Change: a Float stored into an integer array in a block, captured or
|
|
48
|
+
local, that the type has no integer for raises `RangeError`, or
|
|
49
|
+
`FloatDomainError` for a NaN or an infinity, as CArray 3.0.5's own
|
|
50
|
+
store does. Before, it stored whatever the machine's C conversion gave,
|
|
51
|
+
which differed between x86 and ARM. Round and clamp first where the
|
|
52
|
+
value may leave the range. A masked cell, and a `jit_function`'s return
|
|
53
|
+
value or pointer parameter, are not checked.
|
|
54
|
+
|
|
55
|
+
- Fix: an int64 array handed to a C function declared to take
|
|
56
|
+
`long long *` compiles with gcc, as it did with clang; gcc 14 raised
|
|
57
|
+
CompilationError. An array of the wrong element type raises Unsupported,
|
|
58
|
+
naming the type the function takes, under gcc as under clang.
|
|
59
|
+
|
|
60
|
+
- Fix: `finite?` on an integer cell in a block compiles with gcc; it
|
|
61
|
+
raised CompilationError there, while clang accepted it. It is true for
|
|
62
|
+
every integer, as in Ruby.
|
|
63
|
+
|
|
64
|
+
- Fix: a lazy expression or `CArray.fuse` computed here now gives the
|
|
65
|
+
same bits as CArray's own walk where gcc is the compiler. On x86 with
|
|
66
|
+
`-march=native`, a multiply followed by an add could come out one
|
|
67
|
+
rounding off in the last bit. Builds with clang were not affected.
|
|
68
|
+
|
|
69
|
+
## 0.1.5
|
|
70
|
+
|
|
71
|
+
- Change: needs CArray 3.0.4 or later in the 3.0 series; install it
|
|
72
|
+
alongside this release. 0.1.4 accepts CArray 3.0.4 but does not run
|
|
73
|
+
under it, so the two are upgraded together. A store in a block into a
|
|
74
|
+
cell that cannot hold the value still wraps as C does, where CArray
|
|
75
|
+
3.0.4's own `int8[0] = 300` raises RangeError.
|
|
76
|
+
|
|
77
|
+
- Change: `jit_each` and `jit_map` called again with operands of the same
|
|
78
|
+
shapes, types and masking cost less per call -- about 9 us rather than 11
|
|
79
|
+
for a small two-array block -- which a time-stepping loop calling one
|
|
80
|
+
block thousands of times will notice. Nothing changes in what they
|
|
81
|
+
compute; a scalar's value may differ from call to call as before.
|
|
82
|
+
|
|
83
|
+
- Fix: an element-wise `jit_each` or `jit_map` run through CArray's sweep
|
|
84
|
+
could read its scalars and C functions from where they had been before
|
|
85
|
+
GC compaction moved them, and answer wrongly or crash. Only a process
|
|
86
|
+
that compacts (`GC.compact`, `GC.auto_compact = true`) was exposed.
|
|
87
|
+
|
|
40
88
|
## 0.1.4
|
|
41
89
|
|
|
42
90
|
- New: a lazy `then_else` (a lazy condition or branch) is compiled with
|
data/README.md
CHANGED
|
@@ -8,7 +8,7 @@ The block is read with Prism, translated to C if it falls inside that subset, co
|
|
|
8
8
|
|
|
9
9
|
## Status
|
|
10
10
|
|
|
11
|
-
0.1.
|
|
11
|
+
0.1.6 is the current release, and it still moves: behaviour can change between releases.
|
|
12
12
|
|
|
13
13
|
## Features
|
|
14
14
|
|
|
@@ -35,7 +35,7 @@ gem "carray-jit"
|
|
|
35
35
|
Requires:
|
|
36
36
|
|
|
37
37
|
- Ruby >= 3.2
|
|
38
|
-
- CArray >= 3.0.
|
|
38
|
+
- CArray >= 3.0.5, < 3.1
|
|
39
39
|
- A C compiler
|
|
40
40
|
- Prism and Fiddle (both ship with Ruby; Fiddle is a bundled gem)
|
|
41
41
|
|
data/carray-jit.gemspec
CHANGED
|
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
|
|
|
33
33
|
spec.executables = ["carray-jit"]
|
|
34
34
|
|
|
35
35
|
# A kernel reaches CArray's C by address, so the ceiling is the next minor.
|
|
36
|
-
spec.add_dependency "carray", ">= 3.0.
|
|
36
|
+
spec.add_dependency "carray", ">= 3.0.5", "< 3.1"
|
|
37
37
|
# fiddle ships as a bundled gem; depend on it explicitly so Ruby 3.5+ resolves it.
|
|
38
38
|
spec.add_dependency "fiddle"
|
|
39
39
|
end
|
|
@@ -1223,6 +1223,6 @@ Everything else: `for` and `until`, `begin ... end while`, strings, hashes, symb
|
|
|
1223
1223
|
- **A local array's subscript counts from the start.** CArray's `w[-1]` is the last cell; a kernel has no such reading, so a literal negative subscript is refused by name and one the kernel works out raises `IndexError`. This is the difference the captured arrays already have. Write `w[n - 1]`.
|
|
1224
1224
|
- **`CArray.empty` leaves its cells as the stack left them.** Reading one before writing it is out of contract, the way reading under a mask is: the value is whatever was there. What it saves is the clearing, which for a 256-cell array inside a per-cell loop is 2 KiB a cell.
|
|
1225
1225
|
- **The stack limits are per function, and a chain of them is not counted.** One local array up to 4 KiB stands in a frame, and the arrays of one kernel body or one function body up to 16 KiB together; past either, a kernel allocates the array at its entry, while a function body -- which is called once per cell -- refuses it and asks the calling kernel for a pointer instead. A pasted function called from a kernel is a second frame, and a chain of three such functions can therefore stand 48 KiB deep while each of them is inside its own limit -- counting along the chain was considered and dropped, since the depth is not knowable where recursion is in the subset at all. What that costs is the bargain a deep recursion already takes: too deep is a SIGSEGV rather than a `SystemStackError`, and a body carrying arrays reaches it sooner. Both numbers are provisional, and neither counts a future thread pool's stacks.
|
|
1226
|
-
- **Storing a real number into an integer array is
|
|
1226
|
+
- **Storing a real number into an integer array is checked.** It keeps the integer part, as CArray's own store does, and a value past the type's range raises `RangeError` -- `FloatDomainError` for a NaN or an infinity -- where CArray's store raises the same; C leaves that conversion undefined, and x86 and ARM answer it differently. Like any error at a cell it is raised when the pass ends, and the cell holds 0 in the meantime. A masked cell is not checked. The check is a comparison per store; round and clamp before storing where the value may leave the range and the cost shows. A compiled function's return value and a write through its pointer parameter are not this case: they are C's conversion, as the declaration says. Nor are the rounding methods: `floor`, `ceil`, `round`, `truncate` and `to_i` on a Float raise `FloatDomainError` for a NaN or an infinity, as Ruby does, and `RangeError` where the Integer is past int64 -- except written straight into a float cell, which holds it whole (`1e20.floor` is `1e20`). The check costs a comparison per call, and in a loop that does little else it shows.
|
|
1227
1227
|
- **A block's source must be recoverable.** Blocks defined in `eval` or in a console have no file to read back; set `RubyVM.keep_script_lines = true` before defining them, or hand the text to `CArray::JIT.compile`, which takes a kernel as a string rather than as a block. This also ties the gem to CRuby, which CArray requires anyway.
|
|
1228
1228
|
- **Nothing existing is replaced.** `jit_for` is a new method, not a faster `each_index`: the two differ in what they reject, and a caller should be able to choose.
|
data/docs/04_Compiling.md
CHANGED
|
@@ -67,7 +67,7 @@ In `~/.cache/carray-jit` (honouring `XDG_CACHE_HOME`), one directory per version
|
|
|
67
67
|
|
|
68
68
|
A kernel is only good for the version that generated it and the architecture it was built for, so those get their own directories. CArray's version is there for the same reason and one of its own: a kernel is handed CArray's memory, on layouts CArray decides, so one compiled against one version and run against the next would answer wrongly rather than fail to load.
|
|
69
69
|
|
|
70
|
-
The two numbers say different things and move on their own clocks: the first is the version that generated the kernel, the second the version it was generated against. This gem is not versioned with CArray, and one release of it meets more than one -- the dependency is `>= 3.0.
|
|
70
|
+
The two numbers say different things and move on their own clocks: the first is the version that generated the kernel, the second the version it was generated against. This gem is not versioned with CArray, and one release of it meets more than one -- the dependency is `>= 3.0.5, < 3.1`, so 3.0.5 and 3.0.6 both satisfy it -- and a layout the kernel reaches into can move between them. A new release does not spend its cache budget on entries nothing can reach any more, and a home directory shared between machines -- over NFS, or between Rosetta and native -- does not have one architecture evicting the other's kernels. A directory whose newest entry has not been touched in 30 days (`CARRAY_JIT_CACHE_MAX_AGE_DAYS`) is removed.
|
|
71
71
|
|
|
72
72
|
```ruby
|
|
73
73
|
CArray::JIT.cache_root #=> "/home/you/.cache/carray-jit"
|
data/docs/05_DesignNotes.md
CHANGED
|
@@ -4,7 +4,7 @@ Decisions that were not obvious, and why.
|
|
|
4
4
|
|
|
5
5
|
## The type is C's, and so is the arithmetic where the width is real
|
|
6
6
|
|
|
7
|
-
The storage type is CArray's, mapped to C exactly -- `int64_t`, `uint8_t`, `float` -- and everything the type itself decides follows from that: the width, the wrap on store, the bit patterns. There a kernel agrees with CArray, because both are the same C. A shift agrees too, though the two get there differently: a kernel shifts in the width it computes in, `int64_t`, and narrows on store, where CArray shifts in the cell's own width -- and since both take a shift to mean what Ruby's Integer means by it, the low bits that survive the narrowing are the same.
|
|
7
|
+
The storage type is CArray's, mapped to C exactly -- `int64_t`, `uint8_t`, `float` -- and everything the type itself decides follows from that: the width, the wrap on store, the bit patterns. There a kernel agrees with CArray's conversion between arrays (`to_type`), because both are the same C; CArray's store of a single Ruby Integer refuses one the cell cannot hold, where a kernel narrows it. A shift agrees too, though the two get there differently: a kernel shifts in the width it computes in, `int64_t`, and narrows on store, where CArray shifts in the cell's own width -- and since both take a shift to mean what Ruby's Integer means by it, the low bits that survive the narrowing are the same.
|
|
8
8
|
|
|
9
9
|
The arithmetic follows the same rule where the width makes a difference to the answer, and Ruby's where it does not. That splits the types in two.
|
|
10
10
|
|
|
@@ -395,7 +395,7 @@ These are deliberate. Everything not listed here gives Ruby's answer, to the las
|
|
|
395
395
|
- **A complex raised to a power** uses C's `cpow`, which can differ from Ruby in the last bit.
|
|
396
396
|
- **A sine beside a cosine of the same argument** may be computed together by the C compiler, which can change the last bit.
|
|
397
397
|
- **A reduction is reassociated** into partial sums unless `reassociate: false` is given, so its last bits can differ from adding in order.
|
|
398
|
-
- **A real stored into an integer array**
|
|
398
|
+
- **A real stored into an integer array** keeps its integer part, as CArray's store does, and one past the type's range raises `RangeError` where Ruby's `to_i` would grow. A NaN or an infinity raises `FloatDomainError`, as `to_i` does.
|
|
399
399
|
|
|
400
400
|
**The language**
|
|
401
401
|
|
|
@@ -417,6 +417,7 @@ A block reads like Ruby run over one cell at a time, and it answers as that Ruby
|
|
|
417
417
|
| `int8` cells: `a * 2 / 4` with `a = 100` | `-14`: each operation wraps at 8 bits | `50`: computed in `int64`, wrapped only when stored |
|
|
418
418
|
| `int8` + `uint8` | computed and returned in `uint8` | computed in `int64` |
|
|
419
419
|
| a uint8 cell `+ 300` | `RangeError`: 300 does not fit uint8 | `301`, computed in `int64` |
|
|
420
|
+
| `int8` cell `= 300` | `RangeError`: 300 does not fit int8 (`to_type` gives `44`) | `44`: narrowed as C converts |
|
|
420
421
|
| `x.round`, `floor`, `ceil`, `truncate` | a float array | an Integer, as Ruby |
|
|
421
422
|
| boolean arithmetic, `b + b` | `2`: a boolean is 0/1 | refused, as Ruby refuses `true + true` |
|
|
422
423
|
| a boolean compared with a number, `b == 1` | compares as 0/1 | refused: in Ruby `true == 1` is false |
|
|
@@ -424,4 +425,4 @@ A block reads like Ruby run over one cell at a time, and it answers as that Ruby
|
|
|
424
425
|
| `\|` and `&` of a masked boolean (`UNDEF \| true`) | Kleene: `true` | `\|\|` and `&&`: masked whenever an operand is |
|
|
425
426
|
| a `uint64` compared with a negative `int64` | compares by value: `7 < -7` is false | compared as two `uint64`: `7 < -7` is true |
|
|
426
427
|
|
|
427
|
-
Where they agree, the block keeps CArray's answer: integer `/` and `%` floor, a shift means what it means to `Integer`, `INT64_MIN / -1` is `INT64_MIN`, overflow wraps, an Integer meets a Float as two doubles, narrow types compute narrow, `Math` outside its domain is NaN, a real
|
|
428
|
+
Where they agree, the block keeps CArray's answer: integer `/` and `%` floor, a shift means what it means to `Integer`, `INT64_MIN / -1` is `INT64_MIN`, overflow wraps, an Integer meets a Float as two doubles, narrow types compute narrow, `Math` outside its domain is NaN, a real stored into an integer cell keeps its integer part and raises `RangeError` past the type's range (`FloatDomainError` for a NaN or an infinity), and a masked operand gives a masked result.
|
|
@@ -55,6 +55,13 @@ class CArray
|
|
|
55
55
|
"#{text.sub(/\s*\[.*\]\z/, "")} #{name}[#{array if sized?}]"
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
# The pointer an array argument decays to, as C spells it:
|
|
59
|
+
# `const double coef[3]` takes a `const double *`.
|
|
60
|
+
def decayed
|
|
61
|
+
return text unless array
|
|
62
|
+
"#{text.sub(/\s*\[.*\]\z/, "")} *"
|
|
63
|
+
end
|
|
64
|
+
|
|
58
65
|
# @return [String] the type, as C spells it.
|
|
59
66
|
def to_s
|
|
60
67
|
text
|
|
@@ -421,6 +421,7 @@ class CArray
|
|
|
421
421
|
@uses_real_arg = false
|
|
422
422
|
@uses_real_power_complex = false
|
|
423
423
|
@clamp_types = []
|
|
424
|
+
@store_types = []
|
|
424
425
|
@gamma_types = []
|
|
425
426
|
@complex_helpers = []
|
|
426
427
|
@uses_floor_divide = false
|
|
@@ -727,6 +728,7 @@ class CArray
|
|
|
727
728
|
:real_arg => @uses_real_arg,
|
|
728
729
|
:real_power_complex => @uses_real_power_complex,
|
|
729
730
|
:clamp => @clamp_types.dup,
|
|
731
|
+
:store => @store_types.dup,
|
|
730
732
|
:gamma => @gamma_types.dup,
|
|
731
733
|
:complex => @complex_helpers.dup,
|
|
732
734
|
:index_check => @uses_index_check,
|
|
@@ -839,6 +841,7 @@ class CArray
|
|
|
839
841
|
@uses_real_arg ||= needs[:real_arg]
|
|
840
842
|
@uses_real_power_complex ||= needs[:real_power_complex]
|
|
841
843
|
@clamp_types |= needs[:clamp] || []
|
|
844
|
+
@store_types |= needs[:store] || []
|
|
842
845
|
@gamma_types |= needs[:gamma] || []
|
|
843
846
|
@complex_helpers |= needs[:complex] || []
|
|
844
847
|
@uses_index_check ||= needs[:index_check]
|
|
@@ -1095,6 +1098,31 @@ class CArray
|
|
|
1095
1098
|
C
|
|
1096
1099
|
end
|
|
1097
1100
|
text << intrinsic_helpers
|
|
1101
|
+
@store_types.each do |storage|
|
|
1102
|
+
low, high = STORE_BOUNDS.fetch(storage)
|
|
1103
|
+
c_type = storage_c_type(storage)
|
|
1104
|
+
text << <<~C
|
|
1105
|
+
/* A real number stored into an #{storage} cell keeps its integer
|
|
1106
|
+
part, as CArray's store does. One with no #{storage} to keep --
|
|
1107
|
+
a NaN, an infinity, a number past the range -- is refused as
|
|
1108
|
+
CArray refuses it: C leaves that conversion undefined, and the
|
|
1109
|
+
machines answer it differently. */
|
|
1110
|
+
static inline #{c_type}
|
|
1111
|
+
carray_jit_store_#{storage} (double value, int32_t *error)
|
|
1112
|
+
{
|
|
1113
|
+
if ( value > #{low} && value < #{high} ) {
|
|
1114
|
+
return (#{c_type}) value;
|
|
1115
|
+
}
|
|
1116
|
+
if ( error ) {
|
|
1117
|
+
*error = isnan(value) ? #{FLOAT_NAN_CODE} :
|
|
1118
|
+
isinf(value) ? ( value > 0 ? #{FLOAT_INFINITY_CODE} : #{FLOAT_NEGATIVE_INFINITY_CODE} ) :
|
|
1119
|
+
#{STORE_RANGE_CODES.fetch(storage)};
|
|
1120
|
+
}
|
|
1121
|
+
return 0;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
C
|
|
1125
|
+
end
|
|
1098
1126
|
@clamp_types.each do |type|
|
|
1099
1127
|
suffix, c_type, has_nan = CLAMP_C_TYPES.fetch(type)
|
|
1100
1128
|
refusal = lambda { |test, code|
|
|
@@ -1834,6 +1862,14 @@ class CArray
|
|
|
1834
1862
|
FLOAT_NEGATIVE_INFINITY_CODE = -5
|
|
1835
1863
|
INTEGER_RANGE_CODE = -6
|
|
1836
1864
|
|
|
1865
|
+
# And what a real number stored into an integer cell reports where the
|
|
1866
|
+
# cell's type has no integer for it, one code for each type, so the
|
|
1867
|
+
# message can name the type as CArray's own store does.
|
|
1868
|
+
STORE_RANGE_CODES = {
|
|
1869
|
+
"int8" => -7, "int16" => -8, "int32" => -9, "int64" => -10,
|
|
1870
|
+
"uint8" => -11, "uint16" => -12, "uint32" => -13, "uint64" => -14,
|
|
1871
|
+
}.freeze
|
|
1872
|
+
|
|
1837
1873
|
# The fixed failures a body reports, as the exception each one raises.
|
|
1838
1874
|
# A kernel and a compiled function both look here, so the two say the
|
|
1839
1875
|
# same thing for the same cell.
|
|
@@ -1842,6 +1878,24 @@ class CArray
|
|
|
1842
1878
|
FLOAT_INFINITY_CODE => [FloatDomainError, "Infinity"],
|
|
1843
1879
|
FLOAT_NEGATIVE_INFINITY_CODE => [FloatDomainError, "-Infinity"],
|
|
1844
1880
|
INTEGER_RANGE_CODE => [RangeError, "a rounded Float is past the range of int64"],
|
|
1881
|
+
**STORE_RANGE_CODES.to_h { |storage, code|
|
|
1882
|
+
[code, [RangeError, "a Float is out of range for #{storage}"]]
|
|
1883
|
+
},
|
|
1884
|
+
}.freeze
|
|
1885
|
+
|
|
1886
|
+
# The bounds a real number has to fall strictly between for its integer
|
|
1887
|
+
# part to be a value of the type, written as doubles, which hold every
|
|
1888
|
+
# one of them exactly. CArray's store keeps the integer part, so -0.5
|
|
1889
|
+
# goes into a uint8 cell as 0, and 255.9 as 255.
|
|
1890
|
+
STORE_BOUNDS = {
|
|
1891
|
+
"int8" => ["-129.0", "128.0"],
|
|
1892
|
+
"int16" => ["-32769.0", "32768.0"],
|
|
1893
|
+
"int32" => ["-2147483649.0", "2147483648.0"],
|
|
1894
|
+
"int64" => ["-9223372036854777856.0", "9223372036854775808.0"],
|
|
1895
|
+
"uint8" => ["-1.0", "256.0"],
|
|
1896
|
+
"uint16" => ["-1.0", "65536.0"],
|
|
1897
|
+
"uint32" => ["-1.0", "4294967296.0"],
|
|
1898
|
+
"uint64" => ["-1.0", "18446744073709551616.0"],
|
|
1845
1899
|
}.freeze
|
|
1846
1900
|
|
|
1847
1901
|
RAISE_CODE_FLOOR = 3
|
|
@@ -2588,9 +2642,9 @@ class CArray
|
|
|
2588
2642
|
# expression written into it carried, and the branches the write
|
|
2589
2643
|
# stands in. A write reached because a missing cell said so was
|
|
2590
2644
|
# reached for no reason, whichever of the three it writes.
|
|
2645
|
+
masked = @masked ? combine_masks(@carried_masks + [emit_mask(write.expression)]) : nil
|
|
2591
2646
|
mask = if @masked
|
|
2592
|
-
"#{indent}#{local_array_reference(write, :mask)} = "
|
|
2593
|
-
"#{combine_masks(@carried_masks + [emit_mask(write.expression)])};\n"
|
|
2647
|
+
"#{indent}#{local_array_reference(write, :mask)} = #{masked};\n"
|
|
2594
2648
|
else
|
|
2595
2649
|
""
|
|
2596
2650
|
end
|
|
@@ -2600,10 +2654,10 @@ class CArray
|
|
|
2600
2654
|
"#{indent}#{COMPUTATION_C_TYPES.fetch(type)} #{temporary};\n" +
|
|
2601
2655
|
emit_conditional_statement(expression, temporary, type, indent) +
|
|
2602
2656
|
"#{indent}#{target} = " \
|
|
2603
|
-
"#{cast_to_storage(temporary, type, write.storage)};\n" + mask
|
|
2657
|
+
"#{cast_to_storage(temporary, type, write.storage, masked: masked)};\n" + mask
|
|
2604
2658
|
else
|
|
2605
2659
|
type = complex_storage_of_power(expression, write.storage) || expression.type
|
|
2606
|
-
value = cast_to_storage(emit(expression, type), type, write.storage)
|
|
2660
|
+
value = cast_to_storage(emit(expression, type), type, write.storage, masked: masked)
|
|
2607
2661
|
"#{indent}#{target} = #{value};\n" + mask
|
|
2608
2662
|
end
|
|
2609
2663
|
end
|
|
@@ -2999,13 +3053,41 @@ class CArray
|
|
|
2999
3053
|
@storage_types.fetch(array)
|
|
3000
3054
|
end
|
|
3001
3055
|
|
|
3002
|
-
|
|
3056
|
+
# A captured array's element type is matched at the call
|
|
3057
|
+
# (`address_buffer`), after the build, and raises before the kernel
|
|
3058
|
+
# runs. Where the declaration spells the pointer differently from the
|
|
3059
|
+
# array's own, the address is cast to it, so that check is the one that
|
|
3060
|
+
# answers: gcc refuses an incompatible pointer at compile time where
|
|
3061
|
+
# clang only warns. `long long *` is such a spelling for an int64
|
|
3062
|
+
# array, which the check accepts and gcc does not. A body's own
|
|
3063
|
+
# pointer parameter is no captured array and is handed on as it is:
|
|
3064
|
+
# its type was declared in C.
|
|
3065
|
+
def emit_array_argument (argument, parameter)
|
|
3066
|
+
address = emit(argument, :address)
|
|
3067
|
+
return address unless @storage_types.key?(argument.array)
|
|
3068
|
+
own = "#{storage_c_type(array_storage(argument.array))} *"
|
|
3069
|
+
declared = parameter.decayed.sub(/\Aconst\s+/, "")
|
|
3070
|
+
declared == own ? address : "(#{parameter.decayed})#{address}"
|
|
3071
|
+
end
|
|
3072
|
+
|
|
3073
|
+
# `masked` is the C of the written cell's mask, for a write whose mask
|
|
3074
|
+
# the error slot does not already know about: a missing cell holds
|
|
3075
|
+
# whatever was under its mask, which is no value to refuse.
|
|
3076
|
+
def cast_to_storage (text, type, storage, masked: nil)
|
|
3003
3077
|
# Storing into a boolean array normalises: CArray's boolean is a byte
|
|
3004
3078
|
# holding 0 or 1, and a kernel is not the place that invariant stops
|
|
3005
3079
|
# being true.
|
|
3006
3080
|
return "(uint8_t)((#{text}) ? 1 : 0)" if boolean_storage?(storage)
|
|
3007
3081
|
target = storage_c_type(storage)
|
|
3008
3082
|
return text if COMPUTATION_C_TYPES.fetch(type) == target
|
|
3083
|
+
if [:double, :float].include?(type) and STORE_BOUNDS.key?(storage)
|
|
3084
|
+
@store_types |= [storage]
|
|
3085
|
+
error = error_argument
|
|
3086
|
+
if masked and masked != "0"
|
|
3087
|
+
error = "(#{masked} ? (int32_t *) 0 : #{error})"
|
|
3088
|
+
end
|
|
3089
|
+
return "carray_jit_store_#{storage}(#{text}, #{error})"
|
|
3090
|
+
end
|
|
3009
3091
|
"(#{target})(#{text})"
|
|
3010
3092
|
end
|
|
3011
3093
|
|
|
@@ -3563,8 +3645,9 @@ class CArray
|
|
|
3563
3645
|
c_function = @c_functions.fetch(node.name)
|
|
3564
3646
|
arguments = node.arguments.zip(c_function.parameters)
|
|
3565
3647
|
.map { |argument, parameter|
|
|
3566
|
-
if argument.is_a?(ArrayAddress)
|
|
3567
|
-
|
|
3648
|
+
if argument.is_a?(ArrayAddress)
|
|
3649
|
+
emit_array_argument(argument, parameter)
|
|
3650
|
+
elsif argument.is_a?(LocalArrayAddress)
|
|
3568
3651
|
emit(argument, :address)
|
|
3569
3652
|
else
|
|
3570
3653
|
emit(argument, parameter.computation)
|
|
@@ -3623,12 +3706,15 @@ class CArray
|
|
|
3623
3706
|
"#{emit(node.high, type)}, #{error_argument})", LEAF_PRECEDENCE]
|
|
3624
3707
|
end
|
|
3625
3708
|
|
|
3626
|
-
# `isnan` and `isfinite` are C's own, and take a
|
|
3627
|
-
# A Complex answers `finite?` the way Ruby answers
|
|
3628
|
-
# finite -- through a helper, so that the number is
|
|
3629
|
-
# An Integer is finite whatever it holds, and goes
|
|
3630
|
-
# all the same rather than being answered here: the
|
|
3631
|
-
# is a read, and dropping it would drop what the
|
|
3709
|
+
# `isnan` and `isfinite` are C's own, and take a real floating number
|
|
3710
|
+
# of either width. A Complex answers `finite?` the way Ruby answers
|
|
3711
|
+
# it -- both parts finite -- through a helper, so that the number is
|
|
3712
|
+
# computed once. An Integer is finite whatever it holds, and goes
|
|
3713
|
+
# through `isfinite` all the same rather than being answered here: the
|
|
3714
|
+
# read it stands for is a read, and dropping it would drop what the
|
|
3715
|
+
# read reports. It goes in as a double, because C asks for a floating
|
|
3716
|
+
# argument and gcc refuses an integer one where clang lets it pass;
|
|
3717
|
+
# every integer converts to a finite double.
|
|
3632
3718
|
def emit_numeric_predicate (node)
|
|
3633
3719
|
type = node.operand.type
|
|
3634
3720
|
function = node.name == :nan? ? "isnan" : "isfinite"
|
|
@@ -3636,7 +3722,11 @@ class CArray
|
|
|
3636
3722
|
return emit_helper_call("complex_finite", [emit(node.operand, type)],
|
|
3637
3723
|
type == :float_complex)
|
|
3638
3724
|
end
|
|
3639
|
-
|
|
3725
|
+
operand = emit(node.operand, type)
|
|
3726
|
+
if TypeAssignment::INTEGER_TYPES.include?(type)
|
|
3727
|
+
operand = "(double)(#{operand})"
|
|
3728
|
+
end
|
|
3729
|
+
["#{function}(#{operand})", LEAF_PRECEDENCE]
|
|
3640
3730
|
end
|
|
3641
3731
|
|
|
3642
3732
|
def emit_mask_test (node)
|
|
@@ -40,7 +40,17 @@ class CArray
|
|
|
40
40
|
# multiply and an add into one rounding, so it compiles with
|
|
41
41
|
# -ffp-contract=off. Here the reference is the eager kernel, which was
|
|
42
42
|
# built with whatever CArray settled on.
|
|
43
|
-
|
|
43
|
+
#
|
|
44
|
+
# Except between nodes. CArray computes each node in a kernel of its
|
|
45
|
+
# own, so no node's multiply meets the next node's add; this object
|
|
46
|
+
# writes the expression as one statement per node in a single loop.
|
|
47
|
+
# clang contracts within a statement by default, which leaves that
|
|
48
|
+
# alone, but gcc defaults to -ffp-contract=fast and contracts across
|
|
49
|
+
# statements: on x86 with -march=native, `a * b + c` lost a rounding in
|
|
50
|
+
# 3296 of 30000 cells. -ffp-contract=on keeps a contraction inside a
|
|
51
|
+
# node's own body, as CArray's kernel has it, and none between nodes.
|
|
52
|
+
FLAGS = ["-fPIC", "-shared", *CArray::BUILD_FLAGS.split,
|
|
53
|
+
"-ffp-contract=on", *DYNAMIC_LOOKUP].freeze
|
|
44
54
|
|
|
45
55
|
C_TYPES = {
|
|
46
56
|
float64: "double", float32: "float",
|
data/lib/carray/jit/kernel.rb
CHANGED
|
@@ -235,9 +235,10 @@ class CArray
|
|
|
235
235
|
|
|
236
236
|
held = [buffer(reals), buffer(integers), buffer(functions),
|
|
237
237
|
buffer(data), masks, masks, error]
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
238
|
+
error = in_one_block(held) { |context, slots|
|
|
239
|
+
Sweep.call(slab_pointer, fsync, arrays, context)
|
|
240
|
+
slots.last[0, error.bytesize]
|
|
241
|
+
}
|
|
241
242
|
|
|
242
243
|
packed.each_with_index do |(array, item), index|
|
|
243
244
|
next if array.equal?(item)
|
|
@@ -558,6 +559,31 @@ class CArray
|
|
|
558
559
|
packed.empty? ? "\0" * 8 : packed
|
|
559
560
|
end
|
|
560
561
|
|
|
562
|
+
# Copies `parts` into one block of C memory, each part 8-byte aligned,
|
|
563
|
+
# after a table of their addresses, and yields a pointer to the table
|
|
564
|
+
# and a pointer to each part; the block is freed when this returns.
|
|
565
|
+
#
|
|
566
|
+
# For a call that is handed addresses inside other memory rather than
|
|
567
|
+
# arguments: a Ruby String's bytes may be moved by GC compaction, and
|
|
568
|
+
# an address taken from one and packed elsewhere is not one Ruby knows
|
|
569
|
+
# to keep in place. Memory from malloc is never moved.
|
|
570
|
+
def in_one_block (parts)
|
|
571
|
+
table = parts.size * Fiddle::SIZEOF_VOIDP
|
|
572
|
+
offsets = parts.each_with_object([]) { |part, list|
|
|
573
|
+
last = list.empty? ? table : list.last + parts[list.size - 1].bytesize
|
|
574
|
+
list << ((last + 7) & ~7)
|
|
575
|
+
}
|
|
576
|
+
size = offsets.last + parts.last.bytesize
|
|
577
|
+
Fiddle::Pointer.malloc(size, Fiddle::RUBY_FREE) do |block|
|
|
578
|
+
slots = parts.zip(offsets).map { |part, offset|
|
|
579
|
+
block[offset, part.bytesize] = part
|
|
580
|
+
block + offset
|
|
581
|
+
}
|
|
582
|
+
block[0, table] = slots.map(&:to_i).pack("Q*")
|
|
583
|
+
yield block, slots
|
|
584
|
+
end
|
|
585
|
+
end
|
|
586
|
+
|
|
561
587
|
# The xfer tier transfers a box rather than the whole view. The box
|
|
562
588
|
# is the loop range grown by how far the kernel reaches from the cell it
|
|
563
589
|
# is on -- per array, because two arrays in one kernel need not be read
|
|
@@ -778,9 +804,9 @@ class CArray
|
|
|
778
804
|
unless array.is_a?(CArray)
|
|
779
805
|
raise Unsupported, "`#{name}` is not a CArray"
|
|
780
806
|
end
|
|
781
|
-
unless array.
|
|
807
|
+
unless array.ndim == array_rank(name)
|
|
782
808
|
raise Unsupported,
|
|
783
|
-
"`#{name}` has rank #{array.
|
|
809
|
+
"`#{name}` has rank #{array.ndim}, but is indexed with " \
|
|
784
810
|
"#{array_rank(name)} #{array_rank(name) == 1 ? 'index' : 'indices'}"
|
|
785
811
|
end
|
|
786
812
|
unless array.data_type_name == @storage_types.fetch(name)
|
data/lib/carray/jit/version.rb
CHANGED
data/lib/carray/jit.rb
CHANGED
|
@@ -638,10 +638,10 @@ class CArray
|
|
|
638
638
|
"the indices of a term are symbols naming its axes, as in " \
|
|
639
639
|
"`[a, [:i, :k]]`; got #{subscripts.inspect}"
|
|
640
640
|
end
|
|
641
|
-
unless subscripts.size == array.
|
|
641
|
+
unless subscripts.size == array.ndim
|
|
642
642
|
raise Unsupported,
|
|
643
643
|
"a term names one index per axis: this array has rank " \
|
|
644
|
-
"#{array.
|
|
644
|
+
"#{array.ndim} and #{subscripts.size} " \
|
|
645
645
|
"#{subscripts.size == 1 ? 'index' : 'indices'} were given"
|
|
646
646
|
end
|
|
647
647
|
end
|
|
@@ -680,9 +680,9 @@ class CArray
|
|
|
680
680
|
raise Unsupported, "`into:` is an array to write into"
|
|
681
681
|
end
|
|
682
682
|
expected = free.empty? ? 1 : free.size
|
|
683
|
-
unless into.
|
|
683
|
+
unless into.ndim == expected
|
|
684
684
|
raise Unsupported,
|
|
685
|
-
"`into:` has rank #{into.
|
|
685
|
+
"`into:` has rank #{into.ndim}, and the result's axes are " \
|
|
686
686
|
"#{free.empty? ? 'none, which is one cell' : free.map { |name| "`#{name}`" }.join(', ')}"
|
|
687
687
|
end
|
|
688
688
|
end
|
|
@@ -909,7 +909,7 @@ class CArray
|
|
|
909
909
|
return false if masked
|
|
910
910
|
return false if arrays.empty? || arrays.size > Sweep::MAX_ARITY
|
|
911
911
|
return false unless arrays.each_value.all? { |array|
|
|
912
|
-
array.
|
|
912
|
+
array.ndim.zero? || array.dim == shape
|
|
913
913
|
}
|
|
914
914
|
walkable_in_place?(arrays.each_value)
|
|
915
915
|
end
|
|
@@ -933,7 +933,7 @@ class CArray
|
|
|
933
933
|
# 0.9 ns/cell with the driver here against 6.1 ns/cell swept, and no
|
|
934
934
|
# scratch either way -- there was nothing the re-gather was buying.
|
|
935
935
|
def walkable_in_place? (arrays)
|
|
936
|
-
tiers = arrays.map { |array| Access.
|
|
936
|
+
tiers = arrays.map { |array| Access.tier(array) }
|
|
937
937
|
return true if tiers.include?(Access::TIER_XFER)
|
|
938
938
|
tiers.none? { |tier| tier == Access::TIER_STRIDE }
|
|
939
939
|
end
|
|
@@ -1214,6 +1214,23 @@ class CArray
|
|
|
1214
1214
|
# common case allocates nothing.
|
|
1215
1215
|
EMPTY_ARRAYS = {}.freeze
|
|
1216
1216
|
|
|
1217
|
+
# What an element-wise pass works out from its operands' shapes and
|
|
1218
|
+
# types, and not from their data: which of them are walked and which
|
|
1219
|
+
# are handed to a C function whole, the shape the pass covers, whether
|
|
1220
|
+
# lining the walked ones up to it changes any of them, which loop runs
|
|
1221
|
+
# it, the kernel, and what type a map's result is made as.
|
|
1222
|
+
#
|
|
1223
|
+
# @private
|
|
1224
|
+
RunPlan = Data.define(:walked, :addressed, :stretched, :shape,
|
|
1225
|
+
:sweeping, :kernel, :result_type)
|
|
1226
|
+
|
|
1227
|
+
# How many plans one block keeps. Not one: a caller alternating
|
|
1228
|
+
# between two model states would miss on every call, and pay for the
|
|
1229
|
+
# asking on top of what every call paid before. Eight, as a kernel
|
|
1230
|
+
# keeps of its own calls (CompiledKernel::PLAN_LIMIT), and for no
|
|
1231
|
+
# better-measured reason.
|
|
1232
|
+
RUN_PLAN_LIMIT = 8
|
|
1233
|
+
|
|
1217
1234
|
# @private
|
|
1218
1235
|
def run_over_whole_arrays (block, map: false)
|
|
1219
1236
|
node, source, origin = read_block(block)
|
|
@@ -1238,11 +1255,185 @@ class CArray
|
|
|
1238
1255
|
# is before the broadcast.
|
|
1239
1256
|
made = local_array_names(source, node)
|
|
1240
1257
|
refuse_a_shadowed_array(made, outside)
|
|
1241
|
-
|
|
1258
|
+
written = assigned_arrays(made.empty? ? assigned : assigned - made,
|
|
1259
|
+
outside)
|
|
1260
|
+
arrays = arrays.merge(written) unless written.empty?
|
|
1242
1261
|
if arrays.empty?
|
|
1243
1262
|
raise Unsupported, "the block reaches no array"
|
|
1244
1263
|
end
|
|
1245
1264
|
|
|
1265
|
+
# Everything from here to the kernel answers the same for two calls
|
|
1266
|
+
# whose operands agree on shape and type, so it is worked out once
|
|
1267
|
+
# per such agreement rather than once per call (#plan_run). What the
|
|
1268
|
+
# plan leaves to the call is what it has to: the operands themselves,
|
|
1269
|
+
# the views the broadcast makes of them, a generator's state, the
|
|
1270
|
+
# map's result, and the masks.
|
|
1271
|
+
key = run_key(arrays, scalars, c_functions, randoms, map)
|
|
1272
|
+
plan = key && found_run_plan(source, key)
|
|
1273
|
+
unless plan
|
|
1274
|
+
plan = plan_run(source, node, origin, arrays, scalars, c_functions,
|
|
1275
|
+
randoms, map)
|
|
1276
|
+
keep_run_plan(source, key, plan) if key
|
|
1277
|
+
end
|
|
1278
|
+
|
|
1279
|
+
walked = plan.addressed.empty? ? arrays : arrays.slice(*plan.walked)
|
|
1280
|
+
# Where no operand is stretched the broadcast hands back the operands
|
|
1281
|
+
# it was given, so it is not asked. Where one is, the views it makes
|
|
1282
|
+
# are views of these arrays and are made again: a view is exactly
|
|
1283
|
+
# what a plan must not keep.
|
|
1284
|
+
#
|
|
1285
|
+
# Unstretched, `aligned` can be `arrays` itself. That is sound only
|
|
1286
|
+
# because every write into it below puts back the array already
|
|
1287
|
+
# there.
|
|
1288
|
+
aligned = plan.stretched ? broadcast(walked).first : walked
|
|
1289
|
+
plan.addressed.each { |name| aligned[name] = arrays.fetch(name) }
|
|
1290
|
+
# A generator's state joins after the broadcast, never before it.
|
|
1291
|
+
# It is passed whole rather than walked, so the expression's shape
|
|
1292
|
+
# has nothing to say about it -- and it has a shape of its own that
|
|
1293
|
+
# would not line up anyway: four cells, against however many the
|
|
1294
|
+
# expression covers. Lining it up first is what `jit_each` did to a
|
|
1295
|
+
# one-cell state before 314d6cb, and a four-cell one does not even
|
|
1296
|
+
# stretch.
|
|
1297
|
+
unless randoms.empty?
|
|
1298
|
+
states = random_states(randoms)
|
|
1299
|
+
arrays = arrays.merge(states)
|
|
1300
|
+
aligned = aligned.merge(states)
|
|
1301
|
+
end
|
|
1302
|
+
shape = plan.shape
|
|
1303
|
+
if map
|
|
1304
|
+
result = CArray.send(plan.result_type, *(shape.empty? ? [1] : shape))
|
|
1305
|
+
arrays = arrays.merge(MAP_RESULT => result)
|
|
1306
|
+
aligned = aligned.merge(MAP_RESULT => result)
|
|
1307
|
+
end
|
|
1308
|
+
|
|
1309
|
+
kernel = plan.kernel
|
|
1310
|
+
# The shape of each of these was checked against the pass's when the
|
|
1311
|
+
# plan was made, and the key holds the shapes.
|
|
1312
|
+
kernel.written_arrays.each do |name|
|
|
1313
|
+
next if name == MAP_RESULT
|
|
1314
|
+
aligned[name] = arrays.fetch(name)
|
|
1315
|
+
end
|
|
1316
|
+
|
|
1317
|
+
aligned = read_once_where_the_pass_writes(aligned, kernel.written_arrays)
|
|
1318
|
+
|
|
1319
|
+
# An array handed to a C function by address is passed whole rather
|
|
1320
|
+
# than walked, so the expression's shape has nothing to say about it.
|
|
1321
|
+
# Broadcasting it would stretch a one-cell state array into a
|
|
1322
|
+
# read-only `CARepeat`, which the copy-back after the call cannot
|
|
1323
|
+
# write through -- and a state array is exactly the shape a caller
|
|
1324
|
+
# reaches for.
|
|
1325
|
+
kernel.address_arrays.each do |name|
|
|
1326
|
+
aligned[name] = arrays.fetch(name)
|
|
1327
|
+
end
|
|
1328
|
+
|
|
1329
|
+
if kernel.masked
|
|
1330
|
+
kernel.written_arrays.each do |name|
|
|
1331
|
+
array = arrays.fetch(name)
|
|
1332
|
+
array.mask = 0 unless array.has_mask?
|
|
1333
|
+
end
|
|
1334
|
+
end
|
|
1335
|
+
|
|
1336
|
+
result.mask = 0 if map && kernel.masked && !result.has_mask?
|
|
1337
|
+
drive(kernel, aligned, scalars, c_functions, shape, plan.sweeping)
|
|
1338
|
+
map ? result : kernel
|
|
1339
|
+
end
|
|
1340
|
+
|
|
1341
|
+
# What two calls of one block have to agree on for one to be run as
|
|
1342
|
+
# the other was.
|
|
1343
|
+
#
|
|
1344
|
+
# Each operand's name, type and shape, because that is what the kernel
|
|
1345
|
+
# is compiled for and the broadcast lines up; whether it is a CScalar,
|
|
1346
|
+
# because the broadcast stretches one where it leaves a one-cell array
|
|
1347
|
+
# standing. Whether it has a mask, because a mask is runtime state that
|
|
1348
|
+
# leaves the shape and the type alone -- one appearing makes a masked
|
|
1349
|
+
# kernel, and nothing else here would notice it. And the tier it would
|
|
1350
|
+
# be opened at, because that decides whether CArray's sweep or the
|
|
1351
|
+
# tiers here run the pass (#walkable_in_place?). All of it read off
|
|
1352
|
+
# the operand on this call; none of it kept from the last.
|
|
1353
|
+
#
|
|
1354
|
+
# Then the type of each scalar, each C function's signature and each
|
|
1355
|
+
# generator's kind, which is what the kernel is compiled for of those.
|
|
1356
|
+
# Not a scalar's value: a clock or a coefficient changes every step,
|
|
1357
|
+
# and keying on it would miss on every call of the loop this is for.
|
|
1358
|
+
#
|
|
1359
|
+
# Values only, never an operand: a block's plans live as long as the
|
|
1360
|
+
# process, and holding an array would hold its memory as long. Shapes
|
|
1361
|
+
# from `ndim`, `elements` and `dim0`.. rather than `dim`, which builds
|
|
1362
|
+
# an Array on every call.
|
|
1363
|
+
#
|
|
1364
|
+
# nil where a scalar is not one a kernel takes, so that the call is
|
|
1365
|
+
# refused where and as it always was rather than here.
|
|
1366
|
+
def run_key (arrays, scalars, c_functions, randoms, map)
|
|
1367
|
+
key = [map, arrays.size]
|
|
1368
|
+
arrays.each do |name, array|
|
|
1369
|
+
rank = array.ndim
|
|
1370
|
+
key << name << array.data_type << rank << array.is_a?(CScalar) <<
|
|
1371
|
+
array.has_mask? << Access.tier(array)
|
|
1372
|
+
case rank
|
|
1373
|
+
when 1 then key << array.elements
|
|
1374
|
+
when 2 then key << array.dim0 << array.dim1
|
|
1375
|
+
when 3 then key << array.dim0 << array.dim1 << array.dim2
|
|
1376
|
+
else key.concat(array.dim)
|
|
1377
|
+
end
|
|
1378
|
+
end
|
|
1379
|
+
key << scalars.size
|
|
1380
|
+
scalars.each do |name, value|
|
|
1381
|
+
key << name << TypeAssignment.scalar_type(value)
|
|
1382
|
+
end
|
|
1383
|
+
key << c_functions.size
|
|
1384
|
+
c_functions.each { |name, function| key << name << function.kernel_key }
|
|
1385
|
+
randoms.each { |name, generator| key << name << generator.generator }
|
|
1386
|
+
key
|
|
1387
|
+
rescue Unsupported
|
|
1388
|
+
nil
|
|
1389
|
+
end
|
|
1390
|
+
|
|
1391
|
+
# @private
|
|
1392
|
+
def run_plans
|
|
1393
|
+
@run_plans ||= {}
|
|
1394
|
+
end
|
|
1395
|
+
|
|
1396
|
+
# The block's plan for this key, if it has one. The list a plan sits
|
|
1397
|
+
# in is never changed once it is published -- a kernel's call lets go
|
|
1398
|
+
# of the GVL, so another thread may be reading it -- and bringing a
|
|
1399
|
+
# plan to the front replaces the list rather than editing it.
|
|
1400
|
+
def found_run_plan (source, key)
|
|
1401
|
+
plans = run_plans[source]
|
|
1402
|
+
return nil unless plans
|
|
1403
|
+
found = plans.assoc(key)
|
|
1404
|
+
return nil unless found
|
|
1405
|
+
unless plans.first.equal?(found)
|
|
1406
|
+
run_plans[source] =
|
|
1407
|
+
[found, *plans.reject { |entry| entry.equal?(found) }].freeze
|
|
1408
|
+
end
|
|
1409
|
+
found[1]
|
|
1410
|
+
end
|
|
1411
|
+
|
|
1412
|
+
# @private
|
|
1413
|
+
def keep_run_plan (source, key, plan)
|
|
1414
|
+
entry = [frozen_throughout(key), plan].freeze
|
|
1415
|
+
run_plans[source] =
|
|
1416
|
+
[entry, *run_plans[source]].first(RUN_PLAN_LIMIT).freeze
|
|
1417
|
+
end
|
|
1418
|
+
|
|
1419
|
+
# A copy that nothing outside can change under it, and that changes
|
|
1420
|
+
# nothing outside by being frozen: a C function's signature is its own
|
|
1421
|
+
# strings, and freezing those in place would freeze them in the
|
|
1422
|
+
# function.
|
|
1423
|
+
def frozen_throughout (value)
|
|
1424
|
+
case value
|
|
1425
|
+
when Array then value.map { |item| frozen_throughout(item) }.freeze
|
|
1426
|
+
when String then value.frozen? ? value : value.dup.freeze
|
|
1427
|
+
else value
|
|
1428
|
+
end
|
|
1429
|
+
end
|
|
1430
|
+
|
|
1431
|
+
# Works out what #run_over_whole_arrays keeps between calls, the way it
|
|
1432
|
+
# always worked it out on every call. Every refusal a pass can meet is
|
|
1433
|
+
# met here, before a plan exists, so a plan is only ever of a call that
|
|
1434
|
+
# ran.
|
|
1435
|
+
def plan_run (source, node, origin, arrays, scalars, c_functions,
|
|
1436
|
+
randoms, map)
|
|
1246
1437
|
# An array the block only hands to a C function whole is not walked,
|
|
1247
1438
|
# so the expression's shape has nothing to say about it -- and it has
|
|
1248
1439
|
# a shape of its own that need not line up with anything. Left in,
|
|
@@ -1250,15 +1441,10 @@ class CArray
|
|
|
1250
1441
|
# axis rather than the call it was written for.
|
|
1251
1442
|
#
|
|
1252
1443
|
# So it joins after the broadcast, which is what a generator's state
|
|
1253
|
-
# already does and for the same reason. The re-substitution
|
|
1254
|
-
#
|
|
1255
|
-
# the final one.
|
|
1444
|
+
# already does and for the same reason. The re-substitution in the
|
|
1445
|
+
# caller stays: this is a static reading, and the analyzer's answer
|
|
1446
|
+
# is the final one.
|
|
1256
1447
|
handed_over = address_array_names(source, node, c_functions)
|
|
1257
|
-
# The common case is that there are none, and it pays nothing: no
|
|
1258
|
-
# hash is built and the broadcast is handed the operands it always
|
|
1259
|
-
# was. Every call comes through here -- `jit_each` is re-entered per
|
|
1260
|
-
# call -- so the work that is only needed sometimes is done only
|
|
1261
|
-
# then.
|
|
1262
1448
|
addressed = handed_over.empty? ? EMPTY_ARRAYS
|
|
1263
1449
|
: arrays.slice(*handed_over)
|
|
1264
1450
|
walked = addressed.empty? ? arrays
|
|
@@ -1274,22 +1460,13 @@ class CArray
|
|
|
1274
1460
|
end
|
|
1275
1461
|
|
|
1276
1462
|
aligned, shape = broadcast(walked)
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
# It is passed whole rather than walked, so the expression's shape
|
|
1280
|
-
# has nothing to say about it -- and it has a shape of its own that
|
|
1281
|
-
# would not line up anyway: four cells, against however many the
|
|
1282
|
-
# expression covers. Lining it up first is what `jit_each` did to a
|
|
1283
|
-
# one-cell state before 314d6cb, and a four-cell one does not even
|
|
1284
|
-
# stretch.
|
|
1285
|
-
states = random_states(randoms)
|
|
1286
|
-
arrays = arrays.merge(states)
|
|
1287
|
-
aligned = aligned.merge(states)
|
|
1463
|
+
stretched = aligned.any? { |name, array| !array.equal?(walked[name]) }
|
|
1464
|
+
arrays = arrays.merge(random_states(randoms))
|
|
1288
1465
|
if map
|
|
1289
|
-
|
|
1290
|
-
|
|
1466
|
+
result_type = probe_map(source, node, arrays, scalars, c_functions,
|
|
1467
|
+
randoms)
|
|
1468
|
+
result = CArray.send(result_type, *(shape.empty? ? [1] : shape))
|
|
1291
1469
|
arrays = arrays.merge(MAP_RESULT => result)
|
|
1292
|
-
aligned = aligned.merge(MAP_RESULT => result)
|
|
1293
1470
|
end
|
|
1294
1471
|
# Both questions are about the arrays the loop walks. An array
|
|
1295
1472
|
# handed over whole is not one of them: its mask is not something the
|
|
@@ -1321,31 +1498,13 @@ class CArray
|
|
|
1321
1498
|
"expression covers #{shape.inspect}; a stretched array " \
|
|
1322
1499
|
"cannot be written to"
|
|
1323
1500
|
end
|
|
1324
|
-
aligned[name] = written
|
|
1325
|
-
end
|
|
1326
|
-
|
|
1327
|
-
aligned = read_once_where_the_pass_writes(aligned, kernel.written_arrays)
|
|
1328
|
-
|
|
1329
|
-
# An array handed to a C function by address is passed whole rather
|
|
1330
|
-
# than walked, so the expression's shape has nothing to say about it.
|
|
1331
|
-
# Broadcasting it would stretch a one-cell state array into a
|
|
1332
|
-
# read-only `CARepeat`, which the copy-back after the call cannot
|
|
1333
|
-
# write through -- and a state array is exactly the shape a caller
|
|
1334
|
-
# reaches for.
|
|
1335
|
-
kernel.address_arrays.each do |name|
|
|
1336
|
-
aligned[name] = arrays.fetch(name)
|
|
1337
|
-
end
|
|
1338
|
-
|
|
1339
|
-
if kernel.masked
|
|
1340
|
-
kernel.written_arrays.each do |name|
|
|
1341
|
-
array = arrays.fetch(name)
|
|
1342
|
-
array.mask = 0 unless array.has_mask?
|
|
1343
|
-
end
|
|
1344
1501
|
end
|
|
1345
1502
|
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1503
|
+
RunPlan.new(walked: walked.keys.freeze,
|
|
1504
|
+
addressed: addressed.keys.freeze,
|
|
1505
|
+
stretched: stretched, shape: shape.freeze,
|
|
1506
|
+
sweeping: sweeping, kernel: kernel,
|
|
1507
|
+
result_type: result_type)
|
|
1349
1508
|
end
|
|
1350
1509
|
|
|
1351
1510
|
# `a = b * 2.0` reads the whole of the right-hand side and then assigns,
|
|
@@ -1394,7 +1553,7 @@ class CArray
|
|
|
1394
1553
|
return if written.empty?
|
|
1395
1554
|
roots = written.map { |array| root_of(array) }
|
|
1396
1555
|
arrays.each do |name, array|
|
|
1397
|
-
next unless Access.
|
|
1556
|
+
next unless Access.tier(array) == Access::TIER_XFER
|
|
1398
1557
|
root = root_of(array)
|
|
1399
1558
|
# Another operand on the same storage, and one of the two written:
|
|
1400
1559
|
# a copy taken before the loop cannot answer for either of them.
|
|
@@ -1434,7 +1593,7 @@ class CArray
|
|
|
1434
1593
|
def broadcast (arrays)
|
|
1435
1594
|
values = arrays.values
|
|
1436
1595
|
rank = values.reject { |value| value.is_a?(CScalar) }
|
|
1437
|
-
.map(&:
|
|
1596
|
+
.map(&:ndim).max || 1
|
|
1438
1597
|
values = values.map { |value|
|
|
1439
1598
|
value.is_a?(CScalar) ? value.refer(value.data_type, [1] * rank) : value
|
|
1440
1599
|
}
|
|
@@ -1695,6 +1854,9 @@ class CArray
|
|
|
1695
1854
|
@undef_cache = {}
|
|
1696
1855
|
@probe_cache = {}
|
|
1697
1856
|
@local_array_name_cache = {}
|
|
1857
|
+
# A plan holds the kernel it runs, so one left behind would hand the
|
|
1858
|
+
# next call a kernel the registry no longer has.
|
|
1859
|
+
@run_plans = {}
|
|
1698
1860
|
end
|
|
1699
1861
|
|
|
1700
1862
|
# @return [String] the directory this environment's kernels are kept in,
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: carray-jit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- himotoyoshi
|
|
@@ -15,7 +15,7 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 3.0.
|
|
18
|
+
version: 3.0.5
|
|
19
19
|
- - "<"
|
|
20
20
|
- !ruby/object:Gem::Version
|
|
21
21
|
version: '3.1'
|
|
@@ -25,7 +25,7 @@ dependencies:
|
|
|
25
25
|
requirements:
|
|
26
26
|
- - ">="
|
|
27
27
|
- !ruby/object:Gem::Version
|
|
28
|
-
version: 3.0.
|
|
28
|
+
version: 3.0.5
|
|
29
29
|
- - "<"
|
|
30
30
|
- !ruby/object:Gem::Version
|
|
31
31
|
version: '3.1'
|