fpy2 0.0.1__tar.gz → 0.0.2__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 (75) hide show
  1. fpy2-0.0.2/PKG-INFO +39 -0
  2. fpy2-0.0.2/README.md +18 -0
  3. fpy2-0.0.2/fpy2/__init__.py +19 -0
  4. fpy2-0.0.2/fpy2/backend/__init__.py +5 -0
  5. fpy2-0.0.2/fpy2/backend/backend.py +18 -0
  6. fpy2-0.0.2/fpy2/backend/fpc.py +480 -0
  7. fpy2-0.0.2/fpy2/backend/fpy.py +298 -0
  8. fpy2-0.0.2/fpy2/frontend/__init__.py +13 -0
  9. fpy2-0.0.2/fpy2/frontend/codegen.py +278 -0
  10. fpy2-0.0.2/fpy2/frontend/decorator.py +106 -0
  11. fpy2-0.0.2/fpy2/frontend/definition.py +149 -0
  12. fpy2-0.0.2/fpy2/frontend/formatter.py +250 -0
  13. fpy2-0.0.2/fpy2/frontend/fpc.py +713 -0
  14. fpy2-0.0.2/fpy2/frontend/fpyast.py +655 -0
  15. fpy2-0.0.2/fpy2/frontend/live_vars.py +201 -0
  16. fpy2-0.0.2/fpy2/frontend/parser.py +756 -0
  17. fpy2-0.0.2/fpy2/frontend/syntax_check.py +294 -0
  18. fpy2-0.0.2/fpy2/frontend/visitor.py +208 -0
  19. fpy2-0.0.2/fpy2/ir/__init__.py +13 -0
  20. fpy2-0.0.2/fpy2/ir/formatter.py +269 -0
  21. fpy2-0.0.2/fpy2/ir/ir.py +739 -0
  22. fpy2-0.0.2/fpy2/ir/types.py +29 -0
  23. fpy2-0.0.2/fpy2/ir/visitor.py +578 -0
  24. fpy2-0.0.2/fpy2/passes/__init__.py +9 -0
  25. fpy2-0.0.2/fpy2/passes/define_use.py +105 -0
  26. fpy2-0.0.2/fpy2/passes/for_bundling.py +109 -0
  27. fpy2-0.0.2/fpy2/passes/func_update.py +45 -0
  28. fpy2-0.0.2/fpy2/passes/reaching_defs.py +129 -0
  29. fpy2-0.0.2/fpy2/passes/simplify_if.py +109 -0
  30. fpy2-0.0.2/fpy2/passes/ssa.py +330 -0
  31. fpy2-0.0.2/fpy2/passes/verify.py +221 -0
  32. fpy2-0.0.2/fpy2/passes/while_bundling.py +125 -0
  33. fpy2-0.0.2/fpy2/runtime/__init__.py +15 -0
  34. fpy2-0.0.2/fpy2/runtime/common.py +12 -0
  35. fpy2-0.0.2/fpy2/runtime/env.py +23 -0
  36. fpy2-0.0.2/fpy2/runtime/function.py +110 -0
  37. fpy2-0.0.2/fpy2/runtime/metric.py +32 -0
  38. fpy2-0.0.2/fpy2/runtime/native/__init__.py +1 -0
  39. fpy2-0.0.2/fpy2/runtime/native/interpreter.py +512 -0
  40. fpy2-0.0.2/fpy2/runtime/profile/__init__.py +2 -0
  41. fpy2-0.0.2/fpy2/runtime/profile/expr_profiler.py +240 -0
  42. fpy2-0.0.2/fpy2/runtime/profile/function_profiler.py +138 -0
  43. fpy2-0.0.2/fpy2/runtime/real/__init__.py +2 -0
  44. fpy2-0.0.2/fpy2/runtime/real/interpreter.py +583 -0
  45. fpy2-0.0.2/fpy2/runtime/real/interval.py +61 -0
  46. fpy2-0.0.2/fpy2/runtime/real/rival_manager.py +142 -0
  47. fpy2-0.0.2/fpy2/runtime/sampling/__init__.py +5 -0
  48. fpy2-0.0.2/fpy2/runtime/sampling/sample.py +171 -0
  49. fpy2-0.0.2/fpy2/runtime/sampling/table.py +341 -0
  50. fpy2-0.0.2/fpy2/runtime/titanic/__init__.py +1 -0
  51. fpy2-0.0.2/fpy2/runtime/titanic/interpreter.py +616 -0
  52. fpy2-0.0.2/fpy2/typing/__init__.py +20 -0
  53. fpy2-0.0.2/fpy2/typing/__init__.pyi +103 -0
  54. fpy2-0.0.2/fpy2/utils/__init__.py +10 -0
  55. fpy2-0.0.2/fpy2/utils/compare.py +44 -0
  56. fpy2-0.0.2/fpy2/utils/defaults.py +11 -0
  57. fpy2-0.0.2/fpy2/utils/error.py +12 -0
  58. fpy2-0.0.2/fpy2/utils/fraction.py +55 -0
  59. fpy2-0.0.2/fpy2/utils/gensym.py +79 -0
  60. fpy2-0.0.2/fpy2/utils/identifier.py +86 -0
  61. fpy2-0.0.2/fpy2/utils/location.py +31 -0
  62. fpy2-0.0.2/fpy2/utils/string.py +21 -0
  63. fpy2-0.0.2/fpy2.egg-info/PKG-INFO +39 -0
  64. fpy2-0.0.2/fpy2.egg-info/SOURCES.txt +68 -0
  65. fpy2-0.0.2/fpy2.egg-info/requires.txt +7 -0
  66. {fpy2-0.0.1 → fpy2-0.0.2}/pyproject.toml +20 -6
  67. fpy2-0.0.1/PKG-INFO +0 -17
  68. fpy2-0.0.1/README.md +0 -3
  69. fpy2-0.0.1/src/fpy2/__init__.py +0 -0
  70. fpy2-0.0.1/src/fpy2.egg-info/PKG-INFO +0 -17
  71. fpy2-0.0.1/src/fpy2.egg-info/SOURCES.txt +0 -8
  72. {fpy2-0.0.1 → fpy2-0.0.2}/LICENSE +0 -0
  73. {fpy2-0.0.1/src → fpy2-0.0.2}/fpy2.egg-info/dependency_links.txt +0 -0
  74. {fpy2-0.0.1/src → fpy2-0.0.2}/fpy2.egg-info/top_level.txt +0 -0
  75. {fpy2-0.0.1 → fpy2-0.0.2}/setup.cfg +0 -0
