kumi 0.0.23 → 0.0.25

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 (166) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +2 -2
  4. data/golden/array_element/expected/schema_ruby.rb +1 -1
  5. data/golden/array_index/expected/schema_ruby.rb +1 -1
  6. data/golden/array_operations/expected/schema_ruby.rb +1 -1
  7. data/golden/cascade_logic/expected/lir_02_inlined.txt +8 -8
  8. data/golden/cascade_logic/expected/schema_ruby.rb +1 -1
  9. data/golden/chained_fusion/expected/lir_02_inlined.txt +36 -36
  10. data/golden/chained_fusion/expected/lir_03_cse.txt +23 -23
  11. data/golden/chained_fusion/expected/lir_04_1_loop_fusion.txt +25 -25
  12. data/golden/chained_fusion/expected/lir_04_loop_invcm.txt +23 -23
  13. data/golden/chained_fusion/expected/lir_06_const_prop.txt +23 -23
  14. data/golden/chained_fusion/expected/schema_javascript.mjs +23 -23
  15. data/golden/chained_fusion/expected/schema_ruby.rb +28 -28
  16. data/golden/element_arrays/expected/schema_ruby.rb +1 -1
  17. data/golden/empty_and_null_inputs/expected/lir_02_inlined.txt +18 -18
  18. data/golden/empty_and_null_inputs/expected/lir_03_cse.txt +17 -17
  19. data/golden/empty_and_null_inputs/expected/lir_04_1_loop_fusion.txt +17 -17
  20. data/golden/empty_and_null_inputs/expected/lir_04_loop_invcm.txt +17 -17
  21. data/golden/empty_and_null_inputs/expected/lir_06_const_prop.txt +17 -17
  22. data/golden/empty_and_null_inputs/expected/schema_javascript.mjs +13 -13
  23. data/golden/empty_and_null_inputs/expected/schema_ruby.rb +18 -18
  24. data/golden/game_of_life/expected/lir_00_unoptimized.txt +33 -33
  25. data/golden/game_of_life/expected/lir_01_hoist_scalar_references.txt +33 -33
  26. data/golden/game_of_life/expected/lir_02_inlined.txt +1370 -1370
  27. data/golden/game_of_life/expected/lir_03_cse.txt +448 -448
  28. data/golden/game_of_life/expected/lir_04_1_loop_fusion.txt +448 -448
  29. data/golden/game_of_life/expected/lir_04_loop_invcm.txt +448 -448
  30. data/golden/game_of_life/expected/lir_06_const_prop.txt +448 -448
  31. data/golden/game_of_life/expected/schema_javascript.mjs +85 -85
  32. data/golden/game_of_life/expected/schema_ruby.rb +86 -86
  33. data/golden/hash_keys/expected/schema_ruby.rb +1 -1
  34. data/golden/hash_keys/schema.kumi +4 -5
  35. data/golden/hash_value/expected/schema_ruby.rb +1 -1
  36. data/golden/hierarchical_complex/expected/lir_02_inlined.txt +15 -15
  37. data/golden/hierarchical_complex/expected/lir_03_cse.txt +1 -1
  38. data/golden/hierarchical_complex/expected/lir_04_1_loop_fusion.txt +1 -1
  39. data/golden/hierarchical_complex/expected/lir_04_loop_invcm.txt +1 -1
  40. data/golden/hierarchical_complex/expected/lir_06_const_prop.txt +1 -1
  41. data/golden/hierarchical_complex/expected/schema_javascript.mjs +1 -1
  42. data/golden/hierarchical_complex/expected/schema_ruby.rb +2 -2
  43. data/golden/inline_rename_scope_leak/expected/ast.txt +48 -0
  44. data/golden/inline_rename_scope_leak/expected/input_plan.txt +10 -0
  45. data/golden/inline_rename_scope_leak/expected/lir_00_unoptimized.txt +35 -0
  46. data/golden/inline_rename_scope_leak/expected/lir_01_hoist_scalar_references.txt +35 -0
  47. data/golden/inline_rename_scope_leak/expected/lir_02_inlined.txt +49 -0
  48. data/golden/inline_rename_scope_leak/expected/lir_03_cse.txt +49 -0
  49. data/golden/inline_rename_scope_leak/expected/lir_04_1_loop_fusion.txt +49 -0
  50. data/golden/inline_rename_scope_leak/expected/lir_04_loop_invcm.txt +49 -0
  51. data/golden/inline_rename_scope_leak/expected/lir_06_const_prop.txt +49 -0
  52. data/golden/inline_rename_scope_leak/expected/nast.txt +31 -0
  53. data/golden/inline_rename_scope_leak/expected/schema_javascript.mjs +51 -0
  54. data/golden/inline_rename_scope_leak/expected/schema_ruby.rb +82 -0
  55. data/golden/inline_rename_scope_leak/expected/snast.txt +31 -0
  56. data/golden/inline_rename_scope_leak/expected.json +7 -0
  57. data/golden/inline_rename_scope_leak/input.json +4 -0
  58. data/golden/inline_rename_scope_leak/schema.kumi +24 -0
  59. data/golden/input_reference/expected/schema_ruby.rb +1 -1
  60. data/golden/interleaved_fusion/expected/lir_02_inlined.txt +35 -35
  61. data/golden/interleaved_fusion/expected/lir_03_cse.txt +26 -26
  62. data/golden/interleaved_fusion/expected/lir_04_1_loop_fusion.txt +27 -26
  63. data/golden/interleaved_fusion/expected/lir_04_loop_invcm.txt +26 -26
  64. data/golden/interleaved_fusion/expected/lir_06_const_prop.txt +26 -26
  65. data/golden/interleaved_fusion/expected/schema_javascript.mjs +23 -23
  66. data/golden/interleaved_fusion/expected/schema_ruby.rb +29 -29
  67. data/golden/let_inline/expected/schema_ruby.rb +1 -1
  68. data/golden/loop_fusion/expected/lir_02_inlined.txt +17 -17
  69. data/golden/loop_fusion/expected/lir_03_cse.txt +14 -14
  70. data/golden/loop_fusion/expected/lir_04_1_loop_fusion.txt +14 -14
  71. data/golden/loop_fusion/expected/lir_04_loop_invcm.txt +14 -14
  72. data/golden/loop_fusion/expected/lir_06_const_prop.txt +14 -14
  73. data/golden/loop_fusion/expected/schema_javascript.mjs +12 -12
  74. data/golden/loop_fusion/expected/schema_ruby.rb +16 -16
  75. data/golden/min_reduce_scope/expected/schema_ruby.rb +1 -1
  76. data/golden/mixed_dimensions/expected/lir_02_inlined.txt +5 -5
  77. data/golden/mixed_dimensions/expected/lir_03_cse.txt +5 -5
  78. data/golden/mixed_dimensions/expected/lir_04_1_loop_fusion.txt +5 -5
  79. data/golden/mixed_dimensions/expected/lir_04_loop_invcm.txt +5 -5
  80. data/golden/mixed_dimensions/expected/lir_06_const_prop.txt +5 -5
  81. data/golden/mixed_dimensions/expected/schema_javascript.mjs +3 -3
  82. data/golden/mixed_dimensions/expected/schema_ruby.rb +6 -6
  83. data/golden/multirank_hoisting/expected/lir_02_inlined.txt +48 -48
  84. data/golden/multirank_hoisting/expected/lir_03_cse.txt +35 -35
  85. data/golden/multirank_hoisting/expected/lir_04_1_loop_fusion.txt +35 -35
  86. data/golden/multirank_hoisting/expected/lir_04_loop_invcm.txt +35 -35
  87. data/golden/multirank_hoisting/expected/lir_06_const_prop.txt +35 -35
  88. data/golden/multirank_hoisting/expected/schema_javascript.mjs +34 -34
  89. data/golden/multirank_hoisting/expected/schema_ruby.rb +36 -36
  90. data/golden/nested_hash/expected/schema_ruby.rb +1 -1
  91. data/golden/reduction_broadcast/expected/lir_02_inlined.txt +30 -30
  92. data/golden/reduction_broadcast/expected/lir_03_cse.txt +22 -22
  93. data/golden/reduction_broadcast/expected/lir_04_1_loop_fusion.txt +22 -22
  94. data/golden/reduction_broadcast/expected/lir_04_loop_invcm.txt +22 -22
  95. data/golden/reduction_broadcast/expected/lir_06_const_prop.txt +22 -22
  96. data/golden/reduction_broadcast/expected/schema_javascript.mjs +18 -18
  97. data/golden/reduction_broadcast/expected/schema_ruby.rb +23 -23
  98. data/golden/roll/expected/lir_00_unoptimized.txt +8 -8
  99. data/golden/roll/expected/lir_01_hoist_scalar_references.txt +8 -8
  100. data/golden/roll/expected/lir_02_inlined.txt +8 -8
  101. data/golden/roll/expected/lir_03_cse.txt +8 -8
  102. data/golden/roll/expected/lir_04_1_loop_fusion.txt +8 -8
  103. data/golden/roll/expected/lir_04_loop_invcm.txt +8 -8
  104. data/golden/roll/expected/lir_06_const_prop.txt +8 -8
  105. data/golden/roll/expected/schema_ruby.rb +1 -1
  106. data/golden/shift/expected/lir_00_unoptimized.txt +12 -12
  107. data/golden/shift/expected/lir_01_hoist_scalar_references.txt +12 -12
  108. data/golden/shift/expected/lir_02_inlined.txt +12 -12
  109. data/golden/shift/expected/lir_03_cse.txt +12 -12
  110. data/golden/shift/expected/lir_04_1_loop_fusion.txt +12 -12
  111. data/golden/shift/expected/lir_04_loop_invcm.txt +12 -12
  112. data/golden/shift/expected/lir_06_const_prop.txt +12 -12
  113. data/golden/shift/expected/schema_ruby.rb +1 -1
  114. data/golden/shift_2d/expected/lir_00_unoptimized.txt +48 -48
  115. data/golden/shift_2d/expected/lir_01_hoist_scalar_references.txt +48 -48
  116. data/golden/shift_2d/expected/lir_02_inlined.txt +48 -48
  117. data/golden/shift_2d/expected/lir_03_cse.txt +48 -48
  118. data/golden/shift_2d/expected/lir_04_1_loop_fusion.txt +48 -48
  119. data/golden/shift_2d/expected/lir_04_loop_invcm.txt +48 -48
  120. data/golden/shift_2d/expected/lir_06_const_prop.txt +48 -48
  121. data/golden/shift_2d/expected/schema_ruby.rb +1 -1
  122. data/golden/simple_math/expected/schema_ruby.rb +1 -1
  123. data/golden/streaming_basics/expected/lir_02_inlined.txt +25 -25
  124. data/golden/streaming_basics/expected/lir_03_cse.txt +13 -13
  125. data/golden/streaming_basics/expected/lir_04_1_loop_fusion.txt +13 -13
  126. data/golden/streaming_basics/expected/lir_04_loop_invcm.txt +13 -13
  127. data/golden/streaming_basics/expected/lir_06_const_prop.txt +13 -13
  128. data/golden/streaming_basics/expected/schema_javascript.mjs +13 -13
  129. data/golden/streaming_basics/expected/schema_ruby.rb +14 -14
  130. data/golden/tuples/expected/lir_00_unoptimized.txt +4 -4
  131. data/golden/tuples/expected/lir_01_hoist_scalar_references.txt +4 -4
  132. data/golden/tuples/expected/lir_02_inlined.txt +4 -4
  133. data/golden/tuples/expected/lir_03_cse.txt +4 -4
  134. data/golden/tuples/expected/lir_04_1_loop_fusion.txt +4 -4
  135. data/golden/tuples/expected/lir_04_loop_invcm.txt +4 -4
  136. data/golden/tuples/expected/lir_06_const_prop.txt +4 -4
  137. data/golden/tuples/expected/schema_ruby.rb +1 -1
  138. data/golden/tuples_and_arrays/expected/lir_00_unoptimized.txt +1 -1
  139. data/golden/tuples_and_arrays/expected/lir_01_hoist_scalar_references.txt +1 -1
  140. data/golden/tuples_and_arrays/expected/lir_02_inlined.txt +17 -17
  141. data/golden/tuples_and_arrays/expected/lir_03_cse.txt +14 -14
  142. data/golden/tuples_and_arrays/expected/lir_04_1_loop_fusion.txt +14 -14
  143. data/golden/tuples_and_arrays/expected/lir_04_loop_invcm.txt +14 -14
  144. data/golden/tuples_and_arrays/expected/lir_06_const_prop.txt +14 -14
  145. data/golden/tuples_and_arrays/expected/schema_javascript.mjs +13 -13
  146. data/golden/tuples_and_arrays/expected/schema_ruby.rb +14 -14
  147. data/golden/us_tax_2024/expected/ast.txt +865 -0
  148. data/golden/us_tax_2024/expected/input_plan.txt +61 -0
  149. data/golden/us_tax_2024/expected/lir_00_unoptimized.txt +901 -0
  150. data/golden/us_tax_2024/expected/lir_01_hoist_scalar_references.txt +901 -0
  151. data/golden/us_tax_2024/expected/lir_02_inlined.txt +5178 -0
  152. data/golden/us_tax_2024/expected/lir_03_cse.txt +2499 -0
  153. data/golden/us_tax_2024/expected/lir_04_1_loop_fusion.txt +2519 -0
  154. data/golden/us_tax_2024/expected/lir_04_loop_invcm.txt +2499 -0
  155. data/golden/us_tax_2024/expected/lir_06_const_prop.txt +2499 -0
  156. data/golden/us_tax_2024/expected/nast.txt +976 -0
  157. data/golden/us_tax_2024/expected/schema_javascript.mjs +584 -0
  158. data/golden/us_tax_2024/expected/schema_ruby.rb +639 -0
  159. data/golden/us_tax_2024/expected/snast.txt +976 -0
  160. data/golden/us_tax_2024/expected.json +1 -0
  161. data/golden/us_tax_2024/input.json +168 -0
  162. data/golden/us_tax_2024/schema.kumi +203 -0
  163. data/golden/with_constants/expected/schema_ruby.rb +1 -1
  164. data/lib/kumi/core/analyzer/passes/lir/inline_declarations_pass.rb +227 -107
  165. data/lib/kumi/version.rb +1 -1
  166. metadata +33 -1
