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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +10 -0
  3. data/CHANGELOG.md +84 -0
  4. data/LICENSE +21 -0
  5. data/README.md +88 -0
  6. data/bin/carray-jit +194 -0
  7. data/carray-jit.gemspec +41 -0
  8. data/docs/00_Introduction.md +40 -0
  9. data/docs/01_GettingStarted.md +80 -0
  10. data/docs/02_KernelShapes.md +397 -0
  11. data/docs/03_SupportedFeatures.md +595 -0
  12. data/docs/04_Compiling.md +234 -0
  13. data/docs/05_DesignNotes.md +136 -0
  14. data/docs/06_Cheatsheet.md +177 -0
  15. data/examples/README.md +56 -0
  16. data/examples/applications/game_of_life.rb +161 -0
  17. data/examples/applications/heat_equation.rb +117 -0
  18. data/examples/applications/kepler.rb +178 -0
  19. data/examples/applications/mandelbrot.rb +151 -0
  20. data/examples/applications/moving_average.rb +124 -0
  21. data/examples/applications/partial_sums.rb +141 -0
  22. data/examples/applications/point_cloud.rb +110 -0
  23. data/examples/applications/quicksort.rb +118 -0
  24. data/examples/applications/recursion.rb +121 -0
  25. data/examples/applications/relaxation.rb +115 -0
  26. data/examples/applications/sensor_gaps.rb +118 -0
  27. data/examples/applications/sieve.rb +95 -0
  28. data/examples/applications/sobel_edges.rb +80 -0
  29. data/examples/features/01_element_wise.rb +69 -0
  30. data/examples/features/02_stencil.rb +40 -0
  31. data/examples/features/03_recurrence.rb +50 -0
  32. data/examples/features/04_thomas.rb +81 -0
  33. data/examples/features/05_reduction.rb +90 -0
  34. data/examples/features/06_jit_contract.rb +58 -0
  35. data/examples/features/07_masks.rb +55 -0
  36. data/examples/features/08_views.rb +46 -0
  37. data/examples/features/09_inspecting.rb +55 -0
  38. data/examples/features/10_complex.rb +107 -0
  39. data/examples/features/11_c_functions.rb +260 -0
  40. data/examples/features/12_sweep.rb +139 -0
  41. data/examples/features/13_cscalar.rb +80 -0
  42. data/examples/features/14_stencil_window.rb +106 -0
  43. data/examples/features/15_loops.rb +148 -0
  44. data/examples/features/16_raising.rb +69 -0
  45. data/ext/carray_jit_access/carray_jit_access.c +460 -0
  46. data/ext/carray_jit_access/extconf.rb +8 -0
  47. data/lib/carray/jit/analyzer.rb +1847 -0
  48. data/lib/carray/jit/block_reader.rb +139 -0
  49. data/lib/carray/jit/c_function.rb +777 -0
  50. data/lib/carray/jit/c_generator.rb +2305 -0
  51. data/lib/carray/jit/compiler.rb +468 -0
  52. data/lib/carray/jit/errors.rb +37 -0
  53. data/lib/carray/jit/expression.rb +202 -0
  54. data/lib/carray/jit/kernel.rb +509 -0
  55. data/lib/carray/jit/node.rb +573 -0
  56. data/lib/carray/jit/sweep.rb +97 -0
  57. data/lib/carray/jit/type_assignment.rb +811 -0
  58. data/lib/carray/jit/version.rb +5 -0
  59. data/lib/carray/jit.rb +1210 -0
  60. 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