machine-dialect 0.1.0a1__py3-none-any.whl

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 (268) hide show
  1. machine_dialect/__main__.py +667 -0
  2. machine_dialect/agent/__init__.py +5 -0
  3. machine_dialect/agent/agent.py +360 -0
  4. machine_dialect/ast/__init__.py +95 -0
  5. machine_dialect/ast/ast_node.py +35 -0
  6. machine_dialect/ast/call_expression.py +82 -0
  7. machine_dialect/ast/dict_extraction.py +60 -0
  8. machine_dialect/ast/expressions.py +439 -0
  9. machine_dialect/ast/literals.py +309 -0
  10. machine_dialect/ast/program.py +35 -0
  11. machine_dialect/ast/statements.py +1433 -0
  12. machine_dialect/ast/tests/test_ast_string_representation.py +62 -0
  13. machine_dialect/ast/tests/test_boolean_literal.py +29 -0
  14. machine_dialect/ast/tests/test_collection_hir.py +138 -0
  15. machine_dialect/ast/tests/test_define_statement.py +142 -0
  16. machine_dialect/ast/tests/test_desugar.py +541 -0
  17. machine_dialect/ast/tests/test_foreach_desugar.py +245 -0
  18. machine_dialect/cfg/__init__.py +6 -0
  19. machine_dialect/cfg/config.py +156 -0
  20. machine_dialect/cfg/examples.py +221 -0
  21. machine_dialect/cfg/generate_with_ai.py +187 -0
  22. machine_dialect/cfg/openai_generation.py +200 -0
  23. machine_dialect/cfg/parser.py +94 -0
  24. machine_dialect/cfg/tests/__init__.py +1 -0
  25. machine_dialect/cfg/tests/test_cfg_parser.py +252 -0
  26. machine_dialect/cfg/tests/test_config.py +188 -0
  27. machine_dialect/cfg/tests/test_examples.py +391 -0
  28. machine_dialect/cfg/tests/test_generate_with_ai.py +354 -0
  29. machine_dialect/cfg/tests/test_openai_generation.py +256 -0
  30. machine_dialect/codegen/__init__.py +5 -0
  31. machine_dialect/codegen/bytecode_module.py +89 -0
  32. machine_dialect/codegen/bytecode_serializer.py +300 -0
  33. machine_dialect/codegen/opcodes.py +101 -0
  34. machine_dialect/codegen/register_codegen.py +1996 -0
  35. machine_dialect/codegen/symtab.py +208 -0
  36. machine_dialect/codegen/tests/__init__.py +1 -0
  37. machine_dialect/codegen/tests/test_array_operations_codegen.py +295 -0
  38. machine_dialect/codegen/tests/test_bytecode_serializer.py +185 -0
  39. machine_dialect/codegen/tests/test_register_codegen_ssa.py +324 -0
  40. machine_dialect/codegen/tests/test_symtab.py +418 -0
  41. machine_dialect/codegen/vm_serializer.py +621 -0
  42. machine_dialect/compiler/__init__.py +18 -0
  43. machine_dialect/compiler/compiler.py +197 -0
  44. machine_dialect/compiler/config.py +149 -0
  45. machine_dialect/compiler/context.py +149 -0
  46. machine_dialect/compiler/phases/__init__.py +19 -0
  47. machine_dialect/compiler/phases/bytecode_optimization.py +90 -0
  48. machine_dialect/compiler/phases/codegen.py +40 -0
  49. machine_dialect/compiler/phases/hir_generation.py +39 -0
  50. machine_dialect/compiler/phases/mir_generation.py +86 -0
  51. machine_dialect/compiler/phases/optimization.py +110 -0
  52. machine_dialect/compiler/phases/parsing.py +39 -0
  53. machine_dialect/compiler/pipeline.py +143 -0
  54. machine_dialect/compiler/tests/__init__.py +1 -0
  55. machine_dialect/compiler/tests/test_compiler.py +568 -0
  56. machine_dialect/compiler/vm_runner.py +173 -0
  57. machine_dialect/errors/__init__.py +32 -0
  58. machine_dialect/errors/exceptions.py +369 -0
  59. machine_dialect/errors/messages.py +82 -0
  60. machine_dialect/errors/tests/__init__.py +0 -0
  61. machine_dialect/errors/tests/test_expected_token_errors.py +188 -0
  62. machine_dialect/errors/tests/test_name_errors.py +118 -0
  63. machine_dialect/helpers/__init__.py +0 -0
  64. machine_dialect/helpers/stopwords.py +225 -0
  65. machine_dialect/helpers/validators.py +30 -0
  66. machine_dialect/lexer/__init__.py +9 -0
  67. machine_dialect/lexer/constants.py +23 -0
  68. machine_dialect/lexer/lexer.py +907 -0
  69. machine_dialect/lexer/tests/__init__.py +0 -0
  70. machine_dialect/lexer/tests/helpers.py +86 -0
  71. machine_dialect/lexer/tests/test_apostrophe_identifiers.py +122 -0
  72. machine_dialect/lexer/tests/test_backtick_identifiers.py +140 -0
  73. machine_dialect/lexer/tests/test_boolean_literals.py +108 -0
  74. machine_dialect/lexer/tests/test_case_insensitive_keywords.py +188 -0
  75. machine_dialect/lexer/tests/test_comments.py +200 -0
  76. machine_dialect/lexer/tests/test_double_asterisk_keywords.py +127 -0
  77. machine_dialect/lexer/tests/test_lexer_position.py +113 -0
  78. machine_dialect/lexer/tests/test_list_tokens.py +282 -0
  79. machine_dialect/lexer/tests/test_stopwords.py +80 -0
  80. machine_dialect/lexer/tests/test_strict_equality.py +129 -0
  81. machine_dialect/lexer/tests/test_token.py +41 -0
  82. machine_dialect/lexer/tests/test_tokenization.py +294 -0
  83. machine_dialect/lexer/tests/test_underscore_literals.py +343 -0
  84. machine_dialect/lexer/tests/test_url_literals.py +169 -0
  85. machine_dialect/lexer/tokens.py +487 -0
  86. machine_dialect/linter/__init__.py +10 -0
  87. machine_dialect/linter/__main__.py +144 -0
  88. machine_dialect/linter/linter.py +154 -0
  89. machine_dialect/linter/rules/__init__.py +8 -0
  90. machine_dialect/linter/rules/base.py +112 -0
  91. machine_dialect/linter/rules/statement_termination.py +99 -0
  92. machine_dialect/linter/tests/__init__.py +1 -0
  93. machine_dialect/linter/tests/mdrules/__init__.py +0 -0
  94. machine_dialect/linter/tests/mdrules/test_md101_statement_termination.py +181 -0
  95. machine_dialect/linter/tests/test_linter.py +81 -0
  96. machine_dialect/linter/tests/test_rules.py +110 -0
  97. machine_dialect/linter/tests/test_violations.py +71 -0
  98. machine_dialect/linter/violations.py +51 -0
  99. machine_dialect/mir/__init__.py +69 -0
  100. machine_dialect/mir/analyses/__init__.py +20 -0
  101. machine_dialect/mir/analyses/alias_analysis.py +315 -0
  102. machine_dialect/mir/analyses/dominance_analysis.py +49 -0
  103. machine_dialect/mir/analyses/escape_analysis.py +286 -0
  104. machine_dialect/mir/analyses/loop_analysis.py +272 -0
  105. machine_dialect/mir/analyses/tests/test_type_analysis.py +736 -0
  106. machine_dialect/mir/analyses/type_analysis.py +448 -0
  107. machine_dialect/mir/analyses/use_def_chains.py +232 -0
  108. machine_dialect/mir/basic_block.py +385 -0
  109. machine_dialect/mir/dataflow.py +445 -0
  110. machine_dialect/mir/debug_info.py +208 -0
  111. machine_dialect/mir/hir_to_mir.py +1738 -0
  112. machine_dialect/mir/mir_dumper.py +366 -0
  113. machine_dialect/mir/mir_function.py +167 -0
  114. machine_dialect/mir/mir_instructions.py +1877 -0
  115. machine_dialect/mir/mir_interpreter.py +556 -0
  116. machine_dialect/mir/mir_module.py +225 -0
  117. machine_dialect/mir/mir_printer.py +480 -0
  118. machine_dialect/mir/mir_transformer.py +410 -0
  119. machine_dialect/mir/mir_types.py +367 -0
  120. machine_dialect/mir/mir_validation.py +455 -0
  121. machine_dialect/mir/mir_values.py +268 -0
  122. machine_dialect/mir/optimization_config.py +233 -0
  123. machine_dialect/mir/optimization_pass.py +251 -0
  124. machine_dialect/mir/optimization_pipeline.py +355 -0
  125. machine_dialect/mir/optimizations/__init__.py +84 -0
  126. machine_dialect/mir/optimizations/algebraic_simplification.py +733 -0
  127. machine_dialect/mir/optimizations/branch_prediction.py +372 -0
  128. machine_dialect/mir/optimizations/constant_propagation.py +634 -0
  129. machine_dialect/mir/optimizations/cse.py +398 -0
  130. machine_dialect/mir/optimizations/dce.py +288 -0
  131. machine_dialect/mir/optimizations/inlining.py +551 -0
  132. machine_dialect/mir/optimizations/jump_threading.py +487 -0
  133. machine_dialect/mir/optimizations/licm.py +405 -0
  134. machine_dialect/mir/optimizations/loop_unrolling.py +366 -0
  135. machine_dialect/mir/optimizations/strength_reduction.py +422 -0
  136. machine_dialect/mir/optimizations/tail_call.py +207 -0
  137. machine_dialect/mir/optimizations/tests/test_loop_unrolling.py +483 -0
  138. machine_dialect/mir/optimizations/type_narrowing.py +397 -0
  139. machine_dialect/mir/optimizations/type_specialization.py +447 -0
  140. machine_dialect/mir/optimizations/type_specific.py +906 -0
  141. machine_dialect/mir/optimize_mir.py +89 -0
  142. machine_dialect/mir/pass_manager.py +391 -0
  143. machine_dialect/mir/profiling/__init__.py +26 -0
  144. machine_dialect/mir/profiling/profile_collector.py +318 -0
  145. machine_dialect/mir/profiling/profile_data.py +372 -0
  146. machine_dialect/mir/profiling/profile_reader.py +272 -0
  147. machine_dialect/mir/profiling/profile_writer.py +226 -0
  148. machine_dialect/mir/register_allocation.py +302 -0
  149. machine_dialect/mir/reporting/__init__.py +17 -0
  150. machine_dialect/mir/reporting/optimization_reporter.py +314 -0
  151. machine_dialect/mir/reporting/report_formatter.py +289 -0
  152. machine_dialect/mir/ssa_construction.py +342 -0
  153. machine_dialect/mir/tests/__init__.py +1 -0
  154. machine_dialect/mir/tests/test_algebraic_associativity.py +204 -0
  155. machine_dialect/mir/tests/test_algebraic_complex_patterns.py +221 -0
  156. machine_dialect/mir/tests/test_algebraic_division.py +126 -0
  157. machine_dialect/mir/tests/test_algebraic_simplification.py +863 -0
  158. machine_dialect/mir/tests/test_basic_block.py +425 -0
  159. machine_dialect/mir/tests/test_branch_prediction.py +459 -0
  160. machine_dialect/mir/tests/test_call_lowering.py +168 -0
  161. machine_dialect/mir/tests/test_collection_lowering.py +604 -0
  162. machine_dialect/mir/tests/test_cross_block_constant_propagation.py +255 -0
  163. machine_dialect/mir/tests/test_custom_passes.py +166 -0
  164. machine_dialect/mir/tests/test_debug_info.py +285 -0
  165. machine_dialect/mir/tests/test_dict_extraction_lowering.py +192 -0
  166. machine_dialect/mir/tests/test_dictionary_lowering.py +299 -0
  167. machine_dialect/mir/tests/test_double_negation.py +231 -0
  168. machine_dialect/mir/tests/test_escape_analysis.py +233 -0
  169. machine_dialect/mir/tests/test_hir_to_mir.py +465 -0
  170. machine_dialect/mir/tests/test_hir_to_mir_complete.py +389 -0
  171. machine_dialect/mir/tests/test_hir_to_mir_simple.py +130 -0
  172. machine_dialect/mir/tests/test_inlining.py +435 -0
  173. machine_dialect/mir/tests/test_licm.py +472 -0
  174. machine_dialect/mir/tests/test_mir_dumper.py +313 -0
  175. machine_dialect/mir/tests/test_mir_instructions.py +445 -0
  176. machine_dialect/mir/tests/test_mir_module.py +860 -0
  177. machine_dialect/mir/tests/test_mir_printer.py +387 -0
  178. machine_dialect/mir/tests/test_mir_types.py +123 -0
  179. machine_dialect/mir/tests/test_mir_types_enhanced.py +132 -0
  180. machine_dialect/mir/tests/test_mir_validation.py +378 -0
  181. machine_dialect/mir/tests/test_mir_values.py +168 -0
  182. machine_dialect/mir/tests/test_one_based_indexing.py +202 -0
  183. machine_dialect/mir/tests/test_optimization_helpers.py +60 -0
  184. machine_dialect/mir/tests/test_optimization_pipeline.py +554 -0
  185. machine_dialect/mir/tests/test_optimization_reporter.py +318 -0
  186. machine_dialect/mir/tests/test_pass_manager.py +294 -0
  187. machine_dialect/mir/tests/test_pass_registration.py +64 -0
  188. machine_dialect/mir/tests/test_profiling.py +356 -0
  189. machine_dialect/mir/tests/test_register_allocation.py +307 -0
  190. machine_dialect/mir/tests/test_report_formatters.py +372 -0
  191. machine_dialect/mir/tests/test_ssa_construction.py +433 -0
  192. machine_dialect/mir/tests/test_tail_call.py +236 -0
  193. machine_dialect/mir/tests/test_type_annotated_instructions.py +192 -0
  194. machine_dialect/mir/tests/test_type_narrowing.py +277 -0
  195. machine_dialect/mir/tests/test_type_specialization.py +421 -0
  196. machine_dialect/mir/tests/test_type_specific_optimization.py +545 -0
  197. machine_dialect/mir/tests/test_type_specific_optimization_advanced.py +382 -0
  198. machine_dialect/mir/type_inference.py +368 -0
  199. machine_dialect/parser/__init__.py +12 -0
  200. machine_dialect/parser/enums.py +45 -0
  201. machine_dialect/parser/parser.py +3655 -0
  202. machine_dialect/parser/protocols.py +11 -0
  203. machine_dialect/parser/symbol_table.py +169 -0
  204. machine_dialect/parser/tests/__init__.py +0 -0
  205. machine_dialect/parser/tests/helper_functions.py +193 -0
  206. machine_dialect/parser/tests/test_action_statements.py +334 -0
  207. machine_dialect/parser/tests/test_boolean_literal_expressions.py +152 -0
  208. machine_dialect/parser/tests/test_call_statements.py +154 -0
  209. machine_dialect/parser/tests/test_call_statements_errors.py +187 -0
  210. machine_dialect/parser/tests/test_collection_mutations.py +264 -0
  211. machine_dialect/parser/tests/test_conditional_expressions.py +343 -0
  212. machine_dialect/parser/tests/test_define_integration.py +468 -0
  213. machine_dialect/parser/tests/test_define_statements.py +311 -0
  214. machine_dialect/parser/tests/test_dict_extraction.py +115 -0
  215. machine_dialect/parser/tests/test_empty_literal.py +155 -0
  216. machine_dialect/parser/tests/test_float_literal_expressions.py +163 -0
  217. machine_dialect/parser/tests/test_identifier_expressions.py +57 -0
  218. machine_dialect/parser/tests/test_if_empty_block.py +61 -0
  219. machine_dialect/parser/tests/test_if_statements.py +299 -0
  220. machine_dialect/parser/tests/test_illegal_tokens.py +86 -0
  221. machine_dialect/parser/tests/test_infix_expressions.py +680 -0
  222. machine_dialect/parser/tests/test_integer_literal_expressions.py +137 -0
  223. machine_dialect/parser/tests/test_interaction_statements.py +269 -0
  224. machine_dialect/parser/tests/test_list_literals.py +277 -0
  225. machine_dialect/parser/tests/test_no_none_in_ast.py +94 -0
  226. machine_dialect/parser/tests/test_panic_mode_recovery.py +171 -0
  227. machine_dialect/parser/tests/test_parse_errors.py +114 -0
  228. machine_dialect/parser/tests/test_possessive_syntax.py +182 -0
  229. machine_dialect/parser/tests/test_prefix_expressions.py +415 -0
  230. machine_dialect/parser/tests/test_program.py +13 -0
  231. machine_dialect/parser/tests/test_return_statements.py +89 -0
  232. machine_dialect/parser/tests/test_set_statements.py +152 -0
  233. machine_dialect/parser/tests/test_strict_equality.py +258 -0
  234. machine_dialect/parser/tests/test_symbol_table.py +217 -0
  235. machine_dialect/parser/tests/test_url_literal_expressions.py +209 -0
  236. machine_dialect/parser/tests/test_utility_statements.py +423 -0
  237. machine_dialect/parser/token_buffer.py +159 -0
  238. machine_dialect/repl/__init__.py +3 -0
  239. machine_dialect/repl/repl.py +426 -0
  240. machine_dialect/repl/tests/__init__.py +0 -0
  241. machine_dialect/repl/tests/test_repl.py +606 -0
  242. machine_dialect/semantic/__init__.py +12 -0
  243. machine_dialect/semantic/analyzer.py +906 -0
  244. machine_dialect/semantic/error_messages.py +189 -0
  245. machine_dialect/semantic/tests/__init__.py +1 -0
  246. machine_dialect/semantic/tests/test_analyzer.py +364 -0
  247. machine_dialect/semantic/tests/test_error_messages.py +104 -0
  248. machine_dialect/tests/edge_cases/__init__.py +10 -0
  249. machine_dialect/tests/edge_cases/test_boundary_access.py +256 -0
  250. machine_dialect/tests/edge_cases/test_empty_collections.py +166 -0
  251. machine_dialect/tests/edge_cases/test_invalid_operations.py +243 -0
  252. machine_dialect/tests/edge_cases/test_named_list_edge_cases.py +295 -0
  253. machine_dialect/tests/edge_cases/test_nested_structures.py +313 -0
  254. machine_dialect/tests/edge_cases/test_type_mixing.py +277 -0
  255. machine_dialect/tests/integration/test_array_operations_emulation.py +248 -0
  256. machine_dialect/tests/integration/test_list_compilation.py +395 -0
  257. machine_dialect/tests/integration/test_lists_and_dictionaries.py +322 -0
  258. machine_dialect/type_checking/__init__.py +21 -0
  259. machine_dialect/type_checking/tests/__init__.py +1 -0
  260. machine_dialect/type_checking/tests/test_type_system.py +230 -0
  261. machine_dialect/type_checking/type_system.py +270 -0
  262. machine_dialect-0.1.0a1.dist-info/METADATA +128 -0
  263. machine_dialect-0.1.0a1.dist-info/RECORD +268 -0
  264. machine_dialect-0.1.0a1.dist-info/WHEEL +5 -0
  265. machine_dialect-0.1.0a1.dist-info/entry_points.txt +3 -0
  266. machine_dialect-0.1.0a1.dist-info/licenses/LICENSE +201 -0
  267. machine_dialect-0.1.0a1.dist-info/top_level.txt +2 -0
  268. machine_dialect_vm/__init__.pyi +15 -0
