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.
@@ -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 = build(node.predicate)
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(build(node.predicate),
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(build(node.predicate),
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
- return [index_identifier(name), 0] if name && index_in_scope?(name)
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)