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,272 @@
|
|
1
|
+
"""Profile reader for loading persisted profile data.
|
2
|
+
|
3
|
+
This module implements deserialization of profile data from disk
|
4
|
+
for use in profile-guided optimization.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import json
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import Any
|
10
|
+
|
11
|
+
from machine_dialect.mir.profiling.profile_data import (
|
12
|
+
BasicBlockProfile,
|
13
|
+
BranchProfile,
|
14
|
+
FunctionProfile,
|
15
|
+
IndirectCallProfile,
|
16
|
+
LoopProfile,
|
17
|
+
ProfileData,
|
18
|
+
)
|
19
|
+
|
20
|
+
|
21
|
+
class ProfileReader:
|
22
|
+
"""Reads profile data from disk in various formats."""
|
23
|
+
|
24
|
+
def __init__(self) -> None:
|
25
|
+
"""Initialize the profile reader."""
|
26
|
+
pass
|
27
|
+
|
28
|
+
def read_json(self, filepath: Path | str) -> ProfileData:
|
29
|
+
"""Read profile data from JSON format.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
filepath: Path to input file.
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
Loaded profile data.
|
36
|
+
|
37
|
+
Raises:
|
38
|
+
FileNotFoundError: If file doesn't exist.
|
39
|
+
json.JSONDecodeError: If file is not valid JSON.
|
40
|
+
"""
|
41
|
+
filepath = Path(filepath)
|
42
|
+
|
43
|
+
if not filepath.exists():
|
44
|
+
raise FileNotFoundError(f"Profile file not found: {filepath}")
|
45
|
+
|
46
|
+
with open(filepath) as f:
|
47
|
+
data = json.load(f)
|
48
|
+
|
49
|
+
return self._dict_to_profile(data)
|
50
|
+
|
51
|
+
def read_binary(self, filepath: Path | str) -> ProfileData:
|
52
|
+
"""Read profile data from binary format.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
filepath: Path to input file.
|
56
|
+
|
57
|
+
Returns:
|
58
|
+
Loaded profile data.
|
59
|
+
|
60
|
+
Raises:
|
61
|
+
FileNotFoundError: If file doesn't exist.
|
62
|
+
pickle.UnpicklingError: If file is not valid pickle format.
|
63
|
+
"""
|
64
|
+
import pickle
|
65
|
+
|
66
|
+
filepath = Path(filepath)
|
67
|
+
|
68
|
+
if not filepath.exists():
|
69
|
+
raise FileNotFoundError(f"Profile file not found: {filepath}")
|
70
|
+
|
71
|
+
with open(filepath, "rb") as f:
|
72
|
+
data = pickle.load(f)
|
73
|
+
if not isinstance(data, ProfileData):
|
74
|
+
raise ValueError(f"Invalid profile data type: {type(data)}")
|
75
|
+
return data
|
76
|
+
|
77
|
+
def read_auto(self, filepath: Path | str) -> ProfileData:
|
78
|
+
"""Automatically detect format and read profile data.
|
79
|
+
|
80
|
+
Args:
|
81
|
+
filepath: Path to input file.
|
82
|
+
|
83
|
+
Returns:
|
84
|
+
Loaded profile data.
|
85
|
+
|
86
|
+
Raises:
|
87
|
+
FileNotFoundError: If file doesn't exist.
|
88
|
+
ValueError: If format cannot be determined.
|
89
|
+
"""
|
90
|
+
filepath = Path(filepath)
|
91
|
+
|
92
|
+
if not filepath.exists():
|
93
|
+
raise FileNotFoundError(f"Profile file not found: {filepath}")
|
94
|
+
|
95
|
+
# Try to detect format by extension
|
96
|
+
if filepath.suffix == ".json":
|
97
|
+
return self.read_json(filepath)
|
98
|
+
elif filepath.suffix in [".pkl", ".pickle", ".bin"]:
|
99
|
+
return self.read_binary(filepath)
|
100
|
+
|
101
|
+
# Try to detect by content
|
102
|
+
try:
|
103
|
+
return self.read_json(filepath)
|
104
|
+
except json.JSONDecodeError:
|
105
|
+
try:
|
106
|
+
return self.read_binary(filepath)
|
107
|
+
except Exception as e:
|
108
|
+
raise ValueError(f"Cannot determine profile format: {e}") from e
|
109
|
+
|
110
|
+
def merge_profiles(self, filepaths: list[Path | str]) -> ProfileData:
|
111
|
+
"""Merge multiple profile files into one.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
filepaths: List of profile file paths.
|
115
|
+
|
116
|
+
Returns:
|
117
|
+
Merged profile data.
|
118
|
+
"""
|
119
|
+
if not filepaths:
|
120
|
+
raise ValueError("No profile files provided")
|
121
|
+
|
122
|
+
# Read first profile as base
|
123
|
+
merged = self.read_auto(filepaths[0])
|
124
|
+
|
125
|
+
# Merge remaining profiles
|
126
|
+
for filepath in filepaths[1:]:
|
127
|
+
profile = self.read_auto(filepath)
|
128
|
+
merged.merge(profile)
|
129
|
+
|
130
|
+
return merged
|
131
|
+
|
132
|
+
def _dict_to_profile(self, data: dict[str, Any]) -> ProfileData:
|
133
|
+
"""Convert dictionary to profile data.
|
134
|
+
|
135
|
+
Args:
|
136
|
+
data: Dictionary representation.
|
137
|
+
|
138
|
+
Returns:
|
139
|
+
Profile data object.
|
140
|
+
"""
|
141
|
+
profile = ProfileData(
|
142
|
+
module_name=data.get("module_name", "default"),
|
143
|
+
total_samples=data.get("total_samples", 0),
|
144
|
+
metadata=data.get("metadata", {}),
|
145
|
+
)
|
146
|
+
|
147
|
+
# Load functions
|
148
|
+
for name, func_data in data.get("functions", {}).items():
|
149
|
+
profile.functions[name] = self._dict_to_function(func_data)
|
150
|
+
|
151
|
+
# Load branches
|
152
|
+
for loc, branch_data in data.get("branches", {}).items():
|
153
|
+
profile.branches[loc] = self._dict_to_branch(branch_data)
|
154
|
+
|
155
|
+
# Load loops
|
156
|
+
for loc, loop_data in data.get("loops", {}).items():
|
157
|
+
profile.loops[loc] = self._dict_to_loop(loop_data)
|
158
|
+
|
159
|
+
# Load blocks
|
160
|
+
for loc, block_data in data.get("blocks", {}).items():
|
161
|
+
profile.blocks[loc] = self._dict_to_block(block_data)
|
162
|
+
|
163
|
+
# Load indirect calls
|
164
|
+
for loc, call_data in data.get("indirect_calls", {}).items():
|
165
|
+
profile.indirect_calls[loc] = self._dict_to_indirect_call(call_data)
|
166
|
+
|
167
|
+
return profile
|
168
|
+
|
169
|
+
def _dict_to_function(self, data: dict[str, Any]) -> FunctionProfile:
|
170
|
+
"""Convert dictionary to function profile."""
|
171
|
+
profile = FunctionProfile(
|
172
|
+
name=data["name"],
|
173
|
+
call_count=data.get("call_count", 0),
|
174
|
+
total_cycles=data.get("total_cycles", 0),
|
175
|
+
avg_cycles=data.get("avg_cycles", 0.0),
|
176
|
+
call_sites=data.get("call_sites", {}),
|
177
|
+
hot=data.get("hot", False),
|
178
|
+
inline_benefit=data.get("inline_benefit", 0.0),
|
179
|
+
)
|
180
|
+
return profile
|
181
|
+
|
182
|
+
def _dict_to_branch(self, data: dict[str, Any]) -> BranchProfile:
|
183
|
+
"""Convert dictionary to branch profile."""
|
184
|
+
profile = BranchProfile(
|
185
|
+
location=data["location"],
|
186
|
+
taken_count=data.get("taken_count", 0),
|
187
|
+
not_taken_count=data.get("not_taken_count", 0),
|
188
|
+
taken_probability=data.get("taken_probability", 0.5),
|
189
|
+
predictable=data.get("predictable", False),
|
190
|
+
)
|
191
|
+
return profile
|
192
|
+
|
193
|
+
def _dict_to_loop(self, data: dict[str, Any]) -> LoopProfile:
|
194
|
+
"""Convert dictionary to loop profile."""
|
195
|
+
min_iter_raw = data.get("min_iterations")
|
196
|
+
min_iter: int = 2**31 - 1 # Use max int instead of infinity
|
197
|
+
if min_iter_raw is not None:
|
198
|
+
min_iter = int(min_iter_raw)
|
199
|
+
|
200
|
+
profile = LoopProfile(
|
201
|
+
location=data["location"],
|
202
|
+
entry_count=data.get("entry_count", 0),
|
203
|
+
total_iterations=data.get("total_iterations", 0),
|
204
|
+
avg_iterations=data.get("avg_iterations", 0.0),
|
205
|
+
max_iterations=data.get("max_iterations", 0),
|
206
|
+
min_iterations=min_iter,
|
207
|
+
hot=data.get("hot", False),
|
208
|
+
unroll_benefit=data.get("unroll_benefit", 0.0),
|
209
|
+
)
|
210
|
+
return profile
|
211
|
+
|
212
|
+
def _dict_to_block(self, data: dict[str, Any]) -> BasicBlockProfile:
|
213
|
+
"""Convert dictionary to basic block profile."""
|
214
|
+
profile = BasicBlockProfile(
|
215
|
+
location=data["location"],
|
216
|
+
execution_count=data.get("execution_count", 0),
|
217
|
+
instruction_count=data.get("instruction_count", 0),
|
218
|
+
total_cycles=data.get("total_cycles", 0),
|
219
|
+
avg_cycles=data.get("avg_cycles", 0.0),
|
220
|
+
hot=data.get("hot", False),
|
221
|
+
)
|
222
|
+
return profile
|
223
|
+
|
224
|
+
def _dict_to_indirect_call(self, data: dict[str, Any]) -> IndirectCallProfile:
|
225
|
+
"""Convert dictionary to indirect call profile."""
|
226
|
+
profile = IndirectCallProfile(
|
227
|
+
location=data["location"],
|
228
|
+
targets=data.get("targets", {}),
|
229
|
+
total_calls=data.get("total_calls", 0),
|
230
|
+
most_common_target=data.get("most_common_target"),
|
231
|
+
devirtualization_benefit=data.get("devirtualization_benefit", 0.0),
|
232
|
+
)
|
233
|
+
return profile
|
234
|
+
|
235
|
+
def validate_profile(self, profile_data: ProfileData) -> list[str]:
|
236
|
+
"""Validate profile data for consistency.
|
237
|
+
|
238
|
+
Args:
|
239
|
+
profile_data: Profile data to validate.
|
240
|
+
|
241
|
+
Returns:
|
242
|
+
List of validation warnings/errors.
|
243
|
+
"""
|
244
|
+
warnings = []
|
245
|
+
|
246
|
+
# Check for empty profile
|
247
|
+
if profile_data.total_samples == 0:
|
248
|
+
warnings.append("Profile has no samples")
|
249
|
+
|
250
|
+
# Check function consistency
|
251
|
+
for name, func in profile_data.functions.items():
|
252
|
+
if func.call_count == 0 and func.total_cycles > 0:
|
253
|
+
warnings.append(f"Function {name} has cycles but no calls")
|
254
|
+
if func.call_count > 0 and func.avg_cycles == 0:
|
255
|
+
warnings.append(f"Function {name} has calls but no average cycles")
|
256
|
+
|
257
|
+
# Check branch consistency
|
258
|
+
for loc, branch in profile_data.branches.items():
|
259
|
+
total = branch.taken_count + branch.not_taken_count
|
260
|
+
if total == 0:
|
261
|
+
warnings.append(f"Branch {loc} has no executions")
|
262
|
+
elif abs(branch.taken_probability - (branch.taken_count / total)) > 0.01:
|
263
|
+
warnings.append(f"Branch {loc} has inconsistent probability")
|
264
|
+
|
265
|
+
# Check loop consistency
|
266
|
+
for loc, loop in profile_data.loops.items():
|
267
|
+
if loop.entry_count == 0 and loop.total_iterations > 0:
|
268
|
+
warnings.append(f"Loop {loc} has iterations but no entries")
|
269
|
+
if loop.max_iterations < loop.min_iterations:
|
270
|
+
warnings.append(f"Loop {loc} has max < min iterations")
|
271
|
+
|
272
|
+
return warnings
|
@@ -0,0 +1,226 @@
|
|
1
|
+
"""Profile writer for persisting profile data.
|
2
|
+
|
3
|
+
This module implements serialization of profile data to disk for
|
4
|
+
reuse across compilation sessions.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import json
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import Any
|
10
|
+
|
11
|
+
from machine_dialect.mir.profiling.profile_data import (
|
12
|
+
BasicBlockProfile,
|
13
|
+
BranchProfile,
|
14
|
+
FunctionProfile,
|
15
|
+
IndirectCallProfile,
|
16
|
+
LoopProfile,
|
17
|
+
ProfileData,
|
18
|
+
)
|
19
|
+
|
20
|
+
|
21
|
+
class ProfileWriter:
|
22
|
+
"""Writes profile data to disk in various formats."""
|
23
|
+
|
24
|
+
def __init__(self) -> None:
|
25
|
+
"""Initialize the profile writer."""
|
26
|
+
pass
|
27
|
+
|
28
|
+
def write_json(self, profile_data: ProfileData, filepath: Path | str) -> None:
|
29
|
+
"""Write profile data to JSON format.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
profile_data: Profile data to write.
|
33
|
+
filepath: Path to output file.
|
34
|
+
"""
|
35
|
+
filepath = Path(filepath)
|
36
|
+
filepath.parent.mkdir(parents=True, exist_ok=True)
|
37
|
+
|
38
|
+
# Convert profile data to JSON-serializable format
|
39
|
+
data = self._profile_to_dict(profile_data)
|
40
|
+
|
41
|
+
# Write to file with pretty formatting
|
42
|
+
with open(filepath, "w") as f:
|
43
|
+
json.dump(data, f, indent=2, sort_keys=True)
|
44
|
+
|
45
|
+
def write_binary(self, profile_data: ProfileData, filepath: Path | str) -> None:
|
46
|
+
"""Write profile data to efficient binary format.
|
47
|
+
|
48
|
+
Args:
|
49
|
+
profile_data: Profile data to write.
|
50
|
+
filepath: Path to output file.
|
51
|
+
"""
|
52
|
+
import pickle
|
53
|
+
|
54
|
+
filepath = Path(filepath)
|
55
|
+
filepath.parent.mkdir(parents=True, exist_ok=True)
|
56
|
+
|
57
|
+
# Use pickle for binary serialization
|
58
|
+
with open(filepath, "wb") as f:
|
59
|
+
pickle.dump(profile_data, f, protocol=pickle.HIGHEST_PROTOCOL)
|
60
|
+
|
61
|
+
def write_summary(self, profile_data: ProfileData, filepath: Path | str) -> None:
|
62
|
+
"""Write human-readable profile summary.
|
63
|
+
|
64
|
+
Args:
|
65
|
+
profile_data: Profile data to summarize.
|
66
|
+
filepath: Path to output file.
|
67
|
+
"""
|
68
|
+
filepath = Path(filepath)
|
69
|
+
filepath.parent.mkdir(parents=True, exist_ok=True)
|
70
|
+
|
71
|
+
with open(filepath, "w") as f:
|
72
|
+
# Write header
|
73
|
+
f.write(f"Profile Summary for Module: {profile_data.module_name}\n")
|
74
|
+
f.write("=" * 60 + "\n\n")
|
75
|
+
|
76
|
+
# Write statistics
|
77
|
+
summary = profile_data.get_summary()
|
78
|
+
f.write(f"Total Samples: {summary['total_samples']}\n\n")
|
79
|
+
|
80
|
+
# Function statistics
|
81
|
+
f.write("Functions:\n")
|
82
|
+
f.write(f" Total: {summary['functions']['total']}\n")
|
83
|
+
f.write(f" Hot: {summary['functions']['hot']}\n\n")
|
84
|
+
|
85
|
+
# Hot functions details
|
86
|
+
if profile_data.functions:
|
87
|
+
f.write("Hot Functions (Top 10):\n")
|
88
|
+
sorted_funcs = sorted(profile_data.functions.values(), key=lambda x: x.call_count, reverse=True)[:10]
|
89
|
+
for func in sorted_funcs:
|
90
|
+
f.write(f" {func.name}:\n")
|
91
|
+
f.write(f" Calls: {func.call_count}\n")
|
92
|
+
f.write(f" Avg Cycles: {func.avg_cycles:.2f}\n")
|
93
|
+
if func.inline_benefit > 0:
|
94
|
+
f.write(f" Inline Benefit: {func.inline_benefit:.2f}\n")
|
95
|
+
f.write("\n")
|
96
|
+
|
97
|
+
# Branch statistics
|
98
|
+
f.write("Branches:\n")
|
99
|
+
f.write(f" Total: {summary['branches']['total']}\n")
|
100
|
+
f.write(f" Predictable: {summary['branches']['predictable']}\n\n")
|
101
|
+
|
102
|
+
# Predictable branches details
|
103
|
+
predictable = [b for b in profile_data.branches.values() if b.predictable]
|
104
|
+
if predictable:
|
105
|
+
f.write("Predictable Branches (Top 10):\n")
|
106
|
+
for branch in predictable[:10]:
|
107
|
+
f.write(f" {branch.location}:\n")
|
108
|
+
f.write(f" Taken: {branch.taken_probability:.1%}\n")
|
109
|
+
f.write("\n")
|
110
|
+
|
111
|
+
# Loop statistics
|
112
|
+
f.write("Loops:\n")
|
113
|
+
f.write(f" Total: {summary['loops']['total']}\n")
|
114
|
+
f.write(f" Hot: {summary['loops']['hot']}\n\n")
|
115
|
+
|
116
|
+
# Hot loops details
|
117
|
+
hot_loops = [loop for loop in profile_data.loops.values() if loop.hot]
|
118
|
+
if hot_loops:
|
119
|
+
f.write("Hot Loops (Top 10):\n")
|
120
|
+
sorted_loops = sorted(hot_loops, key=lambda x: x.total_iterations, reverse=True)[:10]
|
121
|
+
for loop in sorted_loops:
|
122
|
+
f.write(f" {loop.location}:\n")
|
123
|
+
f.write(f" Iterations: {loop.total_iterations}\n")
|
124
|
+
f.write(f" Avg per Entry: {loop.avg_iterations:.2f}\n")
|
125
|
+
if loop.unroll_benefit > 0:
|
126
|
+
f.write(f" Unroll Benefit: {loop.unroll_benefit:.2f}\n")
|
127
|
+
f.write("\n")
|
128
|
+
|
129
|
+
# Indirect call statistics
|
130
|
+
f.write("Indirect Calls:\n")
|
131
|
+
f.write(f" Total: {summary['indirect_calls']['total']}\n")
|
132
|
+
f.write(f" Devirtualizable: {summary['indirect_calls']['devirtualizable']}\n\n")
|
133
|
+
|
134
|
+
# Devirtualization opportunities
|
135
|
+
devirt = [c for c in profile_data.indirect_calls.values() if c.devirtualization_benefit > 50]
|
136
|
+
if devirt:
|
137
|
+
f.write("Devirtualization Opportunities:\n")
|
138
|
+
for call in devirt:
|
139
|
+
f.write(f" {call.location}:\n")
|
140
|
+
f.write(f" Target: {call.most_common_target}\n")
|
141
|
+
f.write(f" Benefit: {call.devirtualization_benefit:.2f}\n")
|
142
|
+
|
143
|
+
def _profile_to_dict(self, profile_data: ProfileData) -> dict[str, Any]:
|
144
|
+
"""Convert profile data to dictionary.
|
145
|
+
|
146
|
+
Args:
|
147
|
+
profile_data: Profile data to convert.
|
148
|
+
|
149
|
+
Returns:
|
150
|
+
Dictionary representation.
|
151
|
+
"""
|
152
|
+
return {
|
153
|
+
"module_name": profile_data.module_name,
|
154
|
+
"total_samples": profile_data.total_samples,
|
155
|
+
"metadata": profile_data.metadata,
|
156
|
+
"functions": {name: self._function_to_dict(prof) for name, prof in profile_data.functions.items()},
|
157
|
+
"branches": {loc: self._branch_to_dict(prof) for loc, prof in profile_data.branches.items()},
|
158
|
+
"loops": {loc: self._loop_to_dict(prof) for loc, prof in profile_data.loops.items()},
|
159
|
+
"blocks": {loc: self._block_to_dict(prof) for loc, prof in profile_data.blocks.items()},
|
160
|
+
"indirect_calls": {
|
161
|
+
loc: self._indirect_call_to_dict(prof) for loc, prof in profile_data.indirect_calls.items()
|
162
|
+
},
|
163
|
+
}
|
164
|
+
|
165
|
+
def _function_to_dict(self, profile: FunctionProfile) -> dict[str, Any]:
|
166
|
+
"""Convert function profile to dictionary."""
|
167
|
+
return {
|
168
|
+
"name": profile.name,
|
169
|
+
"call_count": profile.call_count,
|
170
|
+
"total_cycles": profile.total_cycles,
|
171
|
+
"avg_cycles": profile.avg_cycles,
|
172
|
+
"call_sites": profile.call_sites,
|
173
|
+
"hot": profile.hot,
|
174
|
+
"inline_benefit": profile.inline_benefit,
|
175
|
+
}
|
176
|
+
|
177
|
+
def _branch_to_dict(self, profile: BranchProfile) -> dict[str, Any]:
|
178
|
+
"""Convert branch profile to dictionary."""
|
179
|
+
return {
|
180
|
+
"location": profile.location,
|
181
|
+
"taken_count": profile.taken_count,
|
182
|
+
"not_taken_count": profile.not_taken_count,
|
183
|
+
"taken_probability": profile.taken_probability,
|
184
|
+
"predictable": profile.predictable,
|
185
|
+
}
|
186
|
+
|
187
|
+
def _loop_to_dict(self, profile: LoopProfile) -> dict[str, Any]:
|
188
|
+
"""Convert loop profile to dictionary."""
|
189
|
+
# Handle max int as None for JSON
|
190
|
+
min_iter = profile.min_iterations
|
191
|
+
if min_iter == 2**31 - 1: # Max int sentinel value
|
192
|
+
min_iter_value: int | None = None
|
193
|
+
else:
|
194
|
+
min_iter_value = min_iter
|
195
|
+
|
196
|
+
return {
|
197
|
+
"location": profile.location,
|
198
|
+
"entry_count": profile.entry_count,
|
199
|
+
"total_iterations": profile.total_iterations,
|
200
|
+
"avg_iterations": profile.avg_iterations,
|
201
|
+
"max_iterations": profile.max_iterations,
|
202
|
+
"min_iterations": min_iter_value,
|
203
|
+
"hot": profile.hot,
|
204
|
+
"unroll_benefit": profile.unroll_benefit,
|
205
|
+
}
|
206
|
+
|
207
|
+
def _block_to_dict(self, profile: BasicBlockProfile) -> dict[str, Any]:
|
208
|
+
"""Convert basic block profile to dictionary."""
|
209
|
+
return {
|
210
|
+
"location": profile.location,
|
211
|
+
"execution_count": profile.execution_count,
|
212
|
+
"instruction_count": profile.instruction_count,
|
213
|
+
"total_cycles": profile.total_cycles,
|
214
|
+
"avg_cycles": profile.avg_cycles,
|
215
|
+
"hot": profile.hot,
|
216
|
+
}
|
217
|
+
|
218
|
+
def _indirect_call_to_dict(self, profile: IndirectCallProfile) -> dict[str, Any]:
|
219
|
+
"""Convert indirect call profile to dictionary."""
|
220
|
+
return {
|
221
|
+
"location": profile.location,
|
222
|
+
"targets": profile.targets,
|
223
|
+
"total_calls": profile.total_calls,
|
224
|
+
"most_common_target": profile.most_common_target,
|
225
|
+
"devirtualization_benefit": profile.devirtualization_benefit,
|
226
|
+
}
|