fpy2-0.0.2/PKG-INFO ADDED
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.4
2
+ Name: fpy2
3
+ Version: 0.0.2
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
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: titanfp==0.1.1
15
+ Provides-Extra: dev
16
+ Requires-Dist: mypy; extra == "dev"
17
+ Requires-Dist: ruff; extra == "dev"
18
+ Requires-Dist: build; extra == "dev"
19
+ Requires-Dist: twine; extra == "dev"
20
+ Dynamic: license-file
21
+
22
+ # FPy
23
+
24
+ An embedded Python DSL for numerical computing.
25
+
26
+ ## Building
27
+
28
+ Create a virtual environment:
29
+ ```bash
30
+ python3 -m venv .env/
31
+ ```
32
+ and activate it using using
33
+ ```bash
34
+ source .env/bin/activate
35
+ ```
36
+ Then, install the dependencies:
37
+ ```bash
38
+ pip install -e .[dev]
39
+ ```
fpy2-0.0.2/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # FPy
2
+
3
+ An embedded Python DSL for numerical computing.
4
+
5
+ ## Building
6
+
7
+ Create a virtual environment:
8
+ ```bash
9
+ python3 -m venv .env/
10
+ ```
11
+ and activate it using using
12
+ ```bash
13
+ source .env/bin/activate
14
+ ```
15
+ Then, install the dependencies:
16
+ ```bash
17
+ pip install -e .[dev]
18
+ ```
@@ -0,0 +1,19 @@
1
+ from .frontend import fpy
2
+ from .backend import (
3
+ Backend,
4
+ FPCoreCompiler,
5
+ FPYCompiler
6
+ )
7
+
8
+ from .runtime import (
9
+ Function,
10
+ Interpreter,
11
+ PythonInterpreter,
12
+ RealInterpreter,
13
+ TitanicInterpreter,
14
+ set_default_interpreter,
15
+ get_default_interpreter,
16
+ FunctionProfiler,
17
+ ExprProfiler
18
+ )
19
+
@@ -0,0 +1,5 @@
1
+ """Compiler backends from FPy IR to various languages"""
2
+
3
+ from .backend import Backend
4
+ from .fpc import FPCoreCompiler
5
+ from .fpy import FPYCompiler
@@ -0,0 +1,18 @@
1
+ """
2
+ FPy backend abstraction.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+
7
+ from ..runtime import Function
8
+
9
+
10
+ class Backend(ABC):
11
+ """
12
+ Abstract base class for FPy backends.
13
+ """
14
+
15
+ @abstractmethod
16
+ def compile(self, func: Function):
17
+ """Compiles `func` to the backend's target language."""
18
+ raise NotImplementedError('virtual method')
@@ -0,0 +1,480 @@
1
+ """Compilation from FPy IR to FPCore"""
2
+
3
+ from typing import Optional
4
+
5
+ import titanfp.fpbench.fpcast as fpc
6
+
7
+ from ..runtime import Function
8
+ from ..passes import *
9
+ from ..ir import *
10
+ from ..utils import Gensym
11
+
12
+ from .backend import Backend
13
+
14
+ _op_table = {
15
+ '+': fpc.Add,
16
+ '-': fpc.Sub,
17
+ '*': fpc.Mul,
18
+ '/': fpc.Div,
19
+ 'fabs': fpc.Fabs,
20
+ 'sqrt': fpc.Sqrt,
21
+ 'fma': fpc.Fma,
22
+ 'neg': fpc.Neg,
23
+ 'copysign': fpc.Copysign,
24
+ 'fdim': fpc.Fdim,
25
+ 'fmax': fpc.Fmax,
26
+ 'fmin': fpc.Fmin,
27
+ 'fmod': fpc.Fmod,
28
+ 'remainder': fpc.Remainder,
29
+ 'hypot': fpc.Hypot,
30
+ 'cbrt': fpc.Cbrt,
31
+ 'ceil': fpc.Ceil,
32
+ 'floor': fpc.Floor,
33
+ 'nearbyint': fpc.Nearbyint,
34
+ 'round': fpc.Round,
35
+ 'trunc': fpc.Trunc,
36
+ 'acos': fpc.Acos,
37
+ 'asin': fpc.Asin,
38
+ 'atan': fpc.Atan,
39
+ 'atan2': fpc.Atan2,
40
+ 'cos': fpc.Cos,
41
+ 'sin': fpc.Sin,
42
+ 'tan': fpc.Tan,
43
+ 'acosh': fpc.Acosh,
44
+ 'asinh': fpc.Asinh,
45
+ 'atanh': fpc.Atanh,
46
+ 'cosh': fpc.Cosh,
47
+ 'sinh': fpc.Sinh,
48
+ 'tanh': fpc.Tanh,
49
+ 'exp': fpc.Exp,
50
+ 'exp2': fpc.Exp2,
51
+ 'expm1': fpc.Expm1,
52
+ 'log': fpc.Log,
53
+ 'log10': fpc.Log10,
54
+ 'log1p': fpc.Log1p,
55
+ 'log2': fpc.Log2,
56
+ 'pow': fpc.Pow,
57
+ 'erf': fpc.Erf,
58
+ 'erfc': fpc.Erfc,
59
+ 'lgamma': fpc.Lgamma,
60
+ 'tgamma': fpc.Tgamma,
61
+ 'isfinite': fpc.Isfinite,
62
+ 'isinf': fpc.Isinf,
63
+ 'isnan': fpc.Isnan,
64
+ 'isnormal': fpc.Isnormal,
65
+ 'signbit': fpc.Signbit,
66
+ 'not': fpc.Not,
67
+ 'or': fpc.Or,
68
+ 'and': fpc.And,
69
+ }
70
+
71
+ class FPCoreCompileError(Exception):
72
+ """Any FPCore compilation error"""
73
+ pass
74
+
75
+ def _nary_mul(args: list[fpc.Expr]):
76
+ assert args != [], 'must be at least 1 argument'
77
+ if len(args) == 1:
78
+ return args[0]
79
+ else:
80
+ e = fpc.Mul(args[0], args[1])
81
+ for arg in args[2:]:
82
+ e = fpc.Mul(e, arg)
83
+ return e
84
+
85
+ def _size0_expr(x: str):
86
+ return fpc.Size(fpc.Var(x), fpc.Integer(0))
87
+
88
+
89
+ class FPCoreCompileInstance(ReduceVisitor):
90
+ """Compilation instance from FPy to FPCore"""
91
+ func: FunctionDef
92
+ gensym: Gensym
93
+
94
+ def __init__(self, func: FunctionDef):
95
+ uses = DefineUse().analyze(func)
96
+ self.func = func
97
+ self.gensym = Gensym(reserved=uses.keys())
98
+
99
+ def compile(self) -> fpc.FPCore:
100
+ f = self._visit_function(self.func, None)
101
+ assert isinstance(f, fpc.FPCore), 'unexpected result type'
102
+ return f
103
+
104
+ def _compile_arg(self, arg: Argument):
105
+ match arg.ty:
106
+ case RealType():
107
+ return arg.name, None, None
108
+ case AnyType():
109
+ return arg.name, None, None
110
+ case _:
111
+ raise FPCoreCompileError('unsupported argument type', arg)
112
+
113
+ def _compile_tuple_binding(self, tuple_id: str, binding: TupleBinding, pos: list[int]):
114
+ tuple_binds: list[tuple[str, fpc.Expr]] = []
115
+ for i, elt in enumerate(binding):
116
+ match elt:
117
+ case Id():
118
+ idxs = [fpc.Integer(idx) for idx in [i, *pos]]
119
+ tuple_binds.append((str(elt), fpc.Ref(fpc.Var(tuple_id), *idxs)))
120
+ case TupleBinding():
121
+ tuple_binds += self._compile_tuple_binding(tuple_id, elt, [i, *pos])
122
+ case _:
123
+ raise FPCoreCompileError('unexpected tensor element', elt)
124
+ return tuple_binds
125
+
126
+ def _compile_compareop(self, op: CompareOp):
127
+ match op:
128
+ case CompareOp.LT:
129
+ return fpc.LT
130
+ case CompareOp.LE:
131
+ return fpc.LEQ
132
+ case CompareOp.GE:
133
+ return fpc.GEQ
134
+ case CompareOp.GT:
135
+ return fpc.GT
136
+ case CompareOp.EQ:
137
+ return fpc.EQ
138
+ case CompareOp.NE:
139
+ return fpc.NEQ
140
+ case _:
141
+ raise NotImplementedError('unreachable', op)
142
+
143
+ def _visit_var(self, e, ctx) -> fpc.Expr:
144
+ return fpc.Var(str(e.name))
145
+
146
+ def _visit_bool(self, e: Bool, ctx: None):
147
+ return fpc.Constant('TRUE' if e.val else 'FALSE')
148
+
149
+ def _visit_decnum(self, e, ctx) -> fpc.Expr:
150
+ return fpc.Decnum(e.val)
151
+
152
+ def _visit_hexnum(self, e, ctx):
153
+ raise fpc.Hexnum(e.val)
154
+
155
+ def _visit_integer(self, e, ctx) -> fpc.Expr:
156
+ return fpc.Integer(e.val)
157
+
158
+ def _visit_rational(self, e, ctx):
159
+ raise fpc.Rational(e.p, e.q)
160
+
161
+ def _visit_constant(self, e, ctx):
162
+ raise fpc.Constant(e.val)
163
+
164
+ def _visit_digits(self, e, ctx) -> fpc.Expr:
165
+ return fpc.Digits(e.m, e.e, e.b)
166
+
167
+ def _visit_unknown(self, e, ctx) -> fpc.Expr:
168
+ args = [self._visit_expr(c, ctx) for c in e.children]
169
+ return fpc.UnknownOperator(e.name, *args)
170
+
171
+ def _visit_nary_expr(self, e, ctx) -> fpc.Expr:
172
+ if e.name == Range.name:
173
+ # expand range expression
174
+ tuple_id = 'i' # only identifier in scope => no need for uniqueness
175
+ size = self._visit_expr(e.children[0], ctx)
176
+ return fpc.Tensor([(tuple_id, size)], fpc.Var(tuple_id))
177
+ else:
178
+ cls = _op_table.get(e.name)
179
+ if cls is None:
180
+ raise NotImplementedError('no FPCore operator for', e.name)
181
+ return cls(*[self._visit_expr(c, ctx) for c in e.children])
182
+
183
+ def _visit_compare(self, e: Compare, ctx: None) -> fpc.Expr:
184
+ assert e.ops != [], 'should not be empty'
185
+ match e.ops:
186
+ case [op]:
187
+ # 2-argument case: just compile
188
+ cls = self._compile_compareop(op)
189
+ arg0 = self._visit_expr(e.children[0], ctx)
190
+ arg1 = self._visit_expr(e.children[1], ctx)
191
+ return cls(arg0, arg1)
192
+ case [op, *ops]:
193
+ # N-argument case:
194
+ # TODO: want to evaluate each argument only once;
195
+ # may need to let-bind in case any argument is
196
+ # used multiple times
197
+ args = [self._visit_expr(arg, ctx) for arg in e.children]
198
+ curr_group = (op, [args[0], args[1]])
199
+ groups: list[tuple[CompareOp, list[fpc.Expr]]] = [curr_group]
200
+ for op, lhs, rhs in zip(ops, args[1:], args[2:]):
201
+ if op == curr_group[0] or isinstance(lhs, fpc.ValueExpr):
202
+ # same op => append
203
+ # different op (terminal) => append
204
+ curr_group[1].append(lhs)
205
+ else:
206
+ # different op (non-terminal) => new group
207
+ new_group = (op, [lhs, rhs])
208
+ groups.append(new_group)
209
+ curr_group = new_group
210
+
211
+ if len(groups) == 1:
212
+ op, args = groups[0]
213
+ cls = self._compile_compareop(op)
214
+ return cls(*args)
215
+ else:
216
+ args = [self._compile_compareop(op)(*args) for op, args in groups]
217
+ return fpc.And(*args)
218
+ case _:
219
+ raise NotImplementedError('unreachable', e.ops)
220
+
221
+ def _visit_tuple_expr(self, e, ctx) -> fpc.Expr:
222
+ return fpc.Array(*[self._visit_expr(c, ctx) for c in e.children])
223
+
224
+ def _visit_tuple_ref(self, e, ctx) -> fpc.Expr:
225
+ value = self._visit_expr(e.value, ctx)
226
+ slices = [self._visit_expr(s, ctx) for s in e.slices]
227
+ return fpc.Ref(value, *slices)
228
+
229
+ def _generate_tuple_set(self, tuple_id: str, iter_id: str, idx_ids: list[str], val_id: str):
230
+ # dimension bindings
231
+ idx_id = idx_ids[0]
232
+ tensor_dims = [(iter_id, _size0_expr(tuple_id))]
233
+ # generate if expression
234
+ cond_expr = fpc.EQ(fpc.Var(iter_id), fpc.Var(idx_id))
235
+ iff_expr = fpc.Ref(fpc.Var(tuple_id), fpc.Var(idx_id))
236
+ if len(idx_ids) == 1:
237
+ ift_expr = fpc.Var(val_id)
238
+ else:
239
+ let_bindings = [(tuple_id, fpc.Ref(fpc.Var(tuple_id), fpc.Var(idx_id)))]
240
+ rec_expr = self._generate_tuple_set(tuple_id, iter_id, idx_ids[1:], val_id)
241
+ ift_expr = fpc.Let(let_bindings, rec_expr)
242
+ if_expr = fpc.If(cond_expr, ift_expr, iff_expr)
243
+ return fpc.Tensor(tensor_dims, if_expr)
244
+
245
+ def _visit_tuple_set(self, e, ctx) -> fpc.Expr:
246
+ # general case:
247
+ #
248
+ # (let ([t <tuple>] [i0 <index>] ... [v <value>]))
249
+ # (tensor ([k (size t 0)])
250
+ # (if (= k i)
251
+ # (let ([t (ref t i0)])
252
+ # <recurse with i1, ...>)
253
+ # (ref t i0)
254
+ #
255
+ # where <recurse with i1, ...> is
256
+ #
257
+ # (tensor ([k (size t 0)])
258
+ # (if (= k i1)
259
+ # (let ([t (ref t i1)])
260
+ # <recurse with i2, ...>)
261
+ # (ref t i1)
262
+ #
263
+ # and <recurse with iN> is
264
+ #
265
+ # (tensor ([k (size t 0)])
266
+ # (if (= k iN) v (ref t iN))
267
+ #
268
+
269
+ # generate temporary variables
270
+ tuple_id = str(self.gensym.fresh('t'))
271
+ idx_ids = [str(self.gensym.fresh('i')) for _ in e.slices]
272
+ iter_id = str(self.gensym.fresh('k'))
273
+ val_id = str(self.gensym.fresh('v'))
274
+
275
+ # compile each component
276
+ tuple_expr = self._visit_expr(e.array, ctx)
277
+ idx_exprs = [self._visit_expr(idx, ctx) for idx in e.slices]
278
+ val_expr = self._visit_expr(e.value, ctx)
279
+
280
+ # create initial let binding
281
+ let_bindings = [(tuple_id, tuple_expr)]
282
+ for idx_id, idx_expr in zip(idx_ids, idx_exprs):
283
+ let_bindings.append((idx_id, idx_expr))
284
+ let_bindings.append((val_id, val_expr))
285
+
286
+ # recursively generate tensor expressions
287
+ tensor_expr = self._generate_tuple_set(tuple_id, iter_id, idx_ids, val_id)
288
+ return fpc.Let(let_bindings, tensor_expr)
289
+
290
+
291
+ def _visit_comp_expr(self, e, ctx) -> fpc.Expr:
292
+ if len(e.vars) == 1:
293
+ # simple case:
294
+ # (let ([t <iterable>]) (tensor ([i (size t)]) (let ([<var> (ref t i)]) <elt>))
295
+ iterable = e.iterables[0]
296
+ var = str(e.vars[0])
297
+
298
+ tuple_id = str(self.gensym.fresh('t'))
299
+ iter_id = str(self.gensym.fresh('i'))
300
+ iterable = self._visit_expr(iterable, ctx)
301
+ elt = self._visit_expr(e.elt, ctx)
302
+
303
+ let_bindings = [(tuple_id, iterable)]
304
+ tensor_dims: list[tuple[str, fpc.Expr]] = [(iter_id, _size0_expr(tuple_id))]
305
+ ref_bindings: list[tuple[str, fpc.Expr]] = [(var, fpc.Ref(fpc.Var(tuple_id), fpc.Var(iter_id)))]
306
+ return fpc.Let(let_bindings, fpc.Tensor(tensor_dims, fpc.Let(ref_bindings, elt)))
307
+ else:
308
+ # hard case:
309
+ # (let ([t0 <iterable>] ...)
310
+ # (let ([n0 (size t0)] ...)
311
+ # (tensor ([k (! :precision integer (* n0 ...))])
312
+ # (let ([i0 (! :precision integer :round toZero (/ k (* n1 ...)))]
313
+ # [i1 (! :precision integer :round toZero (fmod (/ k (* n2 ...)) n1))]
314
+ # ...
315
+ # [iN (! :precision integer :round toZero (fmod k nN))])
316
+ # (let ([v0 (ref t0 i0)] ...)
317
+ # <elt>))))
318
+
319
+ # bind the tuples to temporaries
320
+ tuple_ids = [str(self.gensym.fresh('t')) for _ in e.vars]
321
+ tuple_binds: list[tuple[str, fpc.Expr]] = [
322
+ (tid, self._visit_expr(iterable, ctx))
323
+ for tid, iterable in zip(tuple_ids, e.iterables)
324
+ ]
325
+ # bind the sizes to temporaries
326
+ size_ids = [str(self.gensym.fresh('n')) for _ in e.vars]
327
+ size_binds: list[tuple[str, fpc.Expr]] = [
328
+ (sid, _size0_expr(tid))
329
+ for sid, tid in zip(size_ids, tuple_ids)
330
+ ]
331
+ # bind the indices to temporaries
332
+ idx_ctx = { 'precision': 'integer', 'round': 'toZero' }
333
+ idx_ids = [str(self.gensym.fresh('i')) for _ in e.vars]
334
+ idx_binds: list[tuple[str, fpc.Expr]] = []
335
+ for i, iid in enumerate(idx_ids):
336
+ if i == 0:
337
+ mul_expr = _nary_mul([fpc.Var(id) for id in size_ids[1:]])
338
+ idx_expr = fpc.Ctx(idx_ctx, fpc.Div(fpc.Var('k'), mul_expr))
339
+ elif i == len(size_ids) - 1:
340
+ idx_expr = fpc.Ctx(idx_ctx, fpc.Fmod(fpc.Var('k'), fpc.Var(size_ids[i])))
341
+ else:
342
+ mul_expr = _nary_mul([fpc.Var(id) for id in size_ids[1:]])
343
+ idx_expr = fpc.Ctx(idx_ctx, fpc.Fmod(fpc.Div(fpc.Var('k'), mul_expr), fpc.Var(size_ids[i])))
344
+ idx_binds.append((iid, idx_expr))
345
+ # iteration variable
346
+ iter_ctx = { 'precision': 'integer'}
347
+ iter_id = str(self.gensym.fresh('k'))
348
+ iter_expr = fpc.Ctx(iter_ctx, _nary_mul([fpc.Var(sid) for sid in size_ids]))
349
+ # reference variables
350
+ ref_ids = [str(self.gensym.refresh(var)) for var in e.vars]
351
+ ref_binds: list[tuple[str, fpc.Expr]] = [
352
+ (rid, fpc.Ref(fpc.Var(tid), fpc.Var(iid)))
353
+ for rid, tid, iid in zip(ref_ids, tuple_ids, idx_ids)
354
+ ]
355
+ # element expression
356
+ elt = self._visit_expr(e.elt, ctx)
357
+ # compose the expression
358
+ tensor_expr = fpc.Tensor([(iter_id, iter_expr)], fpc.Let(idx_binds, fpc.Let(ref_binds, elt)))
359
+ return fpc.Let(tuple_binds, fpc.Let(size_binds, tensor_expr))
360
+
361
+ def _visit_if_expr(self, e, ctx) -> fpc.Expr:
362
+ cond = self._visit_expr(e.cond, ctx)
363
+ ift = self._visit_expr(e.ift, ctx)
364
+ iff = self._visit_expr(e.iff, ctx)
365
+ return fpc.If(cond, ift, iff)
366
+
367
+ def _visit_var_assign(self, stmt: VarAssign, ctx: fpc.Expr):
368
+ bindings = [(str(stmt.var), self._visit_expr(stmt.expr, None))]
369
+ return fpc.Let(bindings, ctx)
370
+
371
+ def _visit_tuple_assign(self, stmt: TupleAssign, ctx: fpc.Expr):
372
+ tuple_id = str(self.gensym.fresh('t'))
373
+ tuple_bind = (tuple_id, self._visit_expr(stmt.expr, None))
374
+ destruct_bindings = self._compile_tuple_binding(tuple_id, stmt.binding, [])
375
+ return fpc.Let([tuple_bind] + destruct_bindings, ctx)
376
+
377
+ def _visit_ref_assign(self, stmt: RefAssign, ctx: fpc.Expr):
378
+ raise FPCoreCompileError(f'cannot compile to FPCore: {type(stmt).__name__}')
379
+
380
+ def _visit_if1_stmt(self, stmt, ctx):
381
+ raise FPCoreCompileError(f'cannot compile to FPCore: {type(stmt).__name__}')
382
+
383
+ def _visit_if_stmt(self, stmt, ctx):
384
+ raise FPCoreCompileError(f'cannot compile to FPCore: {type(stmt).__name__}')
385
+
386
+ def _visit_while_stmt(self, stmt, ctx: fpc.Expr):
387
+ if len(stmt.phis) != 1:
388
+ raise FPCoreCompileError('while loops must have exactly one phi node')
389
+ phi = stmt.phis[0]
390
+ name, init, update = str(phi.name), str(phi.lhs), str(phi.rhs)
391
+ cond = self._visit_expr(stmt.cond, None)
392
+ body = self._visit_block(stmt.body, fpc.Var(update))
393
+ return fpc.While(cond, [(name, fpc.Var(init), body)], ctx)
394
+
395
+ def _visit_for_stmt(self, stmt, ctx):
396
+ if len(stmt.phis) != 1:
397
+ raise FPCoreCompileError('for loops must have exactly one phi node')
398
+ # phi nodes
399
+ phi = stmt.phis[0]
400
+ name, init, update = str(phi.name), str(phi.lhs), str(phi.rhs)
401
+ # fresh variable for the iterable value
402
+ tuple_id = str(self.gensym.fresh('t'))
403
+ iterable = self._visit_expr(stmt.iterable, None)
404
+ body = self._visit_block(stmt.body, fpc.Var(update))
405
+ # index variables and state merging
406
+ dim_binding = (str(stmt.var), _size0_expr(tuple_id))
407
+ while_binding = (name, fpc.Var(init), body)
408
+ return fpc.Let([(tuple_id, iterable)], fpc.For([dim_binding], [while_binding], ctx))
409
+
410
+ def _visit_context(self, stmt, ctx):
411
+ body = self._visit_block(stmt.body, ctx)
412
+ return fpc.Ctx(stmt.props, body)
413
+
414
+ def _visit_assert(self, stmt, ctx):
415
+ # strip the assertion
416
+ return ctx
417
+
418
+ def _visit_return(self, stmt, ctx) -> fpc.Expr:
419
+ return self._visit_expr(stmt.expr, ctx)
420
+
421
+ def _visit_phis(self, phis, lctx, rctx):
422
+ raise NotImplementedError('do not call directly')
423
+
424
+ def _visit_loop_phis(self, phis, lctx, rctx):
425
+ raise NotImplementedError('do not call directly')
426
+
427
+ def _visit_block(self, block, ctx: Optional[fpc.Expr]):
428
+ if ctx is None:
429
+ e = self._visit_statement(block.stmts[-1], None)
430
+ stmts = block.stmts[:-1]
431
+ else:
432
+ e = ctx
433
+ stmts = block.stmts
434
+
435
+ for stmt in reversed(stmts):
436
+ if isinstance(stmt, Return):
437
+ raise FPCoreCompileError('return statements must be at the end of blocks')
438
+ e = self._visit_statement(stmt, e)
439
+
440
+ return e
441
+
442
+ def _visit_function(self, func, ctx: Optional[fpc.Expr]):
443
+ args = [self._compile_arg(arg) for arg in func.args]
444
+ body = self._visit_block(func.body, ctx)
445
+ # TODO: parse data
446
+ ident = func.name
447
+ name = func.ctx.get('name', None)
448
+ pre = func.ctx.get('pre', None)
449
+ spec = func.ctx.get('spec', None)
450
+ return fpc.FPCore(
451
+ inputs=args,
452
+ e=body,
453
+ props=func.ctx.copy(),
454
+ ident=ident,
455
+ name=name,
456
+ pre=pre,
457
+ spec=spec
458
+ )
459
+
460
+ # override to get typing hint
461
+ def _visit_expr(self, e: Expr, ctx: None) -> fpc.Expr:
462
+ return super()._visit_expr(e, ctx)
463
+
464
+ # override to get typing hint
465
+ def _visit_statement(self, stmt: Stmt, ctx: fpc.Expr) -> fpc.Expr:
466
+ return super()._visit_statement(stmt, ctx)
467
+
468
+ class FPCoreCompiler(Backend):
469
+ """Compiler from FPy IR to FPCore"""
470
+
471
+ def compile(self, func: Function) -> fpc.FPCore:
472
+ ir = func.ir
473
+
474
+ # normalization passes
475
+ ir = FuncUpdate.apply(ir)
476
+ ir = ForBundling.apply(ir)
477
+ ir = WhileBundling.apply(ir)
478
+ ir = SimplifyIf.apply(ir)
479
+
480
+ return FPCoreCompileInstance(ir).compile()