fpy2 0.0.2__tar.gz → 0.0.4__tar.gz

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.

Potentially problematic release.


This version of fpy2 might be problematic. Click here for more details.

Files changed (135) hide show
  1. fpy2-0.0.4/PKG-INFO +113 -0
  2. fpy2-0.0.4/README.md +88 -0
  3. fpy2-0.0.4/fpy2/__init__.py +113 -0
  4. fpy2-0.0.4/fpy2/analysis/__init__.py +5 -0
  5. fpy2-0.0.4/fpy2/analysis/define_use.py +227 -0
  6. fpy2-0.0.4/fpy2/analysis/live_vars.py +213 -0
  7. fpy2-0.0.4/fpy2/analysis/syntax_check.py +399 -0
  8. fpy2-0.0.4/fpy2/ast/__init__.py +9 -0
  9. {fpy2-0.0.2/fpy2/frontend → fpy2-0.0.4/fpy2/ast}/formatter.py +147 -59
  10. fpy2-0.0.4/fpy2/ast/fpyast.py +1247 -0
  11. fpy2-0.0.4/fpy2/ast/visitor.py +590 -0
  12. {fpy2-0.0.2 → fpy2-0.0.4}/fpy2/backend/__init__.py +0 -1
  13. {fpy2-0.0.2 → fpy2-0.0.4}/fpy2/backend/backend.py +2 -3
  14. fpy2-0.0.4/fpy2/backend/fpc.py +795 -0
  15. fpy2-0.0.4/fpy2/decorator.py +197 -0
  16. fpy2-0.0.4/fpy2/env.py +35 -0
  17. fpy2-0.0.4/fpy2/fpc_context.py +177 -0
  18. fpy2-0.0.4/fpy2/frontend/__init__.py +9 -0
  19. {fpy2-0.0.2 → fpy2-0.0.4}/fpy2/frontend/fpc.py +207 -176
  20. {fpy2-0.0.2 → fpy2-0.0.4}/fpy2/frontend/parser.py +201 -147
  21. fpy2-0.0.4/fpy2/function.py +110 -0
  22. fpy2-0.0.4/fpy2/interpret/__init__.py +8 -0
  23. fpy2-0.0.4/fpy2/interpret/default.py +737 -0
  24. fpy2-0.0.4/fpy2/interpret/interpreter.py +83 -0
  25. fpy2-0.0.4/fpy2/interpret/native.py +673 -0
  26. fpy2-0.0.4/fpy2/libraries/__init__.py +0 -0
  27. fpy2-0.0.4/fpy2/libraries/core.py +158 -0
  28. fpy2-0.0.4/fpy2/math.py +520 -0
  29. fpy2-0.0.4/fpy2/number/__init__.py +295 -0
  30. fpy2-0.0.4/fpy2/number/context.py +219 -0
  31. fpy2-0.0.4/fpy2/number/ext_float.py +613 -0
  32. fpy2-0.0.4/fpy2/number/fixed.py +158 -0
  33. fpy2-0.0.4/fpy2/number/globals.py +44 -0
  34. fpy2-0.0.4/fpy2/number/gmp.py +526 -0
  35. fpy2-0.0.4/fpy2/number/ieee754.py +29 -0
  36. fpy2-0.0.4/fpy2/number/mp_fixed.py +305 -0
  37. fpy2-0.0.4/fpy2/number/mp_float.py +154 -0
  38. fpy2-0.0.4/fpy2/number/mpb_fixed.py +390 -0
  39. fpy2-0.0.4/fpy2/number/mpb_float.py +378 -0
  40. fpy2-0.0.4/fpy2/number/mps_float.py +299 -0
  41. fpy2-0.0.4/fpy2/number/native.py +40 -0
  42. fpy2-0.0.4/fpy2/number/number.py +1473 -0
  43. fpy2-0.0.4/fpy2/number/real.py +206 -0
  44. fpy2-0.0.4/fpy2/number/round.py +65 -0
  45. fpy2-0.0.4/fpy2/primitive.py +33 -0
  46. fpy2-0.0.4/fpy2/rewrite/__init__.py +9 -0
  47. fpy2-0.0.4/fpy2/rewrite/applier.py +237 -0
  48. fpy2-0.0.4/fpy2/rewrite/matcher.py +458 -0
  49. fpy2-0.0.4/fpy2/rewrite/pattern.py +100 -0
  50. fpy2-0.0.4/fpy2/rewrite/rewrite.py +233 -0
  51. fpy2-0.0.4/fpy2/rewrite/subst.py +61 -0
  52. {fpy2-0.0.2/fpy2/runtime/sampling → fpy2-0.0.4/fpy2/sample}/sample.py +20 -12
  53. {fpy2-0.0.2/fpy2/runtime/sampling → fpy2-0.0.4/fpy2/sample}/table.py +21 -21
  54. fpy2-0.0.4/fpy2/transform/__init__.py +13 -0
  55. fpy2-0.0.4/fpy2/transform/context_inline.py +118 -0
  56. fpy2-0.0.4/fpy2/transform/copy_propagate.py +76 -0
  57. fpy2-0.0.4/fpy2/transform/for_bundling.py +177 -0
  58. fpy2-0.0.4/fpy2/transform/for_unpack.py +69 -0
  59. {fpy2-0.0.2/fpy2/passes → fpy2-0.0.4/fpy2/transform}/func_update.py +15 -17
  60. fpy2-0.0.4/fpy2/transform/if_bundling.py +254 -0
  61. fpy2-0.0.4/fpy2/transform/rename_target.py +95 -0
  62. fpy2-0.0.4/fpy2/transform/simplify_if.py +185 -0
  63. fpy2-0.0.4/fpy2/transform/while_bundling.py +136 -0
  64. {fpy2-0.0.2 → fpy2-0.0.4}/fpy2/typing/__init__.pyi +3 -9
  65. fpy2-0.0.4/fpy2/utils/__init__.py +27 -0
  66. fpy2-0.0.4/fpy2/utils/bits.py +26 -0
  67. fpy2-0.0.4/fpy2/utils/decorator.py +105 -0
  68. fpy2-0.0.4/fpy2/utils/float_params.py +30 -0
  69. fpy2-0.0.4/fpy2/utils/fractions.py +136 -0
  70. {fpy2-0.0.2 → fpy2-0.0.4}/fpy2/utils/gensym.py +9 -1
  71. {fpy2-0.0.2 → fpy2-0.0.4}/fpy2/utils/identifier.py +33 -2
  72. fpy2-0.0.4/fpy2/utils/inspect.py +15 -0
  73. fpy2-0.0.4/fpy2/utils/iterator.py +15 -0
  74. fpy2-0.0.4/fpy2/utils/ordering.py +39 -0
  75. fpy2-0.0.4/fpy2.egg-info/PKG-INFO +113 -0
  76. fpy2-0.0.4/fpy2.egg-info/SOURCES.txt +86 -0
  77. {fpy2-0.0.2 → fpy2-0.0.4}/fpy2.egg-info/requires.txt +3 -0
  78. {fpy2-0.0.2 → fpy2-0.0.4}/pyproject.toml +8 -3
  79. fpy2-0.0.2/PKG-INFO +0 -39
  80. fpy2-0.0.2/README.md +0 -18
  81. fpy2-0.0.2/fpy2/__init__.py +0 -19
  82. fpy2-0.0.2/fpy2/backend/fpc.py +0 -480
  83. fpy2-0.0.2/fpy2/backend/fpy.py +0 -298
  84. fpy2-0.0.2/fpy2/frontend/__init__.py +0 -13
  85. fpy2-0.0.2/fpy2/frontend/codegen.py +0 -278
  86. fpy2-0.0.2/fpy2/frontend/decorator.py +0 -106
  87. fpy2-0.0.2/fpy2/frontend/definition.py +0 -149
  88. fpy2-0.0.2/fpy2/frontend/fpyast.py +0 -655
  89. fpy2-0.0.2/fpy2/frontend/live_vars.py +0 -201
  90. fpy2-0.0.2/fpy2/frontend/syntax_check.py +0 -294
  91. fpy2-0.0.2/fpy2/frontend/visitor.py +0 -208
  92. fpy2-0.0.2/fpy2/ir/__init__.py +0 -13
  93. fpy2-0.0.2/fpy2/ir/formatter.py +0 -269
  94. fpy2-0.0.2/fpy2/ir/ir.py +0 -739
  95. fpy2-0.0.2/fpy2/ir/types.py +0 -29
  96. fpy2-0.0.2/fpy2/ir/visitor.py +0 -578
  97. fpy2-0.0.2/fpy2/passes/__init__.py +0 -9
  98. fpy2-0.0.2/fpy2/passes/define_use.py +0 -105
  99. fpy2-0.0.2/fpy2/passes/for_bundling.py +0 -109
  100. fpy2-0.0.2/fpy2/passes/reaching_defs.py +0 -129
  101. fpy2-0.0.2/fpy2/passes/simplify_if.py +0 -109
  102. fpy2-0.0.2/fpy2/passes/ssa.py +0 -330
  103. fpy2-0.0.2/fpy2/passes/verify.py +0 -221
  104. fpy2-0.0.2/fpy2/passes/while_bundling.py +0 -125
  105. fpy2-0.0.2/fpy2/runtime/__init__.py +0 -15
  106. fpy2-0.0.2/fpy2/runtime/common.py +0 -12
  107. fpy2-0.0.2/fpy2/runtime/env.py +0 -23
  108. fpy2-0.0.2/fpy2/runtime/function.py +0 -110
  109. fpy2-0.0.2/fpy2/runtime/metric.py +0 -32
  110. fpy2-0.0.2/fpy2/runtime/native/__init__.py +0 -1
  111. fpy2-0.0.2/fpy2/runtime/native/interpreter.py +0 -512
  112. fpy2-0.0.2/fpy2/runtime/profile/__init__.py +0 -2
  113. fpy2-0.0.2/fpy2/runtime/profile/expr_profiler.py +0 -240
  114. fpy2-0.0.2/fpy2/runtime/profile/function_profiler.py +0 -138
  115. fpy2-0.0.2/fpy2/runtime/real/__init__.py +0 -2
  116. fpy2-0.0.2/fpy2/runtime/real/interpreter.py +0 -583
  117. fpy2-0.0.2/fpy2/runtime/real/interval.py +0 -61
  118. fpy2-0.0.2/fpy2/runtime/real/rival_manager.py +0 -142
  119. fpy2-0.0.2/fpy2/runtime/titanic/__init__.py +0 -1
  120. fpy2-0.0.2/fpy2/runtime/titanic/interpreter.py +0 -616
  121. fpy2-0.0.2/fpy2/utils/__init__.py +0 -10
  122. fpy2-0.0.2/fpy2/utils/defaults.py +0 -11
  123. fpy2-0.0.2/fpy2/utils/fraction.py +0 -55
  124. fpy2-0.0.2/fpy2.egg-info/PKG-INFO +0 -39
  125. fpy2-0.0.2/fpy2.egg-info/SOURCES.txt +0 -68
  126. {fpy2-0.0.2 → fpy2-0.0.4}/LICENSE +0 -0
  127. {fpy2-0.0.2/fpy2/runtime/sampling → fpy2-0.0.4/fpy2/sample}/__init__.py +0 -0
  128. {fpy2-0.0.2 → fpy2-0.0.4}/fpy2/typing/__init__.py +0 -0
  129. {fpy2-0.0.2 → fpy2-0.0.4}/fpy2/utils/compare.py +0 -0
  130. {fpy2-0.0.2 → fpy2-0.0.4}/fpy2/utils/error.py +0 -0
  131. {fpy2-0.0.2 → fpy2-0.0.4}/fpy2/utils/location.py +0 -0
  132. {fpy2-0.0.2 → fpy2-0.0.4}/fpy2/utils/string.py +0 -0
  133. {fpy2-0.0.2 → fpy2-0.0.4}/fpy2.egg-info/dependency_links.txt +0 -0
  134. {fpy2-0.0.2 → fpy2-0.0.4}/fpy2.egg-info/top_level.txt +0 -0
  135. {fpy2-0.0.2 → fpy2-0.0.4}/setup.cfg +0 -0
