kumi 0.0.15 → 0.0.17

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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/golden/cascade_logic/schema.kumi +3 -1
  4. data/lib/kumi/analyzer.rb +11 -9
  5. data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +0 -81
  6. data/lib/kumi/core/analyzer/passes/ir_dependency_pass.rb +18 -20
  7. data/lib/kumi/core/analyzer/passes/ir_execution_schedule_pass.rb +67 -0
  8. data/lib/kumi/core/analyzer/passes/lower_to_ir_pass.rb +0 -36
  9. data/lib/kumi/core/analyzer/passes/toposorter.rb +1 -39
  10. data/lib/kumi/core/analyzer/passes/unsat_detector.rb +8 -191
  11. data/lib/kumi/core/compiler/access_builder.rb +20 -10
  12. data/lib/kumi/core/compiler/access_codegen.rb +61 -0
  13. data/lib/kumi/core/compiler/access_emit/base.rb +173 -0
  14. data/lib/kumi/core/compiler/access_emit/each_indexed.rb +56 -0
  15. data/lib/kumi/core/compiler/access_emit/materialize.rb +45 -0
  16. data/lib/kumi/core/compiler/access_emit/ravel.rb +50 -0
  17. data/lib/kumi/core/compiler/access_emit/read.rb +32 -0
  18. data/lib/kumi/core/ir/execution_engine/interpreter.rb +36 -181
  19. data/lib/kumi/core/ir/execution_engine/values.rb +8 -8
  20. data/lib/kumi/core/ir/execution_engine.rb +3 -19
  21. data/lib/kumi/dev/parse.rb +12 -12
  22. data/lib/kumi/runtime/executable.rb +22 -175
  23. data/lib/kumi/runtime/run.rb +105 -0
  24. data/lib/kumi/schema.rb +8 -13
  25. data/lib/kumi/version.rb +1 -1
  26. data/lib/kumi.rb +3 -2
  27. metadata +10 -25
  28. data/BACKLOG.md +0 -34
  29. data/config/functions.yaml +0 -352
  30. data/docs/functions/analyzer_integration.md +0 -199
  31. data/docs/functions/signatures.md +0 -171
  32. data/examples/hash_objects_demo.rb +0 -138
  33. data/lib/kumi/core/analyzer/passes/function_signature_pass.rb +0 -199
  34. data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +0 -48
  35. data/lib/kumi/core/functions/dimension.rb +0 -98
  36. data/lib/kumi/core/functions/dtypes.rb +0 -20
  37. data/lib/kumi/core/functions/errors.rb +0 -11
  38. data/lib/kumi/core/functions/kernel_adapter.rb +0 -45
  39. data/lib/kumi/core/functions/loader.rb +0 -119
  40. data/lib/kumi/core/functions/registry_v2.rb +0 -68
  41. data/lib/kumi/core/functions/shape.rb +0 -70
  42. data/lib/kumi/core/functions/signature.rb +0 -122
  43. data/lib/kumi/core/functions/signature_parser.rb +0 -86
  44. data/lib/kumi/core/functions/signature_resolver.rb +0 -272
  45. data/lib/kumi/kernels/ruby/aggregate_core.rb +0 -105
  46. data/lib/kumi/kernels/ruby/datetime_scalar.rb +0 -21
  47. data/lib/kumi/kernels/ruby/mask_scalar.rb +0 -15
  48. data/lib/kumi/kernels/ruby/scalar_core.rb +0 -63
  49. data/lib/kumi/kernels/ruby/string_scalar.rb +0 -19
  50. data/lib/kumi/kernels/ruby/vector_struct.rb +0 -39
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5405d7d0612a81e5154bd1d452fdfc150691b022137fc0ee132c47ede1a58e2e
4
- data.tar.gz: '093cf7a6d305c02f92de600b06f62be39f8af90d798a0f93ed3ef59f539ada9b'
3
+ metadata.gz: 3ffbd312f8f3706fb9accbec91a07cc41af173f0d0fcf2a308c2730e730d08ff
4
+ data.tar.gz: 75d5d08b1bb6c9d2aaa4bd175cc3be59a7074e631e2093bf33f5d499a4d7ff43
5
5
  SHA512:
6
- metadata.gz: b3ea711bf465e0c11cc95fabb3809dd632ebbfcc8c36297b161fb1f179fffdda5df1e5c033968e837dd8ed3f983639416de08bd371f30be9f2cefd5543efe1ff
7
- data.tar.gz: 0a63fe824fb604639b4efb9cfc2ce24a93429110adc75cfd3edc905c58b3501273ad07a3cf66415eabce4e099c5fd4d202bd2c4064875d7a73e0c2a26f0689cb
6
+ metadata.gz: 3634f946464d08da52743531023a4edca7a6780f010689109abb478679b4f89e8c249181cfc9e5d484c9f5dfeafa8727d582d95d6bc9798ff0df90fe2c2ae2ca
7
+ data.tar.gz: dcada1e031b7bc1114297ea9704251bcc4ab009c42a1f7d282fc926f7f59c963812a5850f4b274846d8c267be35b61bbae29d4604e031d74e50da9887159750a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.0.17] – 2025-09-03
4
+
5
+ ### Removed
6
+ - Reverted experimental function registry v2 implementation
7
+ - Cleaned up unused analyzer passes and simplified unsat detector logic
8
+
9
+ ## [0.0.16] – 2025-08-22
10
+
11
+ ### Performance
12
+ - Input accessor code generation replaces nested lambda chains with compiled Ruby methods
13
+ - Fix cache handling in Runtime - it was being recreated on updates
14
+ - Add early shortcut for Analyzer Passes.
15
+
3
16
  ## [0.0.15] – 2025-08-21
4
17
  ### Added
5
18
  - (DX) Schema-aware VM profiling with multi-schema performance analysis
@@ -6,9 +6,11 @@ schema do
6
6
 
7
7
  trait :x_positive, input.x > 0
8
8
  trait :y_positive, input.y > 0
9
+
10
+ trait :both_positive, y_positive & x_positive
9
11
 
10
12
  value :status do
11
- on x_positive, y_positive, "both positive"
13
+ on both_positive, "both positive"
12
14
  on x_positive, "x positive"
13
15
  on y_positive, "y positive"
14
16
  base "neither positive"
data/lib/kumi/analyzer.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  module Kumi
4
4
  module Analyzer
5
5
  Result = Struct.new(:definitions, :dependency_graph, :leaf_map, :topo_order, :decl_types, :state, keyword_init: true)
6
+ ERROR_THRESHOLD_PASS = Core::Analyzer::Passes::LowerToIRPass
6
7
 