@@ -15,20 +15,18 @@ module Kumi
15
15
 
16
16
  MAX_PASSES.times do
17
17
  new_ops, changed = run_one_pass(current_ops)
18
-
19
18
  unless changed
20
19
  new_ops.freeze
21
- return state.with(:lir_module, new_ops).with(:lir_02_inlined_ops_by_decl, new_ops)
20
+ return state.with(:lir_module, new_ops)
21
+ .with(:lir_02_inlined_ops_by_decl, new_ops)
22
22
  end
23
23
  current_ops = new_ops
24
24
  end
25
-
26
25
  raise "LIR inlining did not converge after #{MAX_PASSES} passes."
27
26
  end
28
27
 
29
28
  private
30
29
 
31
- # --- UNCHANGED: Top-level pass logic ---
32
30
  def run_one_pass(ops_by_decl)
33
31
  @ops_by_decl = ops_by_decl
34
32
  @gamma = detect_all_gammas(@ops_by_decl)
@@ -36,102 +34,187 @@ module Kumi
36
34
  fused = {}
37
35
  @ops_by_decl.each do |name, payload|
38
36
  original_ops = Array(payload[:operations])
39
-
40
- # Call the new top-level inliner
41
- inlined_ops = inline_top_level_decl(original_ops)
42
-
43
- fused[name] = { operations: inlined_ops }
37
+ inlined_ops = inline_top_level_decl(original_ops)
38
+ fused[name] = { operations: inlined_ops }
44
39
  changed ||= (inlined_ops != original_ops)
