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,472 @@
1
+ """Tests for Loop Invariant Code Motion optimization."""
2
+
3
+ from machine_dialect.mir.mir_function import MIRFunction
4
+ from machine_dialect.mir.mir_instructions import (
5
+ BinaryOp,
6
+ ConditionalJump,
7
+ Copy,
8
+ Jump,
9
+ LoadConst,
10
+ Print,
11
+ Return,
12
+ )
13
+ from machine_dialect.mir.mir_module import MIRModule
14
+ from machine_dialect.mir.mir_types import MIRType
15
+ from machine_dialect.mir.mir_values import Constant, Temp, Variable
16
+ from machine_dialect.mir.pass_manager import PassManager
17
+
18
+
19
+ def create_simple_loop_function() -> MIRFunction:
20
+ """Create a function with a simple loop containing invariant code.
21
+
22
+ Equivalent to:
23
+ def simple_loop(n):
24
+ i = 0
25
+ while i < n:
26
+ x = 10 # Loop invariant
27
+ y = x * 2 # Loop invariant
28
+ z = i + y # Not invariant (depends on i)
29
+ i = i + 1
30
+ return z
31
+ """
32
+ func = MIRFunction("simple_loop", [Variable("n", MIRType.INT)])
33
+
34
+ # Create blocks
35
+ entry = func.cfg.get_or_create_block("entry")
36
+ loop_header = func.cfg.get_or_create_block("loop_header")
37
+ loop_body = func.cfg.get_or_create_block("loop_body")
38
+ loop_exit = func.cfg.get_or_create_block("loop_exit")
39
+
40
+ # Set entry block
41
+ func.cfg.entry_block = entry
42
+
43
+ # Entry block: i = 0
44
+ i_var = Variable("i", MIRType.INT)
45
+ n_var = Variable("n", MIRType.INT)
46
+ entry.instructions = [
47
+ LoadConst(i_var, Constant(0), (1, 1)),
48
+ Jump("loop_header", (1, 1)),
49
+ ]
50
+ func.cfg.connect(entry, loop_header)
51
+
52
+ # Loop header: if i < n goto body else exit
53
+ cond_temp = Temp(MIRType.BOOL, 0)
54
+ loop_header.instructions = [
55
+ BinaryOp(cond_temp, "<", i_var, n_var, (1, 1)),
56
+ ConditionalJump(cond_temp, "loop_body", (1, 1), "loop_exit"),
57
+ ]
58
+ func.cfg.connect(loop_header, loop_body)
59
+ func.cfg.connect(loop_header, loop_exit)
60
+
61
+ # Loop body: x = 10, y = x * 2, z = i + y, i = i + 1
62
+ x_var = Variable("x", MIRType.INT)
63
+ y_var = Variable("y", MIRType.INT)
64
+ z_var = Variable("z", MIRType.INT)
65
+ i_plus_one = Temp(MIRType.INT, 1)
66
+ loop_body.instructions = [
67
+ LoadConst(x_var, Constant(10), (1, 1)), # Invariant
68
+ BinaryOp(y_var, "*", x_var, Constant(2), (1, 1)), # Invariant
69
+ BinaryOp(z_var, "+", i_var, y_var, (1, 1)), # Not invariant
70
+ BinaryOp(i_plus_one, "+", i_var, Constant(1), (1, 1)),
71
+ Copy(i_var, i_plus_one, (1, 1)),
72
+ Jump("loop_header", (1, 1)),
73
+ ]
74
+ func.cfg.connect(loop_body, loop_header)
75
+
76
+ # Loop exit: return z
77
+ loop_exit.instructions = [Return((1, 1), z_var)]
78
+
79
+ return func
80
+
81
+
82
+ def create_nested_loop_function() -> MIRFunction:
83
+ """Create a function with nested loops and invariant code.
84
+
85
+ Equivalent to:
86
+ def nested_loops(n, m):
87
+ result = 0
88
+ for i in range(n):
89
+ x = n * 2 # Invariant to inner loop
90
+ for j in range(m):
91
+ y = m * 3 # Invariant to inner loop
92
+ z = x + y # Invariant to inner loop
93
+ result = result + z + i + j
94
+ return result
95
+ """
96
+ func = MIRFunction("nested_loops", [Variable("n", MIRType.INT), Variable("m", MIRType.INT)])
97
+
98
+ # Create blocks
99
+ entry = func.cfg.get_or_create_block("entry")
100
+ outer_header = func.cfg.get_or_create_block("outer_header")
101
+ outer_body = func.cfg.get_or_create_block("outer_body")
102
+ inner_header = func.cfg.get_or_create_block("inner_header")
103
+ inner_body = func.cfg.get_or_create_block("inner_body")
104
+ inner_exit = func.cfg.get_or_create_block("inner_exit")
105
+ outer_exit = func.cfg.get_or_create_block("outer_exit")
106
+
107
+ # Variables
108
+ n_var = Variable("n", MIRType.INT)
109
+ m_var = Variable("m", MIRType.INT)
110
+ i_var = Variable("i", MIRType.INT)
111
+ j_var = Variable("j", MIRType.INT)
112
+ x_var = Variable("x", MIRType.INT)
113
+ y_var = Variable("y", MIRType.INT)
114
+ z_var = Variable("z", MIRType.INT)
115
+ result_var = Variable("result", MIRType.INT)
116
+
117
+ # Set entry block
118
+ func.cfg.entry_block = entry
119
+
120
+ # Entry: result = 0, i = 0
121
+ entry.instructions = [
122
+ LoadConst(result_var, Constant(0), (1, 1)),
123
+ LoadConst(i_var, Constant(0), (1, 1)),
124
+ Jump("outer_header", (1, 1)),
125
+ ]
126
+ func.cfg.connect(entry, outer_header)
127
+
128
+ # Outer header: if i < n goto outer_body else outer_exit
129
+ outer_cond = Temp(MIRType.BOOL, 10)
130
+ outer_header.instructions = [
131
+ BinaryOp(outer_cond, "<", i_var, n_var, (1, 1)),
132
+ ConditionalJump(outer_cond, "outer_body", (1, 1), "outer_exit"),
133
+ ]
134
+ func.cfg.connect(outer_header, outer_body)
135
+ func.cfg.connect(outer_header, outer_exit)
136
+
137
+ # Outer body: x = n * 2, j = 0
138
+ outer_body.instructions = [
139
+ BinaryOp(x_var, "*", n_var, Constant(2), (1, 1)), # Invariant to inner loop
140
+ LoadConst(j_var, Constant(0), (1, 1)),
141
+ Jump("inner_header", (1, 1)),
142
+ ]
143
+ func.cfg.connect(outer_body, inner_header)
144
+
145
+ # Inner header: if j < m goto inner_body else inner_exit
146
+ inner_cond = Temp(MIRType.BOOL, 11)
147
+ inner_header.instructions = [
148
+ BinaryOp(inner_cond, "<", j_var, m_var, (1, 1)),
149
+ ConditionalJump(inner_cond, "inner_body", (1, 1), "inner_exit"),
150
+ ]
151
+ func.cfg.connect(inner_header, inner_body)
152
+ func.cfg.connect(inner_header, inner_exit)
153
+
154
+ # Inner body: y = m * 3, z = x + y, result = result + z + i + j, j = j + 1
155
+ temp1 = Temp(MIRType.INT, 12)
156
+ temp2 = Temp(MIRType.INT, 13)
157
+ temp3 = Temp(MIRType.INT, 14)
158
+ j_plus_one = Temp(MIRType.INT, 16)
159
+ inner_body.instructions = [
160
+ BinaryOp(y_var, "*", m_var, Constant(3), (1, 1)), # Invariant
161
+ BinaryOp(z_var, "+", x_var, y_var, (1, 1)), # Invariant
162
+ BinaryOp(temp1, "+", result_var, z_var, (1, 1)),
163
+ BinaryOp(temp2, "+", temp1, i_var, (1, 1)),
164
+ BinaryOp(temp3, "+", temp2, j_var, (1, 1)),
165
+ Copy(result_var, temp3, (1, 1)),
166
+ BinaryOp(j_plus_one, "+", j_var, Constant(1), (1, 1)),
167
+ Copy(j_var, j_plus_one, (1, 1)),
168
+ Jump("inner_header", (1, 1)),
169
+ ]
170
+ func.cfg.connect(inner_body, inner_header)
171
+
172
+ # Inner exit: i = i + 1, goto outer_header
173
+ i_plus_one = Temp(MIRType.INT, 17)
174
+ inner_exit.instructions = [
175
+ BinaryOp(i_plus_one, "+", i_var, Constant(1), (1, 1)),
176
+ Copy(i_var, i_plus_one, (1, 1)),
177
+ Jump("outer_header", (1, 1)),
178
+ ]
179
+ func.cfg.connect(inner_exit, outer_header)
180
+
181
+ # Outer exit: return result
182
+ outer_exit.instructions = [Return((1, 1), result_var)]
183
+
184
+ return func
185
+
186
+
187
+ def create_loop_with_side_effects() -> MIRFunction:
188
+ """Create a loop with side effects that shouldn't be hoisted.
189
+
190
+ Equivalent to:
191
+ def loop_with_side_effects(n):
192
+ i = 0
193
+ while i < n:
194
+ x = 10 # Invariant
195
+ print(x) # Side effect - don't hoist
196
+ y = x * 2 # Invariant but after side effect
197
+ i = i + 1
198
+ return i
199
+ """
200
+ func = MIRFunction("loop_with_side_effects", [Variable("n", MIRType.INT)])
201
+
202
+ # Create blocks
203
+ entry = func.cfg.get_or_create_block("entry")
204
+ loop_header = func.cfg.get_or_create_block("loop_header")
205
+ loop_body = func.cfg.get_or_create_block("loop_body")
206
+ loop_exit = func.cfg.get_or_create_block("loop_exit")
207
+
208
+ # Variables
209
+ i_var = Variable("i", MIRType.INT)
210
+ n_var = Variable("n", MIRType.INT)
211
+ x_var = Variable("x", MIRType.INT)
212
+ y_var = Variable("y", MIRType.INT)
213
+
214
+ # Set entry block
215
+ func.cfg.entry_block = entry
216
+
217
+ # Entry block
218
+ entry.instructions = [
219
+ LoadConst(i_var, Constant(0), (1, 1)),
220
+ Jump("loop_header", (1, 1)),
221
+ ]
222
+ func.cfg.connect(entry, loop_header)
223
+
224
+ # Loop header
225
+ cond_temp = Temp(MIRType.BOOL, 20)
226
+ loop_header.instructions = [
227
+ BinaryOp(cond_temp, "<", i_var, n_var, (1, 1)),
228
+ ConditionalJump(cond_temp, "loop_body", (1, 1), "loop_exit"),
229
+ ]
230
+ func.cfg.connect(loop_header, loop_body)
231
+ func.cfg.connect(loop_header, loop_exit)
232
+
233
+ # Loop body
234
+ i_plus_one = Temp(MIRType.INT, 21)
235
+ loop_body.instructions = [
236
+ LoadConst(x_var, Constant(10), (1, 1)), # Invariant
237
+ Print(x_var, (1, 1)), # Side effect - don't hoist
238
+ BinaryOp(y_var, "*", x_var, Constant(2), (1, 1)), # Invariant but after print
239
+ BinaryOp(i_plus_one, "+", i_var, Constant(1), (1, 1)),
240
+ Copy(i_var, i_plus_one, (1, 1)),
241
+ Jump("loop_header", (1, 1)),
242
+ ]
243
+ func.cfg.connect(loop_body, loop_header)
244
+
245
+ # Loop exit
246
+ loop_exit.instructions = [Return((1, 1), i_var)]
247
+
248
+ return func
249
+
250
+
251
+ class TestLICM:
252
+ """Test suite for LICM optimization."""
253
+
254
+ def setup_method(self) -> None:
255
+ """Set up test fixtures."""
256
+ self.pass_manager = PassManager()
257
+ # Register all passes including dependencies
258
+ from machine_dialect.mir.optimizations import register_all_passes
259
+
260
+ register_all_passes(self.pass_manager)
261
+
262
+ def test_simple_loop_hoisting(self) -> None:
263
+ """Test hoisting invariant code from a simple loop."""
264
+ func = create_simple_loop_function()
265
+ module = MIRModule("test")
266
+ module.functions[func.name] = func
267
+
268
+ # Set up and run LICM with proper analysis manager
269
+ from machine_dialect.mir.tests.test_optimization_helpers import run_optimization_with_analyses
270
+
271
+ modified = run_optimization_with_analyses(
272
+ self.pass_manager,
273
+ "licm",
274
+ func,
275
+ required_analyses=["dominance", "loop-analysis", "use-def-chains"],
276
+ )
277
+
278
+ assert modified, "LICM should modify the function"
279
+
280
+ # Check that invariant instructions were hoisted
281
+ # Either a new preheader was created or entry block is used as preheader
282
+ # First, check if instructions were actually hoisted by looking at the loop body
283
+ loop_body = None
284
+ for block in func.cfg.blocks.values():
285
+ if "loop_body" in block.label:
286
+ loop_body = block
287
+ break
288
+
289
+ assert loop_body is not None, "Loop body should exist"
290
+
291
+ # Check if the invariant instructions are still in the loop body
292
+ # If LICM worked, they should have been removed
293
+ loop_has_load_const_10 = any(
294
+ isinstance(inst, LoadConst) and hasattr(inst.constant, "value") and inst.constant.value == 10
295
+ for inst in loop_body.instructions
296
+ )
297
+ from machine_dialect.mir.mir_values import Constant as ConstantValue
298
+
299
+ loop_has_mult = any(
300
+ isinstance(inst, BinaryOp)
301
+ and inst.op == "*"
302
+ and isinstance(inst.right, ConstantValue)
303
+ and inst.right.value == 2
304
+ for inst in loop_body.instructions
305
+ )
306
+
307
+ # If LICM worked, these should NOT be in the loop body anymore
308
+ assert not loop_has_load_const_10, (
309
+ f"LoadConst(10) should be removed from loop body, instructions: {loop_body.instructions}"
310
+ )
311
+ assert not loop_has_mult, (
312
+ f"BinaryOp(*) should be removed from loop body, instructions: {loop_body.instructions}"
313
+ )
314
+
315
+ # Check statistics from the actual instance that ran
316
+ if hasattr(self.pass_manager, "_last_run_pass"):
317
+ licm = self.pass_manager._last_run_pass
318
+ stats = licm.get_statistics()
319
+ assert stats["hoisted"] >= 2, "At least 2 instructions should be hoisted"
320
+ assert stats["loops_processed"] >= 1, "At least 1 loop should be processed"
321
+
322
+ def test_nested_loop_hoisting(self) -> None:
323
+ """Test hoisting from nested loops."""
324
+ func = create_nested_loop_function()
325
+ module = MIRModule("test")
326
+ module.functions[func.name] = func
327
+
328
+ # Run LICM with proper analysis setup
329
+ from machine_dialect.mir.tests.test_optimization_helpers import run_optimization_with_analyses
330
+
331
+ modified = run_optimization_with_analyses(
332
+ self.pass_manager,
333
+ "licm",
334
+ func,
335
+ required_analyses=["dominance", "loop-analysis", "use-def-chains"],
336
+ )
337
+
338
+ assert modified, "LICM should modify nested loops"
339
+
340
+ # Check that some instructions were hoisted
341
+ if hasattr(self.pass_manager, "_last_run_pass"):
342
+ licm = self.pass_manager._last_run_pass
343
+ stats = licm.get_statistics()
344
+ assert stats["hoisted"] > 0, "Instructions should be hoisted from inner loop"
345
+ assert stats["loops_processed"] >= 1, "At least inner loop should be processed"
346
+
347
+ def test_side_effects_not_hoisted(self) -> None:
348
+ """Test that instructions with side effects are not hoisted."""
349
+ func = create_loop_with_side_effects()
350
+ module = MIRModule("test")
351
+ module.functions[func.name] = func
352
+
353
+ # Run LICM with proper analysis setup
354
+ from machine_dialect.mir.tests.test_optimization_helpers import run_optimization_with_analyses
355
+
356
+ run_optimization_with_analyses(
357
+ self.pass_manager,
358
+ "licm",
359
+ func,
360
+ required_analyses=["dominance", "loop-analysis", "use-def-chains"],
361
+ )
362
+
363
+ # The function might be modified (preheader creation)
364
+ # but Print should not be hoisted
365
+
366
+ # Check that Print is still in loop body
367
+ loop_body = None
368
+ for block in func.cfg.blocks.values():
369
+ if "loop_body" in block.label:
370
+ loop_body = block
371
+ break
372
+
373
+ assert loop_body is not None, "Loop body should exist"
374
+
375
+ has_print = any(isinstance(inst, Print) for inst in loop_body.instructions)
376
+ assert has_print, "Print should remain in loop body (not hoisted)"
377
+
378
+ def test_no_loops_no_modification(self) -> None:
379
+ """Test that functions without loops are not modified."""
380
+ func = MIRFunction("no_loops", [Variable("x", MIRType.INT)])
381
+
382
+ # Simple function: return x * 2
383
+ entry = func.cfg.get_or_create_block("entry")
384
+ func.cfg.entry_block = entry
385
+ x_var = Variable("x", MIRType.INT)
386
+ result = Temp(MIRType.INT, 30)
387
+ entry.instructions = [
388
+ BinaryOp(result, "*", x_var, Constant(2), (1, 1)),
389
+ Return((1, 1), result),
390
+ ]
391
+
392
+ # Run LICM with proper analysis setup
393
+ from machine_dialect.mir.tests.test_optimization_helpers import run_optimization_with_analyses
394
+
395
+ modified = run_optimization_with_analyses(
396
+ self.pass_manager,
397
+ "licm",
398
+ func,
399
+ required_analyses=["dominance", "loop-analysis", "use-def-chains"],
400
+ )
401
+
402
+ assert not modified, "Function without loops should not be modified"
403
+
404
+ if hasattr(self.pass_manager, "_last_run_pass"):
405
+ licm = self.pass_manager._last_run_pass
406
+ stats = licm.get_statistics()
407
+ assert stats["hoisted"] == 0, "No instructions should be hoisted"
408
+ assert stats["loops_processed"] == 0, "No loops should be processed"
409
+
410
+ def test_loop_variant_not_hoisted(self) -> None:
411
+ """Test that loop-variant code is not hoisted."""
412
+ func = MIRFunction("loop_variant", [Variable("n", MIRType.INT)])
413
+
414
+ # Create a loop where all computations depend on loop variable
415
+ entry = func.cfg.get_or_create_block("entry")
416
+ header = func.cfg.get_or_create_block("header")
417
+ body = func.cfg.get_or_create_block("body")
418
+ exit_block = func.cfg.get_or_create_block("exit")
419
+
420
+ i_var = Variable("i", MIRType.INT)
421
+ n_var = Variable("n", MIRType.INT)
422
+ x_var = Variable("x", MIRType.INT)
423
+ y_var = Variable("y", MIRType.INT)
424
+
425
+ # Set entry block
426
+ func.cfg.entry_block = entry
427
+
428
+ # Entry
429
+ entry.instructions = [
430
+ LoadConst(i_var, Constant(0), (1, 1)),
431
+ Jump("header", (1, 1)),
432
+ ]
433
+ func.cfg.connect(entry, header)
434
+
435
+ # Header
436
+ cond = Temp(MIRType.BOOL, 40)
437
+ header.instructions = [
438
+ BinaryOp(cond, "<", i_var, n_var, (1, 1)),
439
+ ConditionalJump(cond, "body", (1, 1), "exit"),
440
+ ]
441
+ func.cfg.connect(header, body)
442
+ func.cfg.connect(header, exit_block)
443
+
444
+ # Body - all depend on i
445
+ i_plus_one = Temp(MIRType.INT, 41)
446
+ body.instructions = [
447
+ BinaryOp(x_var, "*", i_var, Constant(2), (1, 1)), # Depends on i
448
+ BinaryOp(y_var, "+", x_var, i_var, (1, 1)), # Depends on i and x
449
+ BinaryOp(i_plus_one, "+", i_var, Constant(1), (1, 1)),
450
+ Copy(i_var, i_plus_one, (1, 1)),
451
+ Jump("header", (1, 1)),
452
+ ]
453
+ func.cfg.connect(body, header)
454
+
455
+ # Exit
456
+ exit_block.instructions = [Return((1, 1), y_var)]
457
+
458
+ # Run LICM with proper analysis setup
459
+ from machine_dialect.mir.tests.test_optimization_helpers import run_optimization_with_analyses
460
+
461
+ run_optimization_with_analyses(
462
+ self.pass_manager,
463
+ "licm",
464
+ func,
465
+ required_analyses=["dominance", "loop-analysis", "use-def-chains"],
466
+ )
467
+
468
+ # Check that loop-variant instructions were not hoisted
469
+ if hasattr(self.pass_manager, "_last_run_pass"):
470
+ licm = self.pass_manager._last_run_pass
471
+ stats = licm.get_statistics()
472
+ assert stats["hoisted"] == 0, "No loop-variant instructions should be hoisted"