7
8
  DEFAULT_PASSES = [
8
9
  Core::Analyzer::Passes::NameIndexer, # 1. Finds all names and checks for duplicates.
@@ -14,15 +15,14 @@ module Kumi
14
15
  Core::Analyzer::Passes::Toposorter, # 8. Creates the final evaluation order, allowing safe cycles.
15
16
  Core::Analyzer::Passes::BroadcastDetector, # 9. Detects which operations should be broadcast over arrays.
16
17
  Core::Analyzer::Passes::TypeInferencerPass, # 10. Infers types for all declarations (uses vectorization metadata).
17
- Core::Analyzer::Passes::TypeConsistencyChecker, # 11. Validates declared vs inferred type consistency.
18
- Core::Analyzer::Passes::FunctionSignaturePass, # 12. Resolves NEP-20 signatures for function calls.
19
- Core::Analyzer::Passes::TypeChecker, # 13. Validates types using inferred information.
20
- Core::Analyzer::Passes::InputAccessPlannerPass, # 14. Plans access strategies for input fields.
21
- Core::Analyzer::Passes::ScopeResolutionPass, # 15. Plans execution scope and lifting needs for declarations.
22
- Core::Analyzer::Passes::JoinReducePlanningPass, # 16. Plans join/reduce operations (Generates IR Structs)
23
- Core::Analyzer::Passes::LowerToIRPass, # 17. Lowers the schema to IR (Generates IR Structs)
24
- Core::Analyzer::Passes::LoadInputCSE, # 18. Eliminates redundant load_input operations
25
- Core::Analyzer::Passes::IRDependencyPass # 19. Extracts IR-level dependencies for VM execution optimization
18
+ Core::Analyzer::Passes::TypeChecker, # 11. Validates types using inferred information.
19
+ Core::Analyzer::Passes::InputAccessPlannerPass, # 12. Plans access strategies for input fields.
20
+ Core::Analyzer::Passes::ScopeResolutionPass, # 13. Plans execution scope and lifting needs for declarations.
21
+ Core::Analyzer::Passes::JoinReducePlanningPass, # 14. Plans join/reduce operations (Generates IR Structs)
22
+ Core::Analyzer::Passes::LowerToIRPass, # 15. Lowers the schema to IR (Generates IR Structs)
23
+ Core::Analyzer::Passes::LoadInputCSE, # 16. Eliminates redundant load_input operations
24
+ Core::Analyzer::Passes::IRDependencyPass, # 17. Extracts IR-level dependencies for VM execution optimization
25
+ Core::Analyzer::Passes::IRExecutionSchedulePass # 18. Builds a precomputed execution schedule.
26
26
  ].freeze
27
27
 
28
28
  def self.analyze!(schema, passes: DEFAULT_PASSES, **opts)
@@ -44,6 +44,8 @@ module Kumi
44
44
  skipping = !!resume_at
45
45
 
46
46
  passes.each_with_index do |pass_class, idx|
47
+ raise handle_analysis_errors(errors) if (ERROR_THRESHOLD_PASS == pass_class) && !errors.empty?
48
+
47
49
  pass_name = pass_class.name.split("::").last
48
50
 
49
51
  if skipping
@@ -656,87 +656,6 @@ module Kumi
656
656
  end
657
657
  end
658
658
 
659
- def extract_dimensional_info_with_context(info, _array_fields, _nested_paths, vectorized_values)
660
- case info[:source]
661
- when :array_field_access, :nested_array_access
662
- # Direct array field access - use the path
663
- source = info[:path]&.first
664
- dimension = info[:path]
665
- [source, dimension]
666
- when :vectorized_declaration
667
- # Reference to another vectorized declaration - look it up
668
- if info[:name] && vectorized_values[info[:name]]
669
- vectorized_info = vectorized_values[info[:name]]
670
- if vectorized_info[:array_source]
671
- # This declaration references an array field, use that source
672
- [vectorized_info[:array_source], [vectorized_info[:array_source]]]
673
- else
674
- # This is a derived vectorized value, try to trace its source
675
- [:vectorized_reference, [:vectorized_reference]]
676
- end
677
- else
678
- [:unknown_declaration, [:unknown_declaration]]
679
- end
680
- else
681
- # Operations and other cases - try to extract from operation args
682
- if info[:operation] && info[:vectorized_args]
683
- # This is an operation result - trace the vectorized arguments
684
- # For now, assume operations inherit the dimension of their first vectorized arg
685
- [:operation_result, [:operation_result]]
686
- else
687
- [:unknown, [:unknown]]
688
- end
689
- end
690
- end
691
-
692
- def extract_dimensional_source(info, _array_fields)
693
- case info[:source]
694
- when :array_field_access
695
- info[:path]&.first
696
- when :nested_array_access
697
- info[:path]&.first
698
- when :vectorized_declaration, :vectorized_value
699
- # Try to extract from the vectorized value info if available
700
- if info[:name] && info.dig(:info, :path)
701
- info[:info][:path].first
702
- else
703
- :vectorized_reference
704
- end
705
- else
706
- # For operations and other cases, try to infer from vectorized args
707
- if info[:vectorized_args]
708
- # This is likely an operation - we should look at its arguments
709
- :operation_result
710
- else
711
- :unknown
712
- end
713
- end
714
- end
715
-
716
- def extract_dimensions(info, _array_fields, _nested_paths)
717
- case info[:source]
718
- when :array_field_access
719
- info[:path]
720
- when :nested_array_access
721
- info[:path]
722
- when :vectorized_declaration, :vectorized_value
723
- # Try to extract from the vectorized value info if available
724
- if info[:name] && info.dig(:info, :path)
725
- info[:info][:path]
726
- else
727
- [:vectorized_reference]
728
- end
729
- else
730
- # For operations, try to infer from the operation context
731
- if info[:vectorized_args]
732
- # This is likely an operation - we should trace its arguments
733
- [:operation_result]
734
- else
735
- [:unknown]
736
- end
737
- end
738
- end
739
-
740
659
  def extract_nested_paths_from_dimensions(dimension, nested_paths)