45
40
  end
46
41
  [fused, changed]
47
42
  end
48
43
 
49
- # --- NEW: Top-level entry point for the recursive processor ---
44
+ # ---------------- core ----------------
45
+
46
+ Hoist = Struct.new(:ops, :target_depth, keyword_init: true)
47
+
50
48
  def inline_top_level_decl(ops)
51
- env = Env.new
52
- reg_map = {}
49
+ env = Env.new
50
+ reg_map = {}
53
51
  rename_map = {}
54
- processed_ops, hoisted_ops = process_and_hoist_block(ops, env, reg_map, rename_map)
52
+ processed, hoist_pkgs = process_and_hoist_block(ops, env, reg_map, rename_map)
55
53
 
56
- # Hoisting is not allowed at the top level of a declaration
57
- raise "Orphaned code was hoisted to top level" unless hoisted_ops.empty?
54
+ top_emit, bubble = hoist_pkgs.partition { |p| p.target_depth == 0 }
55
+ raise "Orphaned code hoist with target depth(s): #{bubble.map(&:target_depth).uniq.inspect}" unless bubble.empty?
58
56
 
59
- processed_ops
57
+ top_emit.flat_map(&:ops) + processed
60
58
  end
61
59
 
62
- # --- NEW: The core recursive block processor ---
63
- # Returns two arrays: [ processed_instructions_for_this_block, instructions_to_hoist_up_one_level ]
60
+ # returns [processed_ops, hoist_pkgs]
61
+ # returns [processed_ops, hoist_pkgs]
64
62
  def process_and_hoist_block(block_ops, env, reg_map, rename_map)
