Class: Kumi::Core::Analyzer::Passes::Codegen::Loop::Js::Emitter::StreamPlan

Inherits:
Object
  • Object
show all
Defined in:
lib/kumi/core/analyzer/passes/codegen/loop/js/emitter.rb

Overview

Decides, per streaming function, which arrays and records can be materialized into caller-owned or module-persistent buffers instead of fresh allocations.

  • managed arrays: the returned array plus any array built solely to be pushed into a managed array. They are written by cursor and truncated, so the previous call's storage is reused.
  • managed objects: records built solely to be pushed into a managed array. The previous call's element at the same slot is mutated instead of allocating a new object.
  • persisted scratch: intermediate arrays that are only written by push and read by index, hoisted to module scope. Reads are always bounded by the current call's loops, so stale tails are inert.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(fn) ⇒ StreamPlan

Returns a new instance of StreamPlan.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/kumi/core/analyzer/passes/codegen/loop/js/emitter.rb', line 50

def initialize(fn)
  @fn = fn
  instrs = fn.entry_block.instructions

  producer_idx = {}
  producer = {}
  use_count = Hash.new(0)
  pushes = [] # [instruction index, parent reg, child reg]
  excluded = {}
  init_depth = {}
  pushed_somewhere = {}
  depth = 0

  instrs.each_with_index do |instr, idx|
    if instr.result
      producer[instr.result] = instr
      producer_idx[instr.result] = idx
    end
    instr.inputs.each { |r| use_count[r] += 1 }
    case instr.opcode
    when :array_push
      pushes << [idx, instr.inputs[0], instr.inputs[1]]
      pushed_somewhere[instr.inputs[1]] = true
    when :loop_start
      excluded[instr.inputs.first] = true
      depth += 1
    when :loop_end then depth -= 1
    when :array_len then excluded[instr.inputs.first] = true
    when :shift_read then excluded[instr.inputs[0]] = true
    when :array_init then init_depth[instr.result] = depth
    end
  end

  @target_reg = direct_return_array_reg(fn)
  @managed_arrays = {} # reg => parent reg (nil for reuse roots)
  @managed_objects = {} # reg => parent reg
  @persisted_scratch = {}

  # Persistence is only safe for arrays created once per call whose
  # identity never escapes into another array.
  instrs.each do |instr|
    next unless instr.opcode == :array_init
    next if instr.result == @target_reg
    next if excluded[instr.result] || pushed_somewhere[instr.result]
    next unless init_depth[instr.result].zero?

    @persisted_scratch[instr.result] = true
  end

  roots = {}
  roots[@target_reg] = true if @target_reg
  @persisted_scratch.each_key { |r| roots[r] = true }
  return if roots.empty?

  @managed_arrays[@target_reg] = nil if @target_reg
  reusable = roots.dup
  loop do
    changed = false
    pushes.each do |push_idx, parent, child|
      next unless reusable.key?(parent)
      next if reusable.key?(child) || @managed_objects.key?(child)

      prod = producer[child]
      next unless prod

      case prod.opcode
      when :array_init
        next if excluded[child]

        pushes_into = pushes.count { |_, p, _| p == child }
        pushed = pushes.count { |_, _, c| c == child }
        next unless pushed == 1 && use_count[child] == pushes_into + 1

        @managed_arrays[child] = parent
        reusable[child] = true
        changed = true
      when :make_object
        next unless use_count[child] == 1 && producer_idx[child] + 1 == push_idx
        next if Array(prod.attributes[:keys]).empty?

        @managed_objects[child] = parent
        changed = true
      end
    end
    break unless changed
  end
end

Instance Attribute Details

#managed_arraysObject (readonly)

Returns the value of attribute managed_arrays.



48
49
50
# File 'lib/kumi/core/analyzer/passes/codegen/loop/js/emitter.rb', line 48

def managed_arrays
  @managed_arrays
end

#managed_objectsObject (readonly)

Returns the value of attribute managed_objects.



48
49
50
# File 'lib/kumi/core/analyzer/passes/codegen/loop/js/emitter.rb', line 48

def managed_objects
  @managed_objects
end

#persisted_scratchObject (readonly)

Returns the value of attribute persisted_scratch.



48
49
50
# File 'lib/kumi/core/analyzer/passes/codegen/loop/js/emitter.rb', line 48

def persisted_scratch
  @persisted_scratch
end

#target_regObject (readonly)

Returns the value of attribute target_reg.



48
49
50
# File 'lib/kumi/core/analyzer/passes/codegen/loop/js/emitter.rb', line 48

def target_reg
  @target_reg
end

Instance Method Details

#cursor?(reg) ⇒ Boolean

Returns:



138
139
140
# File 'lib/kumi/core/analyzer/passes/codegen/loop/js/emitter.rb', line 138

def cursor?(reg)
  @managed_arrays.key?(reg) || @persisted_scratch.key?(reg)
end