@@ -0,0 +1,405 @@
1
+ """Loop Invariant Code Motion (LICM) optimization pass.
2
+
3
+ This module implements LICM to hoist loop-invariant computations out of loops,
4
+ reducing redundant calculations and improving performance.
5
+ """
6
+
7
+ from machine_dialect.mir.analyses.loop_analysis import Loop, LoopAnalysis
8
+ from machine_dialect.mir.analyses.use_def_chains import UseDefChains
9
+ from machine_dialect.mir.basic_block import BasicBlock
10
+ from machine_dialect.mir.mir_function import MIRFunction
11
+ from machine_dialect.mir.mir_instructions import (
12
+ BinaryOp,
13
+ Call,
14
+ ConditionalJump,
15
+ Copy,
16
+ Jump,
17
+ LoadConst,
18
+ MIRInstruction,
19
+ Phi,
20
+ Print,
21
+ Return,
22
+ StoreVar,
23
+ UnaryOp,
24
+ )
25
+ from machine_dialect.mir.mir_transformer import MIRTransformer
26
+ from machine_dialect.mir.mir_values import Constant, MIRValue, Variable
27
+ from machine_dialect.mir.optimization_pass import (
28
+ OptimizationPass,
29
+ PassInfo,
30
+ PassType,
31
+ PreservationLevel,
32
+ )
33
+ from machine_dialect.mir.ssa_construction import DominanceInfo
34
+
35
+
36
+ class LoopInvariantCodeMotion(OptimizationPass):
37
+ """Loop Invariant Code Motion optimization pass."""
38
+
39
+ def __init__(self) -> None:
40
+ """Initialize LICM pass."""
41
+ super().__init__()
42
+ self.loop_analysis: LoopAnalysis | None = None
43
+ self.dominance: DominanceInfo | None = None
44
+ self.use_def: UseDefChains | None = None
45
+ self.stats = {"hoisted": 0, "loops_processed": 0}
46
+
47
+ def get_info(self) -> PassInfo:
48
+ """Get pass information.
49
+
50
+ Returns:
51
+ Pass information.
52
+ """
53
+ return PassInfo(
54
+ name="licm",
55
+ description="Hoist loop-invariant code out of loops",
56
+ pass_type=PassType.OPTIMIZATION,
57
+ requires=["loop-analysis", "dominance", "use-def-chains"],
58
+ preserves=PreservationLevel.NONE,
59
+ )
60
+
61
+ def run_on_function(self, function: MIRFunction) -> bool:
62
+ """Run LICM on a function.
63
+
64
+ Args:
65
+ function: The function to optimize.
66
+
67
+ Returns:
68
+ True if the function was modified.
69
+ """
70
+ # Get required analyses
71
+ loop_info = self.get_analysis("loop-analysis", function)
72
+ self.dominance = self.get_analysis("dominance", function)
73
+ self.use_def = self.get_analysis("use-def-chains", function)
74
+
75
+ if not loop_info or not self.dominance or not self.use_def:
76
+ return False
77
+
78
+ transformer = MIRTransformer(function)
79
+ modified = False
80
+
81
+ # Process loops from innermost to outermost for better optimization
82
+ loops = self._get_loops_in_order(loop_info.loops if hasattr(loop_info, "loops") else [])
83
+
84
+ for loop in loops:
85
+ if self._process_loop(loop, function, transformer):
86
+ modified = True
87
+ self.stats["loops_processed"] += 1
88
+
89
+ return modified
90
+
91
+ def _get_loops_in_order(self, loops: list[Loop]) -> list[Loop]:
92
+ """Get loops ordered from innermost to outermost.
93
+
94
+ Args:
95
+ loops: List of loops.
96
+
97
+ Returns:
98
+ Ordered list of loops.
99
+ """
100
+ # Sort by depth (deeper loops first) to process inner loops before outer
101
+ return sorted(loops, key=lambda loop: loop.depth, reverse=True)
102
+
103
+ def _process_loop(self, loop: Loop, function: MIRFunction, transformer: MIRTransformer) -> bool:
104
+ """Process a single loop for invariant code motion.
105
+
106
+ Args:
107
+ loop: The loop to process.
108
+ function: The containing function.
109
+ transformer: MIR transformer for modifications.
110
+
111
+ Returns:
112
+ True if modifications were made.
113
+ """
114
+ modified = False
115
+ preheader = self._get_or_create_preheader(loop, function, transformer)
116
+
117
+ if not preheader:
118
+ return False
119
+
120
+ # Find loop-invariant instructions
121
+ invariant_instructions = self._find_invariant_instructions(loop, function)
122
+
123
+ # Hoist invariant instructions to preheader
124
+ for inst, block in invariant_instructions:
125
+ if self._can_hoist(inst, block, loop, preheader):
126
+ self._hoist_instruction(inst, block, preheader, transformer)
127
+ self.stats["hoisted"] += 1
128
+ modified = True
129
+
130
+ return modified
131
+
132
+ def _get_or_create_preheader(
133
+ self, loop: Loop, function: MIRFunction, transformer: MIRTransformer
134
+ ) -> BasicBlock | None:
135
+ """Get or create a preheader block for a loop.
136
+
137
+ A preheader is a single-entry block that dominates the loop header
138
+ and is the only predecessor from outside the loop.
139
+
140
+ Args:
141
+ loop: The loop.
142
+ function: The containing function.
143
+ transformer: MIR transformer.
144
+
145
+ Returns:
146
+ The preheader block or None if creation failed.
147
+ """
148
+ header = loop.header
149
+
150
+ # Find predecessors from outside the loop
151
+ outside_preds = [pred for pred in header.predecessors if pred not in loop.blocks]
152
+
153
+ if len(outside_preds) == 1:
154
+ # Single outside predecessor could be the preheader if it only goes to header
155
+ pred = outside_preds[0]
156
+ if len(pred.successors) == 1:
157
+ return pred
158
+
159
+ # Need to create a new preheader
160
+ preheader = BasicBlock("loop_preheader")
161
+ transformer.function.cfg.add_block(preheader)
162
+
163
+ # Redirect outside predecessors to preheader
164
+ for pred in outside_preds:
165
+ # Update the jump/branch instruction in predecessor
166
+ for i, inst in enumerate(pred.instructions):
167
+ if isinstance(inst, Jump) and inst.label == header.label:
168
+ source_loc = inst.source_location if hasattr(inst, "source_location") else (0, 0)
169
+ pred.instructions[i] = Jump(preheader.label, source_loc)
170
+ elif isinstance(inst, ConditionalJump):
171
+ if inst.true_label == header.label:
172
+ inst.true_label = preheader.label
173
+ if inst.false_label == header.label:
174
+ inst.false_label = preheader.label
175
+
176
+ # Update CFG edges
177
+ if header in pred.successors:
178
+ pred.successors.remove(header)
179
+ pred.successors.append(preheader)
180
+ if pred in header.predecessors:
181
+ header.predecessors.remove(pred)
182
+ preheader.predecessors.append(pred)
183
+
184
+ # Add unconditional jump from preheader to header
185
+ # TODO: Verify if (0, 0) is the right approach for synthetic instructions
186
+ preheader.instructions.append(Jump(header.label, (0, 0)))
187
+ preheader.successors.append(header)
188
+ header.predecessors.append(preheader)
189
+
190
+ return preheader
191
+
192
+ def _find_invariant_instructions(
193
+ self, loop: Loop, function: MIRFunction
194
+ ) -> list[tuple[MIRInstruction, BasicBlock]]:
195
+ """Find loop-invariant instructions.
196
+
197
+ An instruction is loop-invariant if:
198
+ 1. All its operands are defined outside the loop or are loop-invariant
199
+ 2. It doesn't have side effects that depend on loop iteration
200
+
201
+ Args:
202
+ loop: The loop to analyze.
203
+ function: The containing function.
204
+
205
+ Returns:
206
+ List of (instruction, block) pairs that are loop-invariant.
207
+ """
208
+ invariant: list[tuple[MIRInstruction, BasicBlock]] = []
209
+ invariant_values: set[MIRValue] = set()
210
+
211
+ # First, find all variables that are modified inside the loop
212
+ modified_in_loop: set[str] = set()
213
+ for block in loop.blocks:
214
+ for inst in block.instructions:
215
+ for def_val in inst.get_defs():
216
+ if isinstance(def_val, Variable):
217
+ modified_in_loop.add(def_val.name)
218
+
219
+ # Function parameters are invariant if not modified in the loop
220
+ # They are implicitly defined outside all loops
221
+ for param in function.params:
222
+ param_name = param.name if isinstance(param, Variable) else str(param)
223
+ if param_name not in modified_in_loop:
224
+ # Find the Variable object for this parameter
225
+ # Look through loop instructions to find the Variable instance
226
+ for block in loop.blocks:
227
+ for inst in block.instructions:
228
+ for use in inst.get_uses():
229
+ if isinstance(use, Variable) and use.name == param_name:
230
+ invariant_values.add(use)
231
+ break
232
+
233
+ # Values defined outside the loop are invariant ONLY if not modified inside
234
+ for block in function.cfg.blocks.values():
235
+ if block not in loop.blocks:
236
+ for inst in block.instructions:
237
+ for def_val in inst.get_defs():
238
+ # Only add if it's not a variable OR if it's a variable not modified in loop
239
+ if isinstance(def_val, Variable):
240
+ if def_val.name not in modified_in_loop:
241
+ invariant_values.add(def_val)
242
+ else:
243
+ # Temps and other values from outside are invariant
244
+ invariant_values.add(def_val)
245
+
246
+ # Constants are always invariant
247
+ for block in loop.blocks:
248
+ for inst in block.instructions:
249
+ for use in inst.get_uses():
250
+ if isinstance(use, Constant):
251
+ invariant_values.add(use)
252
+
253
+ # Fixed-point iteration to find invariant instructions
254
+ changed = True
255
+ while changed:
256
+ changed = False
257
+ for block in loop.blocks:
258
+ for inst in block.instructions:
259
+ # Skip if already marked as invariant
260
+ if any(inst is i for i, _ in invariant):
261
+ continue
262
+
263
+ # Check if instruction is invariant
264
+ if self._is_invariant_instruction(inst, invariant_values, loop):
265
+ invariant.append((inst, block))
266
+ invariant_values.update(inst.get_defs())
267
+ changed = True
268
+
269
+ return invariant
270
+
271
+ def _is_invariant_instruction(self, inst: MIRInstruction, invariant_values: set[MIRValue], loop: Loop) -> bool:
272
+ """Check if an instruction is loop-invariant.
273
+
274
+ Args:
275
+ inst: The instruction to check.
276
+ invariant_values: Set of known invariant values.
277
+ loop: The containing loop.
278
+
279
+ Returns:
280
+ True if the instruction is loop-invariant.
281
+ """
282
+ # Control flow instructions are not invariant
283
+ if isinstance(inst, ConditionalJump | Jump | Return | Phi):
284
+ return False
285
+
286
+ # Side-effect instructions need careful handling
287
+ if isinstance(inst, Call | Print):
288
+ # Conservative: don't hoist calls or prints
289
+ return False
290
+
291
+ # Check if instruction defines a variable that's modified in the loop
292
+ # This is an additional safety check to prevent hoisting loop counters
293
+ for def_val in inst.get_defs():
294
+ if isinstance(def_val, Variable):
295
+ # Check if this variable is modified elsewhere in the loop
296
+ # If so, we can't hoist this instruction
297
+ for block in loop.blocks:
298
+ for other_inst in block.instructions:
299
+ if other_inst is not inst:
300
+ for other_def in other_inst.get_defs():
301
+ if isinstance(other_def, Variable) and other_def.name == def_val.name:
302
+ # This variable is modified elsewhere in loop, can't hoist
303
+ return False
304
+
305
+ # Check if all operands are invariant
306
+ for operand in inst.get_uses():
307
+ if operand not in invariant_values:
308
+ return False
309
+
310
+ # Safe arithmetic and data movement instructions
311
+ if isinstance(inst, BinaryOp | UnaryOp | Copy | LoadConst | StoreVar):
312
+ return True
313
+
314
+ return False
315
+
316
+ def _can_hoist(self, inst: MIRInstruction, block: BasicBlock, loop: Loop, preheader: BasicBlock) -> bool:
317
+ """Check if an instruction can be safely hoisted.
318
+
319
+ Args:
320
+ inst: The instruction to hoist.
321
+ block: The block containing the instruction.
322
+ loop: The loop.
323
+ preheader: The preheader block.
324
+
325
+ Returns:
326
+ True if the instruction can be hoisted.
327
+ """
328
+ # Check if the instruction will execute on every iteration
329
+ # For now, we use a simple heuristic: hoist if the block is in the loop
330
+ # and the instruction is safe to speculatively execute
331
+ if not self.dominance:
332
+ return False
333
+
334
+ # Check if this block is guaranteed to execute when the loop is entered
335
+ # The header always executes, and blocks dominated by the header that
336
+ # don't have conditional predecessors within the loop are safe
337
+ if block == loop.header:
338
+ return True
339
+
340
+ # For other blocks, check if they dominate the latch (back edge source)
341
+ # This ensures they execute on every iteration that continues
342
+ latch = loop.back_edge[0] # Source of the back edge
343
+ if not self.dominance.dominates(block, latch):
344
+ # If it doesn't dominate the latch, it might not execute on every iteration
345
+ # Be conservative and don't hoist
346
+ return False
347
+
348
+ # Don't hoist memory operations that might alias
349
+ # (Conservative for now - could add alias analysis later)
350
+ if isinstance(inst, StoreVar):
351
+ # Check if any other instruction in the loop might read this variable
352
+ var = inst.var
353
+ for loop_block in loop.blocks:
354
+ for loop_inst in loop_block.instructions:
355
+ if loop_inst is inst:
356
+ continue
357
+ # Check for potential aliasing
358
+ for use in loop_inst.get_uses():
359
+ if isinstance(use, Variable) and use.name == var.name:
360
+ return False
361
+
362
+ return True
363
+
364
+ def _hoist_instruction(
365
+ self,
366
+ inst: MIRInstruction,
367
+ from_block: BasicBlock,
368
+ to_block: BasicBlock,
369
+ transformer: MIRTransformer,
370
+ ) -> None:
371
+ """Hoist an instruction from one block to another.
372
+
373
+ Args:
374
+ inst: The instruction to hoist.
375
+ from_block: The source block.
376
+ to_block: The destination block (preheader).
377
+ transformer: MIR transformer.
378
+ """
379
+ # Remove from original block
380
+ from_block.instructions.remove(inst)
381
+
382
+ # Insert at the end of preheader (before the jump)
383
+ if to_block.instructions and isinstance(to_block.instructions[-1], Jump):
384
+ to_block.instructions.insert(-1, inst)
385
+ else:
386
+ to_block.instructions.append(inst)
387
+
388
+ transformer.modified = True
389
+
390
+ def finalize(self) -> None:
391
+ """Finalize the pass after running.
392
+
393
+ Cleans up any temporary state.
394
+ """
395
+ self.loop_analysis = None
396
+ self.dominance = None
397
+ self.use_def = None
398
+
399
+ def get_statistics(self) -> dict[str, int]:
400
+ """Get optimization statistics.
401
+
402
+ Returns:
403
+ Dictionary of statistics.
404
+ """
405
+ return self.stats