65
- out_ops = []
66
- hoisted_out_ops = [] # Operations to be returned to the parent scope
63
+ out = []
64
+ hoisted_pkgs = []
67
65
  i = 0
68
-
69
66
  while i < block_ops.length
70
67
  ins = block_ops[i]
71
68
  case ins.opcode
72
69
  when :LoopStart
73
- end_idx = find_matching_loop_end(block_ops, i)
70
+ end_idx = find_matching_loop_end(block_ops, i)
74
71
  loop_body = block_ops[(i + 1)...end_idx]
75
72
 
76
73
  env.push(ins)
77
- processed_body, hoisted_from_child = process_and_hoist_block(loop_body, env, reg_map, rename_map)
74
+ child_rename = {}
75
+ processed_body, child_hoists =
76
+ process_and_hoist_block(loop_body, env, reg_map, child_rename)
77
+
78
+ depth_here = env.axes.length
79
+ child_el = ins.attributes[:as_element]
80
+ child_ix = ins.attributes[:as_index]
81
+
82
+ # Partition hoists: those that belong *inside* this loop vs bubble upward
83
+ inside_pkgs, bubble_pkgs = child_hoists.partition { |p| p.target_depth == depth_here }
84
+
85
+ # Renames: never let aliases to this loop's el/idx escape upward
86
+ safe_pairs = child_rename.reject { |_, v| v == child_el || v == child_ix }
78
87
  env.pop
79
88
 
80
- # This is the crucial step: emit code hoisted from the child loop
81
- # BEFORE emitting the child loop itself.
82
- out_ops.concat(hoisted_from_child)
89
+ # Merge only safe renames into outer scope
90
+ rename_map.merge!(safe_pairs)
91
+
92
+ # Emit loop shell
93
+ out << rewrite(ins, reg_map, rename_map)
94
+
95
+ # Local view inside loop: apply both local and outer renames, with local taking precedence
96
+ local_map = child_rename.merge(rename_map)
97
+
98
+ # Emit hoists that belong at this depth *inside* the loop, before the body
99
+ inside_ops = inside_pkgs.flat_map(&:ops)
100
+ out.concat(rewrite_block(inside_ops, local_map))
101
+
102
+ # Emit rewritten body
103
+ out.concat(rewrite_block(processed_body, local_map))
104
+
105
+ # Close loop
106
+ out << rewrite(block_ops[end_idx], reg_map, rename_map)
83
107
 
84
- # Now, emit the reconstructed loop with its processed body
85
- out_ops << rewrite(ins, reg_map, rename_map)
86
- out_ops.concat(processed_body)
87
- out_ops << rewrite(block_ops[end_idx], reg_map, rename_map)
108
+ # Bubble remaining hoists to outer scopes
109
+ hoisted_pkgs.concat(bubble_pkgs)
88
110
 