741
660
  return nil unless dimension.is_a?(Array)
742
661
 
@@ -7,19 +7,19 @@ module Kumi
7
7
  # RESPONSIBILITY: Extract IR-level dependencies for VM execution optimization
8
8
  # DEPENDENCIES: :ir_module from LowerToIRPass
9
9
  # PRODUCES: :ir_dependencies - Hash mapping declaration names to referenced bindings
10
- # :name_index - Hash mapping stored binding names to producing declarations
10
+ # :ir_name_index - Hash mapping stored binding names to producing declarations
11
11
  # INTERFACE: new(schema, state).run(errors)
12
- #
12
+ #
13
13
  # NOTE: This pass extracts actual IR-level dependencies by analyzing :ref operations
14
14
  # in the generated IR, providing the dependency information needed for optimized VM scheduling.
15
15
  class IRDependencyPass < PassBase
16
16
  def run(errors)
17
17
  ir_module = get_state(:ir_module, required: true)
18
-
18
+
19
19
  ir_dependencies = build_ir_dependency_map(ir_module)
20
- name_index = build_name_index(ir_module)
21
-
22
- state.with(:ir_dependencies, ir_dependencies).with(:name_index, name_index)
20
+ ir_name_index = build_ir_name_index(ir_module)
21
+
22
+ state.with(:ir_dependencies, ir_dependencies).with(:ir_name_index, ir_name_index)
23
23
  end
24
24
 
25
25
  private
@@ -27,41 +27,39 @@ module Kumi
27
27
  # Build a map of declaration -> [stored_bindings_it_references] from the IR
28
28
  def build_ir_dependency_map(ir_module)
29
29
  deps_map = {}
30
-
30
+
31
31
  ir_module.decls.each do |decl|
32
32
  refs = []
33
33
  decl.ops.each do |op|
34
- if op.tag == :ref
35
- refs << op.attrs[:name]
36
- end
34
+ refs << op.attrs[:name] if op.tag == :ref
37
35
  end
38
36
  deps_map[decl.name] = refs
39
37
  end
40
-
38
+
41
39
  deps_map.freeze
42
40
  end
43
41
 
44
42
  # Build name index to map stored binding names to their producing declarations
45
- def build_name_index(ir_module)
46
- name_index = {}
47
-
43
+ def build_ir_name_index(ir_module)
44
+ ir_name_index = {}
45
+
48
46
  ir_module.decls.each do |decl|
49
47
  # Map the primary declaration name
50
- name_index[decl.name] = decl
51
-
48
+ ir_name_index[decl.name] = decl
49
+
52
50
  # Also map any vectorized twin names produced by this declaration
53
51
  decl.ops.each do |op|
54
52
  if op.tag == :store
