onnxscript 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. onnxscript/__init__.py +131 -0
  2. onnxscript/_framework_apis/__init__.py +3 -0
  3. onnxscript/_framework_apis/torch_2_5.py +117 -0
  4. onnxscript/_framework_apis/torch_2_6.py +45 -0
  5. onnxscript/_internal/__init__.py +0 -0
  6. onnxscript/_internal/analysis.py +229 -0
  7. onnxscript/_internal/ast_utils.py +64 -0
  8. onnxscript/_internal/autocast.py +250 -0
  9. onnxscript/_internal/deprecation.py +78 -0
  10. onnxscript/_internal/param_manipulation.py +148 -0
  11. onnxscript/_internal/runtime_typing.py +43 -0
  12. onnxscript/_internal/utils.py +99 -0
  13. onnxscript/_internal/version_utils.py +118 -0
  14. onnxscript/_legacy_ir/__init__.py +341 -0
  15. onnxscript/_legacy_ir/visitor.py +937 -0
  16. onnxscript/_thirdparty/asciichartpy.py +313 -0
  17. onnxscript/backend/__init__.py +2 -0
  18. onnxscript/backend/onnx_backend.py +303 -0
  19. onnxscript/backend/onnx_export.py +885 -0
  20. onnxscript/converter.py +1470 -0
  21. onnxscript/evaluator.py +619 -0
  22. onnxscript/function_libs/tools/torch_lib/deduce_type_constraints.py +403 -0
  23. onnxscript/function_libs/tools/torch_lib/generate_aten_signatures.py +333 -0
  24. onnxscript/function_libs/tools/torch_lib/generate_prims_signatures.py +331 -0
  25. onnxscript/function_libs/torch_lib/__init__.py +12 -0
  26. onnxscript/function_libs/torch_lib/_constants.py +5 -0
  27. onnxscript/function_libs/torch_lib/_flags.py +58 -0
  28. onnxscript/function_libs/torch_lib/graph_building/__init__.py +56 -0
  29. onnxscript/function_libs/torch_lib/graph_building/_graph_building_ir.py +723 -0
  30. onnxscript/function_libs/torch_lib/graph_building/_graph_building_torch.py +1125 -0
  31. onnxscript/function_libs/torch_lib/ops/__init__.py +27 -0
  32. onnxscript/function_libs/torch_lib/ops/common.py +80 -0
  33. onnxscript/function_libs/torch_lib/ops/core.py +8935 -0
  34. onnxscript/function_libs/torch_lib/ops/fft.py +385 -0
  35. onnxscript/function_libs/torch_lib/ops/linalg.py +399 -0
  36. onnxscript/function_libs/torch_lib/ops/nested.py +25 -0
  37. onnxscript/function_libs/torch_lib/ops/nn.py +2713 -0
  38. onnxscript/function_libs/torch_lib/ops/prims.py +850 -0
  39. onnxscript/function_libs/torch_lib/ops/quantized_decomposed.py +63 -0
  40. onnxscript/function_libs/torch_lib/ops/sparse.py +23 -0
  41. onnxscript/function_libs/torch_lib/ops/special.py +387 -0
  42. onnxscript/function_libs/torch_lib/ops/vision.py +25 -0
  43. onnxscript/function_libs/torch_lib/registration.py +151 -0
  44. onnxscript/function_libs/torch_lib/tensor_typing.py +74 -0
  45. onnxscript/ir/__init__.py +153 -0
  46. onnxscript/ir/_convenience.py +447 -0
  47. onnxscript/ir/_core.py +3119 -0
  48. onnxscript/ir/_display.py +49 -0
  49. onnxscript/ir/_enums.py +163 -0
  50. onnxscript/ir/_graph_comparison.py +23 -0
  51. onnxscript/ir/_io.py +97 -0
  52. onnxscript/ir/_linked_list.py +276 -0
  53. onnxscript/ir/_metadata.py +44 -0
  54. onnxscript/ir/_name_authority.py +72 -0
  55. onnxscript/ir/_polyfill.py +25 -0
  56. onnxscript/ir/_protocols.py +598 -0
  57. onnxscript/ir/_schemas.py +548 -0
  58. onnxscript/ir/_tape.py +120 -0
  59. onnxscript/ir/_type_casting.py +106 -0
  60. onnxscript/ir/convenience.py +32 -0
  61. onnxscript/ir/external_data.py +396 -0
  62. onnxscript/ir/passes/__init__.py +33 -0
  63. onnxscript/ir/passes/_pass_infra.py +172 -0
  64. onnxscript/ir/serde.py +1620 -0
  65. onnxscript/ir/tensor_adapters.py +122 -0
  66. onnxscript/ir/traversal.py +82 -0
  67. onnxscript/irbuilder.py +542 -0
  68. onnxscript/main.py +167 -0
  69. onnxscript/onnx_opset/__init__.py +232 -0
  70. onnxscript/onnx_opset/_impl/opset1.py +4100 -0
  71. onnxscript/onnx_opset/_impl/opset10.py +1227 -0
  72. onnxscript/onnx_opset/_impl/opset11.py +4013 -0
  73. onnxscript/onnx_opset/_impl/opset12.py +1078 -0
  74. onnxscript/onnx_opset/_impl/opset13.py +3924 -0
  75. onnxscript/onnx_opset/_impl/opset14.py +999 -0
  76. onnxscript/onnx_opset/_impl/opset15.py +604 -0
  77. onnxscript/onnx_opset/_impl/opset16.py +1255 -0
  78. onnxscript/onnx_opset/_impl/opset17.py +561 -0
  79. onnxscript/onnx_opset/_impl/opset18.py +1803 -0
  80. onnxscript/onnx_opset/_impl/opset19.py +1942 -0
  81. onnxscript/onnx_opset/_impl/opset2.py +218 -0
  82. onnxscript/onnx_opset/_impl/opset20.py +675 -0
  83. onnxscript/onnx_opset/_impl/opset21.py +1976 -0
  84. onnxscript/onnx_opset/_impl/opset22.py +2588 -0
  85. onnxscript/onnx_opset/_impl/opset3.py +199 -0
  86. onnxscript/onnx_opset/_impl/opset4.py +77 -0
  87. onnxscript/onnx_opset/_impl/opset5.py +84 -0
  88. onnxscript/onnx_opset/_impl/opset6.py +944 -0
  89. onnxscript/onnx_opset/_impl/opset7.py +1243 -0
  90. onnxscript/onnx_opset/_impl/opset8.py +444 -0
  91. onnxscript/onnx_opset/_impl/opset9.py +1485 -0
  92. onnxscript/onnx_opset/_impl/opset_ai_onnx_ml1.py +974 -0
  93. onnxscript/onnx_opset/_impl/opset_ai_onnx_ml2.py +112 -0
  94. onnxscript/onnx_opset/_impl/opset_ai_onnx_ml3.py +308 -0
  95. onnxscript/onnx_opset/_impl/opset_ai_onnx_ml4.py +129 -0
  96. onnxscript/onnx_opset/_impl/opset_ai_onnx_ml5.py +158 -0
  97. onnxscript/onnx_opset/_impl/opset_ai_onnx_preview_training1.py +577 -0
  98. onnxscript/onnx_types.py +229 -0
  99. onnxscript/optimizer/__init__.py +39 -0
  100. onnxscript/optimizer/_constant_folding.py +1083 -0
  101. onnxscript/optimizer/_inliner.py +312 -0
  102. onnxscript/optimizer/_legacy/_optimizer.py +98 -0
  103. onnxscript/optimizer/_legacy/_remove_unused_proto.py +144 -0
  104. onnxscript/optimizer/_legacy/_simple_function_folding.py +243 -0
  105. onnxscript/optimizer/_legacy/constant_folding.py +293 -0
  106. onnxscript/optimizer/_legacy/evaluator.py +439 -0
  107. onnxscript/optimizer/_optimizer.py +61 -0
  108. onnxscript/optimizer/_remove_unused.py +106 -0
  109. onnxscript/optimizer/_remove_unused_function.py +72 -0
  110. onnxscript/py.typed +1 -0
  111. onnxscript/rewriter/__init__.py +56 -0
  112. onnxscript/rewriter/_ir_utils.py +111 -0
  113. onnxscript/rewriter/broadcast_to_matmul.py +180 -0
  114. onnxscript/rewriter/cast_constant_of_shape.py +50 -0
  115. onnxscript/rewriter/collapse_slices.py +141 -0
  116. onnxscript/rewriter/erfgelu.py +27 -0
  117. onnxscript/rewriter/function_rule.py +232 -0
  118. onnxscript/rewriter/gemm_to_matmul_add.py +21 -0
  119. onnxscript/rewriter/generic_pattern.py +700 -0
  120. onnxscript/rewriter/llama_rule_sets.py +287 -0
  121. onnxscript/rewriter/no_op.py +56 -0
  122. onnxscript/rewriter/onnxruntime/__init__.py +50 -0
  123. onnxscript/rewriter/onnxruntime/bfloat16_utils/bfloat16_converter.py +99 -0
  124. onnxscript/rewriter/onnxruntime/fused_matmul_rule_sets.py +177 -0
  125. onnxscript/rewriter/onnxruntime/group_normalization_merge_silu.py +64 -0
  126. onnxscript/rewriter/onnxruntime/instance_to_group_normalization.py +155 -0
  127. onnxscript/rewriter/onnxruntime/softmax.py +63 -0
  128. onnxscript/rewriter/onnxruntime/transformers/__init__.py +21 -0
  129. onnxscript/rewriter/onnxruntime/transformers/biassplitgelu.py +31 -0
  130. onnxscript/rewriter/onnxruntime/transformers/fastgelu.py +29 -0
  131. onnxscript/rewriter/onnxruntime/transformers/layernorm.py +47 -0
  132. onnxscript/rewriter/onnxruntime/transformers/multihead_attention.py +715 -0
  133. onnxscript/rewriter/ort_fusions/__init__.py +9 -0
  134. onnxscript/rewriter/ort_fusions/_core.py +28 -0
  135. onnxscript/rewriter/ort_fusions/_smollm_1.py +253 -0
  136. onnxscript/rewriter/ort_fusions/_smollm_2.py +467 -0
  137. onnxscript/rewriter/ort_fusions/_test_models.py +122 -0
  138. onnxscript/rewriter/ort_fusions/_test_utils.py +42 -0
  139. onnxscript/rewriter/ort_fusions/cos_sin_cache.py +154 -0
  140. onnxscript/rewriter/ort_fusions/gqa.py +156 -0
  141. onnxscript/rewriter/ort_fusions/mha.py +198 -0
  142. onnxscript/rewriter/ort_fusions/rms_normalization.py +95 -0
  143. onnxscript/rewriter/ort_fusions/rotary_embedding.py +64 -0
  144. onnxscript/rewriter/ort_fusions/sdpa.py +75 -0
  145. onnxscript/rewriter/ort_fusions/skip_normalization.py +46 -0
  146. onnxscript/rewriter/pattern.py +1714 -0
  147. onnxscript/rewriter/testing.py +77 -0
  148. onnxscript/sourceinfo.py +59 -0
  149. onnxscript/tensor.py +227 -0
  150. onnxscript/testing/__init__.py +482 -0
  151. onnxscript/tools/__init__.py +4 -0
  152. onnxscript/tools/benchmark/__init__.py +23 -0
  153. onnxscript/tools/benchmark/benchmark_helpers.py +783 -0
  154. onnxscript/tools/benchmark/benchmark_run.py +140 -0
  155. onnxscript/tools/benchmark/export_model.py +207 -0
  156. onnxscript/tools/benchmark/export_model_batch.py +146 -0
  157. onnxscript/tools/memory_peak.py +244 -0
  158. onnxscript/tools/training_helper.py +50 -0
  159. onnxscript/tools/transformers_models/__init__.py +190 -0
  160. onnxscript/tools/transformers_models/llama.py +168 -0
  161. onnxscript/tools/transformers_models/mistral.py +238 -0
  162. onnxscript/tools/transformers_models/phi.py +248 -0
  163. onnxscript/tools/transformers_models/phi3.py +259 -0
  164. onnxscript/type_annotation.py +281 -0
  165. onnxscript/utils/__init__.py +0 -0
  166. onnxscript/utils/evaluation_utils.py +56 -0
  167. onnxscript/utils/timing_utils.py +33 -0
  168. onnxscript/utils/utils.py +84 -0
  169. onnxscript/values.py +790 -0
  170. onnxscript/version_converter/__init__.py +21 -0
  171. onnxscript/version_converter/_version_converter.py +314 -0
  172. onnxscript-0.1.0.dist-info/LICENSE +21 -0
  173. onnxscript-0.1.0.dist-info/METADATA +370 -0
  174. onnxscript-0.1.0.dist-info/RECORD +176 -0
  175. onnxscript-0.1.0.dist-info/WHEEL +5 -0
  176. onnxscript-0.1.0.dist-info/top_level.txt +1 -0