89
- i = end_idx # Jump iterator past the entire loop block
111
+ # Extra safety: ensure no aliases to this loop's el/idx remain in outer map
112
+ rename_map.delete_if { |_, v| v == child_el || v == child_ix }
113
+
114
+ i = end_idx
90
115
 
91
116
  when :LoadDeclaration
92
- # This helper now decides if ops go inline or get returned for hoisting
93
- inline_ops, hoist_ops = handle_load_declaration(ins, env, reg_map, rename_map)
94
- out_ops.concat(inline_ops)
95
- hoisted_out_ops.concat(hoist_ops)
117
+ inline_ops, new_pkgs = handle_load_declaration(ins, env, reg_map, rename_map)
118
+ out.concat(inline_ops)
119
+ hoisted_pkgs.concat(new_pkgs)
96
120
 
97
121
  else
98
- out_ops << rewrite(ins, reg_map, rename_map)
122
+ out << rewrite(ins, reg_map, rename_map)
99
123
  end
100
124
  i += 1
101
125
  end
102
-
103
- [out_ops, hoisted_out_ops]
126
+ [out, hoisted_pkgs]
104
127
  end
105
128
 
106
- # --- NEW: Helper to manage LoadDeclaration logic ---
107
- # Returns two arrays: [ instructions_to_add_inline, instructions_to_hoist ]
108
- def handle_load_declaration(ins, env, reg_map, rename_map)
129
+ # returns [inline_ops, hoist_pkgs]
130
+ def handle_load_declaration(ins, env, _reg_map, rename_map)
109
131
  callee = ins.immediates.first.value.to_sym
110
- decl_axes = ins.attributes.fetch(:axes)
111
- site_axes = env.axes
132
+
133
+ # axes presence and agreement with callee gamma
134
+ decl_axes = ins.attributes.fetch(:axes) { raise "LoadDeclaration missing :axes for #{callee}" }
135
+ gamma_axes = @gamma.fetch(callee).axes
136
+ raise "axes mismatch for #{callee}: decl=#{decl_axes.inspect} gamma=#{gamma_axes.inspect}" unless decl_axes == gamma_axes
112
137
 
113
138
  body, yield_reg, callee_regs = inline_callee_core(callee)
114
139
  remap = remap_axes(callee_regs, env)
115
- _acc, fresh_ops = freshen(body, reg_map, pre_map: remap)
116
140
 
117
- rename_yielded_register(ins, yield_reg, reg_map, remap, rename_map)
141
+ # per-callsite freshening
142
+ local_reg_map = {}
143
+ _acc, fresh_ops = freshen(body, local_reg_map, pre_map: remap)
144
+
145
+ # recursively process nested calls
146
+ processed_inline, nested_pkgs = process_and_hoist_block(fresh_ops, env, {}, rename_map)
147
+
148
+ # compute yielded register mapping, then resolve through any renames created by nested inlines
149
+ mapped_yield =
150
+ local_reg_map[yield_reg] || remap[yield_reg] ||
151
+ (raise "inliner: yielded reg #{yield_reg} not produced in inlined body for #{callee}")
152
+ resolved_yield = resolve_rename(mapped_yield, rename_map)
153
+
154
+ # sanity: resolved_yield must be definable at site
155
+ emitted_defs = processed_inline.map(&:result_register).compact +
156
+ nested_pkgs.flat_map { |p| p.ops }.map(&:result_register).compact
157
+ unless emitted_defs.include?(resolved_yield) || env.ambient_regs.include?(resolved_yield)
158
+ raise "inliner: mapped yield #{resolved_yield} has no def in emitted ops for #{callee}\n" \
159
+ "original yield: #{yield_reg}\n" \
160
+ "inline defs size: #{processed_inline.count { |x| x.result_register }}\n" \
161
+ "nested hoist defs size: #{nested_pkgs.flat_map { |p| p.ops }.count { |x| x.result_register }}"
162
+ end
163
+
164
+ # final rename for call site result uses the resolved register
165
+ rename_map[ins.result_register] = resolved_yield
166
+
167
+ # decide placement by depth
168
+ site_depth = env.axes.length
169
+ callee_depth = decl_axes.length
170
+
171
+ if callee_depth < site_depth
172
+ forb = forbidden_ambient_after(callee_depth, env)
173
+ used = uses_of(processed_inline)
174
+ bad = used & forb
175
+ unless bad.empty?
176
+ raise "scope error: would hoist ops using deeper-axis regs #{bad.inspect} " \
177
+ "(callee_depth=#{callee_depth}, site_depth=#{site_depth})"
178
+ end
179
+ pkgs = nested_pkgs + [Hoist.new(ops: processed_inline, target_depth: callee_depth)]
180
+ [[], pkgs]
181
+
182
+ elsif callee_depth == site_depth
183
+ emit, bubble = nested_pkgs.partition { |p| p.target_depth == site_depth }
184
+ [(emit.flat_map(&:ops) + processed_inline), bubble]
118
185
 
