kumi 0.0.15 → 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.
@@ -42,44 +42,40 @@ module Kumi
42
42
  access_plans = state.fetch(:access_plans)
43
43
  input_metadata = state[:input_metadata] || {}
44
44
  dependents = state[:dependents] || {}
45
- ir_dependencies = state[:ir_dependencies] || {} # <-- from IR dependency pass
46
- name_index = state[:name_index] || {} # <-- from IR dependency pass
45
+ schedules = state[:ir_execution_schedules] || {}
46
+
47
47
  accessors = Dev::Profiler.phase("compiler.access_builder") do
48
48
  Kumi::Core::Compiler::AccessBuilder.build(access_plans)
49
49
  end
50
50
 
51
51
  access_meta = {}
52
- field_to_plan_ids = Hash.new { |h, k| h[k] = [] }
53
52
 
54
- access_plans.each_value do |plans|
55
- plans.each do |p|
56
- access_meta[p.accessor_key] = { mode: p.mode, scope: p.scope }
53
+ # access_plans.each_value do |plans|
54
+ # plans.each do |p|
55
+ # access_meta[p.accessor_key] = { mode: p.mode, scope: p.scope }
57
56
 
58
- # Build precise field -> plan_ids mapping for invalidation
59
- root_field = p.accessor_key.to_s.split(":").first.split(".").first.to_sym
60
- field_to_plan_ids[root_field] << p.accessor_key
61
- end
62
- end
57
+ # # Build precise field -> plan_ids mapping for invalidation
58
+ # root_field = p.accessor_key.to_s.split(":").first.split(".").first.to_sym
59
+ # field_to_plan_ids[root_field] << p.accessor_key
60
+ # end
61
+ # end
63
62
 
64
63
  # Use the internal functions hash that VM expects
65
64
  registry ||= Kumi::Registry.functions
66
65
  new(ir: ir, accessors: accessors, access_meta: access_meta, registry: registry,
67
- input_metadata: input_metadata, field_to_plan_ids: field_to_plan_ids, dependents: dependents,
68
- ir_dependencies: ir_dependencies, name_index: name_index, schema_name: schema_name)
66
+ input_metadata: input_metadata, dependents: dependents,
67
+ schema_name: schema_name, schedules: schedules)
69
68
  end
70
69
 
71
- def initialize(ir:, accessors:, access_meta:, registry:, input_metadata:, field_to_plan_ids: {}, dependents: {}, ir_dependencies: {},
72
- name_index: {}, schema_name: nil)
70
+ def initialize(ir:, accessors:, access_meta:, registry:, input_metadata:, dependents: {}, schedules: {}, schema_name: nil)
73
71
  @ir = ir.freeze
74
72
  @acc = accessors.freeze
75
73
  @meta = access_meta.freeze
76
74
  @reg = registry
77
75
  @input_metadata = input_metadata.freeze
78
- @field_to_plan_ids = field_to_plan_ids.freeze
79
76
  @dependents = dependents.freeze
80
- @ir_dependencies = ir_dependencies.freeze # decl -> [stored_bindings_it_references]
81
- @name_index = name_index.freeze # store_name -> producing decl
82
77
  @schema_name = schema_name
78
+ @schedules = schedules
83
79
  @decl = @ir.decls.map { |d| [d.name, d] }.to_h
84
80
  @accessor_cache = {} # Persistent accessor cache across evaluations
85
81
  end
@@ -87,7 +83,7 @@ module Kumi
87
83
  def decl?(name) = @decl.key?(name)
88
84
 
89
85
  def read(input, mode: :ruby)
90
- Run.new(self, input, mode: mode, input_metadata: @input_metadata, dependents: @dependents)
86
+ Run.new(self, input, mode: mode, input_metadata: @input_metadata, dependents: @dependents, declarations: @decl.keys)
91
87
  end
92
88
 
93
89
  # API compatibility for backward compatibility
@@ -102,73 +98,30 @@ module Kumi
102
98
  end
103
99
  end
104
100
 
105
- def eval_decl(name, input, mode: :ruby, declaration_cache: nil)
101
+ def eval_decl(name, input, mode: :ruby, declaration_cache: {})
106
102
  raise Kumi::Core::Errors::RuntimeError, "unknown decl #{name}" unless decl?(name)
107
103
 
104
+ schedule = @schedules[name]
108
105
  # If the caller asked for a specific binding, schedule deps once
109
- decls_to_run = topo_closure_for_target(name)
110
106
 
