kumi 0.0.14 → 0.0.16
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 +40 -0
- data/README.md +0 -27
- data/docs/dev/vm-profiling.md +95 -0
- data/docs/features/README.md +0 -7
- data/lib/kumi/analyzer.rb +10 -2
- data/lib/kumi/compiler.rb +6 -5
- data/lib/kumi/core/analyzer/passes/ir_dependency_pass.rb +65 -0
- data/lib/kumi/core/analyzer/passes/ir_execution_schedule_pass.rb +67 -0
- data/lib/kumi/core/analyzer/passes/toposorter.rb +15 -50
- data/lib/kumi/core/compiler/access_builder.rb +22 -9
- data/lib/kumi/core/compiler/access_codegen.rb +61 -0
- data/lib/kumi/core/compiler/access_emit/base.rb +173 -0
- data/lib/kumi/core/compiler/access_emit/each_indexed.rb +56 -0
- data/lib/kumi/core/compiler/access_emit/materialize.rb +45 -0
- data/lib/kumi/core/compiler/access_emit/ravel.rb +50 -0
- data/lib/kumi/core/compiler/access_emit/read.rb +32 -0
- data/lib/kumi/core/ir/execution_engine/interpreter.rb +56 -189
- data/lib/kumi/core/ir/execution_engine/profiler.rb +139 -11
- data/lib/kumi/core/ir/execution_engine/values.rb +8 -8
- data/lib/kumi/core/ir/execution_engine.rb +5 -30
- data/lib/kumi/dev/parse.rb +12 -12
- data/lib/kumi/dev/profile_aggregator.rb +301 -0
- data/lib/kumi/dev/profile_runner.rb +199 -0
- data/lib/kumi/dev/runner.rb +3 -1
- data/lib/kumi/dev.rb +14 -0
- data/lib/kumi/runtime/executable.rb +32 -153
- data/lib/kumi/runtime/run.rb +105 -0
- data/lib/kumi/schema.rb +15 -14
- data/lib/kumi/version.rb +1 -1
- data/lib/kumi.rb +4 -2
- metadata +15 -3
- data/docs/features/analysis-cascade-mutual-exclusion.md +0 -89
@@ -0,0 +1,173 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Kumi
|
3
|
+
module Core
|
4
|
+
module Compiler
|
5
|
+
module AccessEmit
|
6
|
+
module Base
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# ---------- IR segmentation ----------
|
10
|
+
def segment_ops(ops)
|
11
|
+
segs, cur = [], []
|
12
|
+
i = 0
|
13
|
+
while i < ops.length
|
14
|
+
case ops[i][:type]
|
15
|
+
when :enter_hash
|
16
|
+
preview = (i + 1 < ops.length) && ops[i + 1][:type] == :enter_array
|
17
|
+
cur << [:enter_hash, ops[i][:key].to_s, preview]
|
18
|
+
when :enter_array
|
19
|
+
segs << cur unless cur.empty?
|
20
|
+
segs << :array
|
21
|
+
cur = []
|
22
|
+
else
|
23
|
+
raise "Unknown operation: #{ops[i].inspect}"
|
24
|
+
end
|
25
|
+
i += 1
|
26
|
+
end
|
27
|
+
segs << cur unless cur.empty?
|
28
|
+
segs
|
29
|
+
end
|
30
|
+
|
31
|
+
# ---------- codegen helpers ----------
|
32
|
+
def fetch_hash_code(node_var:, key:, key_policy:, preview_array:, mode:, policy:, path_key:, map_depth:)
|
33
|
+
effective_policy = preview_array ? :indifferent : (key_policy || :indifferent)
|
34
|
+
str = key.to_s.inspect
|
35
|
+
sym = key.to_sym.inspect
|
36
|
+
|
37
|
+
fetch =
|
38
|
+
case effective_policy
|
39
|
+
when :string
|
40
|
+
%(next_node = #{node_var}.key?(#{str}) ? #{node_var}[#{str}] : :__missing__)
|
41
|
+
when :symbol
|
42
|
+
%(next_node = #{node_var}.key?(#{sym}) ? #{node_var}[#{sym}] : :__missing__)
|
43
|
+
else # :indifferent
|
44
|
+
<<~RB.chomp
|
45
|
+
next_node =
|
46
|
+
if #{node_var}.key?(#{str}); #{node_var}[#{str}]
|
47
|
+
elsif #{node_var}.key?(#{sym}); #{node_var}[#{sym}]
|
48
|
+
elsif #{node_var}.key?(#{str}); #{node_var}[#{str}] # (string twice ok / predictable)
|
49
|
+
else :__missing__
|
50
|
+
end
|
51
|
+
RB
|
52
|
+
end
|
53
|
+
|
54
|
+
miss_action = build_miss_action(policy, mode, map_depth, preview_array, key: key, path_key: path_key)
|
55
|
+
|
56
|
+
<<~RB.chomp
|
57
|
+
raise TypeError, "Expected Hash at '#{path_key}' (#{mode})" unless #{node_var}.is_a?(Hash)
|
58
|
+
#{fetch}
|
59
|
+
if next_node == :__missing__
|
60
|
+
#{miss_action}
|
61
|
+
end
|
62
|
+
#{node_var} = next_node
|
63
|
+
RB
|
64
|
+
end
|
65
|
+
|
66
|
+
def array_guard_code(node_var:, mode:, policy:, path_key:, map_depth:)
|
67
|
+
miss_action = build_array_miss_action(policy, mode, map_depth, path_key)
|
68
|
+
<<~RB.chomp
|
69
|
+
if #{node_var}.nil?
|
70
|
+
#{miss_action}
|
71
|
+
end
|
72
|
+
unless #{node_var}.is_a?(Array)
|
73
|
+
raise TypeError, "Expected Array at '#{path_key}' (#{mode}); got \#{#{node_var}.class}"
|
74
|
+
end
|
75
|
+
RB
|
76
|
+
end
|
77
|
+
|
78
|
+
# ---------- missing behaviors ----------
|
79
|
+
def build_miss_action(policy, mode, map_depth, preview_array, key:, path_key:)
|
80
|
+
case policy
|
81
|
+
when :nil
|
82
|
+
if mode == :ravel
|
83
|
+
base = "out << nil"
|
84
|
+
cont = map_depth.positive? ? "next" : "return out"
|
85
|
+
"#{base}\n#{cont}"
|
86
|
+
elsif mode == :each_indexed
|
87
|
+
if map_depth.positive?
|
88
|
+
<<~RB.chomp
|
89
|
+
if block
|
90
|
+
block.call(nil, idx_vec.dup)
|
91
|
+
next
|
92
|
+
else
|
93
|
+
out << [nil, idx_vec.dup]
|
94
|
+
next
|
95
|
+
end
|
96
|
+
RB
|
97
|
+
else
|
98
|
+
<<~RB.chomp
|
99
|
+
if block
|
100
|
+
block.call(nil, idx_vec.dup)
|
101
|
+
return nil
|
102
|
+
else
|
103
|
+
out << [nil, idx_vec.dup]
|
104
|
+
return out
|
105
|
+
end
|
106
|
+
RB
|
107
|
+
end
|
108
|
+
else # :materialize, :read
|
109
|
+
# Important: for :materialize this is ALWAYS nil (never [])
|
110
|
+
return_val = 'nil'
|
111
|
+
map_depth.positive? ? "next #{return_val}" : "return #{return_val}"
|
112
|
+
end
|
113
|
+
when :skip
|
114
|
+
if mode == :materialize
|
115
|
+
return_val = preview_array ? '[]' : 'nil'
|
116
|
+
map_depth.positive? ? "next #{return_val}" : "return #{return_val}"
|
117
|
+
else
|
118
|
+
map_depth.positive? ? "next" : (mode == :each_indexed ? "if block; return nil; else; return out; end" : "return out")
|
119
|
+
end
|
120
|
+
else # :error
|
121
|
+
%(raise KeyError, "Missing key '#{key}' at '#{path_key}' (#{mode})")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def build_array_miss_action(policy, mode, map_depth, path_key)
|
126
|
+
case policy
|
127
|
+
when :nil
|
128
|
+
if mode == :materialize
|
129
|
+
map_depth.positive? ? "next nil" : "return nil"
|
130
|
+
elsif mode == :each_indexed
|
131
|
+
if map_depth.positive?
|
132
|
+
<<~RB.chomp
|
133
|
+
if block
|
134
|
+
block.call(nil, idx_vec.dup)
|
135
|
+
next
|
136
|
+
else
|
137
|
+
out << [nil, idx_vec.dup]
|
138
|
+
next
|
139
|
+
end
|
140
|
+
RB
|
141
|
+
else
|
142
|
+
<<~RB.chomp
|
143
|
+
if block
|
144
|
+
block.call(nil, idx_vec.dup)
|
145
|
+
return nil
|
146
|
+
else
|
147
|
+
out << [nil, idx_vec.dup]
|
148
|
+
return out
|
149
|
+
end
|
150
|
+
RB
|
151
|
+
end
|
152
|
+
else # :ravel / others
|
153
|
+
base = "out << nil"
|
154
|
+
cont = map_depth.positive? ? "next" : "return out"
|
155
|
+
"#{base}\n#{cont}"
|
156
|
+
end
|
157
|
+
when :skip
|
158
|
+
if mode == :materialize
|
159
|
+
map_depth.positive? ? "next []" : "return []"
|
160
|
+
elsif mode == :each_indexed
|
161
|
+
map_depth.positive? ? "next" : "if block; return nil; else; return out; end"
|
162
|
+
else # :ravel
|
163
|
+
map_depth.positive? ? "next" : "return out"
|
164
|
+
end
|
165
|
+
else
|
166
|
+
%(raise TypeError, "Missing array at '#{path_key}' (#{mode})")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Kumi::Core::Compiler::AccessEmit
|
3
|
+
module EachIndexed
|
4
|
+
extend Base
|
5
|
+
module_function
|
6
|
+
def build(plan)
|
7
|
+
policy = plan.on_missing || :error
|
8
|
+
key_policy = plan.key_policy || :indifferent
|
9
|
+
path_key = plan.path
|
10
|
+
segs = segment_ops(plan.operations)
|
11
|
+
|
12
|
+
code = +"lambda do |data, &block|\n"
|
13
|
+
code << " out = []\n"
|
14
|
+
code << " node0 = data\n"
|
15
|
+
code << " idx_vec = []\n"
|
16
|
+
nodev, depth, loop_depth = "node0", 0, 0
|
17
|
+
|
18
|
+
segs.each do |seg|
|
19
|
+
if seg == :array
|
20
|
+
code << " #{array_guard_code(node_var: nodev, mode: :each_indexed, policy: policy, path_key: path_key, map_depth: loop_depth)}\n"
|
21
|
+
code << " ary#{loop_depth} = #{nodev}\n"
|
22
|
+
code << " len#{loop_depth} = ary#{loop_depth}.length\n"
|
23
|
+
code << " i#{loop_depth} = -1\n"
|
24
|
+
code << " while (i#{loop_depth} += 1) < len#{loop_depth}\n"
|
25
|
+
code << " idx_vec[#{loop_depth}] = i#{loop_depth}\n"
|
26
|
+
child = "node#{depth + 1}"
|
27
|
+
code << " #{child} = ary#{loop_depth}[i#{loop_depth}]\n"
|
28
|
+
nodev = child; depth += 1; loop_depth += 1
|
29
|
+
else
|
30
|
+
seg.each do |(_, key, preview)|
|
31
|
+
code << fetch_hash_code(node_var: nodev, key: key, key_policy: key_policy,
|
32
|
+
preview_array: preview, mode: :each_indexed, policy: policy,
|
33
|
+
path_key: path_key, map_depth: loop_depth)
|
34
|
+
code << "\n"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
code << " if block\n"
|
40
|
+
code << " block.call(#{nodev}, idx_vec.dup)\n"
|
41
|
+
code << " else\n"
|
42
|
+
code << " out << [#{nodev}, idx_vec.dup]\n"
|
43
|
+
code << " end\n"
|
44
|
+
|
45
|
+
while loop_depth.positive?
|
46
|
+
code << " end\n"
|
47
|
+
loop_depth -= 1
|
48
|
+
nodev = "node#{depth - 1}"
|
49
|
+
depth -= 1
|
50
|
+
end
|
51
|
+
|
52
|
+
code << " block ? nil : out\nend\n"
|
53
|
+
code
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Kumi::Core::Compiler::AccessEmit
|
3
|
+
module Materialize
|
4
|
+
extend Base
|
5
|
+
module_function
|
6
|
+
def build(plan)
|
7
|
+
policy = plan.on_missing || :error
|
8
|
+
key_policy = plan.key_policy || :indifferent
|
9
|
+
path_key = plan.path
|
10
|
+
segs = segment_ops(plan.operations)
|
11
|
+
|
12
|
+
code = +"lambda do |data|\n"
|
13
|
+
nodev, depth, map_depth = "node0", 0, 0
|
14
|
+
code << " #{nodev} = data\n"
|
15
|
+
|
16
|
+
segs.each do |seg|
|
17
|
+
if seg == :array
|
18
|
+
code << " #{array_guard_code(node_var: nodev, mode: :materialize, policy: policy, path_key: path_key, map_depth: map_depth)}\n"
|
19
|
+
child = "node#{depth + 1}"
|
20
|
+
code << " #{nodev} = #{nodev}.map do |__e#{depth}|\n"
|
21
|
+
code << " #{child} = __e#{depth}\n"
|
22
|
+
nodev = child; depth += 1; map_depth += 1
|
23
|
+
else
|
24
|
+
seg.each do |(_, key, preview)|
|
25
|
+
code << " "
|
26
|
+
code << fetch_hash_code(node_var: nodev, key: key, key_policy: key_policy,
|
27
|
+
preview_array: preview, mode: :materialize, policy: policy,
|
28
|
+
path_key: path_key, map_depth: map_depth)
|
29
|
+
code << "\n"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
while map_depth.positive?
|
35
|
+
code << " " * map_depth + "#{nodev}\n"
|
36
|
+
code << " " * (map_depth - 1) + "end\n"
|
37
|
+
nodev = "node#{depth - 1}"
|
38
|
+
depth -= 1
|
39
|
+
map_depth -= 1
|
40
|
+
end
|
41
|
+
code << " #{nodev}\nend\n"
|
42
|
+
code
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Kumi::Core::Compiler::AccessEmit
|
3
|
+
module Ravel
|
4
|
+
extend Base
|
5
|
+
module_function
|
6
|
+
def build(plan)
|
7
|
+
policy = plan.on_missing || :error
|
8
|
+
key_policy = plan.key_policy || :indifferent
|
9
|
+
path_key = plan.path
|
10
|
+
segs = segment_ops(plan.operations)
|
11
|
+
|
12
|
+
code = +"lambda do |data|\n"
|
13
|
+
code << " out = []\n"
|
14
|
+
nodev, depth, loop_depth = "node0", 0, 0
|
15
|
+
code << " #{nodev} = data\n"
|
16
|
+
|
17
|
+
segs.each do |seg|
|
18
|
+
if seg == :array
|
19
|
+
code << " #{array_guard_code(node_var: nodev, mode: :ravel, policy: policy, path_key: path_key, map_depth: loop_depth)}\n"
|
20
|
+
code << " ary#{loop_depth} = #{nodev}\n"
|
21
|
+
code << " len#{loop_depth} = ary#{loop_depth}.length\n"
|
22
|
+
code << " i#{loop_depth} = -1\n"
|
23
|
+
code << " while (i#{loop_depth} += 1) < len#{loop_depth}\n"
|
24
|
+
child = "node#{depth + 1}"
|
25
|
+
code << " #{child} = ary#{loop_depth}[i#{loop_depth}]\n"
|
26
|
+
nodev = child; depth += 1; loop_depth += 1
|
27
|
+
else
|
28
|
+
seg.each do |(_, key, preview)|
|
29
|
+
code << " "
|
30
|
+
code << fetch_hash_code(node_var: nodev, key: key, key_policy: key_policy,
|
31
|
+
preview_array: preview, mode: :ravel, policy: policy,
|
32
|
+
path_key: path_key, map_depth: loop_depth)
|
33
|
+
code << "\n"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
code << " out << #{nodev}\n"
|
39
|
+
while loop_depth.positive?
|
40
|
+
code << " end\n"
|
41
|
+
loop_depth -= 1
|
42
|
+
nodev = "node#{depth - 1}"
|
43
|
+
depth -= 1
|
44
|
+
end
|
45
|
+
|
46
|
+
code << " out\nend\n"
|
47
|
+
code
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Kumi::Core::Compiler::AccessEmit
|
3
|
+
module Read
|
4
|
+
extend Base
|
5
|
+
module_function
|
6
|
+
def build(plan)
|
7
|
+
policy = plan.on_missing || :error
|
8
|
+
key_policy = plan.key_policy || :indifferent
|
9
|
+
path_key = plan.path
|
10
|
+
ops = plan.operations
|
11
|
+
|
12
|
+
body = ops.map do |op|
|
13
|
+
case op[:type]
|
14
|
+
when :enter_hash
|
15
|
+
fetch_hash_code(node_var: "node", key: op[:key], key_policy: key_policy,
|
16
|
+
preview_array: false, mode: :read, policy: policy,
|
17
|
+
path_key: path_key, map_depth: 0)
|
18
|
+
when :enter_array
|
19
|
+
%(raise TypeError, "Array encountered in :read accessor at '#{path_key}'")
|
20
|
+
end
|
21
|
+
end.join("\n ")
|
22
|
+
|
23
|
+
<<~RUBY
|
24
|
+
lambda do |data|
|
25
|
+
node = data
|
26
|
+
#{body}
|
27
|
+
node
|
28
|
+
end
|
29
|
+
RUBY
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|