55
53
  stored_name = op.attrs[:name]
56
- name_index[stored_name] = decl
54
+ ir_name_index[stored_name] = decl
57
55
  end
58
56
  end
59
57
  end
60
-
61
- name_index.freeze
58
+
59
+ ir_name_index.freeze
62
60
  end
63
61
  end
64
62
  end
65
63
  end
66
64
  end
67
- end
65
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module Analyzer
6
+ module Passes
7
+ # PRODUCES: :execution_schedules => { store_name(Symbol) => [Decl, ...] }
8
+ class IRExecutionSchedulePass < PassBase
9
+ def run(errors)
10
+ ir = get_state(:ir_module, required: true)
11
+ deps = get_state(:ir_dependencies, required: true) # decl_name => [binding_name, ...]
12
+ name_index = get_state(:ir_name_index, required: true) # binding_name => Decl (← use IR-specific index)
13
+
14
+ by_name = ir.decls.to_h { |d| [d.name, d] }
15
+ pos = ir.decls.each_with_index.to_h # for deterministic ordering
16
+
17
+ closure_cache = {}
18
+ visiting = {}
19
+
20
+ visit = lambda do |dn|
21
+ return closure_cache[dn] if closure_cache.key?(dn)
22
+
23
+ raise Kumi::Core::Errors::TypeError, "cycle detected in IR at #{dn.inspect}" if visiting[dn]
24
+
25
+ visiting[dn] = true
26
+
27
+ # Resolve binding refs -> producing decl names
28
+ preds = Array(deps[dn]).filter_map { |b| name_index[b]&.name }.uniq
29
+
30
+ # Deterministic order: earlier IR decls first
31
+ preds.sort_by! { |n| pos[n] || Float::INFINITY }
32
+
33
+ order = []
34
+ preds.each do |p|
35
+ next if p == dn # guard against self-deps; treat as error if you prefer
36
+
37
+ order.concat(visit.call(p))
38
+ end
39
+ order << dn unless order.last == dn
40
+
41
+ visiting.delete(dn)
42
+ closure_cache[dn] = order.uniq.freeze
43
+ end
44
+
45
+ schedules = {}
46
+
47
+ ir.decls.each do |decl|
48
+ target_names = [decl.name] + decl.ops.select { _1.tag == :store }.map { _1.attrs[:name] }
49
+
50
+ seq = visit.call(decl.name).map { |dn| by_name.fetch(dn) }.freeze
51
+
52
+ target_names.each do |t|
53
+ if schedules.key?(t) && schedules[t] != seq
54
+ raise Kumi::Core::Errors::TypeError,
55
+ "duplicate schedule target #{t.inspect} produced by #{schedules[t].last.name} and #{decl.name}"
56
+ end
57
+ schedules[t] = seq
58
+ end
59
+ end
60
+
61
+ state.with(:ir_execution_schedules, schedules.freeze)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -426,9 +426,6 @@ module Kumi
426
426
  when Syntax::CallExpression
427
427
  entry = Kumi::Registry.entry(expr.fn_name)
428
428
 
429
- # Validate signature metadata from FunctionSignaturePass (read-only assertions)
430
- validate_signature_metadata(expr, entry)
431
-
432
429
  # Constant folding optimization: evaluate expressions with all literal arguments
433
430
  if can_constant_fold?(expr, entry)
434
431
  folded_value = constant_fold(expr, entry)
@@ -888,39 +885,6 @@ module Kumi
888
885
  expr.args.all? { |arg| arg.is_a?(Syntax::Literal) }
889
886
  end
890
887
 