onnxscript/__init__.py ADDED
@@ -0,0 +1,131 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+
4
+ __all__ = [
5
+ "script",
6
+ "graph",
7
+ "ir",
8
+ "optimizer",
9
+ "rewriter",
10
+ "export_onnx_lib",
11
+ "OnnxFunction",
12
+ "TracedOnnxFunction",
13
+ "proto2python",
14
+ "external_tensor",
15
+ "BFLOAT16",
16
+ "FLOAT16",
17
+ "FLOAT8E4M3FN",
18
+ "FLOAT8E4M3FNUZ",
19
+ "FLOAT8E5M2",
20
+ "FLOAT8E5M2FNUZ",
21
+ "FLOAT",
22
+ "DOUBLE",
23
+ "INT8",
24
+ "INT16",
25
+ "INT32",
26
+ "INT64",
27
+ "UINT8",
28
+ "UINT16",
29
+ "UINT32",
30
+ "UINT64",
31
+ "BOOL",
32
+ "STRING",
33
+ "COMPLEX64",
34
+ "COMPLEX128",
35
+ "opset1",
36
+ "opset2",
37
+ "opset3",
38
+ "opset4",
39
+ "opset5",
40
+ "opset6",
41
+ "opset7",
42
+ "opset8",
43
+ "opset9",
44
+ "opset10",
45
+ "opset11",
46
+ "opset12",
47
+ "opset13",
48
+ "opset14",
49
+ "opset15",
50
+ "opset16",
51
+ "opset17",
52
+ "opset18",
53
+ "opset19",
54
+ "opset20",
55
+ "opset_ai_onnx_ml1",
56
+ "opset_ai_onnx_ml2",
57
+ "opset_ai_onnx_ml3",
58
+ "opset_ai_onnx_ml4",
59
+ "DEBUG",
60
+ ]
61
+
62
+ import importlib.metadata
63
+
64
+ from .backend.onnx_export import export2python as proto2python
65
+ from .main import export_onnx_lib, graph, script
66
+
67
+ # isort: off
68
+ from .onnx_opset import (
69
+ opset1,
70
+ opset2,
71
+ opset3,
72
+ opset4,
73
+ opset5,
74
+ opset6,
75
+ opset7,
76
+ opset8,
77
+ opset9,
78
+ opset10,
79
+ opset11,
80
+ opset12,
81
+ opset13,
82
+ opset14,
83
+ opset15,
84
+ opset16,
85
+ opset17,
86
+ opset18,
87
+ opset19,
88
+ opset20,
89
+ opset_ai_onnx_ml1,
90
+ opset_ai_onnx_ml2,
91
+ opset_ai_onnx_ml3,
92
+ opset_ai_onnx_ml4,
93
+ )
94
+
95
+ from .onnx_types import (
96
+ BFLOAT16,
97
+ FLOAT16,
98
+ FLOAT8E4M3FN,
99
+ FLOAT8E4M3FNUZ,
100
+ FLOAT8E5M2,
101
+ FLOAT8E5M2FNUZ,
102
+ FLOAT,
103
+ DOUBLE,
104
+ INT8,
105
+ INT16,
106
+ INT32,
107
+ INT64,
108
+ UINT8,
109
+ UINT16,
110
+ UINT32,
111
+ UINT64,
112
+ BOOL,
113
+ STRING,
114
+ COMPLEX64,
115
+ COMPLEX128,
116
+ )
117
+
118
+ # isort: on
119
+
120
+ from . import ir, optimizer, rewriter
121
+ from ._internal.utils import external_tensor
122
+ from .values import OnnxFunction, TracedOnnxFunction
123
+
124
+ # Set DEBUG to True to enable additional debug checks
125
+ DEBUG: bool = False
126
+
127
+ try: # noqa: SIM105
128
+ __version__ = importlib.metadata.version("onnxscript")
129
+ except importlib.metadata.PackageNotFoundError:
130
+ # package is not installed
131
+ pass
@@ -0,0 +1,3 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Semi-private stable APIs for framework-specific usage only."""
@@ -0,0 +1,117 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Stable APIs for PyTorch 2.5."""
4
+
5
+ from __future__ import annotations
6
+
7
+ __all__ = [
8
+ "check_model",
9
+ "convert_version",
10
+ "get_torchlib_ops",
11
+ "optimize",
12
+ "save_model_with_external_data",
13
+ ]
14
+
15
+ import dataclasses
16
+ import os
17
+ import pathlib
18
+ from typing import Callable
19
+
20
+ from onnxscript import ir, optimizer, version_converter
21
+ from onnxscript.function_libs.torch_lib import registration
22
+
23
+
24
+ @dataclasses.dataclass(frozen=True)
25
+ class _OnnxFunctionMeta:
26
+ """A wrapper of onnx-script function with additional metadata.
27
+
28
+ qualified_name: The qualified name of the aten operator.
29
+ function: The onnx-script function.
30
+ domain: The domain of the function.
31
+ name: The name of the function.
32
+ is_complex: Whether the function is a complex function.
33
+ """
34
+
35
+ qualified_name: str
36
+ function: Callable
37
+ domain: str
38
+ name: str
39
+ is_complex: bool = False
40
+
41
+
42
+ def optimize(model: ir.Model) -> ir.Model:
43
+ """Optimize the model."""
44
+ # Internal flag. Will go away.
45
+ enabled = os.getenv("TORCH_ONNX_ENABLE_OPTIMIZATION") == "1"
46
+ if enabled:
47
+ optimizer.optimize_ir(model)
48
+ return model
49
+
50
+
51
+ def convert_version(model: ir.Model, target_version: int) -> ir.Model:
52
+ """Convert the model to the specified ONNX opset version."""
53
+ # Internal flag. Will go away.
54
+ enabled = os.getenv("TORCH_ONNX_ENABLE_VERSION_CONVERSION") == "1"
55
+ if enabled:
56
+ version_converter.convert_version(model, target_version)
57
+ return model
58
+
59
+
60
+ def check_model(model: ir.Model) -> None:
61
+ """Check the model."""
62
+
63
+ del model # Unused yet
64
+
65
+
66
+ def save_model_with_external_data(model: ir.Model, model_path: str | os.PathLike) -> None:
67
+ """Save the model with external data. The model is unchanged after saving."""
68
+
69
+ # TODO(#1835): Decide if we want to externalize large attributes as well
70
+ for value in model.graph.initializers.values():
71
+ if value.const_value is None:
72
+ raise ValueError(
73
+ "The model contains uninitialized initializer values. "
74
+ "Please make sure all initializer values are initialized."
75
+ )
76
+ destination_path = pathlib.Path(model_path)
77
+ data_path = f"{destination_path.name}.data"
78
+
79
+ ir.save(model, model_path, external_data=data_path)
80
+
81
+
82
+ def get_torchlib_ops() -> list[_OnnxFunctionMeta]:
83
+ # Trigger op registration
84
+ from onnxscript.function_libs.torch_lib import ( # pylint: disable=import-outside-toplevel
85
+ ops,
86
+ )
87
+
88
+ del ops # Unused
89
+
90
+ torchlib_registry = registration.default_registry
91
+ function_metas = []
92
+
93
+ for qualified_name, aten_overloads_func in torchlib_registry.items():
94
+ if qualified_name.startswith("internal::"):
95
+ # Skip the custom defined internal functions
96
+ continue
97
+
98
+ for overload_func in aten_overloads_func.overloads:
99
+ function_meta = _OnnxFunctionMeta(
100
+ qualified_name=qualified_name,
101
+ function=overload_func,
102
+ domain=overload_func.function_ir.domain,
103
+ name=overload_func.name,
104
+ is_complex=False,
105
+ )
106
+ function_metas.append(function_meta)
107
+ for complex_func in aten_overloads_func.complex:
108
+ function_meta = _OnnxFunctionMeta(
109
+ qualified_name=qualified_name,
110
+ function=complex_func,
111
+ domain=complex_func.function_ir.domain,
112
+ name=complex_func.name,
113
+ is_complex=True,
114
+ )
115
+ function_metas.append(function_meta)
116
+
117
+ return function_metas
@@ -0,0 +1,45 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Stable APIs for PyTorch 2.6."""
4
+
5
+ from __future__ import annotations
6
+
7
+ __all__ = [
8
+ "check_model",
9
+ "convert_version",
10
+ "get_torchlib_ops",
11
+ "optimize",
12
+ "save_model_with_external_data",
13
+ "torchlib_opset",
14
+ ]
15
+ from typing import TYPE_CHECKING
16
+
17
+ from onnxscript import ir, optimizer
18
+ from onnxscript._framework_apis.torch_2_5 import (
19
+ check_model,
20
+ convert_version,
21
+ get_torchlib_ops,
22
+ save_model_with_external_data,
23
+ )
24
+
25
+ if TYPE_CHECKING:
26
+ from onnxscript.onnx_opset._impl.opset18 import Opset18
27
+
28
+
29
+ def optimize(model: ir.Model) -> ir.Model:
30
+ """Optimize the model."""
31
+ optimizer.optimize_ir(model)
32
+ return model
33
+
34
+
35
+ def torchlib_opset() -> Opset18:
36
+ """Return the default opset for torchlib."""
37
+ import onnxscript # pylint: disable=import-outside-toplevel
38
+
39
+ return onnxscript.opset18 # type: ignore
40
+
41
+
42
+ def torchlib_opset_version() -> int:
43
+ """Return the default opset version for torchlib."""
44
+
45
+ return torchlib_opset().version
File without changes
@@ -0,0 +1,229 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ from typing import Any, Optional, Sequence, Set
7
+
8
+ from onnxscript import sourceinfo
9
+ from onnxscript._internal import ast_utils
10
+
11
+
12
+ def _get_loop_var(for_stmt: ast.For, formatter: sourceinfo.Formatter) -> str:
13
+ if not isinstance(for_stmt.target, ast.Name):
14
+ raise TypeError(formatter(for_stmt, "For loop target must be a single variable."))
15
+ return for_stmt.target.id
16
+
17
+
18
+ def _used_vars(expr: Optional[ast.expr]) -> Set[str]:
19
+ """Return set of all variables used, including function names, in an expression."""
20
+ if expr is None:
21
+ return set()
22
+ if isinstance(expr, ast.Name):
23
+ return {expr.id}
24
+ result = set()
25
+ if isinstance(expr, ast.Call):
26
+ # The callee-expression is not visited
27
+ children = expr.args
28
+ for keyword in expr.keywords:
29
+ if isinstance(keyword.value, ast.Name):
30
+ result.add(keyword.value.id)
31
+ else:
32
+ children = ast.iter_child_nodes(expr) # type: ignore[assignment]
33
+ for c in children:
34
+ result = result | _used_vars(c)
35
+ return result
36
+
37
+
38
+ def _lhs_vars(lhs: ast.expr) -> Set[str]:
39
+ """Return set of assigned variables in the lhs of an assignment statement."""
40
+
41
+ def get_id(e):
42
+ assert isinstance(e, ast.Name), "Only simple assignments supported."
43
+ return e.id
44
+
45
+ if isinstance(lhs, ast.Tuple):
46
+ return {get_id(x) for x in lhs.elts}
47
+ return {get_id(lhs)}
48
+
49
+
50
+ def assigned_vars(
51
+ stmt: ast.stmt | list[ast.stmt], formatter: sourceinfo.Formatter
52
+ ) -> Set[str]:
53
+ """Return the set of all variables that may be assigned to in an execution of input stmt
54
+ or sequence of statements.
55
+ """
56
+
57
+ def assigned_in_block(block: Sequence[ast.stmt]) -> Set[str]:
58
+ result: set[Any] = set()
59
+ for s in block:
60
+ result = result | assigned_vars(s, formatter)
61
+ return result
62
+
63
+ if isinstance(stmt, ast.Assign):
64
+ return _lhs_vars(stmt.targets[0])
65
+ if isinstance(stmt, ast.AnnAssign):
66
+ return _lhs_vars(stmt.target)
67
+ if isinstance(stmt, ast.Return):
68
+ return set()
69
+ if isinstance(stmt, ast.If):
70
+ return assigned_in_block(stmt.body) | assigned_in_block(stmt.orelse)
71
+ if isinstance(stmt, ast.For):
72
+ return assigned_in_block(stmt.body) | {_get_loop_var(stmt, formatter)}
73
+ if isinstance(stmt, ast.While):
74
+ return assigned_in_block(stmt.body)
75
+ if isinstance(stmt, list):
76
+ return assigned_in_block(stmt)
77
+ if isinstance(stmt, ast.Break):
78
+ return set()
79
+ if ast_utils.is_print_call(stmt):
80
+ return set()
81
+ if ast_utils.is_doc_string(stmt):
82
+ return set()
83
+ error_message = formatter(stmt, f"Unsupported statement type {type(stmt)!r}.")
84
+ raise ValueError(error_message)
85
+
86
+
87
+ def do_liveness_analysis(fun: ast.FunctionDef, formatter: sourceinfo.Formatter):
88
+ """Perform liveness analysis of the given function-ast. The results of the
89
+ analysis are stored directly with each statement-ast `s` as attributes `s.live_in`
90
+ and `s.live_out`.
91
+ """
92
+
93
+ def visit(stmt: ast.stmt, live_out: Set[str]) -> Set[str]:
94
+ stmt.live_out = live_out # type: ignore[attr-defined]
95
+ live = do_visit(stmt, live_out)
96
+ stmt.live_in = live # type: ignore[attr-defined]
97
+ return live
98
+
99
+ def do_visit(stmt: ast.stmt, live_out: Set[str]) -> Set[str]:
100
+ def visitBlock(block: Sequence[ast.stmt], live_out: Set[str]) -> Set[str]:
101
+ for s in reversed(block):
102
+ live_out = visit(s, live_out)
103
+ return live_out
104
+
105
+ if isinstance(stmt, ast.Assign):
106
+ return live_out.difference(_lhs_vars(stmt.targets[0])) | _used_vars(stmt.value)
107
+ if isinstance(stmt, ast.AnnAssign):
108
+ return live_out.difference(_lhs_vars(stmt.target)) | _used_vars(stmt.value)
109
+ if isinstance(stmt, ast.Return):
110
+ return _used_vars(stmt.value)
111
+ if isinstance(stmt, ast.If):
112
+ live1 = visitBlock(stmt.body, live_out)
113
+ live2 = visitBlock(stmt.orelse, live_out)
114
+ return live1 | live2 | _used_vars(stmt.test)
115
+ if isinstance(stmt, ast.For):
116
+ p_loop_var = _get_loop_var(stmt, formatter)
117
+ prev = None
118
+ curr = live_out
119
+ while curr != prev:
120
+ prev = curr
121
+ curr = visitBlock(stmt.body, prev).difference({p_loop_var})
122
+ return curr
123
+ if isinstance(stmt, ast.While):
124
+ cond_vars = _used_vars(stmt.test)
125
+ prev = None
126
+ curr = live_out | cond_vars
127
+ while curr != prev:
128
+ prev = curr
129
+ curr = visitBlock(stmt.body, prev) | cond_vars
130
+ return curr
131
+ if isinstance(stmt, ast.Break):
132
+ # The following is sufficient for the current restricted usage, where
133
+ # a (conditional) break is allowed only as the last statement of a loop.
134
+ # Break statements in the middle of the loop, however, will require
135
+ # a generalization.
136
+ return live_out
137
+ if ast_utils.is_doc_string(stmt):
138
+ return live_out
139
+ if isinstance(stmt, ast.FunctionDef):
140
+ return live_out
141
+ if ast_utils.is_print_call(stmt):
142
+ return live_out
143
+ raise ValueError(formatter(stmt, f"Unsupported statement type {type(stmt)!r}."))
144
+
145
+ assert isinstance(fun, ast.FunctionDef)
146
+ live: set[Any] = set()
147
+ for s in reversed(fun.body):
148
+ live = visit(s, live)
149
+
150
+
151
+ def exposed_uses(stmts: Sequence[ast.stmt], formatter: sourceinfo.Formatter):
152
+ """Return the set of variables that are used before being defined by given block.
153
+ In essence, this identifies the "inputs" to a given code-block.
154
+ For example, consider the following code-block:
155
+ ::
156
+
157
+ x = x + 10
158
+ y = 20
159
+ z = x + y
160
+ x = 30
161
+
162
+ The exposed_uses of this code-block is { x }. The value of z is not used within
163
+ the block. Even though the value of y is used within the block, it is assigned
164
+ a value before it is used. However, in contrast, the incoming value of x is used
165
+ (in the first statement). Hence x is included in the exposed_uses.
166
+ """
167
+
168
+ def visitBlock(block: Sequence[ast.stmt], live_out: Set[str]) -> Set[str]:
169
+ for stmt in reversed(block):
170
+ live_out = visit(stmt, live_out)
171
+ return live_out
172
+
173
+ def visit(stmt: ast.stmt, live_out: Set[str]) -> Set[str]:
174
+ if isinstance(stmt, ast.Assign):
175
+ return live_out.difference(_lhs_vars(stmt.targets[0])) | _used_vars(stmt.value)
176
+ if isinstance(stmt, ast.AnnAssign):
177
+ return live_out.difference(_lhs_vars(stmt.target)) | _used_vars(stmt.value)
178
+ if isinstance(stmt, ast.Return):
179
+ return _used_vars(stmt.value)
180
+ if isinstance(stmt, ast.If):
181
+ live1 = visitBlock(stmt.body, live_out)
182
+ live2 = visitBlock(stmt.orelse, live_out)
183
+ return (live1 | live2) | _used_vars(stmt.test)
184
+ if ast_utils.is_print_call(stmt):
185
+ return live_out
186
+ if ast_utils.is_doc_string(stmt):
187
+ return live_out
188
+ if isinstance(stmt, ast.For):
189
+ # Analysis assumes loop may execute zero times. Results can be improved
190
+ # for loops that execute at least once.
191
+ loop_var_set = {_get_loop_var(stmt, formatter)}
192
+ used_after_loop = live_out.difference(loop_var_set)
193
+ used_inside_loop = visitBlock(stmt.body, set()).difference(loop_var_set)
194
+ used_in_loop_header = _used_vars(stmt.iter)
195
+ return used_inside_loop | used_in_loop_header | used_after_loop
196
+ if isinstance(stmt, ast.While):
197
+ # Analysis assumes loop may execute zero times. Results can be improved
198
+ # for loops that execute at least once.
199
+ used_inside_loop = visitBlock(stmt.body, set())
200
+ used_in_loop_header = _used_vars(stmt.test)
201
+ return used_inside_loop | used_in_loop_header | live_out
202
+ if isinstance(stmt, ast.Break):
203
+ # Currently, we assume that break statements are only allowed as the last
204
+ # statement in a loop, as "if cond: break".
205
+ return live_out
206
+ if isinstance(stmt, ast.FunctionDef):
207
+ if stmt.name in live_out:
208
+ live_out.remove(stmt.name)
209
+ live_out = live_out | outer_scope_variables(stmt, formatter)
210
+ return live_out
211
+ raise ValueError(formatter(stmt, f"Unsupported statement type {type(stmt)!r}."))
212
+
213
+ return visitBlock(stmts, set())
214
+
215
+
216
+ def outer_scope_variables(fun: ast.FunctionDef, formatter: sourceinfo.Formatter):
217
+ """Return the set of outer-scope variables used in a nested function.
218
+
219
+ Args:
220
+ fun: The function-ast to analyze.
221
+ formatter: The formatter object.
222
+
223
+ Returns:
224
+ A set of variable names (strings).
225
+ """
226
+ assert isinstance(fun, ast.FunctionDef)
227
+ used_vars_ = exposed_uses(fun.body, formatter)
228
+ inputs = [x.arg for x in fun.args.args]
229
+ return used_vars_.difference(inputs)
@@ -0,0 +1,64 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Utilities for working with Python ASTs."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import ast
8
+ import inspect
9
+ import sys
10
+ import textwrap
11
+ from typing import Callable
12
+
13
+ PY_VERSION_GE_39 = sys.version_info >= (3, 9)
14
+
15
+
16
+ def get_src_and_ast(func: Callable, /) -> tuple[str, ast.FunctionDef]:
17
+ try:
18
+ src = inspect.getsource(func)
19
+ except OSError as e:
20
+ raise RuntimeError(
21
+ f"Decorator script does not work on dynamically compiled function {func.__name__}."
22
+ ) from e
23
+ src = textwrap.dedent(src)
24
+ top_level_ast = ast.parse(src)
25
+ assert isinstance(top_level_ast, ast.Module)
26
+ assert len(top_level_ast.body) == 1
27
+ f_ast = top_level_ast.body[0]
28
+ assert isinstance(f_ast, ast.FunctionDef)
29
+ return src, f_ast
30
+
31
+
32
+ def normalize_subscript_expr(expr: ast.Subscript):
33
+ # Normalizes the representation of a subscripted expression, handling python version
34
+ # differences as well as variations between A[x] (single-index) and A[x, y] (multiple indices)
35
+ # Returns a list of expressions, denoting the indices, after stripping the extraneous "Index"
36
+ # wrapper present in python versions before 3.9
37
+ index_expr = expr.slice
38
+ if PY_VERSION_GE_39:
39
+ if isinstance(index_expr, ast.Tuple):
40
+ return index_expr.elts # multiple indices
41
+ else:
42
+ return [index_expr] # single index
43
+ else:
44
+ if isinstance(index_expr, ast.ExtSlice):
45
+ indices = index_expr.dims # type: ignore[attr-defined]
46
+ else:
47
+ indices = [index_expr] # single slice-index
48
+ return [x.value if isinstance(x, ast.Index) else x for x in indices] # type: ignore[attr-defined]
49
+
50
+
51
+ def is_print_call(stmt: ast.stmt) -> bool:
52
+ """Return True if the statement is a call to the print function."""
53
+ if isinstance(stmt, ast.Expr):
54
+ if isinstance(stmt.value, ast.Call):
55
+ if isinstance(stmt.value.func, ast.Name):
56
+ return stmt.value.func.id == "print"
57
+ return False
58
+
59
+
60
+ def is_doc_string(stmt: ast.stmt) -> bool:
61
+ """Return True if the statement is a docstring."""
62
+ if isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Constant):
63
+ return isinstance(stmt.value.value, str)
64
+ return False