kumi 0.0.12 → 0.0.13

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dc6d1a550925563fbffcc0f4f96e6609d70734dfff031d9e6e2bfd405ecf45f8
4
- data.tar.gz: '090999f0550fcd66a79cb3f683b8f27680b5921166267bbdf57dd8c593d3c6fd'
3
+ metadata.gz: 7af4bdb11ef5da5a25b799cef8387752636a8224634c70656efc203b9d923185
4
+ data.tar.gz: 4e604513e42dcb8754fab54ead4140d5ca2d8443ae4d0461e62dc3b48bb925d4
5
5
  SHA512:
6
- metadata.gz: 9c18d000d924168fbef1abcc6a19f59f18af8a82fae6d4d7bb14e475ce5b34032f031a14ae3d20faa8deadf96f7e1da9bb2190fca0251da74d629c38f637c139
7
- data.tar.gz: 37f124928afe975bb7138c2856093849b6c15fb23694d77a7a0d1628b2c67f7e262576cb734a522105b8f83253fe112f6cbacc00895ad2daba4212eb7a337384
6
+ metadata.gz: dcbd93268081ff8f0bcf112101603b180932196b1d6fcb59aae2f363d6bbfe4e9ea07cf3cbe811290222dcd15cdf132ea04f409bea34f81599412f729211a5fa
7
+ data.tar.gz: 3e0c2e91609e742c2852457b2dd61a114a03b988ae983e0cfb23c9bbf46bc5c6c1714636961ed46b4b9ae924c6a77a79dc0aab9013cadbb73e49d5374f8b8dbc
data/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.0.13] – 2025-08-14
4
+ ### Added
5
+ - Runtime performance optimizations for interpreter execution
6
+ - Input load deduplication to cache loaded input values and avoid redundant operations
7
+ - Constant folding optimization to evaluate literal expressions during compilation
8
+ - Accessor memoization with proper cache isolation per input context
9
+ - Selective cache invalidation for incremental updates
10
+
11
+ ### Fixed
12
+ - Cache isolation between different input contexts preventing cross-context pollution
13
+ - Cascade mutual exclusion tests now pass correctly with proper trait evaluation
14
+ - Incremental update performance with targeted cache clearing
15
+
1
16
  ## [0.0.12] – 2025-08-14
2
17
  ### Added
3
18
  - Hash objects input declarations with `hash :field do ... end` syntax
@@ -379,6 +379,7 @@ module Kumi
379
379
  when Syntax::InputReference
380
380
  plan_id = pick_plan_id_for_input([expr.name], access_plans,
381
381
  scope_plan: scope_plan, need_indices: need_indices)
382
+
382
383
  plans = access_plans.fetch(expr.name.to_s, [])
383
384
  selected = plans.find { |p| p.accessor_key == plan_id }
384
385
  scope = selected ? selected.scope : []
@@ -430,6 +431,13 @@ module Kumi
430
431
  when Syntax::CallExpression
431
432
  entry = Kumi::Registry.entry(expr.fn_name)
432
433
 
434
+ # Constant folding optimization: evaluate expressions with all literal arguments
435
+ if can_constant_fold?(expr, entry)
436
+ folded_value = constant_fold(expr, entry)
437
+ ops << Kumi::Core::IR::Ops.Const(folded_value)
438
+ return ops.size - 1
439
+ end
440
+
433
441
  if ENV["DEBUG_LOWER"] && has_nested_reducer?(expr)
434
442
  puts " NESTED_REDUCER_DETECTED in #{expr.fn_name} with req_scope=#{required_scope.inspect}"
435
443
  end
@@ -986,6 +994,31 @@ module Kumi
986
994
  end
987
995
  end
988
996
  end
997
+
998
+ # Constant folding optimization helpers
999
+ def can_constant_fold?(expr, entry)
1000
+ return false unless entry&.fn # Skip if function not found
1001
+ return false if entry.reducer # Skip reducer functions for now
1002
+ return false if expr.args.empty? # Need at least one argument
1003
+
1004
+ # Check if all arguments are literals
1005
+ expr.args.all? { |arg| arg.is_a?(Syntax::Literal) }
1006
+ end
1007
+
1008
+ def constant_fold(expr, entry)
1009
+ literal_values = expr.args.map(&:value)
1010
+
1011
+ begin
1012
+ # Call the function with literal values at compile time
1013
+ entry.fn.call(*literal_values)
1014
+ rescue StandardError => e
1015
+ # If constant folding fails, fall back to runtime evaluation
1016
+ # This shouldn't happen with pure functions, but be defensive
1017
+ puts "Constant folding failed for #{expr.fn_name}: #{e.message}" if ENV["DEBUG_LOWER"]
1018
+ raise "Cannot constant fold #{expr.fn_name}: #{e.message}"
1019
+ end
1020
+ end
1021
+
989
1022
  end
