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,385 @@
|
|
1
|
+
"""Basic Blocks and Control Flow Graph.
|
2
|
+
|
3
|
+
This module implements basic blocks (sequences of instructions with single
|
4
|
+
entry and exit points) and the control flow graph that connects them.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from .mir_instructions import ConditionalJump, Jump, Label, MIRInstruction, Phi, Return
|
8
|
+
|
9
|
+
|
10
|
+
class BasicBlock:
|
11
|
+
"""A basic block in the control flow graph.
|
12
|
+
|
13
|
+
A basic block is a sequence of instructions with:
|
14
|
+
- Single entry point (at the beginning)
|
15
|
+
- Single exit point (at the end)
|
16
|
+
- No branches except at the end
|
17
|
+
"""
|
18
|
+
|
19
|
+
def __init__(self, label: str) -> None:
|
20
|
+
"""Initialize a basic block.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
label: The block's label.
|
24
|
+
"""
|
25
|
+
self.label = label
|
26
|
+
self.instructions: list[MIRInstruction] = []
|
27
|
+
self.phi_nodes: list[Phi] = []
|
28
|
+
self.predecessors: list[BasicBlock] = []
|
29
|
+
self.successors: list[BasicBlock] = []
|
30
|
+
|
31
|
+
def add_instruction(self, inst: MIRInstruction) -> None:
|
32
|
+
"""Add an instruction to the block.
|
33
|
+
|
34
|
+
Args:
|
35
|
+
inst: The instruction to add.
|
36
|
+
"""
|
37
|
+
if isinstance(inst, Phi):
|
38
|
+
self.phi_nodes.append(inst)
|
39
|
+
else:
|
40
|
+
self.instructions.append(inst)
|
41
|
+
|
42
|
+
def add_predecessor(self, pred: "BasicBlock") -> None:
|
43
|
+
"""Add a predecessor block.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
pred: The predecessor block.
|
47
|
+
"""
|
48
|
+
if pred not in self.predecessors:
|
49
|
+
self.predecessors.append(pred)
|
50
|
+
pred.successors.append(self)
|
51
|
+
|
52
|
+
def add_successor(self, succ: "BasicBlock") -> None:
|
53
|
+
"""Add a successor block.
|
54
|
+
|
55
|
+
Args:
|
56
|
+
succ: The successor block.
|
57
|
+
"""
|
58
|
+
if succ not in self.successors:
|
59
|
+
self.successors.append(succ)
|
60
|
+
succ.predecessors.append(self)
|
61
|
+
|
62
|
+
def get_terminator(self) -> MIRInstruction | None:
|
63
|
+
"""Get the terminator instruction (last instruction if it's a branch/return).
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
The terminator instruction or None.
|
67
|
+
"""
|
68
|
+
if not self.instructions:
|
69
|
+
return None
|
70
|
+
last = self.instructions[-1]
|
71
|
+
if isinstance(last, Jump | ConditionalJump | Return):
|
72
|
+
return last
|
73
|
+
return None
|
74
|
+
|
75
|
+
def is_terminated(self) -> bool:
|
76
|
+
"""Check if the block has a terminator.
|
77
|
+
|
78
|
+
Returns:
|
79
|
+
True if the block ends with a terminator.
|
80
|
+
"""
|
81
|
+
return self.get_terminator() is not None
|
82
|
+
|
83
|
+
def __str__(self) -> str:
|
84
|
+
"""Return string representation of the block."""
|
85
|
+
lines = [f"{self.label}:"]
|
86
|
+
|
87
|
+
# Phi nodes come first
|
88
|
+
for phi in self.phi_nodes:
|
89
|
+
lines.append(f" {phi}")
|
90
|
+
|
91
|
+
# Then regular instructions
|
92
|
+
for inst in self.instructions:
|
93
|
+
if not isinstance(inst, Label): # Labels are part of block headers
|
94
|
+
lines.append(f" {inst}")
|
95
|
+
|
96
|
+
return "\n".join(lines)
|
97
|
+
|
98
|
+
def __repr__(self) -> str:
|
99
|
+
"""Return debug representation."""
|
100
|
+
pred_labels = [p.label for p in self.predecessors]
|
101
|
+
succ_labels = [s.label for s in self.successors]
|
102
|
+
return f"BasicBlock({self.label}, preds={pred_labels}, succs={succ_labels})"
|
103
|
+
|
104
|
+
|
105
|
+
class CFG:
|
106
|
+
"""Control Flow Graph.
|
107
|
+
|
108
|
+
The CFG represents the control flow structure of a function as a
|
109
|
+
directed graph of basic blocks.
|
110
|
+
"""
|
111
|
+
|
112
|
+
def __init__(self) -> None:
|
113
|
+
"""Initialize a control flow graph."""
|
114
|
+
self.blocks: dict[str, BasicBlock] = {}
|
115
|
+
self.entry_block: BasicBlock | None = None
|
116
|
+
self.exit_block: BasicBlock | None = None
|
117
|
+
self.dominators: dict[BasicBlock, set[BasicBlock]] = {}
|
118
|
+
self.dominance_frontiers: dict[BasicBlock, list[BasicBlock]] = {}
|
119
|
+
self._next_label_id = 0
|
120
|
+
|
121
|
+
def get_or_create_block(self, label: str) -> BasicBlock:
|
122
|
+
"""Get a block by label, creating it if necessary.
|
123
|
+
|
124
|
+
Args:
|
125
|
+
label: The block label.
|
126
|
+
|
127
|
+
Returns:
|
128
|
+
The basic block.
|
129
|
+
"""
|
130
|
+
if label not in self.blocks:
|
131
|
+
self.blocks[label] = BasicBlock(label)
|
132
|
+
return self.blocks[label]
|
133
|
+
|
134
|
+
def add_block(self, block: BasicBlock) -> None:
|
135
|
+
"""Add a block to the CFG.
|
136
|
+
|
137
|
+
Args:
|
138
|
+
block: The block to add.
|
139
|
+
"""
|
140
|
+
self.blocks[block.label] = block
|
141
|
+
|
142
|
+
def set_entry_block(self, block: BasicBlock) -> None:
|
143
|
+
"""Set the entry block of the CFG.
|
144
|
+
|
145
|
+
Args:
|
146
|
+
block: The entry block.
|
147
|
+
"""
|
148
|
+
self.entry_block = block
|
149
|
+
|
150
|
+
def get_block(self, label: str) -> BasicBlock | None:
|
151
|
+
"""Get a block by label.
|
152
|
+
|
153
|
+
Args:
|
154
|
+
label: The block label.
|
155
|
+
|
156
|
+
Returns:
|
157
|
+
The block or None if not found.
|
158
|
+
"""
|
159
|
+
return self.blocks.get(label)
|
160
|
+
|
161
|
+
def connect(self, from_block: BasicBlock, to_block: BasicBlock) -> None:
|
162
|
+
"""Connect two blocks.
|
163
|
+
|
164
|
+
Args:
|
165
|
+
from_block: Source block.
|
166
|
+
to_block: Target block.
|
167
|
+
"""
|
168
|
+
from_block.add_successor(to_block)
|
169
|
+
|
170
|
+
def get_predecessors(self, block: BasicBlock) -> list[BasicBlock]:
|
171
|
+
"""Get predecessors of a block.
|
172
|
+
|
173
|
+
Args:
|
174
|
+
block: The block.
|
175
|
+
|
176
|
+
Returns:
|
177
|
+
List of predecessor blocks.
|
178
|
+
"""
|
179
|
+
return block.predecessors
|
180
|
+
|
181
|
+
def get_successors(self, block: BasicBlock) -> list[BasicBlock]:
|
182
|
+
"""Get successors of a block.
|
183
|
+
|
184
|
+
Args:
|
185
|
+
block: The block.
|
186
|
+
|
187
|
+
Returns:
|
188
|
+
List of successor blocks.
|
189
|
+
"""
|
190
|
+
return block.successors
|
191
|
+
|
192
|
+
def generate_label(self, prefix: str = "L") -> str:
|
193
|
+
"""Generate a unique label.
|
194
|
+
|
195
|
+
Args:
|
196
|
+
prefix: Label prefix.
|
197
|
+
|
198
|
+
Returns:
|
199
|
+
A unique label.
|
200
|
+
"""
|
201
|
+
label = f"{prefix}{self._next_label_id}"
|
202
|
+
self._next_label_id += 1
|
203
|
+
return label
|
204
|
+
|
205
|
+
def connect_blocks(self, from_label: str, to_label: str) -> None:
|
206
|
+
"""Connect two blocks by label.
|
207
|
+
|
208
|
+
Args:
|
209
|
+
from_label: Source block label.
|
210
|
+
to_label: Target block label.
|
211
|
+
"""
|
212
|
+
from_block = self.get_or_create_block(from_label)
|
213
|
+
to_block = self.get_or_create_block(to_label)
|
214
|
+
from_block.add_successor(to_block)
|
215
|
+
|
216
|
+
def find_exit_blocks(self) -> list[BasicBlock]:
|
217
|
+
"""Find all exit blocks (blocks with return instructions).
|
218
|
+
|
219
|
+
Returns:
|
220
|
+
List of exit blocks.
|
221
|
+
"""
|
222
|
+
exit_blocks = []
|
223
|
+
for block in self.blocks.values():
|
224
|
+
terminator = block.get_terminator()
|
225
|
+
if isinstance(terminator, Return):
|
226
|
+
exit_blocks.append(block)
|
227
|
+
return exit_blocks
|
228
|
+
|
229
|
+
def compute_dominance(self) -> None:
|
230
|
+
"""Compute dominance relationships for all blocks.
|
231
|
+
|
232
|
+
A block X dominates block Y if all paths from entry to Y go through X.
|
233
|
+
Stores results in self.dominators.
|
234
|
+
"""
|
235
|
+
if not self.entry_block:
|
236
|
+
return
|
237
|
+
|
238
|
+
# Initialize dominators - block -> set of dominators
|
239
|
+
self.dominators = {}
|
240
|
+
all_blocks = set(self.blocks.values())
|
241
|
+
|
242
|
+
# Entry block is only dominated by itself
|
243
|
+
self.dominators[self.entry_block] = {self.entry_block}
|
244
|
+
|
245
|
+
# All other blocks are initially dominated by all blocks
|
246
|
+
for block in all_blocks:
|
247
|
+
if block != self.entry_block:
|
248
|
+
self.dominators[block] = all_blocks.copy()
|
249
|
+
|
250
|
+
# Iteratively refine dominators
|
251
|
+
changed = True
|
252
|
+
while changed:
|
253
|
+
changed = False
|
254
|
+
for block in all_blocks:
|
255
|
+
if block == self.entry_block:
|
256
|
+
continue
|
257
|
+
|
258
|
+
# New dominators = {self} U (intersection of dominators of predecessors)
|
259
|
+
if block.predecessors:
|
260
|
+
new_doms = set(all_blocks)
|
261
|
+
for pred in block.predecessors:
|
262
|
+
new_doms &= self.dominators[pred]
|
263
|
+
new_doms.add(block)
|
264
|
+
|
265
|
+
if new_doms != self.dominators[block]:
|
266
|
+
self.dominators[block] = new_doms
|
267
|
+
changed = True
|
268
|
+
|
269
|
+
def compute_dominance_frontiers(self) -> None:
|
270
|
+
"""Compute dominance frontiers for all blocks.
|
271
|
+
|
272
|
+
The dominance frontier of a block X is the set of blocks Y where:
|
273
|
+
- X dominates a predecessor of Y
|
274
|
+
- X does not strictly dominate Y
|
275
|
+
|
276
|
+
Must be called after compute_dominance().
|
277
|
+
Stores results in self.dominance_frontiers.
|
278
|
+
"""
|
279
|
+
if not self.dominators:
|
280
|
+
self.compute_dominance()
|
281
|
+
|
282
|
+
self.dominance_frontiers = {block: [] for block in self.blocks.values()}
|
283
|
+
|
284
|
+
for block in self.blocks.values():
|
285
|
+
# Skip if no predecessors
|
286
|
+
if len(block.predecessors) < 2:
|
287
|
+
continue
|
288
|
+
|
289
|
+
for pred in block.predecessors:
|
290
|
+
runner = pred
|
291
|
+
while runner != self._immediate_dominator(block):
|
292
|
+
if block not in self.dominance_frontiers[runner]:
|
293
|
+
self.dominance_frontiers[runner].append(block)
|
294
|
+
runner = self._immediate_dominator(runner)
|
295
|
+
|
296
|
+
def _immediate_dominator(self, block: BasicBlock) -> BasicBlock:
|
297
|
+
"""Find the immediate dominator of a block.
|
298
|
+
|
299
|
+
Args:
|
300
|
+
block: The block to find immediate dominator for.
|
301
|
+
|
302
|
+
Returns:
|
303
|
+
The immediate dominator.
|
304
|
+
"""
|
305
|
+
doms = self.dominators[block] - {block}
|
306
|
+
if not doms:
|
307
|
+
return block # Entry block
|
308
|
+
|
309
|
+
# Find the dominator that doesn't dominate any other dominator
|
310
|
+
for candidate in doms:
|
311
|
+
is_immediate = True
|
312
|
+
for other in doms:
|
313
|
+
if other != candidate and candidate in self.dominators[other]:
|
314
|
+
is_immediate = False
|
315
|
+
break
|
316
|
+
if is_immediate:
|
317
|
+
return candidate
|
318
|
+
|
319
|
+
return block # Shouldn't happen
|
320
|
+
|
321
|
+
def topological_sort(self) -> list[BasicBlock]:
|
322
|
+
"""Perform topological sort of blocks.
|
323
|
+
|
324
|
+
Returns:
|
325
|
+
List of blocks in topological order.
|
326
|
+
"""
|
327
|
+
if not self.entry_block:
|
328
|
+
return []
|
329
|
+
|
330
|
+
visited = set()
|
331
|
+
result = []
|
332
|
+
|
333
|
+
def visit(block: BasicBlock) -> None:
|
334
|
+
if block in visited:
|
335
|
+
return
|
336
|
+
visited.add(block)
|
337
|
+
for succ in block.successors:
|
338
|
+
visit(succ)
|
339
|
+
result.append(block)
|
340
|
+
|
341
|
+
visit(self.entry_block)
|
342
|
+
result.reverse()
|
343
|
+
return result
|
344
|
+
|
345
|
+
def to_dot(self) -> str:
|
346
|
+
"""Generate Graphviz DOT representation of the CFG.
|
347
|
+
|
348
|
+
Returns:
|
349
|
+
DOT format string.
|
350
|
+
"""
|
351
|
+
lines = ["digraph CFG {"]
|
352
|
+
lines.append(" node [shape=box];")
|
353
|
+
|
354
|
+
# Add nodes
|
355
|
+
for label, block in self.blocks.items():
|
356
|
+
# Escape special characters for DOT
|
357
|
+
content = str(block).replace('"', '\\"').replace("\n", "\\l")
|
358
|
+
lines.append(f' "{label}" [label="{content}\\l"];')
|
359
|
+
|
360
|
+
# Add edges
|
361
|
+
for label, block in self.blocks.items():
|
362
|
+
for succ in block.successors:
|
363
|
+
lines.append(f' "{label}" -> "{succ.label}";')
|
364
|
+
|
365
|
+
lines.append("}")
|
366
|
+
return "\n".join(lines)
|
367
|
+
|
368
|
+
def __str__(self) -> str:
|
369
|
+
"""Return string representation of the CFG."""
|
370
|
+
if not self.entry_block:
|
371
|
+
return "<empty CFG>"
|
372
|
+
|
373
|
+
lines = []
|
374
|
+
visited = set()
|
375
|
+
|
376
|
+
def visit(block: BasicBlock) -> None:
|
377
|
+
if block.label in visited:
|
378
|
+
return
|
379
|
+
visited.add(block.label)
|
380
|
+
lines.append(str(block))
|
381
|
+
for succ in block.successors:
|
382
|
+
visit(succ)
|
383
|
+
|
384
|
+
visit(self.entry_block)
|
385
|
+
return "\n\n".join(lines)
|