119
- # Case 1: Hoisting
120
- if prefix?(decl_axes, site_axes) && decl_axes.length < site_axes.length
121
- [[], fresh_ops] # Return ops in the 'hoist' bucket
122
- # Case 2: In-place
123
- elsif decl_axes == site_axes
124
- [fresh_ops, []] # Return ops in the 'inline' bucket
125
- # Case 3: Cannot inline
126
186
  else
127
- [[rewrite(ins, reg_map, rename_map)], []]
187
+ [[rewrite(ins, {}, rename_map)], []]
128
188
  end
129
189
  end
130
190
 
131
- # --- UNCHANGED AND ORIGINAL HELPERS ---
191
+ # ---------------- helpers ----------------
192
+ def rewrite_block(ops, rename)
193
+ # Ensure late-added renames apply to a block we built earlier.
194
+ ops.map { |ins| rewrite(ins, {}, rename) }
195
+ end
196
+
197
+ def resolve_rename(reg, rename)
198
+ seen = {}
199
+ cur = reg
200
+ while (n = rename[cur]) && !seen[n]
201
+ seen[cur] = true
202
+ cur = n
203
+ end
204
+ cur
205
+ end
206
+
207
+ def uses_of(ops)
208
+ ops.flat_map { |x| Array(x.inputs) }.compact
209
+ end
210
+
211
+ def forbidden_ambient_after(depth, env)
212
+ env.frames_after(depth).flat_map { |f| [f[:el], f[:idx]] }
213
+ end
132
214
 
133
215
  def find_matching_loop_end(ops, start_index)
134
- depth = 1; (start_index + 1...ops.length).each do |i|
216
+ depth = 1
217
+ (start_index + 1...ops.length).each do |i|
135
218
  op = ops[i].opcode
136
219
  depth += 1 if op == :LoopStart
137
220
  depth -= 1 if op == :LoopEnd
@@ -143,47 +226,57 @@ module Kumi
143
226
  def remap_axes(callee_axis_regs, env)
144
227
  callee_axis_regs.each_with_object({}) do |r, h|
145
228
  caller = env.reg_for_axis(r[:axis])
146
- h[r[:el]] = caller[:el]
229
+ h[r[:el]] = caller[:el]
147
230
  h[r[:idx]] = caller[:idx]
148
231
  end
149
232
  end
150
233
 
151
- def rename_yielded_register(ins, yielded_reg, reg_map, axis_remap, rename)
152
- return unless ins.result_register && yielded_reg
153
-
154
- mapped = reg_map.fetch(yielded_reg, axis_remap.fetch(yielded_reg, yielded_reg))
155
- rename[ins.result_register] = mapped
156
- end
157
-
158
- # (Your original `detect_all_gammas`, `detect_gamma`, `inline_callee_core`,
159
- # `Env`, `freshen`, `rewrite`, and `prefix?` methods go here, unchanged)
160
234
  class Env
161
235
  def initialize = @frames = []
162
236
  def axes = @frames.map { _1[:axis] }
237
+ def ambient_regs = @frames.flat_map { |f| [f[:el], f[:idx]] }
163
238
 
164
239
  def push(loop_ins)
165
- @frames << { axis: loop_ins.attributes[:axis], el: loop_ins.attributes[:as_element], idx: loop_ins.attributes[:as_index] }
240
+ @frames << {
241
+ axis: loop_ins.attributes[:axis],
242
+ el: loop_ins.attributes[:as_element],
243
+ idx: loop_ins.attributes[:as_index]
244
+ }
166
245
  end
167
246
 
168
247
  def pop = @frames.pop
169
- def reg_for_axis(axis) = @frames.reverse.find { _1[:axis] == axis } || raise("no element for #{axis}")
248
+
249
+ def reg_for_axis(axis)
250
+ @frames.reverse.find { _1[:axis] == axis } ||
251
+ raise("no element for axis=#{axis.inspect}")
252
+ end
253
+
254
+ def frames_after(depth)
255
+ @frames[depth..] || []
256
+ end
257
+ end
258
+
259
+ def detect_all_gammas(ops_by_decl)
260
+ ops_by_decl.transform_values { |p| detect_gamma(Array(p[:operations])) }
170
261
  end
171
262
 
172
- def detect_all_gammas(ops_by_decl) = ops_by_decl.transform_values { |p| detect_gamma(Array(p[:operations])) }
173
263
  GammaInfo = Struct.new(:start_idx, :axes, :axis_regs, keyword_init: true)
264
+
174
265
  def detect_gamma(ops)
175
266
  frames = []
176
267
  ops.each do |ins|
177
268
  case ins.opcode
178
269
  when :LoopStart
179
- frames << { axis: ins.attributes[:axis], el: ins.attributes[:as_element], idx: ins.attributes[:as_index] }
270
+ frames << {
271
+ axis: ins.attributes[:axis],
272
+ el: ins.attributes[:as_element],
273
+ idx: ins.attributes[:as_index]
274
+ }
180
275
  when :LoopEnd
181
276
  frames.pop
182
277
  when :Yield
183
278
  axes = frames.map { _1[:axis] }
