carray-jit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.yardopts +10 -0
- data/CHANGELOG.md +84 -0
- data/LICENSE +21 -0
- data/README.md +88 -0
- data/bin/carray-jit +194 -0
- data/carray-jit.gemspec +41 -0
- data/docs/00_Introduction.md +40 -0
- data/docs/01_GettingStarted.md +80 -0
- data/docs/02_KernelShapes.md +397 -0
- data/docs/03_SupportedFeatures.md +595 -0
- data/docs/04_Compiling.md +234 -0
- data/docs/05_DesignNotes.md +136 -0
- data/docs/06_Cheatsheet.md +177 -0
- data/examples/README.md +56 -0
- data/examples/applications/game_of_life.rb +161 -0
- data/examples/applications/heat_equation.rb +117 -0
- data/examples/applications/kepler.rb +178 -0
- data/examples/applications/mandelbrot.rb +151 -0
- data/examples/applications/moving_average.rb +124 -0
- data/examples/applications/partial_sums.rb +141 -0
- data/examples/applications/point_cloud.rb +110 -0
- data/examples/applications/quicksort.rb +118 -0
- data/examples/applications/recursion.rb +121 -0
- data/examples/applications/relaxation.rb +115 -0
- data/examples/applications/sensor_gaps.rb +118 -0
- data/examples/applications/sieve.rb +95 -0
- data/examples/applications/sobel_edges.rb +80 -0
- data/examples/features/01_element_wise.rb +69 -0
- data/examples/features/02_stencil.rb +40 -0
- data/examples/features/03_recurrence.rb +50 -0
- data/examples/features/04_thomas.rb +81 -0
- data/examples/features/05_reduction.rb +90 -0
- data/examples/features/06_jit_contract.rb +58 -0
- data/examples/features/07_masks.rb +55 -0
- data/examples/features/08_views.rb +46 -0
- data/examples/features/09_inspecting.rb +55 -0
- data/examples/features/10_complex.rb +107 -0
- data/examples/features/11_c_functions.rb +260 -0
- data/examples/features/12_sweep.rb +139 -0
- data/examples/features/13_cscalar.rb +80 -0
- data/examples/features/14_stencil_window.rb +106 -0
- data/examples/features/15_loops.rb +148 -0
- data/examples/features/16_raising.rb +69 -0
- data/ext/carray_jit_access/carray_jit_access.c +460 -0
- data/ext/carray_jit_access/extconf.rb +8 -0
- data/lib/carray/jit/analyzer.rb +1847 -0
- data/lib/carray/jit/block_reader.rb +139 -0
- data/lib/carray/jit/c_function.rb +777 -0
- data/lib/carray/jit/c_generator.rb +2305 -0
- data/lib/carray/jit/compiler.rb +468 -0
- data/lib/carray/jit/errors.rb +37 -0
- data/lib/carray/jit/expression.rb +202 -0
- data/lib/carray/jit/kernel.rb +509 -0
- data/lib/carray/jit/node.rb +573 -0
- data/lib/carray/jit/sweep.rb +97 -0
- data/lib/carray/jit/type_assignment.rb +811 -0
- data/lib/carray/jit/version.rb +5 -0
- data/lib/carray/jit.rb +1210 -0
- metadata +139 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
module JIT
|
|
4
|
+
|
|
5
|
+
# Computes a CArray expression -- what `CArray.fuse { a + b * c }` builds
|
|
6
|
+
# -- by compiling it, instead of walking it a node at a time.
|
|
7
|
+
#
|
|
8
|
+
# CArray asks; this answers or declines. What it is handed is a plan
|
|
9
|
+
# (CArray::Fusion), which already says which operation each node is, what
|
|
10
|
+
# its mask does, and the C that the eager kernel computes it with. So
|
|
11
|
+
# nothing here restates any of that: it substitutes operands into bodies
|
|
12
|
+
# it was given and wraps the result in a loop.
|
|
13
|
+
#
|
|
14
|
+
# Declining is ordinary. An expression this cannot address -- an operand
|
|
15
|
+
# that is not laid out end to end, a data type with no C to write it in --
|
|
16
|
+
# goes back to CArray, which walks it and arrives at the same answer.
|
|
17
|
+
class Expression
|
|
18
|
+
|
|
19
|
+
# Built the way CArray's own kernels were, since that is what the answer
|
|
20
|
+
# is being compared against. The Prism front end wants the opposite of
|
|
21
|
+
# this on one point -- it answers to a Ruby loop, which does not fuse a
|
|
22
|
+
# multiply and an add into one rounding, so it compiles with
|
|
23
|
+
# -ffp-contract=off. Here the reference is the eager kernel, which was
|
|
24
|
+
# built with whatever CArray settled on.
|
|
25
|
+
FLAGS = ["-fPIC", "-shared", *CArray::BUILD_FLAGS.split].freeze
|
|
26
|
+
|
|
27
|
+
C_TYPES = {
|
|
28
|
+
float64: "double", float32: "float",
|
|
29
|
+
int8: "int8_t", int16: "int16_t",
|
|
30
|
+
int32: "int32_t", int64: "int64_t",
|
|
31
|
+
uint8: "uint8_t", uint16: "uint16_t",
|
|
32
|
+
uint32: "uint32_t", uint64: "uint64_t",
|
|
33
|
+
boolean: "uint8_t",
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
def initialize
|
|
37
|
+
@kernels = {}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Fills `out` and returns true, or writes nothing and returns false.
|
|
41
|
+
def call (plan, out)
|
|
42
|
+
return false unless C_TYPES.key?(plan.data_type)
|
|
43
|
+
aliased = plan.leaves.any? { |array| array.equal?(out) }
|
|
44
|
+
kernel = kernel_for(plan, aliased) or return false
|
|
45
|
+
arrays = [out, *plan.leaves]
|
|
46
|
+
writable = [true, *Array.new(plan.leaves.size, false)]
|
|
47
|
+
Access.open(arrays, writable) do |bases|
|
|
48
|
+
return false unless bases.each_with_index.all? { |basis, i|
|
|
49
|
+
basis[:strides] == end_to_end(arrays[i])
|
|
50
|
+
}
|
|
51
|
+
kernel.call(out.elements, *pointers(plan, bases))
|
|
52
|
+
end
|
|
53
|
+
true
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# An expression of the same shape compiles to the same kernel whatever
|
|
59
|
+
# arrays it is over, which is what the plan's signature says.
|
|
60
|
+
def kernel_for (plan, aliased)
|
|
61
|
+
@kernels.fetch([plan.signature, aliased]) do
|
|
62
|
+
@kernels[[plan.signature, aliased]] = compile(plan, aliased)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def compile (plan, aliased)
|
|
67
|
+
source = source_for(plan, aliased) or return nil
|
|
68
|
+
handle, = Compiler.build(source, "carray_jit_expression", flags: FLAGS)
|
|
69
|
+
Fiddle::Function.new(handle["carray_jit_expression"],
|
|
70
|
+
[Fiddle::TYPE_LONG_LONG] +
|
|
71
|
+
[Fiddle::TYPE_VOIDP] * (1 + arity(plan)),
|
|
72
|
+
Fiddle::TYPE_VOID)
|
|
73
|
+
rescue CompilationError
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def arity (plan)
|
|
78
|
+
plan.leaves.size + plan.leaves.count { |array| array.has_mask? } +
|
|
79
|
+
(plan.masked ? 1 : 0)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def pointers (plan, bases)
|
|
83
|
+
args = [bases.first[:pointer]]
|
|
84
|
+
args << bases.first[:mask_pointer] if plan.masked
|
|
85
|
+
bases.drop(1).each_with_index do |basis, i|
|
|
86
|
+
args << basis[:pointer]
|
|
87
|
+
args << basis[:mask_pointer] if plan.leaves[i].has_mask?
|
|
88
|
+
end
|
|
89
|
+
args
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def end_to_end (array)
|
|
93
|
+
steps = Array.new(array.ndim)
|
|
94
|
+
step = array.bytes
|
|
95
|
+
(array.ndim - 1).downto(0) do |axis|
|
|
96
|
+
steps[axis] = step
|
|
97
|
+
step *= array.dim[axis]
|
|
98
|
+
end
|
|
99
|
+
steps
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# -- the C ------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
def source_for (plan, aliased)
|
|
105
|
+
out_type = C_TYPES.fetch(plan.data_type)
|
|
106
|
+
restrict = aliased ? "" : "restrict "
|
|
107
|
+
body = plan.nodes.each_with_index.map { |node, i| line(plan, node, i) }
|
|
108
|
+
return nil if body.any?(&:nil?)
|
|
109
|
+
<<~C
|
|
110
|
+
#include <math.h>
|
|
111
|
+
#include <stdlib.h>
|
|
112
|
+
#include <stdint.h>
|
|
113
|
+
#include <string.h>
|
|
114
|
+
|
|
115
|
+
/* Some kernel bodies call back into CArray: integer division raises
|
|
116
|
+
there rather than trapping. Resolved against the extension,
|
|
117
|
+
which is loaded by the time this is. */
|
|
118
|
+
extern void ca_zerodiv (void);
|
|
119
|
+
|
|
120
|
+
/* The bodies are written in CArray's own C vocabulary. */
|
|
121
|
+
typedef float float32_t;
|
|
122
|
+
typedef double float64_t;
|
|
123
|
+
|
|
124
|
+
void
|
|
125
|
+
carray_jit_expression (int64_t elements, #{out_type} *#{restrict}out#{mask_parameter(plan, restrict)}#{parameters(plan, restrict)})
|
|
126
|
+
{
|
|
127
|
+
for ( int64_t n = 0; n < elements; n++ ) {
|
|
128
|
+
#{body.flatten.map { |l| " " + l }.join("\n")}
|
|
129
|
+
out[n] = v#{plan.nodes.size - 1};#{plan.masked ? "\n out_mask[n] = m#{plan.nodes.size - 1};" : ""}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
C
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def mask_parameter (plan, restrict)
|
|
136
|
+
plan.masked ? ", uint8_t *#{restrict}out_mask" : ""
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def parameters (plan, restrict)
|
|
140
|
+
plan.leaves.each_with_index.map { |array, i|
|
|
141
|
+
text = ", const #{C_TYPES.fetch(array.data_type)} *#{restrict}a#{i}"
|
|
142
|
+
text += ", const uint8_t *#{restrict}k#{i}" if array.has_mask?
|
|
143
|
+
text
|
|
144
|
+
}.join
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def line (plan, node, i)
|
|
148
|
+
type = C_TYPES[node.data_type] or return nil
|
|
149
|
+
case node
|
|
150
|
+
when CArray::Fusion::Leaf
|
|
151
|
+
["#{type} v#{i} = a#{node.index}[n];",
|
|
152
|
+
*(plan.masked ? ["uint8_t m#{i} = #{node.masked ? "k#{node.index}[n]" : "0"};"] : [])]
|
|
153
|
+
when CArray::Fusion::Const
|
|
154
|
+
["#{type} v#{i} = #{literal(node)};",
|
|
155
|
+
*(plan.masked ? ["uint8_t m#{i} = 0;"] : [])]
|
|
156
|
+
when CArray::Fusion::Op
|
|
157
|
+
statement = substitute(node, i, type) or return nil
|
|
158
|
+
[*(plan.masked ? [mask_line(node, i)] : []),
|
|
159
|
+
"#{type} v#{i};",
|
|
160
|
+
*guarded(node, i, statement, plan.masked)]
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def literal (node)
|
|
165
|
+
case node.data_type
|
|
166
|
+
when :float64, :float32 then "%.17g" % node.value
|
|
167
|
+
when :boolean then node.value ? 1 : 0
|
|
168
|
+
else node.value.to_s
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def substitute (node, i, type)
|
|
173
|
+
text = node.body.dup
|
|
174
|
+
node.args.each_with_index { |arg, k| text = text.gsub("##{k + 1}", "v#{arg}") }
|
|
175
|
+
text.gsub("##{node.args.size + 1}", "v#{i}").gsub("<type>", type).lines.map(&:strip)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# A masked cell is not computed where computing it would raise: the
|
|
179
|
+
# divisor there is nobody's business.
|
|
180
|
+
def guarded (node, i, statement, masked)
|
|
181
|
+
return statement unless masked && node.trapping
|
|
182
|
+
["if ( m#{i} ) { v#{i} = 0; } else {", *statement, "}"]
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# The rules the plan states, written out.
|
|
186
|
+
def mask_line (node, i)
|
|
187
|
+
args = node.args
|
|
188
|
+
case node.mask
|
|
189
|
+
when :pass then "uint8_t m#{i} = m#{args[0]};"
|
|
190
|
+
when :union then "uint8_t m#{i} = #{args.map { |a| "m#{a}" }.join(" | ")};"
|
|
191
|
+
when :kleene_or, :kleene_and
|
|
192
|
+
known = if node.mask == :kleene_or
|
|
193
|
+
"((!m#{args[0]} && v#{args[0]}) || (!m#{args[1]} && v#{args[1]}))"
|
|
194
|
+
else
|
|
195
|
+
"((!m#{args[0]} && !v#{args[0]}) || (!m#{args[1]} && !v#{args[1]}))"
|
|
196
|
+
end
|
|
197
|
+
"uint8_t m#{i} = (m#{args[0]} | m#{args[1]}) && ! #{known};"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|