fpy2 0.0.2__tar.gz → 0.0.3__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.
Files changed (120) hide show
  1. fpy2-0.0.3/PKG-INFO +113 -0
  2. fpy2-0.0.3/README.md +88 -0
  3. fpy2-0.0.3/fpy2/__init__.py +73 -0
  4. fpy2-0.0.3/fpy2/analysis/__init__.py +8 -0
  5. {fpy2-0.0.2/fpy2/passes → fpy2-0.0.3/fpy2/analysis}/define_use.py +10 -10
  6. fpy2-0.0.3/fpy2/analysis/live_vars.py +225 -0
  7. {fpy2-0.0.2/fpy2/passes → fpy2-0.0.3/fpy2/analysis}/reaching_defs.py +17 -14
  8. {fpy2-0.0.2/fpy2/passes → fpy2-0.0.3/fpy2/analysis}/verify.py +20 -13
  9. fpy2-0.0.3/fpy2/ast/__init__.py +15 -0
  10. fpy2-0.0.3/fpy2/ast/context_inline.py +115 -0
  11. fpy2-0.0.3/fpy2/ast/define_use.py +86 -0
  12. {fpy2-0.0.2/fpy2/frontend → fpy2-0.0.3/fpy2/ast}/formatter.py +53 -19
  13. fpy2-0.0.3/fpy2/ast/fpyast.py +1070 -0
  14. fpy2-0.0.3/fpy2/ast/live_vars.py +209 -0
  15. {fpy2-0.0.2/fpy2/frontend → fpy2-0.0.3/fpy2/ast}/syntax_check.py +143 -45
  16. fpy2-0.0.3/fpy2/ast/visitor.py +572 -0
  17. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/backend/backend.py +3 -3
  18. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/backend/fpc.py +99 -40
  19. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/backend/fpy.py +105 -97
  20. fpy2-0.0.3/fpy2/decorator.py +146 -0
  21. fpy2-0.0.3/fpy2/fpc_context.py +156 -0
  22. fpy2-0.0.3/fpy2/frontend/__init__.py +9 -0
  23. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/frontend/fpc.py +83 -72
  24. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/frontend/parser.py +105 -35
  25. fpy2-0.0.3/fpy2/function.py +136 -0
  26. fpy2-0.0.3/fpy2/interpret/__init__.py +9 -0
  27. fpy2-0.0.2/fpy2/runtime/titanic/interpreter.py → fpy2-0.0.3/fpy2/interpret/default.py +296 -188
  28. fpy2-0.0.3/fpy2/interpret/interpreter.py +66 -0
  29. fpy2-0.0.2/fpy2/runtime/native/interpreter.py → fpy2-0.0.3/fpy2/interpret/native.py +148 -96
  30. fpy2-0.0.2/fpy2/runtime/real/interpreter.py → fpy2-0.0.3/fpy2/interpret/real.py +140 -76
  31. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/ir/__init__.py +3 -4
  32. {fpy2-0.0.2/fpy2/frontend → fpy2-0.0.3/fpy2/ir}/codegen.py +81 -35
  33. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/ir/formatter.py +50 -18
  34. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/ir/ir.py +113 -48
  35. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/ir/visitor.py +168 -92
  36. fpy2-0.0.3/fpy2/math.py +489 -0
  37. fpy2-0.0.3/fpy2/number/__init__.py +217 -0
  38. fpy2-0.0.3/fpy2/number/context.py +206 -0
  39. fpy2-0.0.3/fpy2/number/ext.py +609 -0
  40. fpy2-0.0.3/fpy2/number/globals.py +45 -0
  41. fpy2-0.0.3/fpy2/number/gmp.py +529 -0
  42. fpy2-0.0.3/fpy2/number/ieee754.py +26 -0
  43. fpy2-0.0.3/fpy2/number/mp.py +150 -0
  44. fpy2-0.0.3/fpy2/number/mpb.py +371 -0
  45. fpy2-0.0.3/fpy2/number/mps.py +295 -0
  46. fpy2-0.0.3/fpy2/number/native.py +41 -0
  47. fpy2-0.0.3/fpy2/number/number.py +427 -0
  48. fpy2-0.0.3/fpy2/number/real.py +1016 -0
  49. fpy2-0.0.3/fpy2/number/round.py +65 -0
  50. {fpy2-0.0.2/fpy2/runtime → fpy2-0.0.3/fpy2}/profile/expr_profiler.py +6 -6
  51. {fpy2-0.0.2/fpy2/runtime → fpy2-0.0.3/fpy2}/profile/function_profiler.py +8 -6
  52. fpy2-0.0.3/fpy2/rewrite/__init__.py +9 -0
  53. fpy2-0.0.3/fpy2/rewrite/applier.py +234 -0
  54. fpy2-0.0.3/fpy2/rewrite/matcher.py +436 -0
  55. fpy2-0.0.3/fpy2/rewrite/pattern.py +99 -0
  56. fpy2-0.0.3/fpy2/rewrite/rewrite.py +233 -0
  57. fpy2-0.0.3/fpy2/rewrite/subst.py +61 -0
  58. fpy2-0.0.3/fpy2/runtime/__init__.py +1 -0
  59. fpy2-0.0.3/fpy2/runtime/env.py +35 -0
  60. fpy2-0.0.3/fpy2/runtime/real/__init__.py +2 -0
  61. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/runtime/real/interval.py +1 -1
  62. {fpy2-0.0.2/fpy2/runtime/sampling → fpy2-0.0.3/fpy2/sample}/sample.py +21 -12
  63. {fpy2-0.0.2/fpy2/runtime/sampling → fpy2-0.0.3/fpy2/sample}/table.py +11 -11
  64. {fpy2-0.0.2/fpy2/passes → fpy2-0.0.3/fpy2/transform}/__init__.py +4 -3
  65. {fpy2-0.0.2/fpy2/passes → fpy2-0.0.3/fpy2/transform}/for_bundling.py +18 -18
  66. {fpy2-0.0.2/fpy2/passes → fpy2-0.0.3/fpy2/transform}/func_update.py +8 -8
  67. {fpy2-0.0.2/fpy2/passes → fpy2-0.0.3/fpy2/transform}/simplify_if.py +41 -36
  68. {fpy2-0.0.2/fpy2/passes → fpy2-0.0.3/fpy2/transform}/ssa.py +40 -33
  69. fpy2-0.0.3/fpy2/transform/unssa.py +196 -0
  70. {fpy2-0.0.2/fpy2/passes → fpy2-0.0.3/fpy2/transform}/while_bundling.py +18 -18
  71. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/typing/__init__.pyi +3 -9
  72. fpy2-0.0.3/fpy2/utils/__init__.py +26 -0
  73. fpy2-0.0.3/fpy2/utils/bits.py +26 -0
  74. fpy2-0.0.3/fpy2/utils/decorator.py +105 -0
  75. fpy2-0.0.3/fpy2/utils/float_params.py +30 -0
  76. fpy2-0.0.3/fpy2/utils/fractions.py +122 -0
  77. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/utils/identifier.py +8 -1
  78. fpy2-0.0.3/fpy2/utils/iterator.py +15 -0
  79. fpy2-0.0.3/fpy2/utils/ordering.py +39 -0
  80. fpy2-0.0.3/fpy2.egg-info/PKG-INFO +113 -0
  81. fpy2-0.0.3/fpy2.egg-info/SOURCES.txt +98 -0
  82. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2.egg-info/requires.txt +3 -0
  83. {fpy2-0.0.2 → fpy2-0.0.3}/pyproject.toml +8 -3
  84. fpy2-0.0.2/PKG-INFO +0 -39
  85. fpy2-0.0.2/README.md +0 -18
  86. fpy2-0.0.2/fpy2/__init__.py +0 -19
  87. fpy2-0.0.2/fpy2/frontend/__init__.py +0 -13
  88. fpy2-0.0.2/fpy2/frontend/decorator.py +0 -106
  89. fpy2-0.0.2/fpy2/frontend/definition.py +0 -149
  90. fpy2-0.0.2/fpy2/frontend/fpyast.py +0 -655
  91. fpy2-0.0.2/fpy2/frontend/live_vars.py +0 -201
  92. fpy2-0.0.2/fpy2/frontend/visitor.py +0 -208
  93. fpy2-0.0.2/fpy2/runtime/__init__.py +0 -15
  94. fpy2-0.0.2/fpy2/runtime/env.py +0 -23
  95. fpy2-0.0.2/fpy2/runtime/function.py +0 -110
  96. fpy2-0.0.2/fpy2/runtime/native/__init__.py +0 -1
  97. fpy2-0.0.2/fpy2/runtime/real/__init__.py +0 -2
  98. fpy2-0.0.2/fpy2/runtime/titanic/__init__.py +0 -1
  99. fpy2-0.0.2/fpy2/utils/__init__.py +0 -10
  100. fpy2-0.0.2/fpy2/utils/defaults.py +0 -11
  101. fpy2-0.0.2/fpy2/utils/fraction.py +0 -55
  102. fpy2-0.0.2/fpy2.egg-info/PKG-INFO +0 -39
  103. fpy2-0.0.2/fpy2.egg-info/SOURCES.txt +0 -68
  104. {fpy2-0.0.2 → fpy2-0.0.3}/LICENSE +0 -0
  105. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/backend/__init__.py +0 -0
  106. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/ir/types.py +0 -0
  107. {fpy2-0.0.2/fpy2/runtime → fpy2-0.0.3/fpy2}/profile/__init__.py +0 -0
  108. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/runtime/metric.py +0 -0
  109. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/runtime/real/rival_manager.py +0 -0
  110. /fpy2-0.0.2/fpy2/runtime/common.py → /fpy2-0.0.3/fpy2/runtime/trace.py +0 -0
  111. {fpy2-0.0.2/fpy2/runtime/sampling → fpy2-0.0.3/fpy2/sample}/__init__.py +0 -0
  112. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/typing/__init__.py +0 -0
  113. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/utils/compare.py +0 -0
  114. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/utils/error.py +0 -0
  115. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/utils/gensym.py +0 -0
  116. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/utils/location.py +0 -0
  117. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2/utils/string.py +0 -0
  118. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2.egg-info/dependency_links.txt +0 -0
  119. {fpy2-0.0.2 → fpy2-0.0.3}/fpy2.egg-info/top_level.txt +0 -0
  120. {fpy2-0.0.2 → fpy2-0.0.3}/setup.cfg +0 -0