891
- def validate_signature_metadata(expr, entry)
892
- # Get the node index to access signature metadata
893
- node_index = get_state(:node_index, required: false)
894
- return unless node_index
895
-
896
- node_entry = node_index[expr.object_id]
897
- return unless node_entry
898
-
899
- metadata = node_entry[:metadata]
900
- return unless metadata
901
-
902
- # Validate that dropped axes make sense for reduction functions
903
- if entry&.reducer && metadata[:dropped_axes]
904
- dropped_axes = metadata[:dropped_axes]
905
- unless dropped_axes.is_a?(Array)
906
- raise "Invalid dropped_axes metadata for reducer #{expr.fn_name}: expected Array, got #{dropped_axes.class}"
907
- end
908
-
909
- # For reductions, we should have at least one dropped axis (or empty for scalar reductions)
910
- puts " SIGNATURE[#{expr.fn_name}] dropped_axes: #{dropped_axes.inspect}" if ENV["DEBUG_LOWER"]
911
- end
912
-
913
- # Validate join_policy is recognized
914
- if metadata[:join_policy] && !%i[zip product].include?(metadata[:join_policy])
915
- raise "Invalid join_policy for #{expr.fn_name}: #{metadata[:join_policy].inspect}"
916
- end
917
-
918
- # Warn about join_policy when no join op exists yet (future integration point)
919
- return unless metadata[:join_policy] && ENV["DEBUG_LOWER"]
920
-
921
- puts " SIGNATURE[#{expr.fn_name}] join_policy: #{metadata[:join_policy]} (join op not yet implemented)"
922
- end
923
-
924
888
  def constant_fold(expr, entry)
925
889
  literal_values = expr.args.map(&:value)
926
890
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "pry"
4
3
  module Kumi
5
4
  module Core
6
5
  module Analyzer
@@ -8,54 +7,18 @@ module Kumi
8
7
  # RESPONSIBILITY: Compute topological ordering of declarations, blocking all cycles
9
8
  # DEPENDENCIES: :dependencies from DependencyResolver, :declarations from NameIndexer
10
9
  # PRODUCES: :evaluation_order - Array of declaration names in evaluation order
11
- # :node_index - Hash mapping object_id to node metadata for later passes
12
10
  # INTERFACE: new(schema, state).run(errors)
13
11
  class Toposorter < PassBase
14
12
  def run(errors)
15
13
  dependency_graph = get_state(:dependencies, required: false) || {}
16
14
  definitions = get_state(:declarations, required: false) || {}
17
15
 
18
- # Create node index for later passes to use
19
- node_index = build_node_index(definitions)
20
16
  order = compute_topological_order(dependency_graph, definitions, errors)
21
-
22
- state.with(:evaluation_order, order).with(:node_index, node_index)
17
+ state.with(:evaluation_order, order)
23
18
  end
24
19
 
25
20
  private
26
21
 
27
- def build_node_index(definitions)
28
- index = {}
29
-
30
- # Walk all declarations and their expressions to index every node
31
- definitions.each_value do |decl|
32
- index_node_recursive(decl, index)
33
- end
34
-
35
- index
36
- end
37
-
38
- def index_node_recursive(node, index)
39
- return unless node
40
-
41
- # Index this node by its object_id
42
- index[node.object_id] = {
43
- node: node,
44
- type: node.class.name.split('::').last,
45
- metadata: {}
46
- }
47
-
48
- # Use the same approach as the visitor pattern - recursively index all children
49
- if node.respond_to?(:children)
50
- node.children.each { |child| index_node_recursive(child, index) }
51
- end
52
-
53
- # Index expression for declaration nodes
54
- if node.respond_to?(:expression)
55
- index_node_recursive(node.expression, index)
56
- end
57
- end
58
-
59
22
  def compute_topological_order(graph, definitions, errors)
60
23
  temp_marks = Set.new
61
24
  perm_marks = Set.new
@@ -96,7 +59,6 @@ module Kumi
96
59
  order.freeze
97
60
  end
98
61
 
99
-
100
62
  def report_unexpected_cycle(temp_marks, current_node, errors)
101
63
  cycle_path = temp_marks.to_a.join(" → ") + " → #{current_node}"
102
64