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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +146 -0
- data/README.md +11 -10
- data/carray-jit.gemspec +1 -1
- data/docs/00_Introduction.md +2 -1
- data/docs/02_KernelShapes.md +1 -1
- data/docs/03_SupportedFeatures.md +88 -11
- data/docs/04_Compiling.md +2 -2
- data/docs/05_DesignNotes.md +1 -1
- data/docs/06_Cheatsheet.md +3 -3
- data/docs/07_StepByStep.ja.md +14 -15
- data/docs/07_StepByStep.md +14 -15
- data/docs/08_LanguageReference.md +427 -0
- data/lib/carray/jit/analyzer.rb +144 -7
- data/lib/carray/jit/c_function.rb +359 -21
- data/lib/carray/jit/c_generator.rb +398 -58
- data/lib/carray/jit/compiler.rb +27 -3
- data/lib/carray/jit/expression.rb +271 -33
- data/lib/carray/jit/kernel.rb +17 -4
- data/lib/carray/jit/node.rb +15 -0
- data/lib/carray/jit/type_assignment.rb +53 -1
- data/lib/carray/jit/version.rb +1 -1
- data/lib/carray/jit.rb +7 -33
- metadata +4 -3
data/lib/carray/jit/analyzer.rb
CHANGED
|
@@ -263,7 +263,7 @@ class CArray
|
|
|
263
263
|
COMPARISON_OPERATORS = [:<, :<=, :>, :>=, :==, :!=].freeze
|
|
264
264
|
|
|
265
265
|
attr_reader :parameter_names, :pointer_names, :address_arrays,
|
|
266
|
-
:address_parameters, :random_names
|
|
266
|
+
:address_parameters, :random_names, :assigned_parameters
|
|
267
267
|
# An offset that is an integer here rather than when the kernel runs.
|
|
268
268
|
# A literal is one; so is arithmetic over literals, which is the same
|
|
269
269
|
# number written a way that says where it came from -- `w[-RADIUS-1]`
|
|
@@ -310,7 +310,7 @@ class CArray
|
|
|
310
310
|
attr_reader :windows
|
|
311
311
|
attr_reader :index_names, :array_names, :scalar_names, :c_function_names, :body,
|
|
312
312
|
:written_arrays, :array_ranks, :subscripts, :inner_ranges,
|
|
313
|
-
:index_sources,
|
|
313
|
+
:index_sources, :unbounded_indices,
|
|
314
314
|
:contracted_names
|
|
315
315
|
# The C arrays the body declares, as [scope, name, storage, shape].
|
|
316
316
|
# Nothing in the operand tables says a body has any, so this is how the
|
|
@@ -503,6 +503,7 @@ class CArray
|
|
|
503
503
|
@inner_names_seen = []
|
|
504
504
|
@array_ranks = {}
|
|
505
505
|
@inner_ranges = {}
|
|
506
|
+
@unbounded_indices = []
|
|
506
507
|
@subscripts = Hash.new { |hash, key| hash[key] = [] }
|
|
507
508
|
@write_subscripts = Hash.new { |hash, key| hash[key] = [] }
|
|
508
509
|
@given_rank = rank
|
|
@@ -535,6 +536,7 @@ class CArray
|
|
|
535
536
|
@scalar_names = []
|
|
536
537
|
@c_function_names = []
|
|
537
538
|
@parameter_names = []
|
|
539
|
+
@assigned_parameters = []
|
|
538
540
|
@pointer_names = []
|
|
539
541
|
@address_arrays = []
|
|
540
542
|
@address_parameters = {}
|
|
@@ -808,6 +810,15 @@ class CArray
|
|
|
808
810
|
raise Unsupported.new("kernel body is empty")
|
|
809
811
|
end
|
|
810
812
|
@assigned_names = collect_assigned_names(block.body)
|
|
813
|
+
read_parameter_assignments(block.body) if @function
|
|
814
|
+
# A parameter the body assigns is a local from the top of the body
|
|
815
|
+
# on, starting at the value the call passed. Bound here, before any
|
|
816
|
+
# statement is built, so every read of it -- the first included --
|
|
817
|
+
# is a read of that local.
|
|
818
|
+
entry = @assigned_parameters.map { |name|
|
|
819
|
+
@scalar_names << name unless @scalar_names.include?(name)
|
|
820
|
+
assign_local(name, CaptureRead.new(name), nil)
|
|
821
|
+
}
|
|
811
822
|
|
|
812
823
|
# A contraction may end in a bare expression rather than an
|
|
813
824
|
# assignment; that is the form that allocates its result and returns.
|
|
@@ -839,7 +850,7 @@ class CArray
|
|
|
839
850
|
built << (returns_value ? build(last) : build_statement(last))
|
|
840
851
|
@map_value = built.last if @map
|
|
841
852
|
end
|
|
842
|
-
@body = KernelBody.new(built)
|
|
853
|
+
@body = KernelBody.new(entry + built)
|
|
843
854
|
|
|
844
855
|
return if @contract == :probe || @map == :probe
|
|
845
856
|
return if @function
|
|
@@ -1239,6 +1250,50 @@ class CArray
|
|
|
1239
1250
|
names.uniq
|
|
1240
1251
|
end
|
|
1241
1252
|
|
|
1253
|
+
# A parameter the body assigns. Left alone, the assignment was taken
|
|
1254
|
+
# for a new local of the same name -- declared a second time in the C,
|
|
1255
|
+
# which the C compiler refused, or read before it was assigned, which
|
|
1256
|
+
# is not what it is.
|
|
1257
|
+
#
|
|
1258
|
+
# In a `jit_function` body it is what C makes of it: the parameter is
|
|
1259
|
+
# the function's own copy of what it was called with, and a body may
|
|
1260
|
+
# count it down or replace it. So a number is kept, and bound as a
|
|
1261
|
+
# local at the top of the body (see #analyze). A pointer is not: the
|
|
1262
|
+
# name is the array the body indexes, and a second array under it is
|
|
1263
|
+
# nothing the body's arrays can be.
|
|
1264
|
+
#
|
|
1265
|
+
# For `jit_call` it is the leak in the block reading as a closure: the
|
|
1266
|
+
# name is a local of the method around the call, and assigning it
|
|
1267
|
+
# looks like writing that local, which a value passed by value cannot
|
|
1268
|
+
# do. An array parameter does come back, through its cells, so the
|
|
1269
|
+
# two look alike and only one of them works.
|
|
1270
|
+
def read_parameter_assignments (node)
|
|
1271
|
+
return unless node
|
|
1272
|
+
if ASSIGNMENT_NODES.any? { |kind| node.is_a?(kind) } &&
|
|
1273
|
+
@parameter_names.include?(node.name)
|
|
1274
|
+
name = node.name
|
|
1275
|
+
if @declared_parameters
|
|
1276
|
+
raise Unsupported.new(
|
|
1277
|
+
"`#{name}` is a parameter of this `jit_call`, so assigning it " \
|
|
1278
|
+
"looks like writing the local of that name -- and a parameter " \
|
|
1279
|
+
"is passed by value, so nothing written here would reach it. " \
|
|
1280
|
+
"An array's cells do come back, as in `#{name}[0] = ...`; for " \
|
|
1281
|
+
"a number, give the body a local of its own name, or pass a " \
|
|
1282
|
+
"one-cell array and write its cell",
|
|
1283
|
+
node.location)
|
|
1284
|
+
end
|
|
1285
|
+
if @pointers.key?(name)
|
|
1286
|
+
raise Unsupported.new(
|
|
1287
|
+
"`#{name}` is a pointer parameter, and the body's arrays are " \
|
|
1288
|
+
"the ones it was called with; write its cells, as in " \
|
|
1289
|
+
"`#{name}[0] = ...`, rather than the name",
|
|
1290
|
+
node.location)
|
|
1291
|
+
end
|
|
1292
|
+
@assigned_parameters << name unless @assigned_parameters.include?(name)
|
|
1293
|
+
end
|
|
1294
|
+
node.compact_child_nodes.each { |child| read_parameter_assignments(child) }
|
|
1295
|
+
end
|
|
1296
|
+
|
|
1242
1297
|
# Accepts `->(i) { ... }`, `proc { |i| ... }` and `lambda { |i| ... }`.
|
|
1243
1298
|
def extract_block (program)
|
|
1244
1299
|
statements = program.statements.body
|
|
@@ -1570,7 +1625,7 @@ class CArray
|
|
|
1570
1625
|
"Ruby; write the test at the top",
|
|
1571
1626
|
node.location)
|
|
1572
1627
|
end
|
|
1573
|
-
condition =
|
|
1628
|
+
condition = build_condition(node.predicate)
|
|
1574
1629
|
@loop_depth += 1
|
|
1575
1630
|
statements = statements_of(node.statements).map { |inner|
|
|
1576
1631
|
build_statement(inner)
|
|
@@ -1715,6 +1770,10 @@ class CArray
|
|
|
1715
1770
|
@inner_aliases[index] = internal
|
|
1716
1771
|
@index_sources[internal] = index
|
|
1717
1772
|
@inner_ranges[internal] = [from, to, step]
|
|
1773
|
+
unless Analyzer.boundable_range?(from, @unbounded_indices) &&
|
|
1774
|
+
Analyzer.boundable_range?(to, @unbounded_indices)
|
|
1775
|
+
@unbounded_indices << internal
|
|
1776
|
+
end
|
|
1718
1777
|
@loop_depth += 1
|
|
1719
1778
|
@scopes.push([])
|
|
1720
1779
|
statements = statements_of(node.block.body).map { |inner|
|
|
@@ -1770,7 +1829,7 @@ class CArray
|
|
|
1770
1829
|
else
|
|
1771
1830
|
raise Unsupported.new("unsupported `if` continuation", node.location)
|
|
1772
1831
|
end
|
|
1773
|
-
Branch.new(
|
|
1832
|
+
Branch.new(build_condition(node.predicate),
|
|
1774
1833
|
statements_of(node.statements).map { |inner|
|
|
1775
1834
|
build_statement(inner)
|
|
1776
1835
|
},
|
|
@@ -3021,6 +3080,42 @@ class CArray
|
|
|
3021
3080
|
CaptureRead.new(name, location)
|
|
3022
3081
|
end
|
|
3023
3082
|
|
|
3083
|
+
# What `if`, `while` and `?:` read. A pointer parameter named bare
|
|
3084
|
+
# there is Ruby's truthiness -- nil is false and an array is true -- so
|
|
3085
|
+
# `if grad` asks whether an address came, which is what NLopt's
|
|
3086
|
+
# derivative-free methods leave out. Only where a condition is read:
|
|
3087
|
+
# anywhere else the bare name would be a value, and a pointer is not
|
|
3088
|
+
# one. `!`, `&&`, `||` and parentheses are looked through, since each
|
|
3089
|
+
# of those reads its operands as conditions too.
|
|
3090
|
+
def build_condition (node)
|
|
3091
|
+
case node
|
|
3092
|
+
when Prism::ParenthesesNode
|
|
3093
|
+
inner = node.body ? node.body.body : []
|
|
3094
|
+
return build_condition(inner.first) if inner.size == 1
|
|
3095
|
+
when Prism::AndNode
|
|
3096
|
+
return LogicalOperation.new(:"&&", build_condition(node.left),
|
|
3097
|
+
build_condition(node.right), node.location)
|
|
3098
|
+
when Prism::OrNode
|
|
3099
|
+
return LogicalOperation.new(:"||", build_condition(node.left),
|
|
3100
|
+
build_condition(node.right), node.location)
|
|
3101
|
+
when Prism::CallNode
|
|
3102
|
+
if node.name == :! && node.receiver && node.arguments.nil?
|
|
3103
|
+
return LogicalNot.new(build_condition(node.receiver), node.location)
|
|
3104
|
+
end
|
|
3105
|
+
end
|
|
3106
|
+
pointer_test(node, false, node.location) || build(node)
|
|
3107
|
+
end
|
|
3108
|
+
|
|
3109
|
+
# A PointerTest when `receiver` names a pointer parameter, nil when it
|
|
3110
|
+
# does not. Asking is not reaching through, so a `void *` may be asked
|
|
3111
|
+
# too: whether a slot was filled is a question about the slot.
|
|
3112
|
+
def pointer_test (receiver, null, location)
|
|
3113
|
+
name = captured_name(receiver)
|
|
3114
|
+
return nil unless name && @pointers.key?(name)
|
|
3115
|
+
return nil if @local_names.include?(name)
|
|
3116
|
+
PointerTest.new(name, null, location)
|
|
3117
|
+
end
|
|
3118
|
+
|
|
3024
3119
|
def build_conditional (node)
|
|
3025
3120
|
unless node.subsequent
|
|
3026
3121
|
raise Unsupported.new("`if` without `else` has no value for every cell",
|
|
@@ -3029,7 +3124,7 @@ class CArray
|
|
|
3029
3124
|
unless node.subsequent.is_a?(Prism::ElseNode)
|
|
3030
3125
|
raise Unsupported.new("`elsif` is not supported", node.location)
|
|
3031
3126
|
end
|
|
3032
|
-
Conditional.new(
|
|
3127
|
+
Conditional.new(build_condition(node.predicate),
|
|
3033
3128
|
build_single(node.statements, node.location, "if"),
|
|
3034
3129
|
build_single(node.subsequent.statements, node.location, "else"),
|
|
3035
3130
|
node.location)
|
|
@@ -3125,6 +3220,20 @@ class CArray
|
|
|
3125
3220
|
return Clamp.new(build(node.receiver), build(arguments.first),
|
|
3126
3221
|
build(arguments.last), node.location)
|
|
3127
3222
|
end
|
|
3223
|
+
if node.name == :nil? && arguments.empty? &&
|
|
3224
|
+
(test = pointer_test(node.receiver, true, node.location))
|
|
3225
|
+
return test
|
|
3226
|
+
end
|
|
3227
|
+
if [:==, :!=].include?(node.name) && arguments.size == 1
|
|
3228
|
+
# `grad == nil` and `nil == grad`, which Ruby answers the way
|
|
3229
|
+
# `grad.nil?` does and so may be written wherever that is.
|
|
3230
|
+
receiver, other = node.receiver, arguments.first
|
|
3231
|
+
receiver, other = other, receiver if receiver.is_a?(Prism::NilNode)
|
|
3232
|
+
if other.is_a?(Prism::NilNode) &&
|
|
3233
|
+
(test = pointer_test(receiver, node.name == :==, node.location))
|
|
3234
|
+
return test
|
|
3235
|
+
end
|
|
3236
|
+
end
|
|
3128
3237
|
if arguments.empty? && NUMERIC_PREDICATES.include?(node.name)
|
|
3129
3238
|
return NumericPredicate.new(node.name, build(node.receiver),
|
|
3130
3239
|
node.location)
|
|
@@ -3662,9 +3771,13 @@ class CArray
|
|
|
3662
3771
|
# in scope, so that the offset is a compile-time constant.
|
|
3663
3772
|
def read_subscript (node, location)
|
|
3664
3773
|
name = index_name(node)
|
|
3665
|
-
|
|
3774
|
+
if name && index_in_scope?(name)
|
|
3775
|
+
return [nil, pinned_subscript(node)] if unbounded_index?(name)
|
|
3776
|
+
return [index_identifier(name), 0]
|
|
3777
|
+
end
|
|
3666
3778
|
if node.is_a?(Prism::CallNode) && [:+, :-].include?(node.name) &&
|
|
3667
3779
|
(receiver = index_name(node.receiver)) && index_in_scope?(receiver)
|
|
3780
|
+
return [nil, pinned_subscript(node)] if unbounded_index?(receiver)
|
|
3668
3781
|
return walked_subscript(node)
|
|
3669
3782
|
end
|
|
3670
3783
|
# Anything else pins the axis at a position the loop does not walk:
|
|
@@ -3678,6 +3791,30 @@ class CArray
|
|
|
3678
3791
|
build(node)
|
|
3679
3792
|
end
|
|
3680
3793
|
|
|
3794
|
+
# An inner index whose range reads a local or a cell. Its range is not
|
|
3795
|
+
# known until the loop is reached, so a subscript it walks cannot be
|
|
3796
|
+
# checked before the kernel runs; it is checked at each access instead,
|
|
3797
|
+
# as `a[b[i]]` is.
|
|
3798
|
+
def unbounded_index? (name)
|
|
3799
|
+
@unbounded_indices.include?(index_identifier(name))
|
|
3800
|
+
end
|
|
3801
|
+
|
|
3802
|
+
# Whether an inner loop's range can be worked out before the kernel
|
|
3803
|
+
# runs: literals, captured integers, the extents, and indices whose own
|
|
3804
|
+
# range can, under `+`, `-` and `*`.
|
|
3805
|
+
def self.boundable_range? (node, unbounded)
|
|
3806
|
+
case node
|
|
3807
|
+
when IntegerLiteral, CaptureRead, BoundsValue then true
|
|
3808
|
+
when IndexVariable then !unbounded.include?(node.name)
|
|
3809
|
+
when UnaryMinus then boundable_range?(node.operand, unbounded)
|
|
3810
|
+
when BinaryOperation
|
|
3811
|
+
[:+, :-, :*].include?(node.operator) &&
|
|
3812
|
+
boundable_range?(node.left, unbounded) &&
|
|
3813
|
+
boundable_range?(node.right, unbounded)
|
|
3814
|
+
else false
|
|
3815
|
+
end
|
|
3816
|
+
end
|
|
3817
|
+
|
|
3681
3818
|
# The one cell a CScalar has: position zero on its one axis, which is a
|
|
3682
3819
|
# pinned subscript like any other and is checked like one.
|
|
3683
3820
|
def cell_subscripts (array, location)
|