fpy2-0.0.3/PKG-INFO ADDED
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: fpy2
3
+ Version: 0.0.3
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 lints:
31
+ - PyPI package: [fpy2](https://pypi.org/project/fpy2/)
32
+ - Documentation: [fpy2.readthedocs.io](https://fpy2.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.3/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # FPy
2
+
3
+ An embedded Python DSL for specifying and simulating numerical algorithms.
4
+
5
+ Important lints:
6
+ - PyPI package: [fpy2](https://pypi.org/project/fpy2/)
7
+ - Documentation: [fpy2.readthedocs.io](https://fpy2.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,73 @@
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 (`MPContext`)
12
+ - multiprecision floatingpoint with subnormalization (`MPSContext`)
13
+ - bounded, multiprecision floating point (`MPBContext`)
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
+ ExtContext,
30
+ MPContext,
31
+ MPSContext,
32
+ MPBContext,
33
+ IEEEContext,
34
+ # rounding utilities
35
+ RoundingMode,
36
+ RoundingDirection, RM,
37
+ # encoding utilities
38
+ ExtNanKind,
39
+ # type aliases
40
+ FP256, FP128, FP64, FP32, FP16,
41
+ TF32, BF16,
42
+ S1E5M2, S1E4M3,
43
+ MX_E5M2, MX_E4M3, MX_E3M2, MX_E2M3, MX_E2M1,
44
+ FP8P1, FP8P2, FP8P3, FP8P4, FP8P5, FP8P6, FP8P7
45
+ )
46
+
47
+ from .fpc_context import FPCoreContext, NoSuchContextError
48
+
49
+ from .decorator import fpy, pattern
50
+
51
+ from .backend import (
52
+ Backend,
53
+ FPCoreCompiler,
54
+ FPYCompiler,
55
+ )
56
+
57
+ from .interpret import (
58
+ Interpreter,
59
+ PythonInterpreter,
60
+ RealInterpreter,
61
+ DefaultInterpreter,
62
+ set_default_interpreter,
63
+ get_default_interpreter,
64
+ )
65
+
66
+ from .function import Function
67
+
68
+ from .utils import (
69
+ fraction,
70
+ digits_to_fraction as digits,
71
+ decnum_to_fraction as decnum,
72
+ hexnum_to_fraction as hexnum,
73
+ )
@@ -0,0 +1,8 @@
1
+ """
2
+ This module defines compiler analyses over FPy IR.
3
+ """
4
+
5
+ from .define_use import DefineUse
6
+ from .live_vars import LiveVars
7
+ from .reaching_defs import ReachingDefs
8
+ from .verify import VerifyIR
@@ -4,11 +4,11 @@ from ..ir import *
4
4
 
5
5
  class _DefineUseInstance(DefaultVisitor):
6
6
  """Per-IR instance of definition-use analysis"""
7
- func: FunctionDef
7
+ func: FuncDef
8
8
  uses: dict[NamedId, set[Var | PhiNode]]
9
9
  done: bool
10
10
 
11
- def __init__(self, func: FunctionDef):
11
+ def __init__(self, func: FuncDef):
12
12
  self.func = func
13
13
  self.uses = {}
14
14
  self.done = False
@@ -33,17 +33,17 @@ class _DefineUseInstance(DefaultVisitor):
33
33
  self.uses[var] = set()
34
34
  self._visit_expr(e.elt, ctx)
35
35
 
36
- def _visit_var_assign(self, stmt: VarAssign, ctx: None):
36
+ def _visit_simple_assign(self, stmt: SimpleAssign, ctx: None):
37
37
  self._visit_expr(stmt.expr, ctx)
38
38
  if isinstance(stmt.var, NamedId):
39
39
  self.uses[stmt.var] = set()
40
40
 
41
- def _visit_tuple_assign(self, stmt: TupleAssign, ctx: None):
41
+ def _visit_tuple_unpack(self, stmt: TupleUnpack, ctx: None):
42
42
  self._visit_expr(stmt.expr, ctx)
43
43
  for var in stmt.binding.names():
44
44
  self.uses[var] = set()
45
45
 
46
- def _visit_if1_stmt(self, stmt: If1Stmt, ctx: None):
46
+ def _visit_if1(self, stmt: If1Stmt, ctx: None):
47
47
  self._visit_expr(stmt.cond, ctx)
48
48
  self._visit_block(stmt.body, ctx)
49
49
  for phi in stmt.phis:
@@ -51,7 +51,7 @@ class _DefineUseInstance(DefaultVisitor):
51
51
  self.uses[phi.lhs].add(phi)
52
52
  self.uses[phi.rhs].add(phi)
53
53
 
54
- def _visit_if_stmt(self, stmt: IfStmt, ctx: None):
54
+ def _visit_if(self, stmt: IfStmt, ctx: None):
55
55
  self._visit_expr(stmt.cond, ctx)
56
56
  self._visit_block(stmt.ift, ctx)
57
57
  self._visit_block(stmt.iff, ctx)
@@ -60,7 +60,7 @@ class _DefineUseInstance(DefaultVisitor):
60
60
  self.uses[phi.lhs].add(phi)
61
61
  self.uses[phi.rhs].add(phi)
62
62
 
63
- def _visit_while_stmt(self, stmt: WhileStmt, ctx: None):
63
+ def _visit_while(self, stmt: WhileStmt, ctx: None):
64
64
  for phi in stmt.phis:
65
65
  self.uses[phi.name] = set()
66
66
  self.uses[phi.lhs].add(phi)
@@ -69,7 +69,7 @@ class _DefineUseInstance(DefaultVisitor):
69
69
  for phi in stmt.phis:
70
70
  self.uses[phi.rhs].add(phi)
71
71
 
72
- def _visit_for_stmt(self, stmt: ForStmt, ctx: None):
72
+ def _visit_for(self, stmt: ForStmt, ctx: None):
73
73
  self._visit_expr(stmt.iterable, ctx)
74
74
  if isinstance(stmt.var, NamedId):
75
75
  self.uses[stmt.var] = set()
@@ -80,7 +80,7 @@ class _DefineUseInstance(DefaultVisitor):
80
80
  for phi in stmt.phis:
81
81
  self.uses[phi.rhs].add(phi)
82
82
 
83
- def _visit_function(self, func: FunctionDef, ctx):
83
+ def _visit_function(self, func: FuncDef, ctx):
84
84
  for arg in func.args:
85
85
  if isinstance(arg.name, NamedId):
86
86
  self.uses[arg.name] = set()
@@ -101,5 +101,5 @@ class DefineUse:
101
101
  analysis_name = 'define_use'
102
102
 
103
103
  @staticmethod
104
- def analyze(func: FunctionDef):
104
+ def analyze(func: FuncDef):
105
105
  return _DefineUseInstance(func).analyze()
@@ -0,0 +1,225 @@
1
+ """Live variable analysis on the FPy IR"""
2
+
3
+ from typing import TypeAlias
4
+
5
+ from ..ir import *
6
+
7
+ _RetType: TypeAlias = set[NamedId]
8
+
9
+ class _LiveVars(ReduceVisitor):
10
+ """Single-instance of the live variable analysis."""
11
+ e: FuncDef | StmtBlock | Stmt | Expr
12
+
13
+ def __init__(self, e: FuncDef | StmtBlock | Stmt | Expr):
14
+ self.e = e
15
+
16
+ def analyze(self):
17
+ """Runs live-variable analysis on `self.e`."""
18
+ match self.e:
19
+ case FuncDef():
20
+ return self._visit_function(self.e, None)
21
+ case StmtBlock():
22
+ return self._visit_block(self.e, set())
23
+ case Stmt():
24
+ return self._visit_statement(self.e, set())
25
+ case Expr():
26
+ return self._visit_expr(self.e, None)
27
+ case _:
28
+ raise RuntimeError('unreachable')
29
+
30
+ def _visit_var(self, e: Var, ctx: None) -> _RetType:
31
+ return { e.name }
32
+
33
+ def _visit_bool(self, e: BoolVal, ctx: None) -> _RetType:
34
+ return set()
35
+
36
+ def _visit_foreign(self, e: ForeignVal, ctx: None) -> _RetType:
37
+ return set()
38
+
39
+ def _visit_decnum(self, e: Decnum, ctx: None) -> _RetType:
40
+ return set()
41
+
42
+ def _visit_hexnum(self, e: Hexnum, ctx: None) -> _RetType:
43
+ return set()
44
+
45
+ def _visit_integer(self, e: Integer, ctx: None) -> _RetType:
46
+ return set()
47
+
48
+ def _visit_rational(self, e: Rational, ctx: None) -> _RetType:
49
+ return set()
50
+
51
+ def _visit_constant(self, e: Constant, ctx: None) -> _RetType:
52
+ return set()
53
+
54
+ def _visit_digits(self, e: Digits, ctx: None) -> _RetType:
55
+ return set()
56
+
57
+ def _visit_unknown(self, e: UnknownCall, ctx: None) -> _RetType:
58
+ fvs = set()
59
+ for arg in e.children:
60
+ fvs.update(self._visit_expr(arg, ctx))
61
+ return fvs
62
+
63
+ def _visit_nary_expr(self, e: NaryExpr, ctx: None) -> _RetType:
64
+ fvs = set()
65
+ for arg in e.children:
66
+ fvs.update(self._visit_expr(arg, ctx))
67
+ return fvs
68
+
69
+ def _visit_compare(self, e: Compare, ctx: None) -> _RetType:
70
+ fvs = set()
71
+ for arg in e.children:
72
+ fvs.update(self._visit_expr(arg, ctx))
73
+ return fvs
74
+
75
+ def _visit_tuple_expr(self, e: TupleExpr, ctx: None) -> _RetType:
76
+ fvs = set()
77
+ for arg in e.children:
78
+ fvs.update(self._visit_expr(arg, ctx))
79
+ return fvs
80
+
81
+ def _visit_tuple_ref(self, e: TupleRef, ctx: None) -> _RetType:
82
+ fvs = self._visit_expr(e.value, ctx)
83
+ for arg in e.slices:
84
+ fvs.update(self._visit_expr(arg, ctx))
85
+ return fvs
86
+
87
+ def _visit_tuple_set(self, e: TupleSet, ctx: None) -> _RetType:
88
+ fvs = self._visit_expr(e.array, ctx)
89
+ for arg in e.slices:
90
+ fvs.update(self._visit_expr(arg, ctx))
91
+ fvs.update(self._visit_expr(e.value, ctx))
92
+ return fvs
93
+
94
+ def _visit_comp_expr(self, e: CompExpr, ctx: None) -> _RetType:
95
+ fvs = set()
96
+ for iterable in e.iterables:
97
+ fvs.update(self._visit_expr(iterable, ctx))
98
+ fvs.update(self._visit_expr(e.elt, ctx))
99
+ return fvs
100
+
101
+ def _visit_if_expr(self, e: IfExpr, ctx: None) -> _RetType:
102
+ fvs = self._visit_expr(e.cond, ctx)
103
+ fvs.update(self._visit_expr(e.ift, ctx))
104
+ fvs.update(self._visit_expr(e.iff, ctx))
105
+ return fvs
106
+
107
+ def _visit_simple_assign(self, stmt: SimpleAssign, ctx: _RetType) -> _RetType:
108
+ fvs = ctx | self._visit_expr(stmt.expr, None)
109
+ if isinstance(stmt.var, NamedId):
110
+ fvs.discard(stmt.var)
111
+ return fvs
112
+
113
+ def _visit_tuple_unpack(self, stmt: TupleUnpack, ctx: _RetType) -> _RetType:
114
+ fvs = ctx | self._visit_expr(stmt.expr, None)
115
+ for var in stmt.binding.names():
116
+ if isinstance(var, NamedId):
117
+ fvs.discard(var)
118
+ return fvs
119
+
120
+ def _visit_index_assign(self, stmt: IndexAssign, ctx: _RetType) -> _RetType:
121
+ fvs = ctx | self._visit_expr(stmt.expr, None)
122
+ for slice in stmt.slices:
123
+ fvs.update(self._visit_expr(slice, None))
124
+ fvs.add(stmt.var)
125
+ return fvs
126
+
127
+ def _visit_if1(self, stmt: If1Stmt, ctx: _RetType) -> _RetType:
128
+ ift_fvs = self._visit_block(stmt.body, ctx)
129
+ cond_fvs = self._visit_expr(stmt.cond, None)
130
+ return ctx | ift_fvs | cond_fvs
131
+
132
+ def _visit_if(self, stmt: IfStmt, ctx: _RetType) -> _RetType:
133
+ ift_fvs = self._visit_block(stmt.ift, ctx)
134
+ iff_fvs = self._visit_block(stmt.iff, ctx)
135
+ cond_fvs = self._visit_expr(stmt.cond, None)
136
+ return ift_fvs | iff_fvs | cond_fvs
137
+
138
+ def _visit_while(self, stmt: WhileStmt, ctx: _RetType) -> _RetType:
139
+ ctx = ctx | self._visit_block(stmt.body, ctx)
140
+ return ctx | self._visit_expr(stmt.cond, None)
141
+
142
+ def _visit_for(self, stmt: ForStmt, ctx: _RetType) -> _RetType:
143
+ body_fvs = self._visit_block(stmt.body, ctx)
144
+ if isinstance(stmt.var, NamedId):
145
+ body_fvs -= { stmt.var }
146
+ ctx = ctx | body_fvs
147
+ return ctx | self._visit_expr(stmt.iterable, None)
148
+
149
+ def _visit_foreign_attr(self, e: ForeignAttribute, ctx: None) -> _RetType:
150
+ return { e.name }
151
+
152
+ def _visit_context_expr(self, e: ContextExpr, ctx: None) -> _RetType:
153
+ match e.ctor:
154
+ case Var():
155
+ fvs = self._visit_var(e.ctor, None)
156
+ case ForeignAttribute():
157
+ fvs = self._visit_foreign_attr(e.ctor, None)
158
+ case _:
159
+ raise RuntimeError(f'unreachable {e.ctor}')
160
+
161
+ for arg in e.args:
162
+ match arg:
163
+ case ForeignAttribute():
164
+ fvs |= self._visit_foreign_attr(arg, None)
165
+ case _:
166
+ fvs |= self._visit_expr(arg, None)
167
+
168
+ return fvs
169
+
170
+ def _visit_context(self, stmt: ContextStmt, ctx: _RetType) -> _RetType:
171
+ body_fvs = self._visit_block(stmt.body, ctx)
172
+ if isinstance(stmt.name, NamedId):
173
+ body_fvs -= { stmt.name }
174
+ ctx = ctx | body_fvs
175
+ match stmt.ctx:
176
+ case Var():
177
+ ctx |= self._visit_var(stmt.ctx, None)
178
+ case ContextExpr():
179
+ ctx |= self._visit_context_expr(stmt.ctx, None)
180
+ case _:
181
+ raise RuntimeError(f'unreachable {stmt.ctx}')
182
+ return ctx
183
+
184
+ def _visit_assert(self, stmt: AssertStmt, ctx: _RetType) -> _RetType:
185
+ return ctx | self._visit_expr(stmt.test, None)
186
+
187
+ def _visit_effect(self, stmt: EffectStmt, ctx: _RetType) -> _RetType:
188
+ return ctx | self._visit_expr(stmt.expr, None)
189
+
190
+ def _visit_return(self, stmt: ReturnStmt, ctx: _RetType) -> _RetType:
191
+ return self._visit_expr(stmt.expr, None)
192
+
193
+ def _visit_block(self, block: StmtBlock, ctx: _RetType) -> _RetType:
194
+ fvs = ctx.copy()
195
+ for stmt in reversed(block.stmts):
196
+ if isinstance(stmt, ReturnStmt):
197
+ fvs = set()
198
+ fvs = self._visit_statement(stmt, fvs)
199
+ return fvs
200
+
201
+ def _visit_function(self, func: FuncDef, ctx: None) -> _RetType:
202
+ fvs = self._visit_block(func.body, set())
203
+ for arg in func.args:
204
+ if isinstance(arg, NamedId):
205
+ fvs.remove(arg)
206
+ return fvs
207
+
208
+ # overriden for typing hint
209
+ def _visit_expr(self, e: Expr, ctx: None) -> _RetType:
210
+ return super()._visit_expr(e, ctx)
211
+
212
+ # overriden for typing hint
213
+ def _visit_statement(self, e: Stmt, ctx: _RetType) -> _RetType:
214
+ return super()._visit_statement(e, ctx)
215
+
216
+
217
+ class LiveVars:
218
+ """Live variable analysis for the FPy AST."""
219
+
220
+ @staticmethod
221
+ def analyze(e: FuncDef | StmtBlock | Stmt | Expr):
222
+ """Analyze the live variables in a function."""
223
+ if not isinstance(e, (FuncDef, StmtBlock, Stmt, Expr)):
224
+ raise TypeError(f'Expected FunctionDef, Block, Stmt or Expr, got {type(e)}')
225
+ return _LiveVars(e).analyze()
@@ -18,10 +18,10 @@ _RetType = tuple[set[NamedId], set[NamedId]]
18
18
 
19
19
  class _ReachingDefsInstance(DefaultVisitor):
20
20
  """Single-use instance of the reaching definitions analysis."""
21
- func: FunctionDef
22
- reaches: dict[Block, Reach]
21
+ func: FuncDef
22
+ reaches: dict[StmtBlock, Reach]
23
23
 
24
- def __init__(self, func: FunctionDef):
24
+ def __init__(self, func: FuncDef):
25
25
  self.func = func
26
26
  self.reaches = {}
27
27
 
@@ -29,23 +29,23 @@ class _ReachingDefsInstance(DefaultVisitor):
29
29
  self._visit_function(self.func, None)
30
30
  return self.reaches
31
31
 
32
- def _visit_var_assign(self, stmt: VarAssign, ctx: _StmtCtx) -> _RetType:
32
+ def _visit_simple_assign(self, stmt: SimpleAssign, ctx: _StmtCtx) -> _RetType:
33
33
  defs_in, kill_in = ctx
34
34
  defs_out = { *defs_in, stmt.var } if isinstance(stmt.var, NamedId) else defs_in
35
35
  kill_out = kill_in | (defs_in & { stmt.var })
36
36
  return defs_out, kill_out
37
37
 
38
- def _visit_tuple_assign(self, stmt: TupleAssign, ctx: _StmtCtx) -> _RetType:
38
+ def _visit_tuple_unpack(self, stmt: TupleUnpack, ctx: _StmtCtx) -> _RetType:
39
39
  defs_in, kill_in = ctx
40
40
  gen = set(stmt.binding.names())
41
41
  defs_out = defs_in | gen
42
42
  kill_out = kill_in | (defs_in & gen)
43
43
  return defs_out, kill_out
44
44
 
45
- def _visit_ref_assign(self, stmt: RefAssign, ctx: _StmtCtx) -> _RetType:
45
+ def _visit_index_assign(self, stmt: IndexAssign, ctx: _StmtCtx) -> _RetType:
46
46
  return ctx
47
47
 
48
- def _visit_if1_stmt(self, stmt: If1Stmt, ctx: _StmtCtx) -> _RetType:
48
+ def _visit_if1(self, stmt: If1Stmt, ctx: _StmtCtx) -> _RetType:
49
49
  defs_in, kill_in = ctx
50
50
  _, block_kill = self._visit_block(stmt.body, defs_in)
51
51
 
@@ -53,7 +53,7 @@ class _ReachingDefsInstance(DefaultVisitor):
53
53
  kill_out = kill_in | (defs_in & block_kill)
54
54
  return defs_out, kill_out
55
55
 
56
- def _visit_if_stmt(self, stmt: IfStmt, ctx: _StmtCtx) -> _RetType:
56
+ def _visit_if(self, stmt: IfStmt, ctx: _StmtCtx) -> _RetType:
57
57
  defs_in, kill_in = ctx
58
58
  ift_defs, ift_kill = self._visit_block(stmt.ift, defs_in)
59
59
  iff_defs, iff_kill = self._visit_block(stmt.iff, defs_in)
@@ -62,7 +62,7 @@ class _ReachingDefsInstance(DefaultVisitor):
62
62
  kill_out = kill_in | (defs_in & (ift_kill | iff_kill))
63
63
  return defs_out, kill_out
64
64
 
65
- def _visit_while_stmt(self, stmt: WhileStmt, ctx: _StmtCtx) -> _RetType:
65
+ def _visit_while(self, stmt: WhileStmt, ctx: _StmtCtx) -> _RetType:
66
66
  defs_in, kill_in = ctx
67
67
  _, block_kill = self._visit_block(stmt.body, defs_in)
68
68
 
@@ -70,7 +70,7 @@ class _ReachingDefsInstance(DefaultVisitor):
70
70
  kill_out = kill_in | (defs_in & block_kill)
71
71
  return defs_out, kill_out
72
72
 
73
- def _visit_for_stmt(self, stmt: ForStmt, ctx: _StmtCtx) -> _RetType:
73
+ def _visit_for(self, stmt: ForStmt, ctx: _StmtCtx) -> _RetType:
74
74
  defs_in, kill_in = ctx
75
75
  defs = { *defs_in, stmt.var } if isinstance(stmt.var, NamedId) else defs_in
76
76
  _, block_kill = self._visit_block(stmt.body, defs)
@@ -91,10 +91,13 @@ class _ReachingDefsInstance(DefaultVisitor):
91
91
  def _visit_assert(self, stmt: AssertStmt, ctx: _StmtCtx) -> _RetType:
92
92
  return ctx
93
93
 
94
- def _visit_return(self, stmt: Return, ctx: _StmtCtx) -> _RetType:
94
+ def _visit_effect(self, stmt: EffectStmt, ctx: _StmtCtx) -> _RetType:
95
95
  return ctx
96
96
 
97
- def _visit_block(self, block: Block, ctx: _BlockCtx):
97
+ def _visit_return(self, stmt: ReturnStmt, ctx: _StmtCtx) -> _RetType:
98
+ return ctx
99
+
100
+ def _visit_block(self, block: StmtBlock, ctx: _BlockCtx):
98
101
  reach_in = ctx.copy()
99
102
 
100
103
  defs: set[NamedId] = reach_in.copy()
@@ -106,7 +109,7 @@ class _ReachingDefsInstance(DefaultVisitor):
106
109
  self.reaches[block] = Reach(reach_in, reach_out, kill)
107
110
  return defs, kill
108
111
 
109
- def _visit_function(self, func: FunctionDef, _: None):
112
+ def _visit_function(self, func: FuncDef, _: None):
110
113
  ctx: set[NamedId] = set()
111
114
  for arg in func.args:
112
115
  if isinstance(arg.name, NamedId):
@@ -125,5 +128,5 @@ class ReachingDefs:
125
128
  analysis_name = 'reaching_defs'
126
129
 
127
130
  @staticmethod
128
- def analyze(func: FunctionDef) -> dict[Block, Reach]:
131
+ def analyze(func: FuncDef) -> dict[StmtBlock, Reach]:
129
132
  return _ReachingDefsInstance(func).analyze()