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,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
|