fpy2 0.0.3__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.
- {fpy2-0.0.3 → fpy2-0.0.4}/PKG-INFO +3 -3
- {fpy2-0.0.3 → fpy2-0.0.4}/README.md +2 -2
- fpy2-0.0.4/fpy2/__init__.py +113 -0
- fpy2-0.0.4/fpy2/analysis/__init__.py +5 -0
- fpy2-0.0.4/fpy2/analysis/define_use.py +227 -0
- {fpy2-0.0.3/fpy2/ast → fpy2-0.0.4/fpy2/analysis}/live_vars.py +22 -18
- {fpy2-0.0.3/fpy2/ast → fpy2-0.0.4/fpy2/analysis}/syntax_check.py +51 -44
- fpy2-0.0.4/fpy2/ast/__init__.py +9 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/ast/formatter.py +101 -47
- fpy2-0.0.4/fpy2/ast/fpyast.py +1247 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/ast/visitor.py +77 -59
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/backend/__init__.py +0 -1
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/backend/backend.py +2 -3
- fpy2-0.0.4/fpy2/backend/fpc.py +795 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/decorator.py +61 -10
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/fpc_context.py +28 -7
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/frontend/fpc.py +152 -132
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/frontend/parser.py +120 -136
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/function.py +2 -28
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/interpret/__init__.py +0 -1
- fpy2-0.0.4/fpy2/interpret/default.py +737 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/interpret/interpreter.py +27 -10
- fpy2-0.0.4/fpy2/interpret/native.py +673 -0
- fpy2-0.0.4/fpy2/libraries/__init__.py +0 -0
- fpy2-0.0.4/fpy2/libraries/core.py +158 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/math.py +107 -76
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/number/__init__.py +97 -19
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/number/context.py +18 -5
- fpy2-0.0.3/fpy2/number/ext.py → fpy2-0.0.4/fpy2/number/ext_float.py +39 -35
- fpy2-0.0.4/fpy2/number/fixed.py +158 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/number/globals.py +1 -2
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/number/gmp.py +155 -158
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/number/ieee754.py +7 -4
- fpy2-0.0.4/fpy2/number/mp_fixed.py +305 -0
- fpy2-0.0.3/fpy2/number/mp.py → fpy2-0.0.4/fpy2/number/mp_float.py +10 -6
- fpy2-0.0.4/fpy2/number/mpb_fixed.py +390 -0
- fpy2-0.0.3/fpy2/number/mpb.py → fpy2-0.0.4/fpy2/number/mpb_float.py +22 -15
- fpy2-0.0.3/fpy2/number/mps.py → fpy2-0.0.4/fpy2/number/mps_float.py +14 -10
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/number/native.py +1 -2
- fpy2-0.0.3/fpy2/number/real.py → fpy2-0.0.4/fpy2/number/number.py +469 -12
- fpy2-0.0.4/fpy2/number/real.py +206 -0
- fpy2-0.0.4/fpy2/primitive.py +33 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/rewrite/__init__.py +1 -1
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/rewrite/applier.py +31 -28
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/rewrite/matcher.py +49 -27
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/rewrite/pattern.py +3 -2
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/rewrite/rewrite.py +1 -1
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/sample/sample.py +9 -10
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/sample/table.py +15 -15
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/transform/__init__.py +5 -2
- {fpy2-0.0.3/fpy2/ast → fpy2-0.0.4/fpy2/transform}/context_inline.py +7 -4
- fpy2-0.0.4/fpy2/transform/copy_propagate.py +76 -0
- fpy2-0.0.4/fpy2/transform/for_bundling.py +177 -0
- fpy2-0.0.4/fpy2/transform/for_unpack.py +69 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/transform/func_update.py +11 -13
- fpy2-0.0.4/fpy2/transform/if_bundling.py +254 -0
- fpy2-0.0.4/fpy2/transform/rename_target.py +95 -0
- fpy2-0.0.4/fpy2/transform/simplify_if.py +185 -0
- fpy2-0.0.4/fpy2/transform/while_bundling.py +136 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/typing/__init__.pyi +3 -3
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/__init__.py +1 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/fractions.py +38 -24
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/gensym.py +9 -1
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/identifier.py +25 -1
- fpy2-0.0.4/fpy2/utils/inspect.py +15 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2.egg-info/PKG-INFO +3 -3
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2.egg-info/SOURCES.txt +18 -30
- {fpy2-0.0.3 → fpy2-0.0.4}/pyproject.toml +1 -1
- fpy2-0.0.3/fpy2/__init__.py +0 -73
- fpy2-0.0.3/fpy2/analysis/__init__.py +0 -8
- fpy2-0.0.3/fpy2/analysis/define_use.py +0 -105
- fpy2-0.0.3/fpy2/analysis/live_vars.py +0 -225
- fpy2-0.0.3/fpy2/analysis/reaching_defs.py +0 -132
- fpy2-0.0.3/fpy2/analysis/verify.py +0 -228
- fpy2-0.0.3/fpy2/ast/__init__.py +0 -15
- fpy2-0.0.3/fpy2/ast/define_use.py +0 -86
- fpy2-0.0.3/fpy2/ast/fpyast.py +0 -1070
- fpy2-0.0.3/fpy2/backend/fpc.py +0 -539
- fpy2-0.0.3/fpy2/backend/fpy.py +0 -306
- fpy2-0.0.3/fpy2/interpret/default.py +0 -724
- fpy2-0.0.3/fpy2/interpret/native.py +0 -564
- fpy2-0.0.3/fpy2/interpret/real.py +0 -647
- fpy2-0.0.3/fpy2/ir/__init__.py +0 -12
- fpy2-0.0.3/fpy2/ir/codegen.py +0 -324
- fpy2-0.0.3/fpy2/ir/formatter.py +0 -301
- fpy2-0.0.3/fpy2/ir/ir.py +0 -804
- fpy2-0.0.3/fpy2/ir/types.py +0 -29
- fpy2-0.0.3/fpy2/ir/visitor.py +0 -654
- fpy2-0.0.3/fpy2/number/number.py +0 -427
- fpy2-0.0.3/fpy2/profile/__init__.py +0 -2
- fpy2-0.0.3/fpy2/profile/expr_profiler.py +0 -240
- fpy2-0.0.3/fpy2/profile/function_profiler.py +0 -140
- fpy2-0.0.3/fpy2/runtime/__init__.py +0 -1
- fpy2-0.0.3/fpy2/runtime/metric.py +0 -32
- fpy2-0.0.3/fpy2/runtime/real/__init__.py +0 -2
- fpy2-0.0.3/fpy2/runtime/real/interval.py +0 -61
- fpy2-0.0.3/fpy2/runtime/real/rival_manager.py +0 -142
- fpy2-0.0.3/fpy2/runtime/trace.py +0 -12
- fpy2-0.0.3/fpy2/transform/for_bundling.py +0 -109
- fpy2-0.0.3/fpy2/transform/simplify_if.py +0 -114
- fpy2-0.0.3/fpy2/transform/ssa.py +0 -337
- fpy2-0.0.3/fpy2/transform/unssa.py +0 -196
- fpy2-0.0.3/fpy2/transform/while_bundling.py +0 -125
- {fpy2-0.0.3 → fpy2-0.0.4}/LICENSE +0 -0
- {fpy2-0.0.3/fpy2/runtime → fpy2-0.0.4/fpy2}/env.py +0 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/frontend/__init__.py +0 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/number/round.py +0 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/rewrite/subst.py +0 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/sample/__init__.py +0 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/typing/__init__.py +0 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/bits.py +0 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/compare.py +0 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/decorator.py +0 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/error.py +0 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/float_params.py +0 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/iterator.py +0 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/location.py +0 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/ordering.py +0 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/string.py +0 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2.egg-info/dependency_links.txt +0 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2.egg-info/requires.txt +0 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/fpy2.egg-info/top_level.txt +0 -0
- {fpy2-0.0.3 → fpy2-0.0.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fpy2
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: An embedded DSL for numerical computing
|
|
5
5
|
Author-email: Brett Saiki <bsaiki@cs.washington.edu>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -27,9 +27,9 @@ Dynamic: license-file
|
|
|
27
27
|
|
|
28
28
|
An embedded Python DSL for specifying and simulating numerical algorithms.
|
|
29
29
|
|
|
30
|
-
Important
|
|
30
|
+
Important links:
|
|
31
31
|
- PyPI package: [fpy2](https://pypi.org/project/fpy2/)
|
|
32
|
-
- Documentation: [
|
|
32
|
+
- Documentation: [fpy.readthedocs.io](https://fpy.readthedocs.io/)
|
|
33
33
|
- GitHub: [fpy](https://github.com/bksaiki/fpy)
|
|
34
34
|
|
|
35
35
|
## Installation
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
An embedded Python DSL for specifying and simulating numerical algorithms.
|
|
4
4
|
|
|
5
|
-
Important
|
|
5
|
+
Important links:
|
|
6
6
|
- PyPI package: [fpy2](https://pypi.org/project/fpy2/)
|
|
7
|
-
- Documentation: [
|
|
7
|
+
- Documentation: [fpy.readthedocs.io](https://fpy.readthedocs.io/)
|
|
8
8
|
- GitHub: [fpy](https://github.com/bksaiki/fpy)
|
|
9
9
|
|
|
10
10
|
## Installation
|
|
@@ -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,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()
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""Live variable analysis for the FPy AST."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from ..ast import *
|
|
4
4
|
|
|
5
5
|
_LiveSet = set[NamedId]
|
|
6
6
|
|
|
7
|
-
class LiveVarsInstance(
|
|
7
|
+
class LiveVarsInstance(Visitor):
|
|
8
8
|
"""Single-use live variable analyzer"""
|
|
9
9
|
|
|
10
10
|
ast: FuncDef | Expr
|
|
@@ -53,12 +53,12 @@ class LiveVarsInstance(AstVisitor):
|
|
|
53
53
|
return self._visit_expr(e.arg, ctx)
|
|
54
54
|
|
|
55
55
|
def _visit_binaryop(self, e: BinaryOp, ctx: None) -> _LiveSet:
|
|
56
|
-
return self._visit_expr(e.
|
|
56
|
+
return self._visit_expr(e.first, ctx) | self._visit_expr(e.second, ctx)
|
|
57
57
|
|
|
58
58
|
def _visit_ternaryop(self, e: TernaryOp, ctx: None) -> _LiveSet:
|
|
59
|
-
live0 = self._visit_expr(e.
|
|
60
|
-
live1 = self._visit_expr(e.
|
|
61
|
-
live2 = self._visit_expr(e.
|
|
59
|
+
live0 = self._visit_expr(e.first, ctx)
|
|
60
|
+
live1 = self._visit_expr(e.second, ctx)
|
|
61
|
+
live2 = self._visit_expr(e.third, ctx)
|
|
62
62
|
return live0 | live1 | live2
|
|
63
63
|
|
|
64
64
|
def _visit_naryop(self, e: NaryOp, ctx: None) -> _LiveSet:
|
|
@@ -87,7 +87,8 @@ class LiveVarsInstance(AstVisitor):
|
|
|
87
87
|
|
|
88
88
|
def _visit_comp_expr(self, e: CompExpr, ctx: None) -> _LiveSet:
|
|
89
89
|
live = self._visit_expr(e.elt, ctx)
|
|
90
|
-
|
|
90
|
+
for target in e.targets:
|
|
91
|
+
live |= target.names()
|
|
91
92
|
for iterable in e.iterables:
|
|
92
93
|
live |= self._visit_expr(iterable, ctx)
|
|
93
94
|
return live
|
|
@@ -98,6 +99,13 @@ class LiveVarsInstance(AstVisitor):
|
|
|
98
99
|
live |= self._visit_expr(s, ctx)
|
|
99
100
|
return live
|
|
100
101
|
|
|
102
|
+
def _visit_tuple_set(self, e: TupleSet, ctx: None) -> _LiveSet:
|
|
103
|
+
live = self._visit_expr(e.array, ctx)
|
|
104
|
+
for s in e.slices:
|
|
105
|
+
live |= self._visit_expr(s, ctx)
|
|
106
|
+
live |= self._visit_expr(e.value, ctx)
|
|
107
|
+
return live
|
|
108
|
+
|
|
101
109
|
def _visit_if_expr(self, e: IfExpr, ctx: None) -> _LiveSet:
|
|
102
110
|
cond_live = self._visit_expr(e.cond, ctx)
|
|
103
111
|
ift_live = self._visit_expr(e.ift, ctx)
|
|
@@ -111,20 +119,13 @@ class LiveVarsInstance(AstVisitor):
|
|
|
111
119
|
live.add(arg)
|
|
112
120
|
return live
|
|
113
121
|
|
|
114
|
-
def
|
|
115
|
-
live = set(live)
|
|
116
|
-
if isinstance(stmt.var, NamedId):
|
|
117
|
-
live -= { stmt.var }
|
|
118
|
-
live |= self._visit_expr(stmt.expr, None)
|
|
119
|
-
return live
|
|
120
|
-
|
|
121
|
-
def _visit_tuple_unpack(self, stmt: TupleUnpack, live: _LiveSet) -> _LiveSet:
|
|
122
|
+
def _visit_assign(self, stmt: Assign, live: _LiveSet) -> _LiveSet:
|
|
122
123
|
live = set(live)
|
|
123
124
|
live -= stmt.binding.names()
|
|
124
125
|
live |= self._visit_expr(stmt.expr, None)
|
|
125
126
|
return live
|
|
126
127
|
|
|
127
|
-
def
|
|
128
|
+
def _visit_indexed_assign(self, stmt: IndexedAssign, live: _LiveSet) -> _LiveSet:
|
|
128
129
|
live = set(live)
|
|
129
130
|
live |= self._visit_expr(stmt.expr, None)
|
|
130
131
|
for s in stmt.slices:
|
|
@@ -154,8 +155,11 @@ class LiveVarsInstance(AstVisitor):
|
|
|
154
155
|
def _visit_for(self, stmt: ForStmt, live: _LiveSet) -> _LiveSet:
|
|
155
156
|
live = set(live)
|
|
156
157
|
live |= self._visit_block(stmt.body, live)
|
|
157
|
-
|
|
158
|
-
|
|
158
|
+
match stmt.target:
|
|
159
|
+
case NamedId():
|
|
160
|
+
live -= { stmt.target }
|
|
161
|
+
case TupleBinding():
|
|
162
|
+
live -= stmt.target.names()
|
|
159
163
|
live |= self._visit_expr(stmt.iterable, None)
|
|
160
164
|
return live
|
|
161
165
|
|
|
@@ -4,8 +4,8 @@ from typing import Optional, Self
|
|
|
4
4
|
|
|
5
5
|
from ..utils import FPySyntaxError
|
|
6
6
|
|
|
7
|
-
from .fpyast import *
|
|
8
|
-
from .visitor import
|
|
7
|
+
from ..ast.fpyast import *
|
|
8
|
+
from ..ast.visitor import Visitor
|
|
9
9
|
from .live_vars import LiveVars
|
|
10
10
|
|
|
11
11
|
class _Env:
|
|
@@ -41,10 +41,10 @@ _Ctx = tuple[_Env, bool]
|
|
|
41
41
|
2nd element: whether the current block is at the top-level.
|
|
42
42
|
"""
|
|
43
43
|
|
|
44
|
-
class SyntaxCheckInstance(
|
|
44
|
+
class SyntaxCheckInstance(Visitor):
|
|
45
45
|
"""Single-use instance of syntax checking"""
|
|
46
46
|
func: FuncDef
|
|
47
|
-
free_vars: set[
|
|
47
|
+
free_vars: set[NamedId]
|
|
48
48
|
ignore_unknown: bool
|
|
49
49
|
ignore_noreturn: bool
|
|
50
50
|
allow_wildcard: bool
|
|
@@ -55,7 +55,7 @@ class SyntaxCheckInstance(AstVisitor):
|
|
|
55
55
|
def __init__(
|
|
56
56
|
self,
|
|
57
57
|
func: FuncDef,
|
|
58
|
-
free_vars: set[
|
|
58
|
+
free_vars: set[NamedId],
|
|
59
59
|
ignore_unknown: bool,
|
|
60
60
|
ignore_noreturn: bool,
|
|
61
61
|
allow_wildcard: bool
|
|
@@ -90,7 +90,7 @@ class SyntaxCheckInstance(AstVisitor):
|
|
|
90
90
|
raise FPySyntaxError(f'unbound variable `{name}`')
|
|
91
91
|
if not env[name]:
|
|
92
92
|
raise FPySyntaxError(f'variable `{name}` not defined along all paths')
|
|
93
|
-
if name
|
|
93
|
+
if name in self.free_vars:
|
|
94
94
|
self.free_var_args.add(name)
|
|
95
95
|
|
|
96
96
|
def _visit_var(self, e: Var, ctx: _Ctx):
|
|
@@ -148,15 +148,15 @@ class SyntaxCheckInstance(AstVisitor):
|
|
|
148
148
|
|
|
149
149
|
def _visit_binaryop(self, e: BinaryOp, ctx: _Ctx):
|
|
150
150
|
env, _ = ctx
|
|
151
|
-
self._visit_expr(e.
|
|
152
|
-
self._visit_expr(e.
|
|
151
|
+
self._visit_expr(e.first, ctx)
|
|
152
|
+
self._visit_expr(e.second, ctx)
|
|
153
153
|
return env
|
|
154
154
|
|
|
155
155
|
def _visit_ternaryop(self, e: TernaryOp, ctx: _Ctx):
|
|
156
156
|
env, _ = ctx
|
|
157
|
-
self._visit_expr(e.
|
|
158
|
-
self._visit_expr(e.
|
|
159
|
-
self._visit_expr(e.
|
|
157
|
+
self._visit_expr(e.first, ctx)
|
|
158
|
+
self._visit_expr(e.second, ctx)
|
|
159
|
+
self._visit_expr(e.third, ctx)
|
|
160
160
|
return env
|
|
161
161
|
|
|
162
162
|
def _visit_naryop(self, e: NaryOp, ctx: _Ctx):
|
|
@@ -173,7 +173,7 @@ class SyntaxCheckInstance(AstVisitor):
|
|
|
173
173
|
|
|
174
174
|
def _visit_call(self, e: Call, ctx: _Ctx):
|
|
175
175
|
env, _ = ctx
|
|
176
|
-
self._mark_use(NamedId(e.
|
|
176
|
+
self._mark_use(NamedId(e.name), env, ignore_missing=self.ignore_unknown)
|
|
177
177
|
for c in e.args:
|
|
178
178
|
self._visit_expr(c, ctx)
|
|
179
179
|
return env
|
|
@@ -188,9 +188,8 @@ class SyntaxCheckInstance(AstVisitor):
|
|
|
188
188
|
env, _ = ctx
|
|
189
189
|
for iterable in e.iterables:
|
|
190
190
|
self._visit_expr(iterable, ctx)
|
|
191
|
-
for
|
|
192
|
-
|
|
193
|
-
env = env.extend(var)
|
|
191
|
+
for target in e.targets:
|
|
192
|
+
env = self._visit_binding(target, env)
|
|
194
193
|
self._visit_expr(e.elt, (env, False))
|
|
195
194
|
return env
|
|
196
195
|
|
|
@@ -201,6 +200,14 @@ class SyntaxCheckInstance(AstVisitor):
|
|
|
201
200
|
self._visit_expr(s, ctx)
|
|
202
201
|
return env
|
|
203
202
|
|
|
203
|
+
def _visit_tuple_set(self, e: TupleSet, ctx: _Ctx):
|
|
204
|
+
env, _ = ctx
|
|
205
|
+
self._visit_expr(e.array, ctx)
|
|
206
|
+
for s in e.slices:
|
|
207
|
+
self._visit_expr(s, ctx)
|
|
208
|
+
self._visit_expr(e.value, ctx)
|
|
209
|
+
return env
|
|
210
|
+
|
|
204
211
|
def _visit_if_expr(self, e: IfExpr, ctx: _Ctx):
|
|
205
212
|
env, _ = ctx
|
|
206
213
|
self._visit_expr(e.cond, ctx)
|
|
@@ -231,32 +238,29 @@ class SyntaxCheckInstance(AstVisitor):
|
|
|
231
238
|
if free not in self.free_vars:
|
|
232
239
|
raise FPySyntaxError('context is data-dependent')
|
|
233
240
|
|
|
234
|
-
def
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
241
|
+
def _visit_binding(self, binding: Id | TupleBinding, env: _Env):
|
|
242
|
+
match binding:
|
|
243
|
+
case NamedId():
|
|
244
|
+
env = env.extend(binding)
|
|
245
|
+
case UnderscoreId():
|
|
246
|
+
pass
|
|
247
|
+
case TupleBinding():
|
|
248
|
+
env = self._visit_tuple_binding(binding, env)
|
|
249
|
+
case _:
|
|
250
|
+
raise RuntimeError('unreachable', binding)
|
|
239
251
|
return env
|
|
240
252
|
|
|
241
|
-
def _visit_tuple_binding(self, binding: TupleBinding,
|
|
242
|
-
env, _ = ctx
|
|
253
|
+
def _visit_tuple_binding(self, binding: TupleBinding, env: _Env):
|
|
243
254
|
for elt in binding.elts:
|
|
244
|
-
|
|
245
|
-
case NamedId():
|
|
246
|
-
env = env.extend(elt)
|
|
247
|
-
case TupleBinding():
|
|
248
|
-
env = self._visit_tuple_binding(elt, ctx)
|
|
249
|
-
case UnderscoreId():
|
|
250
|
-
pass
|
|
251
|
-
case _:
|
|
252
|
-
raise NotImplementedError('unreachable', elt)
|
|
255
|
+
env = self._visit_binding(elt, env)
|
|
253
256
|
return env
|
|
254
257
|
|
|
255
|
-
def
|
|
258
|
+
def _visit_assign(self, stmt: Assign, ctx: _Ctx):
|
|
259
|
+
env, _ = ctx
|
|
256
260
|
self._visit_expr(stmt.expr, ctx)
|
|
257
|
-
return self.
|
|
261
|
+
return self._visit_binding(stmt.binding, env)
|
|
258
262
|
|
|
259
|
-
def
|
|
263
|
+
def _visit_indexed_assign(self, stmt: IndexedAssign, ctx: _Ctx):
|
|
260
264
|
env, _ = ctx
|
|
261
265
|
self._mark_use(stmt.var, env)
|
|
262
266
|
for s in stmt.slices:
|
|
@@ -286,8 +290,7 @@ class SyntaxCheckInstance(AstVisitor):
|
|
|
286
290
|
def _visit_for(self, stmt: ForStmt, ctx: _Ctx):
|
|
287
291
|
env, _ = ctx
|
|
288
292
|
self._visit_expr(stmt.iterable, ctx)
|
|
289
|
-
|
|
290
|
-
env = env.extend(stmt.var)
|
|
293
|
+
env = self._visit_binding(stmt.target, env)
|
|
291
294
|
body_env = self._visit_block(stmt.body, (env, False))
|
|
292
295
|
return env.merge(body_env)
|
|
293
296
|
|
|
@@ -332,7 +335,7 @@ class SyntaxCheckInstance(AstVisitor):
|
|
|
332
335
|
def _visit_function(self, func: FuncDef, ctx: _Ctx):
|
|
333
336
|
env, _ = ctx
|
|
334
337
|
for var in self.free_vars:
|
|
335
|
-
env = env.extend(
|
|
338
|
+
env = env.extend(var)
|
|
336
339
|
for arg in func.args:
|
|
337
340
|
if isinstance(arg.name, NamedId):
|
|
338
341
|
env = env.extend(arg.name)
|
|
@@ -357,22 +360,25 @@ class SyntaxCheck:
|
|
|
357
360
|
Rules enforced:
|
|
358
361
|
|
|
359
362
|
Variables:
|
|
363
|
+
|
|
360
364
|
- any variables must be defined before it is used;
|
|
361
365
|
|
|
362
366
|
Return statements:
|
|
363
|
-
|
|
364
|
-
|
|
367
|
+
|
|
368
|
+
- all functions must have exactly one return statement,
|
|
369
|
+
- must be at the end of the function definiton;
|
|
365
370
|
|
|
366
371
|
If statements
|
|
367
|
-
|
|
368
|
-
|
|
372
|
+
|
|
373
|
+
- any variable must be defined along both branches when
|
|
374
|
+
used after the `if` statement
|
|
369
375
|
"""
|
|
370
376
|
|
|
371
377
|
@staticmethod
|
|
372
|
-
def
|
|
378
|
+
def check(
|
|
373
379
|
func: FuncDef,
|
|
374
380
|
*,
|
|
375
|
-
free_vars: Optional[set[
|
|
381
|
+
free_vars: Optional[set[NamedId]] = None,
|
|
376
382
|
ignore_unknown: bool = False,
|
|
377
383
|
ignore_noreturn: bool = False,
|
|
378
384
|
allow_wildcard: bool = False
|
|
@@ -385,8 +391,9 @@ class SyntaxCheck:
|
|
|
385
391
|
|
|
386
392
|
if not isinstance(func, FuncDef):
|
|
387
393
|
raise TypeError(f'expected a Function, got {func}')
|
|
394
|
+
|
|
388
395
|
if free_vars is None:
|
|
389
|
-
free_vars = set()
|
|
396
|
+
free_vars = set(func.free_vars)
|
|
390
397
|
|
|
391
398
|
inst = SyntaxCheckInstance(func, free_vars, ignore_unknown, ignore_noreturn, allow_wildcard)
|
|
392
399
|
return inst.analyze()
|