kumi 0.0.10 → 0.0.11
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 +18 -0
- data/CLAUDE.md +7 -231
- data/README.md +1 -1
- data/docs/VECTOR_SEMANTICS.md +286 -0
- data/docs/features/hierarchical-broadcasting.md +1 -1
- data/docs/features/s-expression-printer.md +2 -2
- data/examples/deep_schema_compilation_and_evaluation_benchmark.rb +21 -15
- data/lib/kumi/analyzer.rb +34 -12
- data/lib/kumi/compiler.rb +2 -12
- data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +157 -64
- data/lib/kumi/core/analyzer/passes/dependency_resolver.rb +1 -1
- data/lib/kumi/core/analyzer/passes/input_access_planner_pass.rb +47 -0
- data/lib/kumi/core/analyzer/passes/input_collector.rb +118 -101
- data/lib/kumi/core/analyzer/passes/join_reduce_planning_pass.rb +293 -0
- data/lib/kumi/core/analyzer/passes/lower_to_ir_pass.rb +993 -0
- data/lib/kumi/core/analyzer/passes/pass_base.rb +2 -2
- data/lib/kumi/core/analyzer/passes/scope_resolution_pass.rb +346 -0
- data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +2 -1
- data/lib/kumi/core/analyzer/passes/toposorter.rb +9 -3
- data/lib/kumi/core/analyzer/passes/type_checker.rb +3 -3
- data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +2 -2
- data/lib/kumi/core/analyzer/passes/{type_inferencer.rb → type_inferencer_pass.rb} +4 -4
- data/lib/kumi/core/analyzer/passes/unsat_detector.rb +2 -2
- data/lib/kumi/core/analyzer/plans.rb +52 -0
- data/lib/kumi/core/analyzer/structs/access_plan.rb +20 -0
- data/lib/kumi/core/analyzer/structs/input_meta.rb +29 -0
- data/lib/kumi/core/compiler/access_builder.rb +36 -0
- data/lib/kumi/core/compiler/access_planner.rb +219 -0
- data/lib/kumi/core/compiler/accessors/base.rb +69 -0
- data/lib/kumi/core/compiler/accessors/each_indexed_accessor.rb +84 -0
- data/lib/kumi/core/compiler/accessors/materialize_accessor.rb +55 -0
- data/lib/kumi/core/compiler/accessors/ravel_accessor.rb +73 -0
- data/lib/kumi/core/compiler/accessors/read_accessor.rb +41 -0
- data/lib/kumi/core/compiler_base.rb +2 -2
- data/lib/kumi/core/error_reporter.rb +6 -5
- data/lib/kumi/core/errors.rb +4 -0
- data/lib/kumi/core/explain.rb +157 -205
- data/lib/kumi/core/export/node_builders.rb +2 -2
- data/lib/kumi/core/export/node_serializers.rb +1 -1
- data/lib/kumi/core/function_registry/collection_functions.rb +21 -10
- data/lib/kumi/core/function_registry/conditional_functions.rb +14 -4
- data/lib/kumi/core/function_registry/function_builder.rb +142 -55
- data/lib/kumi/core/function_registry/logical_functions.rb +5 -5
- data/lib/kumi/core/function_registry/stat_functions.rb +2 -2
- data/lib/kumi/core/function_registry.rb +126 -108
- data/lib/kumi/core/ir/execution_engine/combinators.rb +117 -0
- data/lib/kumi/core/ir/execution_engine/interpreter.rb +336 -0
- data/lib/kumi/core/ir/execution_engine/values.rb +46 -0
- data/lib/kumi/core/ir/execution_engine.rb +50 -0
- data/lib/kumi/core/ir.rb +58 -0
- data/lib/kumi/core/ruby_parser/build_context.rb +2 -2
- data/lib/kumi/core/ruby_parser/declaration_reference_proxy.rb +0 -12
- data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +36 -15
- data/lib/kumi/core/ruby_parser/input_builder.rb +5 -5
- data/lib/kumi/core/ruby_parser/parser.rb +1 -1
- data/lib/kumi/core/ruby_parser/schema_builder.rb +2 -2
- data/lib/kumi/core/ruby_parser/sugar.rb +7 -0
- data/lib/kumi/registry.rb +14 -79
- data/lib/kumi/runtime/executable.rb +213 -0
- data/lib/kumi/schema.rb +14 -3
- data/lib/kumi/schema_metadata.rb +2 -2
- data/lib/kumi/support/ir_dump.rb +491 -0
- data/lib/kumi/support/s_expression_printer.rb +1 -1
- data/lib/kumi/syntax/location.rb +5 -0
- data/lib/kumi/syntax/node.rb +0 -1
- data/lib/kumi/syntax/root.rb +2 -2
- data/lib/kumi/version.rb +1 -1
- data/lib/kumi.rb +6 -15
- metadata +26 -15
- data/lib/kumi/core/cascade_executor_builder.rb +0 -132
- data/lib/kumi/core/compiled_schema.rb +0 -43
- data/lib/kumi/core/compiler/expression_compiler.rb +0 -146
- data/lib/kumi/core/compiler/function_invoker.rb +0 -55
- data/lib/kumi/core/compiler/path_traversal_compiler.rb +0 -158
- data/lib/kumi/core/compiler/reference_compiler.rb +0 -46
- data/lib/kumi/core/evaluation_wrapper.rb +0 -40
- data/lib/kumi/core/nested_structure_utils.rb +0 -78
- data/lib/kumi/core/schema_instance.rb +0 -115
- data/lib/kumi/core/vectorized_function_builder.rb +0 -88
- data/lib/kumi/js/compiler.rb +0 -878
- data/lib/kumi/js/function_registry.rb +0 -333
- data/migrate_to_core_iterative.rb +0 -938
data/lib/kumi/analyzer.rb
CHANGED
@@ -7,15 +7,19 @@ module Kumi
|
|
7
7
|
DEFAULT_PASSES = [
|
8
8
|
Core::Analyzer::Passes::NameIndexer, # 1. Finds all names and checks for duplicates.
|
9
9
|
Core::Analyzer::Passes::InputCollector, # 2. Collects field metadata from input declarations.
|
10
|
-
Core::Analyzer::Passes::DeclarationValidator, #
|
11
|
-
Core::Analyzer::Passes::SemanticConstraintValidator, #
|
12
|
-
Core::Analyzer::Passes::DependencyResolver, #
|
13
|
-
Core::Analyzer::Passes::UnsatDetector, #
|
14
|
-
Core::Analyzer::Passes::Toposorter, #
|
15
|
-
Core::Analyzer::Passes::BroadcastDetector,
|
16
|
-
Core::Analyzer::Passes::
|
17
|
-
Core::Analyzer::Passes::TypeConsistencyChecker, #
|
18
|
-
Core::Analyzer::Passes::TypeChecker
|
10
|
+
Core::Analyzer::Passes::DeclarationValidator, # 4. Checks the basic structure of each rule.
|
11
|
+
Core::Analyzer::Passes::SemanticConstraintValidator, # 5. Validates DSL semantic constraints at AST level.
|
12
|
+
Core::Analyzer::Passes::DependencyResolver, # 6. Builds the dependency graph with conditional dependencies.
|
13
|
+
Core::Analyzer::Passes::UnsatDetector, # 7. Detects unsatisfiable constraints and analyzes cascade mutual exclusion.
|
14
|
+
Core::Analyzer::Passes::Toposorter, # 8. Creates the final evaluation order, allowing safe cycles.
|
15
|
+
Core::Analyzer::Passes::BroadcastDetector, # 9. Detects which operations should be broadcast over arrays.
|
16
|
+
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::TypeChecker, # 12. Validates types using inferred information.
|
19
|
+
Core::Analyzer::Passes::InputAccessPlannerPass, # 13. Plans access strategies for input fields.
|
20
|
+
Core::Analyzer::Passes::ScopeResolutionPass, # 14. Plans execution scope and lifting needs for declarations.
|
21
|
+
Core::Analyzer::Passes::JoinReducePlanningPass, # 15. Plans join/reduce operations (Generates IR Structs)
|
22
|
+
Core::Analyzer::Passes::LowerToIRPass # 16. Lowers the schema to IR (Generates IR Structs)
|
19
23
|
].freeze
|
20
24
|
|
21
25
|
def self.analyze!(schema, passes: DEFAULT_PASSES, **opts)
|
@@ -33,7 +37,13 @@ module Kumi
|
|
33
37
|
begin
|
34
38
|
state = pass_instance.run(errors)
|
35
39
|
rescue StandardError => e
|
36
|
-
|
40
|
+
# TODO: - GREATLY improve this, need to capture the context of the error
|
41
|
+
# and the pass that failed and line number if relevant
|
42
|
+
pass_name = pass_class.name.split("::").last
|
43
|
+
message = "Error in Analysis Pass(#{pass_name}): #{e.message}"
|
44
|
+
errors << Core::ErrorReporter.create_error(message, location: nil, type: :semantic, backtrace: e.backtrace)
|
45
|
+
|
46
|
+
raise
|
37
47
|
end
|
38
48
|
end
|
39
49
|
state
|
@@ -41,11 +51,14 @@ module Kumi
|
|
41
51
|
|
42
52
|
def self.handle_analysis_errors(errors)
|
43
53
|
type_errors = errors.select { |e| e.type == :type }
|
54
|
+
semantic_errors = errors.select { |e| e.type == :semantic }
|
44
55
|
first_error_location = errors.first.location
|
45
56
|
|
46
57
|
raise Errors::TypeError.new(format_errors(errors), first_error_location) if type_errors.any?
|
47
58
|
|
48
|
-
raise Errors::SemanticError.new(format_errors(errors), first_error_location)
|
59
|
+
raise Errors::SemanticError.new(format_errors(errors), first_error_location) if first_error_location || semantic_errors
|
60
|
+
|
61
|
+
raise Errors::AnalysisError.new(format_errors(errors))
|
49
62
|
end
|
50
63
|
|
51
64
|
def self.create_analysis_result(state)
|
@@ -63,7 +76,16 @@ module Kumi
|
|
63
76
|
def self.format_errors(errors)
|
64
77
|
return "" if errors.empty?
|
65
78
|
|
66
|
-
errors.
|
79
|
+
backtrace = errors.first.backtrace
|
80
|
+
|
81
|
+
message = errors.map(&:to_s).join("\n")
|
82
|
+
|
83
|
+
message.tap do |msg|
|
84
|
+
if backtrace && !backtrace.empty?
|
85
|
+
msg << "\n\nBacktrace:\n"
|
86
|
+
msg << backtrace[0..10].join("\n") # Limit to first 10 lines for readability
|
87
|
+
end
|
88
|
+
end
|
67
89
|
end
|
68
90
|
end
|
69
91
|
end
|
data/lib/kumi/compiler.rb
CHANGED
@@ -3,11 +3,6 @@
|
|
3
3
|
module Kumi
|
4
4
|
# Compiles an analyzed schema into executable lambdas
|
5
5
|
class Compiler < Core::CompilerBase
|
6
|
-
include Kumi::Core::Compiler::ReferenceCompiler
|
7
|
-
include Kumi::Core::Compiler::PathTraversalCompiler
|
8
|
-
include Kumi::Core::Compiler::ExpressionCompiler
|
9
|
-
include Kumi::Core::Compiler::FunctionInvoker
|
10
|
-
|
11
6
|
def self.compile(schema, analyzer:)
|
12
7
|
new(schema, analyzer).compile
|
13
8
|
end
|
@@ -18,13 +13,8 @@ module Kumi
|
|
18
13
|
end
|
19
14
|
|
20
15
|
def compile
|
21
|
-
|
22
|
-
@analysis.
|
23
|
-
decl = @index[name] or raise("Unknown binding #{name}")
|
24
|
-
compile_declaration(decl)
|
25
|
-
end
|
26
|
-
|
27
|
-
Core::CompiledSchema.new(@bindings.freeze)
|
16
|
+
# Switch to LIR: Use the analysis state instead of old compilation
|
17
|
+
Runtime::Executable.from_analysis(@analysis.state)
|
28
18
|
end
|
29
19
|
end
|
30
20
|
end
|
@@ -5,11 +5,11 @@ module Kumi
|
|
5
5
|
module Analyzer
|
6
6
|
module Passes
|
7
7
|
# Detects which operations should be broadcast over arrays
|
8
|
-
# DEPENDENCIES: :
|
8
|
+
# DEPENDENCIES: :input_metadata, :declarations
|
9
9
|
# PRODUCES: :broadcasts
|
10
10
|
class BroadcastDetector < PassBase
|
11
11
|
def run(errors)
|
12
|
-
input_meta = get_state(:
|
12
|
+
input_meta = get_state(:input_metadata) || {}
|
13
13
|
definitions = get_state(:declarations) || {}
|
14
14
|
|
15
15
|
# Find array fields with their element types
|
@@ -40,6 +40,10 @@ module Kumi
|
|
40
40
|
result = analyze_value_vectorization(name, decl.expression, array_fields, nested_paths, vectorized_values, errors,
|
41
41
|
definitions)
|
42
42
|
|
43
|
+
if ENV["DEBUG_BROADCAST_CLEAN"]
|
44
|
+
puts "#{name}: #{result[:type]} #{format_broadcast_info(result)}"
|
45
|
+
end
|
46
|
+
|
43
47
|
case result[:type]
|
44
48
|
when :vectorized
|
45
49
|
compiler_metadata[:vectorized_operations][name] = result[:info]
|
@@ -70,6 +74,43 @@ module Kumi
|
|
70
74
|
|
71
75
|
private
|
72
76
|
|
77
|
+
def infer_argument_scope(arg, array_fields, nested_paths)
|
78
|
+
case arg
|
79
|
+
when Kumi::Syntax::InputElementReference
|
80
|
+
if nested_paths.key?(arg.path)
|
81
|
+
# Extract scope from path - each array dimension in the path
|
82
|
+
arg.path.select.with_index { |_seg, i| nested_paths[arg.path[0..i]] }
|
83
|
+
else
|
84
|
+
arg.path.select { |seg| array_fields.key?(seg) }
|
85
|
+
end
|
86
|
+
when Kumi::Syntax::CallExpression
|
87
|
+
# For nested calls, find the deepest input reference
|
88
|
+
deepest_scope = []
|
89
|
+
arg.args.each do |nested_arg|
|
90
|
+
scope = infer_argument_scope(nested_arg, array_fields, nested_paths)
|
91
|
+
deepest_scope = scope if scope.length > deepest_scope.length
|
92
|
+
end
|
93
|
+
deepest_scope
|
94
|
+
else
|
95
|
+
[]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def format_broadcast_info(result)
|
100
|
+
case result[:type]
|
101
|
+
when :vectorized
|
102
|
+
info = result[:info]
|
103
|
+
"→ #{info[:source]} (path: #{info[:path]&.join('.')})"
|
104
|
+
when :reduction
|
105
|
+
info = result[:info]
|
106
|
+
"→ fn:#{info[:function]} (arg: #{info[:argument]&.class&.name&.split('::')&.last})"
|
107
|
+
when :scalar
|
108
|
+
"→ scalar"
|
109
|
+
else
|
110
|
+
"→ #{result[:info]}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
73
114
|
def compute_compilation_metadata(name, _decl, compiler_metadata, _vectorized_values, _array_fields)
|
74
115
|
metadata = {
|
75
116
|
operation_mode: :broadcast, # Default mode
|
@@ -169,7 +210,7 @@ module Kumi
|
|
169
210
|
current_access_mode = parent_access_mode
|
170
211
|
if current_meta[:type] == :array
|
171
212
|
array_depth += 1
|
172
|
-
current_access_mode = current_meta[:access_mode] || :
|
213
|
+
current_access_mode = current_meta[:access_mode] || :field # Default to :field if not specified
|
173
214
|
end
|
174
215
|
|
175
216
|
# If this field has children, recurse into them
|
@@ -208,7 +249,6 @@ module Kumi
|
|
208
249
|
# Check if this path exists in nested_paths metadata (supports nested arrays)
|
209
250
|
if nested_paths.key?(expr.path)
|
210
251
|
{ type: :vectorized, info: { source: :nested_array_access, path: expr.path, nested_metadata: nested_paths[expr.path] } }
|
211
|
-
# Fallback to old array_fields detection for backward compatibility
|
212
252
|
elsif array_fields.key?(expr.path.first)
|
213
253
|
{ type: :vectorized, info: { source: :array_field_access, path: expr.path } }
|
214
254
|
else
|
@@ -236,76 +276,106 @@ module Kumi
|
|
236
276
|
end
|
237
277
|
|
238
278
|
def analyze_call_vectorization(_name, expr, array_fields, nested_paths, vectorized_values, errors, definitions = nil)
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
arg_info = analyze_argument_vectorization(expr.args.first, array_fields, nested_paths, vectorized_values, definitions)
|
243
|
-
if arg_info[:vectorized]
|
244
|
-
# Pre-compute which argument indices need flattening
|
245
|
-
flatten_indices = []
|
246
|
-
expr.args.each_with_index do |arg, index|
|
247
|
-
arg_vectorization = analyze_argument_vectorization(arg, array_fields, nested_paths, vectorized_values, definitions)
|
248
|
-
flatten_indices << index if arg_vectorization[:vectorized]
|
249
|
-
end
|
250
|
-
|
251
|
-
{ type: :reduction, info: {
|
252
|
-
function: expr.fn_name,
|
253
|
-
source: arg_info[:source],
|
254
|
-
argument: expr.args.first,
|
255
|
-
flatten_argument_indices: flatten_indices
|
256
|
-
} }
|
257
|
-
else
|
258
|
-
# Not a vectorized reduction - just a regular function call
|
259
|
-
{ type: :scalar }
|
260
|
-
end
|
279
|
+
entry = Kumi::Registry.entry(expr.fn_name)
|
280
|
+
is_reducer = entry&.reducer
|
281
|
+
is_structure = entry&.structure_function
|
261
282
|
|
262
|
-
|
283
|
+
# 1) Analyze all args once
|
284
|
+
arg_infos = expr.args.map do |arg|
|
285
|
+
analyze_argument_vectorization(arg, array_fields, nested_paths, vectorized_values, definitions)
|
286
|
+
end
|
287
|
+
vec_idx = arg_infos.each_index.select { |i| arg_infos[i][:vectorized] }
|
288
|
+
vec_any = !vec_idx.empty?
|
263
289
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
arg.is_a?(Kumi::Syntax::DeclarationReference) && vectorized_values[arg.name]&.[](:vectorized)
|
269
|
-
end
|
270
|
-
if vectorized_trait
|
271
|
-
return { type: :vectorized, info: { source: :cascade_condition_with_vectorized_trait, trait: vectorized_trait.name } }
|
272
|
-
end
|
290
|
+
# 2) Special form: cascade_and (vectorized if any trait arg is vectorized)
|
291
|
+
if expr.fn_name == :cascade_and
|
292
|
+
vectorized_trait = expr.args.find do |arg|
|
293
|
+
arg.is_a?(Kumi::Syntax::DeclarationReference) && vectorized_values[arg.name]&.[](:vectorized)
|
273
294
|
end
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
analyze_argument_vectorization(arg, array_fields, nested_paths, vectorized_values, definitions)
|
295
|
+
if vectorized_trait
|
296
|
+
return { type: :vectorized,
|
297
|
+
info: { source: :cascade_condition_with_vectorized_trait, trait: vectorized_trait&.name } }
|
278
298
|
end
|
279
299
|
|
280
|
-
|
281
|
-
|
282
|
-
vectorized_sources = arg_infos.select { |info| info[:vectorized] }.filter_map { |info| info[:array_source] }.uniq
|
300
|
+
return { type: :scalar }
|
301
|
+
end
|
283
302
|
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
enhanced_message = build_dimension_mismatch_error(expr, arg_infos, array_fields, vectorized_sources)
|
303
|
+
# 3) Reducers: only reduce when the input is actually vectorized
|
304
|
+
if is_reducer
|
305
|
+
return { type: :scalar } unless vec_any
|
288
306
|
|
289
|
-
|
290
|
-
|
291
|
-
|
307
|
+
# which args were vectorized?
|
308
|
+
flatten_indices = vec_idx.dup
|
309
|
+
vectorized_arg_index = vec_idx.first
|
310
|
+
argument_ast = expr.args[vectorized_arg_index]
|
311
|
+
|
312
|
+
src_info = arg_infos[vectorized_arg_index]
|
313
|
+
|
314
|
+
return {
|
315
|
+
type: :reduction,
|
316
|
+
info: {
|
317
|
+
function: expr.fn_name,
|
318
|
+
source: src_info[:source],
|
319
|
+
argument: argument_ast, # << keep AST of the vectorized argument
|
320
|
+
flatten_argument_indices: flatten_indices
|
321
|
+
}
|
322
|
+
}
|
323
|
+
end
|
292
324
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
325
|
+
# 4) Structure (non-reducer) functions like `size`
|
326
|
+
if is_structure
|
327
|
+
# If any arg is itself a PURE reducer call (e.g., size(sum(x))), the inner collapses first ⇒ outer is scalar
|
328
|
+
# But dual-nature functions (both reducer AND structure) should be treated as structure functions when nested
|
329
|
+
return { type: :scalar } if expr.args.any? do |a|
|
330
|
+
if a.is_a?(Kumi::Syntax::CallExpression)
|
331
|
+
arg_entry = Kumi::Registry.entry(a.fn_name)
|
332
|
+
arg_entry&.reducer && !arg_entry&.structure_function # Pure reducer only
|
297
333
|
else
|
298
|
-
|
299
|
-
{ type: :vectorized, info: {
|
300
|
-
operation: expr.fn_name,
|
301
|
-
vectorized_args: arg_infos.map.with_index { |info, i| [i, info[:vectorized]] }.to_h
|
302
|
-
} }
|
334
|
+
false
|
303
335
|
end
|
304
|
-
else
|
305
|
-
# No vectorized arguments - regular scalar function
|
306
|
-
{ type: :scalar }
|
307
336
|
end
|
337
|
+
|
338
|
+
# Structure fn over a vectorized element path ⇒ per-parent vectorization
|
339
|
+
return { type: :scalar } unless vec_any
|
340
|
+
|
341
|
+
src_info = arg_infos[vec_idx.first]
|
342
|
+
parent_scope = src_info[:parent_scope] || src_info[:source] # fallback if analyzer encodes parent separately
|
343
|
+
return {
|
344
|
+
type: :vectorized,
|
345
|
+
info: {
|
346
|
+
operation: expr.fn_name,
|
347
|
+
source: src_info[:source],
|
348
|
+
parent_scope: parent_scope,
|
349
|
+
vectorized_args: vec_idx.to_h { |i| [i, true] }
|
350
|
+
}
|
351
|
+
}
|
352
|
+
|
353
|
+
# Structure fn over a scalar/materialized container ⇒ scalar
|
354
|
+
|
308
355
|
end
|
356
|
+
|
357
|
+
# 5) Generic vectorized map (non-structure, non-reducer)
|
358
|
+
if vec_any
|
359
|
+
# Dimension / source compatibility check
|
360
|
+
sources = vec_idx.map { |i| arg_infos[i][:array_source] }.compact.uniq
|
361
|
+
if sources.size > 1
|
362
|
+
enhanced_message = build_dimension_mismatch_error(expr, arg_infos, array_fields, sources)
|
363
|
+
report_error(errors, enhanced_message, location: expr.loc, type: :semantic)
|
364
|
+
return { type: :scalar } # fail safe to prevent cascading errors
|
365
|
+
end
|
366
|
+
|
367
|
+
return {
|
368
|
+
type: :vectorized,
|
369
|
+
info: {
|
370
|
+
operation: expr.fn_name,
|
371
|
+
source: arg_infos[vec_idx.first][:source],
|
372
|
+
vectorized_args: vec_idx.to_h { |i| [i, true] }
|
373
|
+
}
|
374
|
+
}
|
375
|
+
end
|
376
|
+
|
377
|
+
# 6) Pure scalar
|
378
|
+
{ type: :scalar }
|
309
379
|
end
|
310
380
|
|
311
381
|
def structure_function?(fn_name)
|
@@ -337,9 +407,32 @@ module Kumi
|
|
337
407
|
end
|
338
408
|
|
339
409
|
when Kumi::Syntax::CallExpression
|
340
|
-
# Recursively check
|
410
|
+
# Recursively check nested call
|
341
411
|
result = analyze_value_vectorization(nil, arg, array_fields, nested_paths, vectorized_values, [], definitions)
|
342
|
-
|
412
|
+
# Handle different result types appropriately
|
413
|
+
case result[:type]
|
414
|
+
when :reduction
|
415
|
+
# Reductions can produce vectors if they preserve some dimensions
|
416
|
+
# This aligns with lower_to_ir logic for grouped reductions
|
417
|
+
info = result[:info]
|
418
|
+
if info && info[:argument]
|
419
|
+
# Check if the reduction argument has array scope that would be preserved
|
420
|
+
arg_scope = infer_argument_scope(info[:argument], array_fields, nested_paths)
|
421
|
+
if arg_scope.length > 1
|
422
|
+
# Multi-dimensional reduction - likely preserves outer dimension (per-player)
|
423
|
+
{ vectorized: true, source: :grouped_reduction, array_source: arg_scope.first }
|
424
|
+
else
|
425
|
+
# Single dimension or scalar reduction
|
426
|
+
{ vectorized: false, source: :scalar_from_reduction }
|
427
|
+
end
|
428
|
+
else
|
429
|
+
{ vectorized: false, source: :scalar_from_reduction }
|
430
|
+
end
|
431
|
+
when :vectorized
|
432
|
+
{ vectorized: true, source: :expression }
|
433
|
+
else
|
434
|
+
{ vectorized: false, source: :scalar }
|
435
|
+
end
|
343
436
|
|
344
437
|
else
|
345
438
|
{ vectorized: false }
|
@@ -26,7 +26,7 @@ module Kumi
|
|
26
26
|
|
27
27
|
def run(errors)
|
28
28
|
definitions = get_state(:declarations)
|
29
|
-
input_meta = get_state(:
|
29
|
+
input_meta = get_state(:input_metadata)
|
30
30
|
|
31
31
|
dependency_graph = Hash.new { |h, k| h[k] = [] }
|
32
32
|
reverse_dependencies = Hash.new { |h, k| h[k] = [] }
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module Analyzer
|
6
|
+
module Passes
|
7
|
+
class InputAccessPlannerPass < PassBase
|
8
|
+
def run(errors)
|
9
|
+
input_metadata = get_state(:input_metadata)
|
10
|
+
|
11
|
+
options = {
|
12
|
+
on_missing: :error,
|
13
|
+
key_policy: :indifferent
|
14
|
+
}
|
15
|
+
|
16
|
+
# TODO : Allow by input definition on policies or at least general policy definition
|
17
|
+
plans = Kumi::Core::Compiler::AccessPlanner.plan(input_metadata, options)
|
18
|
+
|
19
|
+
# Quick validation
|
20
|
+
validate_plans!(plans, errors)
|
21
|
+
|
22
|
+
# Create new state with access plans
|
23
|
+
state.with(:access_plans, plans.freeze)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def validate_plans!(plans, errors)
|
29
|
+
plans.each do |path, plan_list|
|
30
|
+
add_error(errors, nil, "No access plans generated for path: #{path}") if plan_list.nil? || plan_list.empty?
|
31
|
+
|
32
|
+
plan_list&.each do |plan|
|
33
|
+
unless plan[:operations].is_a?(Array)
|
34
|
+
add_error(errors, nil, "Invalid operations for path #{path}: expected Array, got #{plan[:operations].class}")
|
35
|
+
end
|
36
|
+
|
37
|
+
unless plan[:mode].is_a?(Symbol)
|
38
|
+
add_error(errors, nil, "Invalid mode for path #{path}: expected Symbol, got #{plan[:mode].class}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|