184
- axis_regs = frames.map do |f|
185
- { axis: f[:axis], el: f[:el], idx: f[:idx] }
186
- end
279
+ axis_regs = frames.map { |f| { axis: f[:axis], el: f[:el], idx: f[:idx] } }
187
280
  return GammaInfo.new(start_idx: nil, axes: axes, axis_regs: axis_regs)
188
281
  end
189
282
  end
@@ -191,33 +284,34 @@ module Kumi
191
284
  end
192
285
 
193
286
  def inline_callee_core(callee_name)
194
- ops = Array(@ops_by_decl.fetch(callee_name)[:operations])
287
+ ops = Array(@ops_by_decl.fetch(callee_name)[:operations])
195
288
  info = @gamma.fetch(callee_name)
196
289
  axes = info.axes
197
- k = axes.length
198
- yield_index = ops.rindex { |ins| ins.opcode == :Yield } or raise "callee #{callee_name} has no Yield"
199
- yielded_reg = Array(ops[yield_index].inputs).first
200
- first_loop_index = ops.index { |ins| ins.opcode == :LoopStart }
201
- return [ops[0...yield_index], yielded_reg, info.axis_regs] unless first_loop_index
202
-
203
- prologue = ops[0...first_loop_index]
204
- main_part = ops[first_loop_index...yield_index]
290
+ k = axes.length
291
+
292
+ yi = ops.rindex { |x| x.opcode == :Yield } or raise "callee #{callee_name} has no Yield"
293
+ yielded_reg = Array(ops[yi].inputs).first
294
+
295
+ first_loop = ops.index { |x| x.opcode == :LoopStart }
296
+ return [ops[0...yi], yielded_reg, info.axis_regs] unless first_loop
297
+
298
+ prologue = ops[0...first_loop]
299
+ main = ops[first_loop...yi]
205
300
  inner_body = []
206
301
  open_gamma = 0
207
- kind_stack = []
208
- main_part.each do |ins|
302
+ stack = []
303
+ main.each do |ins|
209
304
  case ins.opcode
210
305
  when :LoopStart
211
306
  if open_gamma < k && ins.attributes[:axis] == axes[open_gamma]
212
- kind_stack << :gamma
307
+ stack << :gamma
213
308
  open_gamma += 1
214
309
  else
215
- kind_stack << :inner
310
+ stack << :inner
216
311
  inner_body << ins
217
312
  end
218
313
  when :LoopEnd
219
- kind = kind_stack.pop or raise "unbalanced loops in #{callee_name}"
220
- inner_body << ins if kind == :inner
314
+ inner_body << ins if stack.pop == :inner
221
315
  else
222
316
  inner_body << ins
223
317
  end
@@ -228,44 +322,70 @@ module Kumi
228
322
  def freshen(block_ops, reg_map, pre_map: {})
229
323
  acc_map = {}
230
324
  new_ops = block_ops.map do |ins|
325
+ attrs = (ins.attributes || {}).dup
326
+
327
+ if ins.opcode == :LoopStart
328
+ attrs[:id] = @ids.generate_loop_id
329
+ new_el = @ids.generate_temp
330
+ new_idx = @ids.generate_temp
331
+ reg_map[attrs[:as_element]] = new_el
332
+ reg_map[attrs[:as_index]] = new_idx
333
+ attrs[:as_element] = new_el
334
+ attrs[:as_index] = new_idx
335
+ end
336
+
231
337
  res = ins.result_register
232
338
  reg_map[res] ||= @ids.generate_temp if res
339
+
233
340
  new_inputs = Array(ins.inputs).map do |r|
234
341
  r1 = pre_map.fetch(r, r)
235
342
  reg_map.fetch(r1, r1)
236
343
  end
237
- attrs = (ins.attributes || {}).dup
344
+
238
345
  case ins.opcode
239
346
  when :DeclareAccumulator
240
- original_acc = ins.result_register
241
- acc_map[original_acc] ||= @ids.generate_acc
242
- res = acc_map[original_acc]
347
+ orig = ins.result_register
348
+ acc_map[orig] ||= @ids.generate_acc
349
+ res = acc_map[orig]
243
350
  when :Accumulate
244
- original_acc = ins.result_register
245
- acc_map[original_acc] ||= @ids.generate_acc
246
- res = acc_map[original_acc]
351
+ orig = ins.result_register
352
+ acc_map[orig] ||= @ids.generate_acc
353
+ res = acc_map[orig]
247
354
  when :LoadAccumulator
248
- original_acc = ins.inputs.first
249
- acc_map[original_acc] ||= @ids.generate_acc
250
- new_inputs[0] = acc_map[original_acc]
251
- when :LoopStart
252
- attrs[:id] = @ids.generate_loop_id
355
+ orig = ins.inputs.first
356
+ acc_map[orig] ||= @ids.generate_acc
357
+ new_inputs[0] = acc_map[orig]
253
358
  end
