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.
- machine_dialect/__main__.py +667 -0
- machine_dialect/agent/__init__.py +5 -0
- machine_dialect/agent/agent.py +360 -0
- machine_dialect/ast/__init__.py +95 -0
- machine_dialect/ast/ast_node.py +35 -0
- machine_dialect/ast/call_expression.py +82 -0
- machine_dialect/ast/dict_extraction.py +60 -0
- machine_dialect/ast/expressions.py +439 -0
- machine_dialect/ast/literals.py +309 -0
- machine_dialect/ast/program.py +35 -0
- machine_dialect/ast/statements.py +1433 -0
- machine_dialect/ast/tests/test_ast_string_representation.py +62 -0
- machine_dialect/ast/tests/test_boolean_literal.py +29 -0
- machine_dialect/ast/tests/test_collection_hir.py +138 -0
- machine_dialect/ast/tests/test_define_statement.py +142 -0
- machine_dialect/ast/tests/test_desugar.py +541 -0
- machine_dialect/ast/tests/test_foreach_desugar.py +245 -0
- machine_dialect/cfg/__init__.py +6 -0
- machine_dialect/cfg/config.py +156 -0
- machine_dialect/cfg/examples.py +221 -0
- machine_dialect/cfg/generate_with_ai.py +187 -0
- machine_dialect/cfg/openai_generation.py +200 -0
- machine_dialect/cfg/parser.py +94 -0
- machine_dialect/cfg/tests/__init__.py +1 -0
- machine_dialect/cfg/tests/test_cfg_parser.py +252 -0
- machine_dialect/cfg/tests/test_config.py +188 -0
- machine_dialect/cfg/tests/test_examples.py +391 -0
- machine_dialect/cfg/tests/test_generate_with_ai.py +354 -0
- machine_dialect/cfg/tests/test_openai_generation.py +256 -0
- machine_dialect/codegen/__init__.py +5 -0
- machine_dialect/codegen/bytecode_module.py +89 -0
- machine_dialect/codegen/bytecode_serializer.py +300 -0
- machine_dialect/codegen/opcodes.py +101 -0
- machine_dialect/codegen/register_codegen.py +1996 -0
- machine_dialect/codegen/symtab.py +208 -0
- machine_dialect/codegen/tests/__init__.py +1 -0
- machine_dialect/codegen/tests/test_array_operations_codegen.py +295 -0
- machine_dialect/codegen/tests/test_bytecode_serializer.py +185 -0
- machine_dialect/codegen/tests/test_register_codegen_ssa.py +324 -0
- machine_dialect/codegen/tests/test_symtab.py +418 -0
- machine_dialect/codegen/vm_serializer.py +621 -0
- machine_dialect/compiler/__init__.py +18 -0
- machine_dialect/compiler/compiler.py +197 -0
- machine_dialect/compiler/config.py +149 -0
- machine_dialect/compiler/context.py +149 -0
- machine_dialect/compiler/phases/__init__.py +19 -0
- machine_dialect/compiler/phases/bytecode_optimization.py +90 -0
- machine_dialect/compiler/phases/codegen.py +40 -0
- machine_dialect/compiler/phases/hir_generation.py +39 -0
- machine_dialect/compiler/phases/mir_generation.py +86 -0
- machine_dialect/compiler/phases/optimization.py +110 -0
- machine_dialect/compiler/phases/parsing.py +39 -0
- machine_dialect/compiler/pipeline.py +143 -0
- machine_dialect/compiler/tests/__init__.py +1 -0
- machine_dialect/compiler/tests/test_compiler.py +568 -0
- machine_dialect/compiler/vm_runner.py +173 -0
- machine_dialect/errors/__init__.py +32 -0
- machine_dialect/errors/exceptions.py +369 -0
- machine_dialect/errors/messages.py +82 -0
- machine_dialect/errors/tests/__init__.py +0 -0
- machine_dialect/errors/tests/test_expected_token_errors.py +188 -0
- machine_dialect/errors/tests/test_name_errors.py +118 -0
- machine_dialect/helpers/__init__.py +0 -0
- machine_dialect/helpers/stopwords.py +225 -0
- machine_dialect/helpers/validators.py +30 -0
- machine_dialect/lexer/__init__.py +9 -0
- machine_dialect/lexer/constants.py +23 -0
- machine_dialect/lexer/lexer.py +907 -0
- machine_dialect/lexer/tests/__init__.py +0 -0
- machine_dialect/lexer/tests/helpers.py +86 -0
- machine_dialect/lexer/tests/test_apostrophe_identifiers.py +122 -0
- machine_dialect/lexer/tests/test_backtick_identifiers.py +140 -0
- machine_dialect/lexer/tests/test_boolean_literals.py +108 -0
- machine_dialect/lexer/tests/test_case_insensitive_keywords.py +188 -0
- machine_dialect/lexer/tests/test_comments.py +200 -0
- machine_dialect/lexer/tests/test_double_asterisk_keywords.py +127 -0
- machine_dialect/lexer/tests/test_lexer_position.py +113 -0
- machine_dialect/lexer/tests/test_list_tokens.py +282 -0
- machine_dialect/lexer/tests/test_stopwords.py +80 -0
- machine_dialect/lexer/tests/test_strict_equality.py +129 -0
- machine_dialect/lexer/tests/test_token.py +41 -0
- machine_dialect/lexer/tests/test_tokenization.py +294 -0
- machine_dialect/lexer/tests/test_underscore_literals.py +343 -0
- machine_dialect/lexer/tests/test_url_literals.py +169 -0
- machine_dialect/lexer/tokens.py +487 -0
- machine_dialect/linter/__init__.py +10 -0
- machine_dialect/linter/__main__.py +144 -0
- machine_dialect/linter/linter.py +154 -0
- machine_dialect/linter/rules/__init__.py +8 -0
- machine_dialect/linter/rules/base.py +112 -0
- machine_dialect/linter/rules/statement_termination.py +99 -0
- machine_dialect/linter/tests/__init__.py +1 -0
- machine_dialect/linter/tests/mdrules/__init__.py +0 -0
- machine_dialect/linter/tests/mdrules/test_md101_statement_termination.py +181 -0
- machine_dialect/linter/tests/test_linter.py +81 -0
- machine_dialect/linter/tests/test_rules.py +110 -0
- machine_dialect/linter/tests/test_violations.py +71 -0
- machine_dialect/linter/violations.py +51 -0
- machine_dialect/mir/__init__.py +69 -0
- machine_dialect/mir/analyses/__init__.py +20 -0
- machine_dialect/mir/analyses/alias_analysis.py +315 -0
- machine_dialect/mir/analyses/dominance_analysis.py +49 -0
- machine_dialect/mir/analyses/escape_analysis.py +286 -0
- machine_dialect/mir/analyses/loop_analysis.py +272 -0
- machine_dialect/mir/analyses/tests/test_type_analysis.py +736 -0
- machine_dialect/mir/analyses/type_analysis.py +448 -0
- machine_dialect/mir/analyses/use_def_chains.py +232 -0
- machine_dialect/mir/basic_block.py +385 -0
- machine_dialect/mir/dataflow.py +445 -0
- machine_dialect/mir/debug_info.py +208 -0
- machine_dialect/mir/hir_to_mir.py +1738 -0
- machine_dialect/mir/mir_dumper.py +366 -0
- machine_dialect/mir/mir_function.py +167 -0
- machine_dialect/mir/mir_instructions.py +1877 -0
- machine_dialect/mir/mir_interpreter.py +556 -0
- machine_dialect/mir/mir_module.py +225 -0
- machine_dialect/mir/mir_printer.py +480 -0
- machine_dialect/mir/mir_transformer.py +410 -0
- machine_dialect/mir/mir_types.py +367 -0
- machine_dialect/mir/mir_validation.py +455 -0
- machine_dialect/mir/mir_values.py +268 -0
- machine_dialect/mir/optimization_config.py +233 -0
- machine_dialect/mir/optimization_pass.py +251 -0
- machine_dialect/mir/optimization_pipeline.py +355 -0
- machine_dialect/mir/optimizations/__init__.py +84 -0
- machine_dialect/mir/optimizations/algebraic_simplification.py +733 -0
- machine_dialect/mir/optimizations/branch_prediction.py +372 -0
- machine_dialect/mir/optimizations/constant_propagation.py +634 -0
- machine_dialect/mir/optimizations/cse.py +398 -0
- machine_dialect/mir/optimizations/dce.py +288 -0
- machine_dialect/mir/optimizations/inlining.py +551 -0
- machine_dialect/mir/optimizations/jump_threading.py +487 -0
- machine_dialect/mir/optimizations/licm.py +405 -0
- machine_dialect/mir/optimizations/loop_unrolling.py +366 -0
- machine_dialect/mir/optimizations/strength_reduction.py +422 -0
- machine_dialect/mir/optimizations/tail_call.py +207 -0
- machine_dialect/mir/optimizations/tests/test_loop_unrolling.py +483 -0
- machine_dialect/mir/optimizations/type_narrowing.py +397 -0
- machine_dialect/mir/optimizations/type_specialization.py +447 -0
- machine_dialect/mir/optimizations/type_specific.py +906 -0
- machine_dialect/mir/optimize_mir.py +89 -0
- machine_dialect/mir/pass_manager.py +391 -0
- machine_dialect/mir/profiling/__init__.py +26 -0
- machine_dialect/mir/profiling/profile_collector.py +318 -0
- machine_dialect/mir/profiling/profile_data.py +372 -0
- machine_dialect/mir/profiling/profile_reader.py +272 -0
- machine_dialect/mir/profiling/profile_writer.py +226 -0
- machine_dialect/mir/register_allocation.py +302 -0
- machine_dialect/mir/reporting/__init__.py +17 -0
- machine_dialect/mir/reporting/optimization_reporter.py +314 -0
- machine_dialect/mir/reporting/report_formatter.py +289 -0
- machine_dialect/mir/ssa_construction.py +342 -0
- machine_dialect/mir/tests/__init__.py +1 -0
- machine_dialect/mir/tests/test_algebraic_associativity.py +204 -0
- machine_dialect/mir/tests/test_algebraic_complex_patterns.py +221 -0
- machine_dialect/mir/tests/test_algebraic_division.py +126 -0
- machine_dialect/mir/tests/test_algebraic_simplification.py +863 -0
- machine_dialect/mir/tests/test_basic_block.py +425 -0
- machine_dialect/mir/tests/test_branch_prediction.py +459 -0
- machine_dialect/mir/tests/test_call_lowering.py +168 -0
- machine_dialect/mir/tests/test_collection_lowering.py +604 -0
- machine_dialect/mir/tests/test_cross_block_constant_propagation.py +255 -0
- machine_dialect/mir/tests/test_custom_passes.py +166 -0
- machine_dialect/mir/tests/test_debug_info.py +285 -0
- machine_dialect/mir/tests/test_dict_extraction_lowering.py +192 -0
- machine_dialect/mir/tests/test_dictionary_lowering.py +299 -0
- machine_dialect/mir/tests/test_double_negation.py +231 -0
- machine_dialect/mir/tests/test_escape_analysis.py +233 -0
- machine_dialect/mir/tests/test_hir_to_mir.py +465 -0
- machine_dialect/mir/tests/test_hir_to_mir_complete.py +389 -0
- machine_dialect/mir/tests/test_hir_to_mir_simple.py +130 -0
- machine_dialect/mir/tests/test_inlining.py +435 -0
- machine_dialect/mir/tests/test_licm.py +472 -0
- machine_dialect/mir/tests/test_mir_dumper.py +313 -0
- machine_dialect/mir/tests/test_mir_instructions.py +445 -0
- machine_dialect/mir/tests/test_mir_module.py +860 -0
- machine_dialect/mir/tests/test_mir_printer.py +387 -0
- machine_dialect/mir/tests/test_mir_types.py +123 -0
- machine_dialect/mir/tests/test_mir_types_enhanced.py +132 -0
- machine_dialect/mir/tests/test_mir_validation.py +378 -0
- machine_dialect/mir/tests/test_mir_values.py +168 -0
- machine_dialect/mir/tests/test_one_based_indexing.py +202 -0
- machine_dialect/mir/tests/test_optimization_helpers.py +60 -0
- machine_dialect/mir/tests/test_optimization_pipeline.py +554 -0
- machine_dialect/mir/tests/test_optimization_reporter.py +318 -0
- machine_dialect/mir/tests/test_pass_manager.py +294 -0
- machine_dialect/mir/tests/test_pass_registration.py +64 -0
- machine_dialect/mir/tests/test_profiling.py +356 -0
- machine_dialect/mir/tests/test_register_allocation.py +307 -0
- machine_dialect/mir/tests/test_report_formatters.py +372 -0
- machine_dialect/mir/tests/test_ssa_construction.py +433 -0
- machine_dialect/mir/tests/test_tail_call.py +236 -0
- machine_dialect/mir/tests/test_type_annotated_instructions.py +192 -0
- machine_dialect/mir/tests/test_type_narrowing.py +277 -0
- machine_dialect/mir/tests/test_type_specialization.py +421 -0
- machine_dialect/mir/tests/test_type_specific_optimization.py +545 -0
- machine_dialect/mir/tests/test_type_specific_optimization_advanced.py +382 -0
- machine_dialect/mir/type_inference.py +368 -0
- machine_dialect/parser/__init__.py +12 -0
- machine_dialect/parser/enums.py +45 -0
- machine_dialect/parser/parser.py +3655 -0
- machine_dialect/parser/protocols.py +11 -0
- machine_dialect/parser/symbol_table.py +169 -0
- machine_dialect/parser/tests/__init__.py +0 -0
- machine_dialect/parser/tests/helper_functions.py +193 -0
- machine_dialect/parser/tests/test_action_statements.py +334 -0
- machine_dialect/parser/tests/test_boolean_literal_expressions.py +152 -0
- machine_dialect/parser/tests/test_call_statements.py +154 -0
- machine_dialect/parser/tests/test_call_statements_errors.py +187 -0
- machine_dialect/parser/tests/test_collection_mutations.py +264 -0
- machine_dialect/parser/tests/test_conditional_expressions.py +343 -0
- machine_dialect/parser/tests/test_define_integration.py +468 -0
- machine_dialect/parser/tests/test_define_statements.py +311 -0
- machine_dialect/parser/tests/test_dict_extraction.py +115 -0
- machine_dialect/parser/tests/test_empty_literal.py +155 -0
- machine_dialect/parser/tests/test_float_literal_expressions.py +163 -0
- machine_dialect/parser/tests/test_identifier_expressions.py +57 -0
- machine_dialect/parser/tests/test_if_empty_block.py +61 -0
- machine_dialect/parser/tests/test_if_statements.py +299 -0
- machine_dialect/parser/tests/test_illegal_tokens.py +86 -0
- machine_dialect/parser/tests/test_infix_expressions.py +680 -0
- machine_dialect/parser/tests/test_integer_literal_expressions.py +137 -0
- machine_dialect/parser/tests/test_interaction_statements.py +269 -0
- machine_dialect/parser/tests/test_list_literals.py +277 -0
- machine_dialect/parser/tests/test_no_none_in_ast.py +94 -0
- machine_dialect/parser/tests/test_panic_mode_recovery.py +171 -0
- machine_dialect/parser/tests/test_parse_errors.py +114 -0
- machine_dialect/parser/tests/test_possessive_syntax.py +182 -0
- machine_dialect/parser/tests/test_prefix_expressions.py +415 -0
- machine_dialect/parser/tests/test_program.py +13 -0
- machine_dialect/parser/tests/test_return_statements.py +89 -0
- machine_dialect/parser/tests/test_set_statements.py +152 -0
- machine_dialect/parser/tests/test_strict_equality.py +258 -0
- machine_dialect/parser/tests/test_symbol_table.py +217 -0
- machine_dialect/parser/tests/test_url_literal_expressions.py +209 -0
- machine_dialect/parser/tests/test_utility_statements.py +423 -0
- machine_dialect/parser/token_buffer.py +159 -0
- machine_dialect/repl/__init__.py +3 -0
- machine_dialect/repl/repl.py +426 -0
- machine_dialect/repl/tests/__init__.py +0 -0
- machine_dialect/repl/tests/test_repl.py +606 -0
- machine_dialect/semantic/__init__.py +12 -0
- machine_dialect/semantic/analyzer.py +906 -0
- machine_dialect/semantic/error_messages.py +189 -0
- machine_dialect/semantic/tests/__init__.py +1 -0
- machine_dialect/semantic/tests/test_analyzer.py +364 -0
- machine_dialect/semantic/tests/test_error_messages.py +104 -0
- machine_dialect/tests/edge_cases/__init__.py +10 -0
- machine_dialect/tests/edge_cases/test_boundary_access.py +256 -0
- machine_dialect/tests/edge_cases/test_empty_collections.py +166 -0
- machine_dialect/tests/edge_cases/test_invalid_operations.py +243 -0
- machine_dialect/tests/edge_cases/test_named_list_edge_cases.py +295 -0
- machine_dialect/tests/edge_cases/test_nested_structures.py +313 -0
- machine_dialect/tests/edge_cases/test_type_mixing.py +277 -0
- machine_dialect/tests/integration/test_array_operations_emulation.py +248 -0
- machine_dialect/tests/integration/test_list_compilation.py +395 -0
- machine_dialect/tests/integration/test_lists_and_dictionaries.py +322 -0
- machine_dialect/type_checking/__init__.py +21 -0
- machine_dialect/type_checking/tests/__init__.py +1 -0
- machine_dialect/type_checking/tests/test_type_system.py +230 -0
- machine_dialect/type_checking/type_system.py +270 -0
- machine_dialect-0.1.0a1.dist-info/METADATA +128 -0
- machine_dialect-0.1.0a1.dist-info/RECORD +268 -0
- machine_dialect-0.1.0a1.dist-info/WHEEL +5 -0
- machine_dialect-0.1.0a1.dist-info/entry_points.txt +3 -0
- machine_dialect-0.1.0a1.dist-info/licenses/LICENSE +201 -0
- machine_dialect-0.1.0a1.dist-info/top_level.txt +2 -0
- 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"
|