990
1023
  end
991
1024
  end
@@ -42,7 +42,36 @@ module Kumi
42
42
  # - DEBUG_GROUP_ROWS=1 prints grouping decisions during Lift.
43
43
  module ExecutionEngine
44
44
  def self.run(ir_module, ctx, accessors:, registry:)
45
- Interpreter.run(ir_module, ctx, accessors: accessors, registry: registry)
45
+ # Use persistent accessor cache if available, otherwise create temporary one
46
+ if ctx[:accessor_cache]
47
+ # Include input data in cache key to avoid cross-context pollution
48
+ input_key = ctx[:input]&.hash || ctx["input"]&.hash || 0
49
+ memoized_accessors = add_persistent_memoization(accessors, ctx[:accessor_cache], input_key)
50
+ else
51
+ memoized_accessors = add_temporary_memoization(accessors)
52
+ end
53
+
54
+ Interpreter.run(ir_module, ctx, accessors: memoized_accessors, registry: registry)
55
+ end
56
+
57
+ private
58
+
59
+ def self.add_persistent_memoization(accessors, cache, input_key)
60
+ accessors.map do |plan_id, accessor_fn|
61
+ [plan_id, lambda do |input_data|
62
+ cache_key = [plan_id, input_key]
63
+ cache[cache_key] ||= accessor_fn.call(input_data)
64
+ end]
65
+ end.to_h
66
+ end
67
+
68
+ def self.add_temporary_memoization(accessors)
69
+ cache = {}
70
+ accessors.map do |plan_id, accessor_fn|
71
+ [plan_id, lambda do |input_data|
72
+ cache[plan_id] ||= accessor_fn.call(input_data)
73
+ end]
74
+ end.to_h
46
75
  end
47
76
  end
48
77
  end
@@ -62,6 +62,7 @@ module Kumi
62
62
  @reg = registry
63
63
  @input_metadata = input_metadata.freeze
64
64
  @decl = @ir.decls.map { |d| [d.name, d] }.to_h
65
+ @accessor_cache = {} # Persistent accessor cache across evaluations
65
66
  end
66
67
 
67
68
  def decl?(name) = @decl.key?(name)
@@ -85,12 +86,20 @@ module Kumi
85
86
  def eval_decl(name, input, mode: :ruby)
86
87
  raise Kumi::Core::Errors::RuntimeError, "unknown decl #{name}" unless decl?(name)
87
88
 
88
- out = Kumi::Core::IR::ExecutionEngine.run(@ir, { input: input, target: name },
89
+ out = Kumi::Core::IR::ExecutionEngine.run(@ir, { input: input, target: name, accessor_cache: @accessor_cache },
89
90
  accessors: @acc, registry: @reg).fetch(name)
90
91
 
91
92
  mode == :ruby ? unwrap(@decl[name], out) : out
92
93
  end
93
94
 
95
+ def clear_field_accessor_cache(field_name)
96
+ # Clear cache entries for all accessor plans related to this field
97
+ # Cache keys are now [plan_id, input_key] arrays
98
+ @accessor_cache.delete_if { |cache_key, _|
99
+ cache_key.is_a?(Array) && cache_key[0].to_s.start_with?("#{field_name}:")
100
+ }
101
+ end
102
+
94
103
  private
95
104
 
96
105
  def validate_keys(keys)
@@ -153,9 +162,12 @@ module Kumi
153
162
 
154
163
  # Update the input data
155
164
  @input = deep_merge(@input, { field => value })
165
+
166
+ # Clear accessor cache for this specific field
167
+ @program.clear_field_accessor_cache(field)
156
168
  end
157
169
 
158
- # Clear cache after all updates
170
+ # Clear declaration evaluation cache after all updates
159
171
  @cache.clear
160
172
  self
161
173
  end
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.12"
4
+ VERSION = "0.0.13"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kumi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.12
4
+ version: 0.0.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - André Muta