kumi 0.0.29 → 0.0.30
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/.rubocop.yml +2 -1
- data/CHANGELOG.md +12 -0
- data/README.md +5 -6
- data/docs/DEVELOPMENT.md +23 -1
- data/docs/GOLDEN_QUICK_START.md +141 -0
- data/docs/GOLDEN_TESTS.md +240 -0
- data/golden/array_element/expected/schema_ruby.rb +1 -1
- data/golden/array_index/expected/schema_ruby.rb +1 -1
- data/golden/array_operations/expected/schema_ruby.rb +1 -1
- data/golden/cascade_logic/expected/schema_ruby.rb +1 -1
- data/golden/chained_fusion/expected/schema_ruby.rb +1 -1
- data/golden/decimal_explicit/expected/schema_ruby.rb +1 -1
- data/golden/element_arrays/expected/schema_ruby.rb +1 -1
- data/golden/empty_and_null_inputs/expected/schema_ruby.rb +1 -1
- data/golden/function_overload/expected/schema_ruby.rb +1 -1
- data/golden/game_of_life/expected/schema_ruby.rb +1 -1
- data/golden/hash_keys/expected/schema_ruby.rb +1 -1
- data/golden/hash_value/expected/schema_ruby.rb +1 -1
- data/golden/hierarchical_complex/expected/schema_ruby.rb +1 -1
- data/golden/inline_rename_scope_leak/expected/schema_ruby.rb +1 -1
- data/golden/input_reference/expected/schema_ruby.rb +1 -1
- data/golden/interleaved_fusion/expected/schema_ruby.rb +1 -1
- data/golden/let_inline/expected/schema_ruby.rb +1 -1
- data/golden/loop_fusion/expected/schema_ruby.rb +1 -1
- data/golden/min_reduce_scope/expected/schema_ruby.rb +1 -1
- data/golden/mixed_dimensions/expected/schema_ruby.rb +1 -1
- data/golden/multirank_hoisting/expected/schema_ruby.rb +1 -1
- data/golden/nested_hash/expected/schema_ruby.rb +1 -1
- data/golden/reduction_broadcast/expected/schema_ruby.rb +1 -1
- data/golden/roll/expected/schema_ruby.rb +1 -1
- data/golden/shift/expected/schema_ruby.rb +1 -1
- data/golden/shift_2d/expected/schema_ruby.rb +1 -1
- data/golden/simple_math/expected/schema_ruby.rb +1 -1
- data/golden/streaming_basics/expected/schema_ruby.rb +1 -1
- data/golden/tuples/expected/schema_ruby.rb +1 -1
- data/golden/tuples_and_arrays/expected/schema_ruby.rb +1 -1
- data/golden/us_tax_2024/expected/schema_ruby.rb +1 -1
- data/golden/with_constants/expected/schema_ruby.rb +1 -1
- data/lib/kumi/analyzer.rb +39 -79
- data/lib/kumi/core/analyzer/analysis_state.rb +2 -0
- data/lib/kumi/core/analyzer/constant_evaluator.rb +0 -2
- data/lib/kumi/core/analyzer/execution_phase.rb +24 -0
- data/lib/kumi/core/analyzer/execution_result.rb +38 -0
- data/lib/kumi/core/analyzer/pass_failure.rb +26 -0
- data/lib/kumi/core/analyzer/pass_manager.rb +136 -0
- data/lib/kumi/core/analyzer/passes/codegen/js/declaration_emitter.rb +1 -1
- data/lib/kumi/core/analyzer/passes/codegen/js_pass.rb +1 -1
- data/lib/kumi/core/analyzer/passes/codegen/ruby/declaration_emitter.rb +1 -1
- data/lib/kumi/core/analyzer/passes/codegen/ruby_pass.rb +1 -1
- data/lib/kumi/core/analyzer/passes/constant_folding_pass.rb +1 -1
- data/lib/kumi/core/analyzer/passes/formal_constraint_propagator.rb +13 -31
- data/lib/kumi/core/analyzer/passes/input_access_planner_pass.rb +1 -1
- data/lib/kumi/core/analyzer/passes/input_collector.rb +1 -1
- data/lib/kumi/core/analyzer/passes/input_form_schema_pass.rb +1 -1
- data/lib/kumi/core/analyzer/passes/lir/loop_invariant_code_motion_pass.rb +1 -1
- data/lib/kumi/core/analyzer/passes/lir/stencil_emitter.rb +0 -2
- data/lib/kumi/core/analyzer/passes/normalize_to_nast_pass.rb +5 -1
- data/lib/kumi/core/analyzer/passes/output_schema_pass.rb +2 -1
- data/lib/kumi/core/analyzer/passes/snast_pass.rb +1 -1
- data/lib/kumi/core/analyzer/passes/unsat_detector.rb +8 -10
- data/lib/kumi/core/compiler/access_planner_v2.rb +1 -2
- data/lib/kumi/core/functions/overload_resolver.rb +15 -19
- data/lib/kumi/core/functions/type_categories.rb +3 -3
- data/lib/kumi/core/functions/type_error_reporter.rb +1 -3
- data/lib/kumi/core/input/type_matcher.rb +1 -1
- data/lib/kumi/core/types/normalizer.rb +8 -10
- data/lib/kumi/core/types/validator.rb +1 -1
- data/lib/kumi/core/types/value_objects.rb +5 -2
- data/lib/kumi/core/types.rb +2 -4
- data/lib/kumi/dev/codegen.rb +1 -1
- data/lib/kumi/dev/golden/generator.rb +14 -10
- data/lib/kumi/dev/golden/reporter.rb +5 -5
- data/lib/kumi/dev/golden/representation.rb +1 -3
- data/lib/kumi/dev/golden/result.rb +1 -1
- data/lib/kumi/dev/golden/runtime_test.rb +0 -2
- data/lib/kumi/dev/golden/suite.rb +20 -4
- data/lib/kumi/dev/golden/value_normalizer.rb +1 -3
- data/lib/kumi/doc_generator/formatters/json.rb +11 -11
- data/lib/kumi/doc_generator/formatters/markdown.rb +35 -37
- data/lib/kumi/doc_generator/loader.rb +8 -6
- data/lib/kumi/doc_generator/merger.rb +12 -12
- data/lib/kumi/frontends/text.rb +4 -6
- data/lib/kumi/registry_v2/loader.rb +32 -33
- data/lib/kumi/schema.rb +2 -2
- data/lib/kumi/version.rb +1 -1
- metadata +13 -14
- data/debug_ordering.rb +0 -52
- data/examples/deep_schema_compilation_and_evaluation_benchmark.rb +0 -106
- data/examples/federal_tax_calculator_2024.rb +0 -115
- data/examples/game_of_life.rb +0 -95
- data/examples/simple_rpg_game.rb +0 -1000
- data/examples/static_analysis_errors.rb +0 -178
- data/examples/wide_schema_compilation_and_evaluation_benchmark.rb +0 -80
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Autogenerated by Kumi Codegen
|
|
2
|
-
module Kumi::Compiled::
|
|
2
|
+
module Kumi::Compiled::KUMI_a1445291f04923fe3d936c1514d8bf48488a469a9328b03080feb08b70093276
|
|
3
3
|
def self.from(input_data = nil)
|
|
4
4
|
instance = Object.new
|
|
5
5
|
instance.extend(self)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Autogenerated by Kumi Codegen
|
|
2
|
-
module Kumi::Compiled::
|
|
2
|
+
module Kumi::Compiled::KUMI_d17fb0388f7014e59c7e9a892d540e54a949f21eb86a9e8e8a396bc3488d6f35
|
|
3
3
|
def self.from(input_data = nil)
|
|
4
4
|
instance = Object.new
|
|
5
5
|
instance.extend(self)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Autogenerated by Kumi Codegen
|
|
2
|
-
module Kumi::Compiled::
|
|
2
|
+
module Kumi::Compiled::KUMI_10a8c4eef85aad586921c958580baafbe11b5bd9ca21b75e678a47f7dd65daab
|
|
3
3
|
def self.from(input_data = nil)
|
|
4
4
|
instance = Object.new
|
|
5
5
|
instance.extend(self)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Autogenerated by Kumi Codegen
|
|
2
|
-
module Kumi::Compiled::
|
|
2
|
+
module Kumi::Compiled::KUMI_b5eff95b28c57576e4613ae2a1fa9784e744318ef6565131ada24c407fec28e7
|
|
3
3
|
def self.from(input_data = nil)
|
|
4
4
|
instance = Object.new
|
|
5
5
|
instance.extend(self)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Autogenerated by Kumi Codegen
|
|
2
|
-
module Kumi::Compiled::
|
|
2
|
+
module Kumi::Compiled::KUMI_d972b45a945bf808f0713ddc12e431b18405a54a9d90bfa262d81a86e42b46ca
|
|
3
3
|
def self.from(input_data = nil)
|
|
4
4
|
instance = Object.new
|
|
5
5
|
instance.extend(self)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Autogenerated by Kumi Codegen
|
|
2
|
-
module Kumi::Compiled::
|
|
2
|
+
module Kumi::Compiled::KUMI_2eed3f0a8d77c4064660318dbbae64a67ba86a5d882fba6f60715e64d9677ba2
|
|
3
3
|
def self.from(input_data = nil)
|
|
4
4
|
instance = Object.new
|
|
5
5
|
instance.extend(self)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Autogenerated by Kumi Codegen
|
|
2
|
-
module Kumi::Compiled::
|
|
2
|
+
module Kumi::Compiled::KUMI_4d2cbd3183abfbb3252e36a42fbc36925bef07ceb2aaa302e586496362f48566
|
|
3
3
|
def self.from(input_data = nil)
|
|
4
4
|
instance = Object.new
|
|
5
5
|
instance.extend(self)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Autogenerated by Kumi Codegen
|
|
2
|
-
module Kumi::Compiled::
|
|
2
|
+
module Kumi::Compiled::KUMI_f570bd1470fbdb4fe98673449f7d2106c6eeb0a11dbd0d656e2d19485860c206
|
|
3
3
|
def self.from(input_data = nil)
|
|
4
4
|
instance = Object.new
|
|
5
5
|
instance.extend(self)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Autogenerated by Kumi Codegen
|
|
2
|
-
module Kumi::Compiled::
|
|
2
|
+
module Kumi::Compiled::KUMI_e200083314e8307addf1cf2056b27031a7256e95b7cde0d0bc3865b4aef4ead2
|
|
3
3
|
def self.from(input_data = nil)
|
|
4
4
|
instance = Object.new
|
|
5
5
|
instance.extend(self)
|
data/lib/kumi/analyzer.rb
CHANGED
|
@@ -51,7 +51,7 @@ module Kumi
|
|
|
51
51
|
def self.analyze!(schema, passes: DEFAULT_PASSES, registry: nil, **opts)
|
|
52
52
|
errors = []
|
|
53
53
|
schema_digest = schema.digest
|
|
54
|
-
|
|
54
|
+
Core::Analyzer::Checkpoint.stop_after
|
|
55
55
|
|
|
56
56
|
registry ||= Kumi::RegistryV2.load
|
|
57
57
|
state = Core::Analyzer::AnalysisState.new(opts).with(:registry, registry).with(:schema_digest, schema_digest)
|
|
@@ -61,7 +61,7 @@ module Kumi
|
|
|
61
61
|
state, stopped = run_analysis_passes(schema, HIR_TO_LIR_PASSES, state, errors)
|
|
62
62
|
return create_analysis_result(state) if stopped
|
|
63
63
|
|
|
64
|
-
state,
|
|
64
|
+
state, = run_analysis_passes(schema, RUBY_TARGET_PASSES, state, errors)
|
|
65
65
|
|
|
66
66
|
handle_analysis_errors(errors) unless errors.empty?
|
|
67
67
|
create_analysis_result(state)
|
|
@@ -71,91 +71,51 @@ module Kumi
|
|
|
71
71
|
# Resume from a saved state if configured
|
|
72
72
|
state = Core::Analyzer::Checkpoint.load_initial_state(state)
|
|
73
73
|
|
|
74
|
+
# Prepare options for PassManager
|
|
74
75
|
debug_on = Core::Analyzer::Debug.enabled?
|
|
75
76
|
resume_at = Core::Analyzer::Checkpoint.resume_at
|
|
76
77
|
stop_after = Core::Analyzer::Checkpoint.stop_after
|
|
77
|
-
skipping = !!resume_at
|
|
78
|
-
stopped = false
|
|
79
|
-
|
|
80
|
-
passes.each_with_index do |pass_class, idx|
|
|
81
|
-
raise handle_analysis_errors(errors) if !errors.empty? && (ERROR_THRESHOLD_PASS == pass_class)
|
|
82
|
-
|
|
83
|
-
pass_name = pass_class.name.split("::").last
|
|
84
78
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
rescue StandardError => e
|
|
102
|
-
# TODO: - GREATLY improve this, need to capture the context of the error
|
|
103
|
-
# and the pass that failed and line number if relevant
|
|
104
|
-
message = "Error in Analysis Pass(#{pass_name}): #{e.message}"
|
|
105
|
-
errors << Core::ErrorReporter.create_error(message, location: nil, type: :semantic, backtrace: e.backtrace)
|
|
106
|
-
|
|
107
|
-
if debug_on
|
|
108
|
-
logs = Core::Analyzer::Debug.drain_log
|
|
109
|
-
elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1000).round(2)
|
|
110
|
-
|
|
111
|
-
Core::Analyzer::Debug.emit(
|
|
112
|
-
pass: pass_name,
|
|
113
|
-
diff: {},
|
|
114
|
-
elapsed_ms: elapsed_ms,
|
|
115
|
-
logs: logs + [{ level: :error, id: :exception, message: e.message, error_class: e.class.name }]
|
|
116
|
-
)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
raise
|
|
120
|
-
end
|
|
121
|
-
unless state.is_a? Kumi::Core::Analyzer::AnalysisState
|
|
122
|
-
raise "Pass #{pass_name} returned a '#{state.class}', expected 'AnalysisState'"
|
|
123
|
-
end
|
|
79
|
+
# Filter passes based on checkpoint resume point
|
|
80
|
+
filtered_passes = if resume_at
|
|
81
|
+
passes.each_with_index do |pass_class, idx|
|
|
82
|
+
pass_name = pass_class.name.split("::").last
|
|
83
|
+
if pass_name == resume_at
|
|
84
|
+
break passes[idx..]
|
|
85
|
+
end
|
|
86
|
+
end.flatten.compact
|
|
87
|
+
else
|
|
88
|
+
passes
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Check for error threshold pass
|
|
92
|
+
if !errors.empty? && filtered_passes.include?(ERROR_THRESHOLD_PASS)
|
|
93
|
+
raise handle_analysis_errors(errors)
|
|
94
|
+
end
|
|
124
95
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
Core::Analyzer::Debug.emit(
|
|
144
|
-
pass: pass_name,
|
|
145
|
-
diff: diff,
|
|
146
|
-
elapsed_ms: elapsed_ms,
|
|
147
|
-
logs: logs
|
|
96
|
+
# Use PassManager for orchestration
|
|
97
|
+
manager = Core::Analyzer::PassManager.new(filtered_passes)
|
|
98
|
+
options = {
|
|
99
|
+
checkpoint_enabled: true,
|
|
100
|
+
debug_enabled: debug_on,
|
|
101
|
+
profiling_enabled: true,
|
|
102
|
+
stop_after: stop_after
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
result = manager.run(schema, state, errors, options)
|
|
106
|
+
|
|
107
|
+
# Convert PassFailure errors back to ErrorEntry for consistency
|
|
108
|
+
if result.failed?
|
|
109
|
+
result.errors.each do |pass_failure|
|
|
110
|
+
errors << Core::ErrorReporter.create_error(
|
|
111
|
+
pass_failure.message,
|
|
112
|
+
location: pass_failure.location,
|
|
113
|
+
type: :semantic
|
|
148
114
|
)
|
|
149
115
|
end
|
|
150
|
-
|
|
151
|
-
Core::Analyzer::Checkpoint.leaving(pass_name:, idx:, state:)
|
|
152
|
-
|
|
153
|
-
if stop_after && pass_name == stop_after
|
|
154
|
-
stopped = true
|
|
155
|
-
break
|
|
156
|
-
end
|
|
157
116
|
end
|
|
158
|
-
|
|
117
|
+
|
|
118
|
+
[result.final_state, result.stopped || false]
|
|
159
119
|
end
|
|
160
120
|
|
|
161
121
|
def self.handle_analysis_errors(errors)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kumi
|
|
4
|
+
module Core
|
|
5
|
+
module Analyzer
|
|
6
|
+
class ExecutionPhase
|
|
7
|
+
attr_reader :pass_class, :index
|
|
8
|
+
|
|
9
|
+
def initialize(pass_class:, index:)
|
|
10
|
+
@pass_class = pass_class
|
|
11
|
+
@index = index
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def pass_name
|
|
15
|
+
@pass_class.name.split("::").last
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_s
|
|
19
|
+
"Phase #{index}: #{pass_name}"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kumi
|
|
4
|
+
module Core
|
|
5
|
+
module Analyzer
|
|
6
|
+
class ExecutionResult
|
|
7
|
+
attr_reader :final_state, :errors, :failed_at_phase, :stopped
|
|
8
|
+
|
|
9
|
+
def initialize(final_state:, errors: [], failed_at_phase: nil, stopped: false)
|
|
10
|
+
@final_state = final_state
|
|
11
|
+
@errors = errors
|
|
12
|
+
@failed_at_phase = failed_at_phase
|
|
13
|
+
@stopped = stopped
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.success(final_state:, stopped: false)
|
|
17
|
+
new(final_state: final_state, errors: [], failed_at_phase: nil, stopped: stopped)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.failure(final_state:, errors:, failed_at_phase:)
|
|
21
|
+
new(final_state: final_state, errors: errors, failed_at_phase: failed_at_phase, stopped: false)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def succeeded?
|
|
25
|
+
errors.empty?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def failed?
|
|
29
|
+
!succeeded?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def error_count
|
|
33
|
+
errors.size
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kumi
|
|
4
|
+
module Core
|
|
5
|
+
module Analyzer
|
|
6
|
+
class PassFailure
|
|
7
|
+
attr_reader :message, :phase, :pass_name, :location
|
|
8
|
+
|
|
9
|
+
def initialize(message:, phase:, pass_name:, location:)
|
|
10
|
+
@message = message
|
|
11
|
+
@phase = phase
|
|
12
|
+
@pass_name = pass_name
|
|
13
|
+
@location = location
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_s
|
|
17
|
+
if location
|
|
18
|
+
"#{pass_name} (phase #{phase}) at #{location}: #{message}"
|
|
19
|
+
else
|
|
20
|
+
"#{pass_name} (phase #{phase}): #{message}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kumi
|
|
4
|
+
module Core
|
|
5
|
+
module Analyzer
|
|
6
|
+
class PassManager
|
|
7
|
+
attr_reader :passes, :errors
|
|
8
|
+
|
|
9
|
+
def initialize(passes)
|
|
10
|
+
@passes = passes
|
|
11
|
+
@errors = []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run(syntax_tree, initial_state = nil, errors = [], options = {})
|
|
15
|
+
state = initial_state || AnalysisState.new
|
|
16
|
+
|
|
17
|
+
passes.each_with_index do |pass_class, phase_index|
|
|
18
|
+
pass_name = pass_class.name.split("::").last
|
|
19
|
+
|
|
20
|
+
# Checkpoint support
|
|
21
|
+
Checkpoint.entering(pass_name:, idx: phase_index, state:) if options[:checkpoint_enabled]
|
|
22
|
+
|
|
23
|
+
# Debug support
|
|
24
|
+
debug_on = options[:debug_enabled]
|
|
25
|
+
before = state.to_h if debug_on
|
|
26
|
+
Debug.reset_log(pass: pass_name) if debug_on
|
|
27
|
+
|
|
28
|
+
# Performance profiling support
|
|
29
|
+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC) if options[:profiling_enabled]
|
|
30
|
+
|
|
31
|
+
begin
|
|
32
|
+
pass_instance = pass_class.new(syntax_tree, state)
|
|
33
|
+
|
|
34
|
+
if options[:profiling_enabled]
|
|
35
|
+
state = Dev::Profiler.phase("analyzer.pass", pass: pass_name) do
|
|
36
|
+
pass_instance.run(errors)
|
|
37
|
+
end
|
|
38
|
+
else
|
|
39
|
+
state = pass_instance.run(errors)
|
|
40
|
+
end
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
# Capture exception context
|
|
43
|
+
message = "Error in Analysis Pass(#{pass_name}): #{e.message}"
|
|
44
|
+
error_obj = ErrorReporter.create_error(message, location: nil, type: :semantic, backtrace: e.backtrace)
|
|
45
|
+
errors << error_obj
|
|
46
|
+
|
|
47
|
+
if debug_on
|
|
48
|
+
logs = Debug.drain_log
|
|
49
|
+
elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1000).round(2) if options[:profiling_enabled]
|
|
50
|
+
Debug.emit(
|
|
51
|
+
pass: pass_name,
|
|
52
|
+
diff: {},
|
|
53
|
+
elapsed_ms: elapsed_ms || 0,
|
|
54
|
+
logs: logs + [{ level: :error, id: :exception, message: e.message, error_class: e.class.name }]
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Return failure result instead of raising - let caller decide what to do
|
|
59
|
+
phase = ExecutionPhase.new(pass_class: pass_class, index: phase_index)
|
|
60
|
+
converted_error = PassFailure.new(
|
|
61
|
+
message: error_obj.message,
|
|
62
|
+
phase: phase_index,
|
|
63
|
+
pass_name: phase.pass_name,
|
|
64
|
+
location: error_obj.location
|
|
65
|
+
)
|
|
66
|
+
return ExecutionResult.failure(
|
|
67
|
+
final_state: state,
|
|
68
|
+
errors: [converted_error],
|
|
69
|
+
failed_at_phase: phase_index
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Type checking (PassManager enforces AnalysisState)
|
|
74
|
+
unless state.is_a?(AnalysisState)
|
|
75
|
+
raise "Pass #{pass_name} returned #{state.class}, expected AnalysisState"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Debug logging with state diff
|
|
79
|
+
if debug_on
|
|
80
|
+
after = state.to_h
|
|
81
|
+
|
|
82
|
+
# Optional immutability guard
|
|
83
|
+
if ENV["KUMI_DEBUG_REQUIRE_FROZEN"] == "1"
|
|
84
|
+
(after || {}).each do |k, v|
|
|
85
|
+
if v.nil? || v.is_a?(Numeric) || v.is_a?(Symbol) || v.is_a?(TrueClass) || v.is_a?(FalseClass) || (v.is_a?(String) && v.frozen?)
|
|
86
|
+
next
|
|
87
|
+
end
|
|
88
|
+
raise "State[#{k}] not frozen: #{v.class}" unless v.frozen?
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
diff = Debug.diff_state(before, after)
|
|
93
|
+
logs = Debug.drain_log
|
|
94
|
+
elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1000).round(2) if options[:profiling_enabled]
|
|
95
|
+
|
|
96
|
+
Debug.emit(
|
|
97
|
+
pass: pass_name,
|
|
98
|
+
diff: diff,
|
|
99
|
+
elapsed_ms: elapsed_ms || 0,
|
|
100
|
+
logs: logs
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Checkpoint support
|
|
105
|
+
Checkpoint.leaving(pass_name:, idx: phase_index, state:) if options[:checkpoint_enabled]
|
|
106
|
+
|
|
107
|
+
# Handle errors
|
|
108
|
+
if !errors.empty?
|
|
109
|
+
phase = ExecutionPhase.new(pass_class: pass_class, index: phase_index)
|
|
110
|
+
converted_errors = errors.map do |error|
|
|
111
|
+
PassFailure.new(
|
|
112
|
+
message: error.message,
|
|
113
|
+
phase: phase_index,
|
|
114
|
+
pass_name: phase.pass_name,
|
|
115
|
+
location: error.respond_to?(:location) ? error.location : nil
|
|
116
|
+
)
|
|
117
|
+
end
|
|
118
|
+
return ExecutionResult.failure(
|
|
119
|
+
final_state: state,
|
|
120
|
+
errors: converted_errors,
|
|
121
|
+
failed_at_phase: phase_index
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Check stop_after
|
|
126
|
+
if options[:stop_after] && pass_name == options[:stop_after]
|
|
127
|
+
return ExecutionResult.success(final_state: state, stopped: true)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
ExecutionResult.success(final_state: state)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -12,7 +12,7 @@ module Kumi
|
|
|
12
12
|
schema_digest = get_state(:schema_digest)
|
|
13
13
|
hints = get_state(:hints)
|
|
14
14
|
|
|
15
|
-
decls = decls.reject { |n,
|
|
15
|
+
decls = decls.reject { |n, _v| hints[n][:inline] }
|
|
16
16
|
emitter = Codegen::Js::Emitter.new(manifest["kernels"], manifest["bindings"])
|
|
17
17
|
src = emitter.emit(decls, schema_digest: schema_digest)
|
|
18
18
|
|
|
@@ -18,7 +18,7 @@ module Kumi
|
|
|
18
18
|
# The codegen pass no longer needs direct access to the registry
|
|
19
19
|
emitter = Codegen::Ruby::Emitter.new(manifest["kernels"], manifest["bindings"])
|
|
20
20
|
|
|
21
|
-
decls = decls.reject { |n,
|
|
21
|
+
decls = decls.reject { |n, _v| hints[n][:inline] }
|
|
22
22
|
src = emitter.emit(decls, schema_digest:)
|
|
23
23
|
|
|
24
24
|
files = { "codegen.rb" => src }
|
|
@@ -7,7 +7,7 @@ module Kumi
|
|
|
7
7
|
class ConstantFoldingPass < PassBase
|
|
8
8
|
NAST = Kumi::Core::NAST
|
|
9
9
|
|
|
10
|
-
def run(
|
|
10
|
+
def run(_errors)
|
|
11
11
|
nast_module = get_state(:nast_module, required: true)
|
|
12
12
|
order = get_state(:evaluation_order, required: true)
|
|
13
13
|
@registry = get_state(:registry, required: true)
|
|
@@ -25,8 +25,6 @@ module Kumi
|
|
|
25
25
|
propagate_equality_forward(constraint, operation_spec, operand_map)
|
|
26
26
|
when :range
|
|
27
27
|
propagate_range_forward(constraint, operation_spec, operand_map)
|
|
28
|
-
else
|
|
29
|
-
nil
|
|
30
28
|
end
|
|
31
29
|
end
|
|
32
30
|
|
|
@@ -37,8 +35,6 @@ module Kumi
|
|
|
37
35
|
propagate_equality_reverse(constraint, operation_spec, operand_map)
|
|
38
36
|
when :range
|
|
39
37
|
propagate_range_reverse(constraint, operation_spec, operand_map)
|
|
40
|
-
else
|
|
41
|
-
nil
|
|
42
38
|
end
|
|
43
39
|
end
|
|
44
40
|
|
|
@@ -47,7 +43,7 @@ module Kumi
|
|
|
47
43
|
# FORWARD PROPAGATION: Compute output value from input constraints
|
|
48
44
|
def propagate_equality_forward(constraint, operation_spec, operand_map)
|
|
49
45
|
result_var = operand_map[:result]
|
|
50
|
-
|
|
46
|
+
constraint[:variable]
|
|
51
47
|
input_value = constraint[:value]
|
|
52
48
|
|
|
53
49
|
case operation_spec.id
|
|
@@ -75,8 +71,6 @@ module Kumi
|
|
|
75
71
|
output_value = input_value - other_operand
|
|
76
72
|
{ variable: result_var, op: :==, value: output_value }
|
|
77
73
|
|
|
78
|
-
else
|
|
79
|
-
nil
|
|
80
74
|
end
|
|
81
75
|
end
|
|
82
76
|
|
|
@@ -122,14 +116,12 @@ module Kumi
|
|
|
122
116
|
output_max = input_max - other
|
|
123
117
|
{ variable: result_var, op: :range, min: output_min, max: output_max }
|
|
124
118
|
|
|
125
|
-
else
|
|
126
|
-
nil
|
|
127
119
|
end
|
|
128
120
|
end
|
|
129
121
|
|
|
130
122
|
# REVERSE PROPAGATION: Derive input equality from output equality
|
|
131
123
|
def propagate_equality_reverse(constraint, operation_spec, operand_map)
|
|
132
|
-
|
|
124
|
+
constraint[:variable]
|
|
133
125
|
result_value = constraint[:value]
|
|
134
126
|
left_var = operand_map[:left_operand]
|
|
135
127
|
right_var = operand_map[:right_operand]
|
|
@@ -141,20 +133,18 @@ module Kumi
|
|
|
141
133
|
{ variable: left_var, op: :==, value: result_value - right_var }
|
|
142
134
|
elsif right_var.is_a?(Symbol) && left_var.is_a?(Numeric)
|
|
143
135
|
{ variable: right_var, op: :==, value: result_value - left_var }
|
|
144
|
-
else
|
|
145
|
-
nil
|
|
146
136
|
end
|
|
147
137
|
|
|
148
138
|
when "core.mul"
|
|
149
139
|
# result == V, result = x * C => x == V / C (if C != 0)
|
|
150
140
|
if left_var.is_a?(Symbol) && right_var.is_a?(Numeric) && right_var != 0
|
|
151
141
|
return nil unless (result_value % right_var).zero?
|
|
142
|
+
|
|
152
143
|
{ variable: left_var, op: :==, value: result_value / right_var }
|
|
153
144
|
elsif right_var.is_a?(Symbol) && left_var.is_a?(Numeric) && left_var != 0
|
|
154
145
|
return nil unless (result_value % left_var).zero?
|
|
146
|
+
|
|
155
147
|
{ variable: right_var, op: :==, value: result_value / left_var }
|
|
156
|
-
else
|
|
157
|
-
nil
|
|
158
148
|
end
|
|
159
149
|
|
|
160
150
|
when "core.sub"
|
|
@@ -163,12 +153,8 @@ module Kumi
|
|
|
163
153
|
{ variable: left_var, op: :==, value: result_value + right_var }
|
|
164
154
|
elsif right_var.is_a?(Symbol) && left_var.is_a?(Numeric)
|
|
165
155
|
{ variable: right_var, op: :==, value: left_var - result_value }
|
|
166
|
-
else
|
|
167
|
-
nil
|
|
168
156
|
end
|
|
169
157
|
|
|
170
|
-
else
|
|
171
|
-
nil
|
|
172
158
|
end
|
|
173
159
|
end
|
|
174
160
|
|
|
@@ -191,17 +177,15 @@ module Kumi
|
|
|
191
177
|
when "core.mul"
|
|
192
178
|
# result in [min, max], result = x * C => x in [min/C, max/C] (depends on sign)
|
|
193
179
|
if left_var.is_a?(Symbol) && right_var.is_a?(Numeric) && right_var != 0
|
|
194
|
-
if right_var > 0
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
end
|
|
180
|
+
return { variable: left_var, op: :range, min: result_min / right_var, max: result_max / right_var } if right_var > 0
|
|
181
|
+
|
|
182
|
+
return { variable: left_var, op: :range, min: result_max / right_var, max: result_min / right_var }
|
|
183
|
+
|
|
199
184
|
elsif right_var.is_a?(Symbol) && left_var.is_a?(Numeric) && left_var != 0
|
|
200
|
-
if left_var > 0
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
end
|
|
185
|
+
return { variable: right_var, op: :range, min: result_min / left_var, max: result_max / left_var } if left_var > 0
|
|
186
|
+
|
|
187
|
+
return { variable: right_var, op: :range, min: result_max / left_var, max: result_min / left_var }
|
|
188
|
+
|
|
205
189
|
end
|
|
206
190
|
|
|
207
191
|
when "core.sub"
|
|
@@ -216,7 +200,7 @@ module Kumi
|
|
|
216
200
|
nil
|
|
217
201
|
end
|
|
218
202
|
|
|
219
|
-
def get_other_operand_value(constraint, operand_map,
|
|
203
|
+
def get_other_operand_value(constraint, operand_map, _operation)
|
|
220
204
|
input_var = constraint[:variable]
|
|
221
205
|
left_var = operand_map[:left_operand] || operand_map.values[0]
|
|
222
206
|
right_var = operand_map[:right_operand] || operand_map.values[1]
|
|
@@ -225,8 +209,6 @@ module Kumi
|
|
|
225
209
|
right_var
|
|
226
210
|
elsif input_var == right_var && left_var.is_a?(Numeric)
|
|
227
211
|
left_var
|
|
228
|
-
else
|
|
229
|
-
nil
|
|
230
212
|
end
|
|
231
213
|
end
|
|
232
214
|
end
|
|
@@ -108,7 +108,7 @@ module Kumi
|
|
|
108
108
|
when Kumi::Core::Types::ArrayType
|
|
109
109
|
:array
|
|
110
110
|
when Kumi::Core::Types::TupleType
|
|
111
|
-
:array
|
|
111
|
+
:array # Tuples behave like arrays for input access
|
|
112
112
|
when :array, Kumi::Core::Types::ScalarType
|
|
113
113
|
# Check if it's a hash scalar or :hash symbol
|
|
114
114
|
if t.is_a?(Kumi::Core::Types::ScalarType) && t.kind == :hash
|