fpy2-0.0.4/PKG-INFO ADDED
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: fpy2
3
+ Version: 0.0.4
4
+ Summary: An embedded DSL for numerical computing
5
+ Author-email: Brett Saiki <bsaiki@cs.washington.edu>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/bksaiki/fpy
8
+ Project-URL: Issues, https://github.com/bksaiki/fpy/issues
9
+ Project-URL: Documentation, https://fpy.readthedocs.io/
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.11
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: titanfp==0.1.1
16
+ Requires-Dist: gmpy2==2.2
17
+ Provides-Extra: dev
18
+ Requires-Dist: mypy; extra == "dev"
19
+ Requires-Dist: ruff; extra == "dev"
20
+ Requires-Dist: build; extra == "dev"
21
+ Requires-Dist: twine; extra == "dev"
22
+ Requires-Dist: sphinx; extra == "dev"
23
+ Requires-Dist: sphinx-rtd-theme; extra == "dev"
24
+ Dynamic: license-file
25
+
26
+ # FPy
27
+
28
+ An embedded Python DSL for specifying and simulating numerical algorithms.
29
+
30
+ Important links:
31
+ - PyPI package: [fpy2](https://pypi.org/project/fpy2/)
32
+ - Documentation: [fpy.readthedocs.io](https://fpy.readthedocs.io/)
33
+ - GitHub: [fpy](https://github.com/bksaiki/fpy)
34
+
35
+ ## Installation
36
+
37
+ Requirements:
38
+ - Python 3.11 or later
39
+
40
+ The following instructions assume a `bash`-like shell.
41
+ If you do not have a Python virtual environment,
42
+ create one using
43
+ ```bash
44
+ python3 -m venv .env/
45
+ ```
46
+ and activate it using using
47
+ ```bash
48
+ source .env/bin/activate
49
+ ```
50
+
51
+ To install a _frozen_ instance of FPy, run:
52
+ ```bash
53
+ pip install .
54
+ ```
55
+ or with `make`, run
56
+ ```bash
57
+ make install
58
+ ```
59
+ Note that this will not install the necessary dependencies for
60
+ development and installs a copy of the `fpy2` package.
61
+ If you checkout a different commit or branch, you will
62
+ need to reinstall FPy to overwrite the previous version.
63
+
64
+ To uninstall FPy, run:
65
+ ```bash
66
+ pip uninstall fpy2
67
+ ```
68
+
69
+ ## Development
70
+
71
+ Developers of FPy should read this section since
72
+ installing FPy is actually different.
73
+
74
+ Requirements:
75
+ - Python 3.11 or later
76
+ - `make`
77
+
78
+ ### Installation
79
+
80
+ If you do not have a Python virtual environment,
81
+ create one using
82
+ ```bash
83
+ python3 -m venv .env/
84
+ ```
85
+ and activate it using using
86
+ ```bash
87
+ source .env/bin/activate
88
+ ```
89
+ To install an instance of FPy for development, run:
90
+ ```bash
91
+ pip install -e .[dev]
92
+ ```
93
+ or with `make`, run
94
+ ```bash
95
+ make install-dev
96
+ ```
97
+
98
+ ### Testing
99
+
100
+ There are a number of tests that can be run through
101
+ the `Makefile` including
102
+ ```bash
103
+ make lint
104
+ ```
105
+ to ensure formatting and type safety;
106
+ ```bash
107
+ make unittest
108
+ ```
109
+ to run the unit tests;
110
+ ```bash
111
+ make infratest
112
+ ```
113
+ to run the infrastructure tests.
fpy2-0.0.4/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # FPy
2
+
3
+ An embedded Python DSL for specifying and simulating numerical algorithms.
4
+
5
+ Important links:
6
+ - PyPI package: [fpy2](https://pypi.org/project/fpy2/)
7
+ - Documentation: [fpy.readthedocs.io](https://fpy.readthedocs.io/)
8
+ - GitHub: [fpy](https://github.com/bksaiki/fpy)
9
+
10
+ ## Installation
11
+
12
+ Requirements:
13
+ - Python 3.11 or later
14
+
15
+ The following instructions assume a `bash`-like shell.
16
+ If you do not have a Python virtual environment,
17
+ create one using
18
+ ```bash
19
+ python3 -m venv .env/
20
+ ```
21
+ and activate it using using
22
+ ```bash
23
+ source .env/bin/activate
24
+ ```
25
+
26
+ To install a _frozen_ instance of FPy, run:
27
+ ```bash
28
+ pip install .
29
+ ```
30
+ or with `make`, run
31
+ ```bash
32
+ make install
33
+ ```
34
+ Note that this will not install the necessary dependencies for
35
+ development and installs a copy of the `fpy2` package.
36
+ If you checkout a different commit or branch, you will
37
+ need to reinstall FPy to overwrite the previous version.
38
+
39
+ To uninstall FPy, run:
40
+ ```bash
41
+ pip uninstall fpy2
42
+ ```
43
+
44
+ ## Development
45
+
46
+ Developers of FPy should read this section since
47
+ installing FPy is actually different.
48
+
49
+ Requirements:
50
+ - Python 3.11 or later
51
+ - `make`
52
+
53
+ ### Installation
54
+
55
+ If you do not have a Python virtual environment,
56
+ create one using
57
+ ```bash
58
+ python3 -m venv .env/
59
+ ```
60
+ and activate it using using
61
+ ```bash
62
+ source .env/bin/activate
63
+ ```
64
+ To install an instance of FPy for development, run:
65
+ ```bash
66
+ pip install -e .[dev]
67
+ ```
68
+ or with `make`, run
69
+ ```bash
70
+ make install-dev
71
+ ```
72
+
73
+ ### Testing
74
+
75
+ There are a number of tests that can be run through
76
+ the `Makefile` including
77
+ ```bash
78
+ make lint
79
+ ```
80
+ to ensure formatting and type safety;
81
+ ```bash
82
+ make unittest
83
+ ```
84
+ to run the unit tests;
85
+ ```bash
86
+ make infratest
87
+ ```
88
+ to run the infrastructure tests.
@@ -0,0 +1,113 @@
1
+ """
2
+ FPy is a library for simulating numerical programs
3
+ with many different number systems.
4
+
5
+ It provides an embedded DSL for specifying programs via its `@fpy` decorator.
6
+ The language has a runtime that can simulate programs
7
+ under different number systems and compilers to other languages.
8
+
9
+ The numbers library supports many different number types including:
10
+
11
+ - multiprecision floating point (`MPFloatContext`)
12
+ - multiprecision floatingpoint with subnormalization (`MPSFloatContext`)
13
+ - bounded, multiprecision floating point (`MPBFloatContext`)
14
+ - IEEE 754 floating point (`IEEEContext`)
15
+
16
+ These number systems guarantee correct rounding via MPFR.
17
+ """
18
+
19
+ from .number import (
20
+ # number types
21
+ Float,
22
+ RealFloat,
23
+ # abstract context types
24
+ Context,
25
+ OrdinalContext,
26
+ SizedContext,
27
+ EncodableContext,
28
+ # concrete context types
29
+ ExtFloatContext,
30
+ FixedContext,
31
+ MPFixedContext,
32
+ MPFloatContext,
33
+ MPBFixedContext,
34
+ MPBFloatContext,
35
+ MPSFloatContext,
36
+ IEEEContext,
37
+ RealContext,
38
+ # rounding utilities
39
+ RoundingMode,
40
+ RoundingDirection, RM,
41
+ # encoding utilities
42
+ ExtFloatNanKind,
43
+ FixedOverflowKind, OF,
44
+ # type aliases
45
+ FP256, FP128, FP64, FP32, FP16,
46
+ TF32, BF16,
47
+ S1E5M2, S1E4M3,
48
+ MX_E5M2, MX_E4M3, MX_E3M2, MX_E2M3, MX_E2M1,
49
+ FP8P1, FP8P2, FP8P3, FP8P4, FP8P5, FP8P6, FP8P7,
50
+ INTEGER,
51
+ SINT8, SINT16, SINT32, SINT64,
52
+ UINT8, UINT16, UINT32, UINT64
53
+ )
54
+
55
+ from .fpc_context import FPCoreContext, NoSuchContextError
56
+
57
+ from .decorator import fpy, pattern, fpy_prim
58
+
59
+ from .backend import (
60
+ Backend,
61
+ FPCoreCompiler
62
+ )
63
+
64
+ from .interpret import (
65
+ Interpreter,
66
+ PythonInterpreter,
67
+ DefaultInterpreter,
68
+ set_default_interpreter,
69
+ get_default_interpreter,
70
+ )
71
+
72
+ from .function import Function
73
+ from .env import ForeignEnv
74
+
75
+ from .utils import (
76
+ fraction,
77
+ digits_to_fraction as digits,
78
+ decnum_to_fraction as decnum,
79
+ hexnum_to_fraction as hexnum,
80
+ )
81
+
82
+ ###########################################################
83
+ # Re-exports
84
+
85
+ from titanfp.titanic.ndarray import NDArray
86
+
87
+ ###########################################################
88
+ # typing hints
89
+
90
+ import typing
91
+
92
+ from typing import Literal as Dim
93
+
94
+ _Dims = typing.TypeVarTuple('_Dims')
95
+ _DType = typing.TypeVar('_DType')
96
+
97
+ class Tensor(tuple, typing.Generic[*_Dims, _DType]):
98
+ """
99
+ FPy type hint for a homogenous tensor object::
100
+
101
+ from fpy2 import Tensor, Real
102
+ from typing import TypeAlias
103
+
104
+ MatrixN: TypeAlias = Tensor[Literal['N', 'N'], Real]
105
+ Matrix3: TypeAlias = Tensor[Literal[3, 3], Real]
106
+
107
+ Tensors have fixed or symbolic sizes and a uniform scalar data type.
108
+
109
+ Values of this type should not be constructed directly.
110
+ """
111
+
112
+ def __init__(self, *args, **kwargs):
113
+ super().__init__(*args, **kwargs)
@@ -0,0 +1,5 @@
1
+ """Program analyses for FPy programs"""
2
+
3
+ from .define_use import DefineUse, DefineUseAnalysis, Definition, DefinitionCtx
4
+ from .live_vars import LiveVars
5
+ from .syntax_check import SyntaxCheck
@@ -0,0 +1,227 @@
1
+ """Definition use analysis for FPy ASTs"""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TypeAlias, Union
5
+
6
+ from ..ast.fpyast import *
7
+ from ..ast.visitor import DefaultVisitor
8
+ from ..utils import default_repr
9
+
10
+ Definition: TypeAlias = Argument | Stmt | CompExpr
11
+
12
+ @default_repr
13
+ class _DefineUnion:
14
+ """Union of possible definition sites."""
15
+ defs: set[Definition]
16
+
17
+ def __init__(self, defs: set[Definition]):
18
+ self.defs = set(defs)
19
+
20
+ def __eq__(self, other):
21
+ if not isinstance(other, _DefineUnion):
22
+ return NotImplemented
23
+ return self.defs == other.defs
24
+
25
+ def __hash__(self):
26
+ return hash(tuple(self.defs))
27
+
28
+ @staticmethod
29
+ def union(*defs_or_unions: Union[Definition, '_DefineUnion']):
30
+ """Create a union of definitions from a set of definitions or unions."""
31
+ defs: set[Definition] = set()
32
+ for item in defs_or_unions:
33
+ if isinstance(item, _DefineUnion):
34
+ defs.update(item.defs)
35
+ else:
36
+ defs.add(item)
37
+
38
+ if len(defs) == 0:
39
+ raise ValueError('Cannot create a union of an empty set of definitions')
40
+ elif len(defs) == 1:
41
+ return defs.pop()
42
+ else:
43
+ return _DefineUnion(defs)
44
+
45
+ class DefinitionCtx(dict[NamedId, Definition | _DefineUnion]):
46
+ """Mapping from variable to its definition (or possible definitions)."""
47
+
48
+ def copy(self) -> 'DefinitionCtx':
49
+ """Returns a shallow copy of the context."""
50
+ return DefinitionCtx(self)
51
+
52
+ def mutated_in(self, other: 'DefinitionCtx') -> list[NamedId]:
53
+ """
54
+ Returns the set of variables that are defined in `self`
55
+ and mutated in `other`.
56
+ """
57
+ names: set[NamedId] = set()
58
+ for name in self.keys() & other.keys():
59
+ if self[name] != other[name]:
60
+ names.add(name)
61
+ return list(names)
62
+
63
+ def fresh_in(self, other: 'DefinitionCtx') -> set[NamedId]:
64
+ """
65
+ Returns the set of variables that are defined in `other`
66
+ but not in `self`.
67
+ """
68
+ return set(other.keys() - self.keys())
69
+
70
+
71
+ @dataclass
72
+ class DefineUseAnalysis:
73
+ """Result of definition-use analysis"""
74
+ defs: dict[NamedId, set[Definition]]
75
+ uses: dict[Definition, set[Var | IndexedAssign]]
76
+ stmts: dict[Stmt, tuple[DefinitionCtx, DefinitionCtx]]
77
+ blocks: dict[StmtBlock, tuple[DefinitionCtx, DefinitionCtx]]
78
+
79
+ @staticmethod
80
+ def default():
81
+ """Default analysis with empty definitions and uses"""
82
+ return DefineUseAnalysis({}, {}, {}, {})
83
+
84
+ @property
85
+ def names(self) -> set[NamedId]:
86
+ """Returns the set of all variable names in the analysis"""
87
+ return set(self.defs.keys())
88
+
89
+
90
+ class _DefineUseInstance(DefaultVisitor):
91
+ """Per-IR instance of definition-use analysis"""
92
+ ast: FuncDef | StmtBlock
93
+ analysis: DefineUseAnalysis
94
+
95
+ def __init__(self, ast: FuncDef | StmtBlock):
96
+ self.ast = ast
97
+ self.analysis = DefineUseAnalysis.default()
98
+
99
+ def analyze(self):
100
+ match self.ast:
101
+ case FuncDef():
102
+ self._visit_function(self.ast, DefinitionCtx())
103
+ case StmtBlock():
104
+ self._visit_block(self.ast, DefinitionCtx())
105
+ case _:
106
+ raise RuntimeError(f'unreachable case: {self.ast}')
107
+ return self.analysis
108
+
109
+ def _add_def(self, name: NamedId, definition: Definition):
110
+ if name not in self.analysis.defs:
111
+ self.analysis.defs[name] = set()
112
+ self.analysis.defs[name].add(definition)
113
+ self.analysis.uses[definition] = set()
114
+
115
+ def _add_use(self, name: NamedId, use: Var | IndexedAssign, ctx: DefinitionCtx):
116
+ def_or_union = ctx[name]
117
+ if isinstance(def_or_union, _DefineUnion):
118
+ for def_ in def_or_union.defs:
119
+ self.analysis.uses[def_].add(use)
120
+ else:
121
+ self.analysis.uses[def_or_union].add(use)
122
+
123
+ def _visit_var(self, e: Var, ctx: DefinitionCtx):
124
+ if e.name not in ctx:
125
+ raise NotImplementedError(f'undefined variable {e.name}')
126
+ self._add_use(e.name, e, ctx)
127
+
128
+ def _visit_comp_expr(self, e: CompExpr, ctx: DefinitionCtx):
129
+ for iterable in e.iterables:
130
+ self._visit_expr(iterable, ctx)
131
+ ctx = ctx.copy()
132
+ for target in e.targets:
133
+ for name in target.names():
134
+ self._add_def(name, e)
135
+ ctx[name] = e
136
+ self._visit_expr(e.elt, ctx)
137
+
138
+ def _visit_assign(self, stmt: Assign, ctx: DefinitionCtx):
139
+ self._visit_expr(stmt.expr, ctx)
140
+ for var in stmt.binding.names():
141
+ self._add_def(var, stmt)
142
+ ctx[var] = stmt
143
+
144
+ def _visit_indexed_assign(self, stmt: IndexedAssign, ctx: DefinitionCtx):
145
+ self._add_use(stmt.var, stmt, ctx)
146
+ for slice in stmt.slices:
147
+ self._visit_expr(slice, ctx)
148
+ self._visit_expr(stmt.expr, ctx)
149
+
150
+ def _visit_if1(self, stmt: If1Stmt, ctx: DefinitionCtx):
151
+ self._visit_expr(stmt.cond, ctx)
152
+ body_ctx = self._visit_block(stmt.body, ctx.copy())
153
+ # merge contexts along both paths
154
+ # definitions cannot be introduced in the body
155
+ for var in ctx:
156
+ ctx[var] = _DefineUnion.union(ctx[var], body_ctx[var])
157
+
158
+ def _visit_if(self, stmt: IfStmt, ctx: DefinitionCtx):
159
+ self._visit_expr(stmt.cond, ctx)
160
+ ift_ctx = self._visit_block(stmt.ift, ctx.copy())
161
+ iff_ctx = self._visit_block(stmt.iff, ctx.copy())
162
+ # merge contexts along both paths
163
+ for var in ift_ctx.keys() & iff_ctx.keys():
164
+ ctx[var] = _DefineUnion.union(ift_ctx[var], iff_ctx[var])
165
+
166
+ def _visit_while(self, stmt: WhileStmt, ctx: DefinitionCtx):
167
+ self._visit_expr(stmt.cond, ctx)
168
+ body_ctx = self._visit_block(stmt.body, ctx.copy())
169
+ # merge contexts along both paths
170
+ # definitions cannot be introduced in the body
171
+ for var in ctx:
172
+ ctx[var] = _DefineUnion.union(ctx[var], body_ctx[var])
173
+
174
+ def _visit_for(self, stmt: ForStmt, ctx: DefinitionCtx):
175
+ self._visit_expr(stmt.iterable, ctx)
176
+ body_ctx = ctx.copy()
177
+ match stmt.target:
178
+ case NamedId():
179
+ self._add_def(stmt.target, stmt)
180
+ body_ctx[stmt.target] = stmt
181
+ case TupleBinding():
182
+ for var in stmt.target.names():
183
+ self._add_def(var, stmt)
184
+ body_ctx[var] = stmt
185
+
186
+ body_ctx = self._visit_block(stmt.body, body_ctx)
187
+ # merge contexts along both paths
188
+ # definitions cannot be introduced in the body
189
+ for var in ctx:
190
+ ctx[var] = _DefineUnion.union(ctx[var], body_ctx[var])
191
+
192
+ def _visit_statement(self, stmt: Stmt, ctx: DefinitionCtx):
193
+ ctx_in = ctx.copy()
194
+ super()._visit_statement(stmt, ctx)
195
+ self.analysis.stmts[stmt] = (ctx_in, ctx.copy())
196
+
197
+ def _visit_block(self, block: StmtBlock, ctx: DefinitionCtx):
198
+ ctx_in = ctx.copy()
199
+ for stmt in block.stmts:
200
+ self._visit_statement(stmt, ctx)
201
+ self.analysis.blocks[block] = (ctx_in, ctx.copy())
202
+ return ctx
203
+
204
+ def _visit_function(self, func: FuncDef, ctx: DefinitionCtx):
205
+ for arg in func.args:
206
+ if isinstance(arg.name, NamedId):
207
+ self._add_def(arg.name, arg)
208
+ ctx[arg.name] = arg
209
+ self._visit_block(func.body, ctx.copy())
210
+
211
+
212
+ class DefineUse:
213
+ """
214
+ Definition-use analyzer for the FPy IR.
215
+
216
+ Computes definition-use chains for each variable.
217
+
218
+ name ---> definition ---> use1, use2, ...
219
+ ---> definition ---> use1, use2, ...
220
+ ...
221
+ """
222
+
223
+ @staticmethod
224
+ def analyze(ast: FuncDef | StmtBlock):
225
+ if not isinstance(ast, FuncDef | StmtBlock):
226
+ raise TypeError(f'Expected \'FuncDef\' or \'StmtBlock\', got {type(ast)} for {ast}')
227
+ return _DefineUseInstance(ast).analyze()