111
- vm_context = {
112
- input: input,
107
+ runtime = {
113
108
  accessor_cache: @accessor_cache,
114
- declaration_cache: declaration_cache || {}, # run-local cache
115
- decls_to_run: decls_to_run, # <-- explicit schedule
116
- strict_refs: true, # <-- refs must be precomputed
117
- name_index: @name_index, # for error messages, twins, etc.
118
- schema_name: @schema_name
109
+ declaration_cache: declaration_cache, # run-local cache
110
+ schema_name: @schema_name,
111
+ target: name
119
112
  }
120
113
 
121
114
  out = Dev::Profiler.phase("vm.run", target: name) do
122
- Kumi::Core::IR::ExecutionEngine.run(@ir, vm_context, accessors: @acc, registry: @reg).fetch(name)
115
+ Kumi::Core::IR::ExecutionEngine.run(schedule, input: input, runtime: runtime, accessors: @acc, registry: @reg).fetch(name)
123
116
  end
124
117
 
125
118
  mode == :ruby ? unwrap(@decl[name], out) : out
126
119
  end
127
120
 
128
- def clear_field_accessor_cache(field_name)
129
- # Use precise field -> plan_ids mapping for exact invalidation
130
- plan_ids = @field_to_plan_ids[field_name] || []
131
- # Cache keys are [plan_id, input_object_id] arrays
132
- @accessor_cache.delete_if { |(pid, _), _| plan_ids.include?(pid) }
133
- end
134
-
135
121
  def unwrap(_decl, v)
136
122
  v[:k] == :scalar ? v[:v] : v # no grouping needed
137
123
  end
138
124
 
139
- def topo_closure_for_target(store_name)
140
- target_decl = @name_index[store_name]
141
- raise "Unknown target store #{store_name}" unless target_decl
142
-
143
- # DFS collect closure of decl names using pre-computed IR-level dependencies
144
- seen = {}
145
- order = []
146
- visiting = {}
147
-
148
- visit = lambda do |dname|
149
- return if seen[dname]
150
- raise "Cycle detected in DAG scheduler: #{dname}. Mutual recursion should be caught earlier by UnsatDetector." if visiting[dname]
151
-
152
- visiting[dname] = true
153
-
154
- # Visit declarations that produce the bindings this decl references
155
- Array(@ir_dependencies[dname]).each do |ref_binding|
156
- # Find which declaration produces this binding
157
- producer = @name_index[ref_binding]
158
- visit.call(producer.name) if producer
159
- end
160
-
161
- visiting.delete(dname)
162
- seen[dname] = true
163
- order << dname
164
- end
165
-
166
- visit.call(target_decl.name)
167
-
168
- # 'order' is postorder; it already yields producers before consumers
169
- order.map { |dname| @decl[dname] }
170
- end
171
-
172
125
  private
173
126
 
174
127
  def validate_keys(keys)
@@ -178,111 +131,5 @@ module Kumi
178
131
  raise Kumi::Errors::RuntimeError, "No binding named #{unknown_keys.first}"
179
132
  end
180
133
  end
181
-
182
- class Run
183
- def initialize(program, input, mode:, input_metadata:, dependents:)
184
- @program = program
185
- @input = input
186
- @mode = mode
187
- @input_metadata = input_metadata
188
- @dependents = dependents
189
- @cache = {}
190
- end
191
-
192
- def get(name)
193
- unless @cache.key?(name)
194
- # Get the result in VM internal format
195
- vm_result = @program.eval_decl(name, @input, mode: :wrapped, declaration_cache: @cache)
196
- # Store VM format for cross-VM caching
197
- @cache[name] = vm_result
198
- end
199
-
200
- # Convert to requested format when returning
201
- vm_result = @cache[name]
202
- @mode == :wrapped ? vm_result : @program.unwrap(nil, vm_result)
203
- end
204
-
205
- def [](name)
206
- get(name)
207
- end
208
-
209
- def slice(*keys)
210
- return {} if keys.empty?
211
-
212
- keys.each_with_object({}) { |key, result| result[key] = get(key) }
213
- end
214
-
215
- def compiled_schema
216
- @program
217
- end
218
-
219
- def method_missing(sym, *args, **kwargs, &)
220
- return super unless args.empty? && kwargs.empty? && @program.decl?(sym)
221
-
222
- get(sym)
223
- end
224
-
225
- def respond_to_missing?(sym, priv = false)
226
- @program.decl?(sym) || super
227
- end
228
-
229
- def update(**changes)
230
- affected_declarations = Set.new
231
-
232
- changes.each do |field, value|
233
- # Validate field exists
234
- raise ArgumentError, "unknown input field: #{field}" unless input_field_exists?(field)
235
-
236
- # Validate domain constraints
237
- validate_domain_constraint(field, value)
238
-
239
- # Update the input data IN-PLACE to preserve object_id for cache keys
240
- @input[field] = value
241
-
242
- # Clear accessor cache for this specific field
243
- @program.clear_field_accessor_cache(field)
244
-
245
- # Collect all declarations that depend on this input field
246
- field_dependents = @dependents[field] || []
247
- affected_declarations.merge(field_dependents)
248
- end
249
-
250
- # Only clear cache for affected declarations, not all declarations
251
- affected_declarations.each { |decl| @cache.delete(decl) }
252
-
253
- self
254
- end
255
-
256
- private
257
-
258
- def input_field_exists?(field)
259
- # Check if field is declared in input block
260
- @input_metadata.key?(field) || @input.key?(field)
261
- end
262
-
263
- def validate_domain_constraint(field, value)
264
- field_meta = @input_metadata[field]
265
- return unless field_meta&.dig(:domain)
266
-
267
- domain = field_meta[:domain]
268
- return unless violates_domain?(value, domain)
269
-
270
- raise ArgumentError, "value #{value} is not in domain #{domain}"
271
- end
272
-
273
- def violates_domain?(value, domain)
274
- case domain
275
- when Range
276
- !domain.include?(value)
277
- when Array
278
- !domain.include?(value)
279
- when Proc
280
- # For Proc domains, we can't statically analyze
281
- false
282
- else
283
- false
284
- end
285
- end
286
- end
287
134
  end
288
135
  end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Runtime
5
+ class Run
6
+ def initialize(program, input, mode:, input_metadata:, dependents:, declarations:)
7
+ @program = program
8
+ @input = input
9
+ @mode = mode
10
+ @input_metadata = input_metadata
11
+ @declarations = declarations
12
+ @dependents = dependents
13
+ @cache = {}
14
+ end
15
+
16
+ def key?(name)
17
+ @declarations.include? name
18
+ end
19
+
20
+ def get(name)
21
+ unless @cache.key?(name)
22
+ # Get the result in VM internal format
23
+ vm_result = @program.eval_decl(name, @input, mode: :wrapped, declaration_cache: @cache)
24
+ # Store VM format for cross-VM caching
25
+ @cache[name] = vm_result
26
+ end
27
+
28
+ # Convert to requested format when returning
29
+ vm_result = @cache[name]
30
+ @mode == :wrapped ? vm_result : @program.unwrap(nil, vm_result)
31
+ end
32
+
33
+ def to_h
34
+ slice(*@declarations)
35
+ end
36
+
37
+ def [](name)
38
+ get(name)
39
+ end
40
+
41
+ def slice(*keys)
42
+ return {} if keys.empty?
43
+
44
+ keys.each_with_object({}) { |key, result| result[key] = get(key) }
45
+ end
46
+
47
+ def compiled_schema
48
+ @program
49
+ end
50
+
51
+ def method_missing(sym, *args, **kwargs, &)
52
+ return super unless args.empty? && kwargs.empty? && key?(sym)
53
+
54
+ get(sym)
55
+ end
56
+
57
+ def respond_to_missing?(sym, priv = false)
58
+ key?(sym) || super
59
+ end
60
+
61
+ def update(**changes)
62
+ affected_declarations = Set.new
63
+
64
+ changes.each do |field, value|
65
+ raise ArgumentError, "unknown input field: #{field}" unless input_field_exists?(field)
66
+
67
+ validate_domain_constraint(field, value)
68
+
69
+ @input[field] = value
70
+ if (deps = @dependents[field])
71
+ deps.each { |d| @cache.delete(d) }
72
+ end
73
+ end
74
+
75
+ self
76
+ end
77
+
78
+ private
79
+
80
+ def input_field_exists?(field)
81
+ # Check if field is declared in input block
82
+ @input_metadata.key?(field)
83
+ end
84
+
85
+ def validate_domain_constraint(field, value)
86
+ field_meta = @input_metadata[field]
87
+ return unless field_meta&.dig(:domain)
88
+
89
+ domain = field_meta[:domain]
90
+ return unless violates_domain?(value, domain)
91
+
92
+ raise ArgumentError, "value #{value} is not in domain #{domain}"
93
+ end
94
+
95
+ def violates_domain?(value, domain)
96
+ case domain
97
+ when Range, Array
98
+ !domain.include?(value)
99
+ else
100
+ false
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
data/lib/kumi/schema.rb CHANGED
@@ -4,18 +4,12 @@ require "ostruct"
4
4
 
5
5
  module Kumi
6
6
  module Schema
7
- attr_reader :__syntax_tree__, :__analyzer_result__, :__compiled_schema__
8
-
9
- Inspector = Struct.new(:syntax_tree, :analyzer_result, :compiled_schema) do
10
- def inspect
11
- "#<#{self.class} syntax_tree: #{syntax_tree.inspect}, analyzer_result: #{analyzer_result.inspect}, compiled_schema: #{compiled_schema.inspect}>"
12
- end
13
- end
7
+ attr_reader :__syntax_tree__, :__analyzer_result__, :__executable__
14
8
 
15
9
  def from(context)
16
10
  # VERY IMPORTANT: This method is overriden on specs in order to use dual mode.
17
11
 
18
- raise("No schema defined") unless @__compiled_schema__
12
+ raise("No schema defined") unless @__executable__
19
13
 
20
14
  # Validate input types and domain constraints
21
15
  input_meta = @__analyzer_result__.state[:input_metadata] || {}
@@ -23,11 +17,12 @@ module Kumi
23
17
 
24
18
  raise Errors::InputValidationError, violations unless violations.empty?
25
19
 
26
- @__compiled_schema__.read(context, mode: :ruby)
20
+ # TODO: Lazily start a Runner
21
+ @__executable__.read(context, mode: :ruby)
27
22
  end
28
23
 
29
24
  def explain(context, *keys)
30
- raise("No schema defined") unless @__compiled_schema__
25
+ raise("No schema defined") unless @__executable__
31
26
 
32
27
  # Validate input types and domain constraints
33
28
  input_meta = @__analyzer_result__.state[:input_metadata] || {}
@@ -58,11 +53,11 @@ module Kumi
58
53
  @__analyzer_result__ = Dev::Profiler.phase("analyzer") do
59
54
  Analyzer.analyze!(@__syntax_tree__).freeze
60
55
  end
61
- @__compiled_schema__ = Dev::Profiler.phase("compiler") do
62
- Compiler.compile(@__syntax_tree__, analyzer: @__analyzer_result__, schema_name: self.name).freeze
56
+ @__executable__ = Dev::Profiler.phase("compiler") do
57
+ Compiler.compile(@__syntax_tree__, analyzer: @__analyzer_result__, schema_name: name).freeze
63
58
  end
64
59
 
65
- Inspector.new(@__syntax_tree__, @__analyzer_result__, @__compiled_schema__)
60
+ nil
66
61
  end
67
62
 
68
63
  def schema_metadata
data/lib/kumi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kumi
4
- VERSION = "0.0.15"
4
+ VERSION = "0.0.16"
5
5
  end
data/lib/kumi.rb CHANGED
@@ -11,8 +11,9 @@ loader.inflector.inflect(
11
11
  "ir_dependency_pass" => "IRDependencyPass",
12
12
  "vm" => "VM",
13
13
  "ir" => "IR",
14
- 'ir_dump' => 'IRDump',
15
- 'ir_render' => 'IRRender',
14
+ "ir_dump" => "IRDump",
15
+ "ir_render" => "IRRender",
16
+ "ir_execution_schedule_pass" => "IRExecutionSchedulePass"
16
17
  )
17
18
  loader.setup
18
19
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kumi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.15
4
+ version: 0.0.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - André Muta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-21 00:00:00.000000000 Z
11
+ date: 2025-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk
@@ -94,6 +94,7 @@ files:
94
94
  - lib/kumi/core/analyzer/passes/input_access_planner_pass.rb
95
95
  - lib/kumi/core/analyzer/passes/input_collector.rb
96
96
  - lib/kumi/core/analyzer/passes/ir_dependency_pass.rb
97
+ - lib/kumi/core/analyzer/passes/ir_execution_schedule_pass.rb
97
98
  - lib/kumi/core/analyzer/passes/join_reduce_planning_pass.rb
98
99
  - lib/kumi/core/analyzer/passes/load_input_cse.rb
99
100
  - lib/kumi/core/analyzer/passes/lower_to_ir_pass.rb
@@ -113,6 +114,12 @@ files:
113
114
  - lib/kumi/core/analyzer/structs/input_meta.rb
114
115
  - lib/kumi/core/atom_unsat_solver.rb
115
116
  - lib/kumi/core/compiler/access_builder.rb
117
+ - lib/kumi/core/compiler/access_codegen.rb
118
+ - lib/kumi/core/compiler/access_emit/base.rb
119
+ - lib/kumi/core/compiler/access_emit/each_indexed.rb
120
+ - lib/kumi/core/compiler/access_emit/materialize.rb
121
+ - lib/kumi/core/compiler/access_emit/ravel.rb
122
+ - lib/kumi/core/compiler/access_emit/read.rb
116
123
  - lib/kumi/core/compiler/access_planner.rb
117
124
  - lib/kumi/core/compiler/accessors/base.rb
118
125
  - lib/kumi/core/compiler/accessors/each_indexed_accessor.rb
@@ -207,6 +214,7 @@ files:
207
214
  - lib/kumi/kernels/ruby/vector_struct.rb
208
215
  - lib/kumi/registry.rb
209
216
  - lib/kumi/runtime/executable.rb
217
+ - lib/kumi/runtime/run.rb
210
218
  - lib/kumi/schema.rb
211
219
  - lib/kumi/schema_metadata.rb
212
220
  - lib/kumi/support/diff.rb