254
- LIR::Instruction.new(opcode: ins.opcode, result_register: res ? reg_map.fetch(res, res) : nil, stamp: ins.stamp, inputs: new_inputs,
255
- immediates: ins.immediates, attributes: attrs, location: ins.location)
359
+
360
+ LIR::Instruction.new(
361
+ opcode: ins.opcode,
362
+ result_register: res ? reg_map.fetch(res, res) : nil,
363
+ stamp: ins.stamp,
364
+ inputs: new_inputs,
365
+ immediates: ins.immediates,
366
+ attributes: attrs,
367
+ location: ins.location
368
+ )
256
369
  end
257
370
  [acc_map, new_ops]
258
371
  end
259
372
 
260
373
  def rewrite(ins, _reg_map, rename)
261
- new_inputs = Array(ins.inputs).map do |r|
262
- rename.fetch(r, r)
263
- end
264
- LIR::Instruction.new(opcode: ins.opcode, result_register: ins.result_register, stamp: ins.stamp, inputs: new_inputs,
265
- immediates: ins.immediates, attributes: ins.attributes, location: ins.location)
374
+ new_inputs = Array(ins.inputs).map { |r| rename.fetch(r, r) }
375
+ LIR::Instruction.new(
376
+ opcode: ins.opcode,
377
+ result_register: ins.result_register,
378
+ stamp: ins.stamp,
379
+ inputs: new_inputs,
380
+ immediates: ins.immediates,
381
+ attributes: ins.attributes,
382
+ location: ins.location
383
+ )
266
384
  end
267
385
 
268
- def prefix?(pre, full) = pre.each_with_index.all? { |tok, i| full[i] == tok }
386
+ def prefix?(pre, full)
387
+ pre.each_with_index.all? { |tok, i| full[i] == tok }
388
+ end
269
389
  end
270
390
  end
271
391
  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.23"
4
+ VERSION = "0.0.25"
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.23
4
+ version: 0.0.25
5
5
  platform: ruby
6
6
  authors:
7
7
  - André Muta
@@ -265,6 +265,22 @@ files:
265
265
  - golden/hierarchical_complex/expected/snast.txt
266
266
  - golden/hierarchical_complex/input.json
267
267
  - golden/hierarchical_complex/schema.kumi
268
+ - golden/inline_rename_scope_leak/expected.json
269
+ - golden/inline_rename_scope_leak/expected/ast.txt
270
+ - golden/inline_rename_scope_leak/expected/input_plan.txt
271
+ - golden/inline_rename_scope_leak/expected/lir_00_unoptimized.txt
272
+ - golden/inline_rename_scope_leak/expected/lir_01_hoist_scalar_references.txt
273
+ - golden/inline_rename_scope_leak/expected/lir_02_inlined.txt
274
+ - golden/inline_rename_scope_leak/expected/lir_03_cse.txt
275
+ - golden/inline_rename_scope_leak/expected/lir_04_1_loop_fusion.txt
276
+ - golden/inline_rename_scope_leak/expected/lir_04_loop_invcm.txt
277
+ - golden/inline_rename_scope_leak/expected/lir_06_const_prop.txt
278
+ - golden/inline_rename_scope_leak/expected/nast.txt
279
+ - golden/inline_rename_scope_leak/expected/schema_javascript.mjs
280
+ - golden/inline_rename_scope_leak/expected/schema_ruby.rb
281
+ - golden/inline_rename_scope_leak/expected/snast.txt
282
+ - golden/inline_rename_scope_leak/input.json
283
+ - golden/inline_rename_scope_leak/schema.kumi
268
284
  - golden/input_reference/expected.json
269
285
  - golden/input_reference/expected/ast.txt
270
286
  - golden/input_reference/expected/input_plan.txt
@@ -521,6 +537,22 @@ files:
521
537
  - golden/tuples_and_arrays/expected/snast.txt
522
538
  - golden/tuples_and_arrays/input.json
523
539
  - golden/tuples_and_arrays/schema.kumi
540
+ - golden/us_tax_2024/expected.json
541
+ - golden/us_tax_2024/expected/ast.txt
542
+ - golden/us_tax_2024/expected/input_plan.txt
543
+ - golden/us_tax_2024/expected/lir_00_unoptimized.txt
544
+ - golden/us_tax_2024/expected/lir_01_hoist_scalar_references.txt
545
+ - golden/us_tax_2024/expected/lir_02_inlined.txt
546
+ - golden/us_tax_2024/expected/lir_03_cse.txt
547
+ - golden/us_tax_2024/expected/lir_04_1_loop_fusion.txt
548
+ - golden/us_tax_2024/expected/lir_04_loop_invcm.txt
549
+ - golden/us_tax_2024/expected/lir_06_const_prop.txt
550
+ - golden/us_tax_2024/expected/nast.txt
551
+ - golden/us_tax_2024/expected/schema_javascript.mjs
552
+ - golden/us_tax_2024/expected/schema_ruby.rb
553
+ - golden/us_tax_2024/expected/snast.txt
554
+ - golden/us_tax_2024/input.json
555
+ - golden/us_tax_2024/schema.kumi
524
556
  - golden/with_constants/expected/ast.txt
525
557
  - golden/with_constants/expected/input_plan.txt
526
558
  - golden/with_constants/expected/lir_00_unoptimized.txt