guppylang-internals 0.21.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- guppylang_internals/__init__.py +3 -0
- guppylang_internals/ast_util.py +350 -0
- guppylang_internals/cfg/__init__.py +0 -0
- guppylang_internals/cfg/analysis.py +230 -0
- guppylang_internals/cfg/bb.py +221 -0
- guppylang_internals/cfg/builder.py +606 -0
- guppylang_internals/cfg/cfg.py +117 -0
- guppylang_internals/checker/__init__.py +0 -0
- guppylang_internals/checker/cfg_checker.py +388 -0
- guppylang_internals/checker/core.py +550 -0
- guppylang_internals/checker/errors/__init__.py +0 -0
- guppylang_internals/checker/errors/comptime_errors.py +106 -0
- guppylang_internals/checker/errors/generic.py +45 -0
- guppylang_internals/checker/errors/linearity.py +300 -0
- guppylang_internals/checker/errors/type_errors.py +344 -0
- guppylang_internals/checker/errors/wasm.py +34 -0
- guppylang_internals/checker/expr_checker.py +1413 -0
- guppylang_internals/checker/func_checker.py +269 -0
- guppylang_internals/checker/linearity_checker.py +821 -0
- guppylang_internals/checker/stmt_checker.py +447 -0
- guppylang_internals/compiler/__init__.py +0 -0
- guppylang_internals/compiler/cfg_compiler.py +233 -0
- guppylang_internals/compiler/core.py +613 -0
- guppylang_internals/compiler/expr_compiler.py +989 -0
- guppylang_internals/compiler/func_compiler.py +97 -0
- guppylang_internals/compiler/hugr_extension.py +224 -0
- guppylang_internals/compiler/qtm_platform_extension.py +0 -0
- guppylang_internals/compiler/stmt_compiler.py +212 -0
- guppylang_internals/decorator.py +246 -0
- guppylang_internals/definition/__init__.py +0 -0
- guppylang_internals/definition/common.py +214 -0
- guppylang_internals/definition/const.py +74 -0
- guppylang_internals/definition/custom.py +492 -0
- guppylang_internals/definition/declaration.py +171 -0
- guppylang_internals/definition/extern.py +89 -0
- guppylang_internals/definition/function.py +302 -0
- guppylang_internals/definition/overloaded.py +150 -0
- guppylang_internals/definition/parameter.py +82 -0
- guppylang_internals/definition/pytket_circuits.py +405 -0
- guppylang_internals/definition/struct.py +392 -0
- guppylang_internals/definition/traced.py +151 -0
- guppylang_internals/definition/ty.py +51 -0
- guppylang_internals/definition/value.py +115 -0
- guppylang_internals/definition/wasm.py +61 -0
- guppylang_internals/diagnostic.py +523 -0
- guppylang_internals/dummy_decorator.py +76 -0
- guppylang_internals/engine.py +295 -0
- guppylang_internals/error.py +107 -0
- guppylang_internals/experimental.py +92 -0
- guppylang_internals/ipython_inspect.py +28 -0
- guppylang_internals/nodes.py +427 -0
- guppylang_internals/py.typed +0 -0
- guppylang_internals/span.py +150 -0
- guppylang_internals/std/__init__.py +0 -0
- guppylang_internals/std/_internal/__init__.py +0 -0
- guppylang_internals/std/_internal/checker.py +573 -0
- guppylang_internals/std/_internal/compiler/__init__.py +0 -0
- guppylang_internals/std/_internal/compiler/arithmetic.py +136 -0
- guppylang_internals/std/_internal/compiler/array.py +569 -0
- guppylang_internals/std/_internal/compiler/either.py +131 -0
- guppylang_internals/std/_internal/compiler/frozenarray.py +68 -0
- guppylang_internals/std/_internal/compiler/futures.py +30 -0
- guppylang_internals/std/_internal/compiler/list.py +348 -0
- guppylang_internals/std/_internal/compiler/mem.py +13 -0
- guppylang_internals/std/_internal/compiler/option.py +78 -0
- guppylang_internals/std/_internal/compiler/prelude.py +271 -0
- guppylang_internals/std/_internal/compiler/qsystem.py +48 -0
- guppylang_internals/std/_internal/compiler/quantum.py +118 -0
- guppylang_internals/std/_internal/compiler/tket_bool.py +55 -0
- guppylang_internals/std/_internal/compiler/tket_exts.py +59 -0
- guppylang_internals/std/_internal/compiler/wasm.py +135 -0
- guppylang_internals/std/_internal/compiler.py +0 -0
- guppylang_internals/std/_internal/debug.py +95 -0
- guppylang_internals/std/_internal/util.py +271 -0
- guppylang_internals/tracing/__init__.py +0 -0
- guppylang_internals/tracing/builtins_mock.py +62 -0
- guppylang_internals/tracing/frozenlist.py +57 -0
- guppylang_internals/tracing/function.py +186 -0
- guppylang_internals/tracing/object.py +551 -0
- guppylang_internals/tracing/state.py +69 -0
- guppylang_internals/tracing/unpacking.py +194 -0
- guppylang_internals/tracing/util.py +86 -0
- guppylang_internals/tys/__init__.py +0 -0
- guppylang_internals/tys/arg.py +115 -0
- guppylang_internals/tys/builtin.py +382 -0
- guppylang_internals/tys/common.py +110 -0
- guppylang_internals/tys/const.py +114 -0
- guppylang_internals/tys/errors.py +178 -0
- guppylang_internals/tys/param.py +251 -0
- guppylang_internals/tys/parsing.py +425 -0
- guppylang_internals/tys/printing.py +174 -0
- guppylang_internals/tys/subst.py +112 -0
- guppylang_internals/tys/ty.py +876 -0
- guppylang_internals/tys/var.py +49 -0
- guppylang_internals-0.21.0.dist-info/METADATA +253 -0
- guppylang_internals-0.21.0.dist-info/RECORD +98 -0
- guppylang_internals-0.21.0.dist-info/WHEEL +4 -0
- guppylang_internals-0.21.0.dist-info/licenses/LICENCE +201 -0
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
from abc import ABC
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from collections.abc import Callable, Iterator, Sequence
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import TYPE_CHECKING, Any, ClassVar, cast
|
|
8
|
+
|
|
9
|
+
import tket_exts
|
|
10
|
+
from hugr import Hugr, Node, Wire, ops
|
|
11
|
+
from hugr import tys as ht
|
|
12
|
+
from hugr.build import function as hf
|
|
13
|
+
from hugr.build.dfg import DP, DefinitionBuilder, DfBase
|
|
14
|
+
from hugr.hugr.base import OpVarCov
|
|
15
|
+
from hugr.hugr.node_port import ToNode
|
|
16
|
+
from hugr.std import PRELUDE
|
|
17
|
+
from typing_extensions import assert_never
|
|
18
|
+
|
|
19
|
+
from guppylang_internals.checker.core import (
|
|
20
|
+
FieldAccess,
|
|
21
|
+
Globals,
|
|
22
|
+
Place,
|
|
23
|
+
PlaceId,
|
|
24
|
+
TupleAccess,
|
|
25
|
+
Variable,
|
|
26
|
+
)
|
|
27
|
+
from guppylang_internals.definition.common import (
|
|
28
|
+
CheckedDef,
|
|
29
|
+
CompilableDef,
|
|
30
|
+
CompiledDef,
|
|
31
|
+
DefId,
|
|
32
|
+
MonomorphizableDef,
|
|
33
|
+
)
|
|
34
|
+
from guppylang_internals.definition.ty import TypeDef
|
|
35
|
+
from guppylang_internals.definition.value import CompiledCallableDef
|
|
36
|
+
from guppylang_internals.diagnostic import Error
|
|
37
|
+
from guppylang_internals.engine import ENGINE
|
|
38
|
+
from guppylang_internals.error import GuppyError, InternalGuppyError
|
|
39
|
+
from guppylang_internals.tys.arg import ConstArg, TypeArg
|
|
40
|
+
from guppylang_internals.tys.builtin import nat_type
|
|
41
|
+
from guppylang_internals.tys.common import ToHugrContext
|
|
42
|
+
from guppylang_internals.tys.const import BoundConstVar, ConstValue
|
|
43
|
+
from guppylang_internals.tys.param import ConstParam, Parameter, TypeParam
|
|
44
|
+
from guppylang_internals.tys.subst import Inst
|
|
45
|
+
from guppylang_internals.tys.ty import (
|
|
46
|
+
BoundTypeVar,
|
|
47
|
+
NumericType,
|
|
48
|
+
StructType,
|
|
49
|
+
TupleType,
|
|
50
|
+
Type,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if TYPE_CHECKING:
|
|
54
|
+
from guppylang_internals.tys.arg import Argument
|
|
55
|
+
|
|
56
|
+
CompiledLocals = dict[PlaceId, Wire]
|
|
57
|
+
|
|
58
|
+
#: Partial instantiation of generic type parameters for monomorphization.
|
|
59
|
+
#:
|
|
60
|
+
#: When compiling polymorphic definitions to Hugr, some of their generic parameters will
|
|
61
|
+
#: need to be monomorphized (e.g. to support language features that cannot be encoded
|
|
62
|
+
#: in Hugr, see `requires_monomorphization` for details). However, other generic
|
|
63
|
+
#: parameters can be faithfully captured in Hugr. Thus, we want to perform a *partial*
|
|
64
|
+
#: monomorphization when compiling to Hugr.
|
|
65
|
+
#:
|
|
66
|
+
#: A `mono_args: PartiallyMonomorphizedArgs` represents such a partial monomorphization
|
|
67
|
+
#: of generic parameters as a sequence of `Argument | None`. Concretely,
|
|
68
|
+
#: `mono_args[i] == None` means that the argument with index `i` is left as a
|
|
69
|
+
#: polymorphic Hugr type parameter, i.e. it is not monomorphised by Guppy. Otherwise,
|
|
70
|
+
#: `mono_args[i]` specifies the monomorphic instantiation for parameter `i`. For
|
|
71
|
+
#: example, a function with 3 generic parameters that should all be retained in Hugr
|
|
72
|
+
#: will have mono_args = `(None, None, None)`.
|
|
73
|
+
#:
|
|
74
|
+
#: Finally, note that this sequence is required to be a tuple to ensure hashability.
|
|
75
|
+
PartiallyMonomorphizedArgs = tuple["Argument | None", ...]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass(frozen=True)
|
|
79
|
+
class GlobalConstId:
|
|
80
|
+
id: int
|
|
81
|
+
base_name: str
|
|
82
|
+
|
|
83
|
+
_fresh_ids = itertools.count()
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def fresh(base_name: str) -> "GlobalConstId":
|
|
87
|
+
return GlobalConstId(next(GlobalConstId._fresh_ids), base_name)
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def name(self) -> str:
|
|
91
|
+
return f"{self.base_name}.{self.id}"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
#: Unique identifier for a partially monomorphized definition.
|
|
95
|
+
#:
|
|
96
|
+
#: If the `DefId` corresponds to a `MonomorphizableDef`, then the second tuple entry
|
|
97
|
+
#: should hold the corresponding partial monomorphization. Otherwise, the second entry
|
|
98
|
+
#: should be `None`.
|
|
99
|
+
#:
|
|
100
|
+
#: Note the following subtlety: Functions are instances of `MonomorphizableDef`, even if
|
|
101
|
+
#: they aren't actually generic! This means that for non-generic function definitions we
|
|
102
|
+
#: will have an empty tuple `()` as `PartiallyMonomorphizedArgs`, but never `None`.
|
|
103
|
+
#: `None` only shows up for kinds of definitions that are never monomorphized (e.g.
|
|
104
|
+
#: definitions of constants).
|
|
105
|
+
MonoDefId = tuple[DefId, PartiallyMonomorphizedArgs | None]
|
|
106
|
+
|
|
107
|
+
#: Unique identifier for global Hugr constants and functions, the latter with optional
|
|
108
|
+
#: monomorphized type arguments
|
|
109
|
+
MonoGlobalConstId = tuple[GlobalConstId, PartiallyMonomorphizedArgs | None]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass(frozen=True)
|
|
113
|
+
class EntryMonomorphizeError(Error):
|
|
114
|
+
title: ClassVar[str] = "Invalid entry point"
|
|
115
|
+
span_label: ClassVar[str] = (
|
|
116
|
+
"Function `{name}` is not a valid compilation entry point since the value of "
|
|
117
|
+
"generic paramater `{param}` is not known"
|
|
118
|
+
)
|
|
119
|
+
name: str
|
|
120
|
+
param: Parameter
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class CompilerContext(ToHugrContext):
|
|
124
|
+
"""Compilation context containing all available definitions.
|
|
125
|
+
|
|
126
|
+
Maintains a `worklist` of definitions which have been used by other compiled code
|
|
127
|
+
(i.e. `compile_outer` has been called) but have not yet been compiled/lowered
|
|
128
|
+
themselves (i.e. `compile_inner` has not yet been called).
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
module: DefinitionBuilder[ops.Module]
|
|
132
|
+
|
|
133
|
+
#: The definitions compiled so far. For `MonomorphizableDef`s, their id can occur
|
|
134
|
+
#: multiple times here with respectively different partial monomorphizations. See
|
|
135
|
+
#: `MonoDefId` and `PartiallyMonomorphizedArgs` for details.
|
|
136
|
+
compiled: dict[MonoDefId, CompiledDef]
|
|
137
|
+
|
|
138
|
+
# use dict over set for deterministic iteration order
|
|
139
|
+
worklist: dict[MonoDefId, None]
|
|
140
|
+
|
|
141
|
+
global_funcs: dict[MonoGlobalConstId, hf.Function]
|
|
142
|
+
|
|
143
|
+
#: Partial instantiation for some of the type parameters of the current function for
|
|
144
|
+
#: the purpose of monomorphization
|
|
145
|
+
current_mono_args: PartiallyMonomorphizedArgs | None
|
|
146
|
+
|
|
147
|
+
checked_globals: Globals
|
|
148
|
+
|
|
149
|
+
def __init__(
|
|
150
|
+
self,
|
|
151
|
+
module: DefinitionBuilder[ops.Module],
|
|
152
|
+
) -> None:
|
|
153
|
+
self.module = module
|
|
154
|
+
self.worklist = {}
|
|
155
|
+
self.compiled = {}
|
|
156
|
+
self.global_funcs = {}
|
|
157
|
+
self.checked_globals = Globals(None)
|
|
158
|
+
self.current_mono_args = None
|
|
159
|
+
|
|
160
|
+
@contextmanager
|
|
161
|
+
def set_monomorphized_args(
|
|
162
|
+
self, mono_args: PartiallyMonomorphizedArgs | None
|
|
163
|
+
) -> Iterator[None]:
|
|
164
|
+
"""Context manager to set the partial monomorphization for the function that is
|
|
165
|
+
compiled currently being compiled.
|
|
166
|
+
"""
|
|
167
|
+
old = self.current_mono_args
|
|
168
|
+
self.current_mono_args = mono_args
|
|
169
|
+
yield
|
|
170
|
+
self.current_mono_args = old
|
|
171
|
+
|
|
172
|
+
def build_compiled_def(
|
|
173
|
+
self, def_id: DefId, type_args: Inst
|
|
174
|
+
) -> tuple[CompiledDef, Inst]:
|
|
175
|
+
"""Returns the compiled definitions corresponding to the given ID, along with
|
|
176
|
+
the remaining type args that have not been monomorphized away.
|
|
177
|
+
|
|
178
|
+
Might mutate the current Hugr if this definition has never been compiled before.
|
|
179
|
+
"""
|
|
180
|
+
# TODO: The check below is a hack to support nested function definitions. We
|
|
181
|
+
# forgot to insert frames for nested functions into the DEF_STORE, which would
|
|
182
|
+
# make the call to `ENGINE.get_checked` below fail. For now, let's just short-
|
|
183
|
+
# cut if the function doesn't take any generic params (as is the case for all
|
|
184
|
+
# nested functions).
|
|
185
|
+
# See https://github.com/CQCL/guppylang/issues/1032
|
|
186
|
+
if (def_id, ()) in self.compiled:
|
|
187
|
+
assert type_args == []
|
|
188
|
+
return self.compiled[def_id, ()], type_args
|
|
189
|
+
|
|
190
|
+
mono_args: PartiallyMonomorphizedArgs | None = None
|
|
191
|
+
rem_args = type_args
|
|
192
|
+
compile_outer: Callable[[], CompiledDef]
|
|
193
|
+
match ENGINE.get_checked(def_id):
|
|
194
|
+
case MonomorphizableDef(params=params) as monomorphizable:
|
|
195
|
+
mono_args, rem_args = partially_monomorphize_args(
|
|
196
|
+
params, type_args, self
|
|
197
|
+
)
|
|
198
|
+
compile_outer = lambda: monomorphizable.monomorphize( # noqa: E731 (assign-lambda)
|
|
199
|
+
self.module, mono_args, self
|
|
200
|
+
)
|
|
201
|
+
case CompilableDef() as compilable:
|
|
202
|
+
compile_outer = lambda: compilable.compile_outer(self.module, self) # noqa: E731
|
|
203
|
+
case CompiledDef() as compiled_defn:
|
|
204
|
+
compile_outer = lambda: compiled_defn # noqa: E731
|
|
205
|
+
case defn:
|
|
206
|
+
compile_outer = assert_never(defn)
|
|
207
|
+
if (def_id, mono_args) not in self.compiled:
|
|
208
|
+
self.compiled[def_id, mono_args] = compile_outer()
|
|
209
|
+
self.worklist[def_id, mono_args] = None
|
|
210
|
+
return self.compiled[def_id, mono_args], rem_args
|
|
211
|
+
|
|
212
|
+
def compile(self, defn: CheckedDef) -> CompiledDef:
|
|
213
|
+
"""Compiles the given definition and all of its dependencies into the current
|
|
214
|
+
Hugr."""
|
|
215
|
+
# Check and compile the entry point
|
|
216
|
+
entry_mono_args: PartiallyMonomorphizedArgs | None = None
|
|
217
|
+
entry_compiled: CompiledDef
|
|
218
|
+
match ENGINE.get_checked(defn.id):
|
|
219
|
+
case MonomorphizableDef(params=params) as defn:
|
|
220
|
+
# Entry point is not allowed to require monomorphization
|
|
221
|
+
for param in params:
|
|
222
|
+
if requires_monomorphization(param):
|
|
223
|
+
err = EntryMonomorphizeError(defn.defined_at, defn.name, param)
|
|
224
|
+
raise GuppyError(err)
|
|
225
|
+
# Thus, the partial monomorphization for the entry point is always empty
|
|
226
|
+
entry_mono_args = tuple(None for _ in params)
|
|
227
|
+
entry_compiled = defn.monomorphize(self.module, entry_mono_args, self)
|
|
228
|
+
case CompilableDef() as defn:
|
|
229
|
+
entry_compiled = defn.compile_outer(self.module, self)
|
|
230
|
+
case CompiledDef() as defn:
|
|
231
|
+
entry_compiled = defn
|
|
232
|
+
case defn:
|
|
233
|
+
entry_compiled = assert_never(defn)
|
|
234
|
+
|
|
235
|
+
self.compiled[defn.id, entry_mono_args] = entry_compiled
|
|
236
|
+
self.worklist[defn.id, entry_mono_args] = None
|
|
237
|
+
|
|
238
|
+
# Compile definition bodies
|
|
239
|
+
while self.worklist:
|
|
240
|
+
next_id, mono_args = self.worklist.popitem()[0]
|
|
241
|
+
next_def = self.compiled[next_id, mono_args]
|
|
242
|
+
with track_hugr_side_effects(), self.set_monomorphized_args(mono_args):
|
|
243
|
+
next_def.compile_inner(self)
|
|
244
|
+
|
|
245
|
+
return entry_compiled
|
|
246
|
+
|
|
247
|
+
def build_compiled_instance_func(
|
|
248
|
+
self,
|
|
249
|
+
ty: Type | TypeDef,
|
|
250
|
+
name: str,
|
|
251
|
+
type_args: Inst,
|
|
252
|
+
) -> tuple[CompiledCallableDef, Inst] | None:
|
|
253
|
+
"""Returns s compiled instance method along with its remaining type args that
|
|
254
|
+
have not been monomorphized away, or `None` if the type doesn't have a matching
|
|
255
|
+
method.
|
|
256
|
+
|
|
257
|
+
Compiles the definition and all of its dependencies into the current Hugr.
|
|
258
|
+
"""
|
|
259
|
+
from guppylang_internals.engine import ENGINE
|
|
260
|
+
|
|
261
|
+
parsed_func = self.checked_globals.get_instance_func(ty, name)
|
|
262
|
+
if parsed_func is None:
|
|
263
|
+
return None
|
|
264
|
+
checked_func = ENGINE.get_checked(parsed_func.id)
|
|
265
|
+
compiled_func, rem_args = self.build_compiled_def(checked_func.id, type_args)
|
|
266
|
+
assert isinstance(compiled_func, CompiledCallableDef)
|
|
267
|
+
return compiled_func, rem_args
|
|
268
|
+
|
|
269
|
+
def declare_global_func(
|
|
270
|
+
self,
|
|
271
|
+
const_id: GlobalConstId,
|
|
272
|
+
func_ty: ht.PolyFuncType,
|
|
273
|
+
mono_args: PartiallyMonomorphizedArgs | None = None,
|
|
274
|
+
) -> tuple[hf.Function, bool]:
|
|
275
|
+
"""
|
|
276
|
+
Creates a function builder for a global function if it doesn't already exist,
|
|
277
|
+
else returns the existing one.
|
|
278
|
+
"""
|
|
279
|
+
if (const_id, mono_args) in self.global_funcs:
|
|
280
|
+
return self.global_funcs[const_id, mono_args], True
|
|
281
|
+
func = self.module.module_root_builder().define_function(
|
|
282
|
+
name=const_id.name,
|
|
283
|
+
input_types=func_ty.body.input,
|
|
284
|
+
output_types=func_ty.body.output,
|
|
285
|
+
type_params=func_ty.params,
|
|
286
|
+
)
|
|
287
|
+
self.global_funcs[const_id, mono_args] = func
|
|
288
|
+
return func, False
|
|
289
|
+
|
|
290
|
+
def type_var_to_hugr(self, var: BoundTypeVar) -> ht.Type:
|
|
291
|
+
"""Compiles a bound Guppy type variable into a Hugr type.
|
|
292
|
+
|
|
293
|
+
Takes care of performing partial monomorphization as specified in the current
|
|
294
|
+
context.
|
|
295
|
+
"""
|
|
296
|
+
if self.current_mono_args is None:
|
|
297
|
+
# If we're not inside a monomorphized context, just return the Hugr
|
|
298
|
+
# variable with the same de Bruijn index
|
|
299
|
+
return ht.Variable(var.idx, var.hugr_bound)
|
|
300
|
+
|
|
301
|
+
match self.current_mono_args[var.idx]:
|
|
302
|
+
# Either we have a decided to monomorphize the corresponding parameter...
|
|
303
|
+
case TypeArg(ty=ty):
|
|
304
|
+
return ty.to_hugr(self)
|
|
305
|
+
# ... or we're still want to be generic in Hugr
|
|
306
|
+
case None:
|
|
307
|
+
# But in that case we'll have to down-shift the de Bruijn index to
|
|
308
|
+
# account for earlier params that have been monomorphized away
|
|
309
|
+
hugr_idx = compile_variable_idx(var.idx, self.current_mono_args)
|
|
310
|
+
return ht.Variable(hugr_idx, var.hugr_bound)
|
|
311
|
+
case _:
|
|
312
|
+
raise InternalGuppyError("Invalid monomorphization")
|
|
313
|
+
|
|
314
|
+
def const_var_to_hugr(self, var: BoundConstVar) -> ht.TypeArg:
|
|
315
|
+
"""Compiles a bound Guppy constant variable into a Hugr type argument.
|
|
316
|
+
|
|
317
|
+
Takes care of performing partial monomorphization as specified in the current
|
|
318
|
+
context.
|
|
319
|
+
"""
|
|
320
|
+
if var.ty != nat_type():
|
|
321
|
+
raise InternalGuppyError(
|
|
322
|
+
"Tried to convert non-nat const type argument to Hugr. This should "
|
|
323
|
+
"have been monomprhized away."
|
|
324
|
+
)
|
|
325
|
+
param = ht.BoundedNatParam(upper_bound=None)
|
|
326
|
+
|
|
327
|
+
if self.current_mono_args is None:
|
|
328
|
+
# If we're not inside a monomorphized context, just return the Hugr
|
|
329
|
+
# variable with the same de Bruijn index
|
|
330
|
+
return ht.VariableArg(var.idx, param)
|
|
331
|
+
|
|
332
|
+
match self.current_mono_args[var.idx]:
|
|
333
|
+
# Either we have a decided to monomorphize the corresponding parameter...
|
|
334
|
+
case ConstArg(const=ConstValue(value=int(v))):
|
|
335
|
+
return ht.BoundedNatArg(n=v)
|
|
336
|
+
# ... or we're still want to be generic in Hugr
|
|
337
|
+
case None:
|
|
338
|
+
# But in that case we'll have to down-shift the de Bruijn index to
|
|
339
|
+
# account for earlier params that have been monomorphized away
|
|
340
|
+
hugr_idx = compile_variable_idx(var.idx, self.current_mono_args)
|
|
341
|
+
return ht.VariableArg(hugr_idx, param)
|
|
342
|
+
case _:
|
|
343
|
+
raise InternalGuppyError("Invalid monomorphization")
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@dataclass
|
|
347
|
+
class DFContainer:
|
|
348
|
+
"""A dataflow graph under construction.
|
|
349
|
+
|
|
350
|
+
This class is passed through the entire compilation pipeline and stores a builder
|
|
351
|
+
for the dataflow child-graph currently being constructed as well as all live local
|
|
352
|
+
variables. Note that the variable map is mutated in-place and always reflects the
|
|
353
|
+
current compilation state.
|
|
354
|
+
"""
|
|
355
|
+
|
|
356
|
+
builder: DfBase[ops.DfParentOp]
|
|
357
|
+
ctx: CompilerContext
|
|
358
|
+
locals: CompiledLocals = field(default_factory=dict)
|
|
359
|
+
|
|
360
|
+
def __init__(
|
|
361
|
+
self,
|
|
362
|
+
builder: DfBase[DP],
|
|
363
|
+
ctx: CompilerContext,
|
|
364
|
+
locals: CompiledLocals | None = None,
|
|
365
|
+
) -> None:
|
|
366
|
+
generic_builder = cast(DfBase[ops.DfParentOp], builder)
|
|
367
|
+
if locals is None:
|
|
368
|
+
locals = {}
|
|
369
|
+
self.builder = generic_builder
|
|
370
|
+
self.ctx = ctx
|
|
371
|
+
self.locals = locals
|
|
372
|
+
|
|
373
|
+
def __getitem__(self, place: Place) -> Wire:
|
|
374
|
+
"""Constructs a wire for a local place in this DFG.
|
|
375
|
+
|
|
376
|
+
Note that this mutates the Hugr since we might need to pack or unpack some
|
|
377
|
+
tuples to obtain a port for places that involve struct fields.
|
|
378
|
+
"""
|
|
379
|
+
# First check, if we already have a wire for this place
|
|
380
|
+
if place.id in self.locals:
|
|
381
|
+
return self.locals[place.id]
|
|
382
|
+
# Otherwise, our only hope is that it's a struct or tuple value that we can
|
|
383
|
+
# rebuild by packing the wires of its constituting fields
|
|
384
|
+
elif isinstance(place.ty, StructType):
|
|
385
|
+
children: list[Place] = [
|
|
386
|
+
FieldAccess(place, field, None) for field in place.ty.fields
|
|
387
|
+
]
|
|
388
|
+
elif isinstance(place.ty, TupleType):
|
|
389
|
+
children = [
|
|
390
|
+
TupleAccess(place, elem, idx, None)
|
|
391
|
+
for idx, elem in enumerate(place.ty.element_types)
|
|
392
|
+
]
|
|
393
|
+
else:
|
|
394
|
+
raise InternalGuppyError(f"Couldn't obtain a port for `{place}`")
|
|
395
|
+
child_types = [child.ty.to_hugr(self.ctx) for child in children]
|
|
396
|
+
child_wires = [self[child] for child in children]
|
|
397
|
+
wire = self.builder.add_op(ops.MakeTuple(child_types), *child_wires)[0]
|
|
398
|
+
for child in children:
|
|
399
|
+
if child.ty.linear:
|
|
400
|
+
self.locals.pop(child.id)
|
|
401
|
+
self.locals[place.id] = wire
|
|
402
|
+
return wire
|
|
403
|
+
|
|
404
|
+
def __setitem__(self, place: Place, port: Wire) -> None:
|
|
405
|
+
# When assigning a struct value, we immediately unpack it recursively and only
|
|
406
|
+
# store the leaf wires.
|
|
407
|
+
is_return = isinstance(place, Variable) and is_return_var(place.name)
|
|
408
|
+
if isinstance(place.ty, StructType) and not is_return:
|
|
409
|
+
unpack = self.builder.add_op(
|
|
410
|
+
ops.UnpackTuple([t.ty.to_hugr(self.ctx) for t in place.ty.fields]), port
|
|
411
|
+
)
|
|
412
|
+
for field, field_port in zip(place.ty.fields, unpack, strict=True):
|
|
413
|
+
self[FieldAccess(place, field, None)] = field_port
|
|
414
|
+
# If we had a previous wire assigned to this place, we need forget about it.
|
|
415
|
+
# Otherwise, we might use this old value when looking up the place later
|
|
416
|
+
self.locals.pop(place.id, None)
|
|
417
|
+
# Same for tuples.
|
|
418
|
+
elif isinstance(place.ty, TupleType) and not is_return:
|
|
419
|
+
hugr_elem_tys = [ty.to_hugr(self.ctx) for ty in place.ty.element_types]
|
|
420
|
+
unpack = self.builder.add_op(ops.UnpackTuple(hugr_elem_tys), port)
|
|
421
|
+
for idx, (elem, elem_port) in enumerate(
|
|
422
|
+
zip(place.ty.element_types, unpack, strict=True)
|
|
423
|
+
):
|
|
424
|
+
self[TupleAccess(place, elem, idx, None)] = elem_port
|
|
425
|
+
self.locals.pop(place.id, None)
|
|
426
|
+
else:
|
|
427
|
+
self.locals[place.id] = port
|
|
428
|
+
|
|
429
|
+
def __contains__(self, place: Place) -> bool:
|
|
430
|
+
return place.id in self.locals
|
|
431
|
+
|
|
432
|
+
def __copy__(self) -> "DFContainer":
|
|
433
|
+
# Make a copy of the var map so that mutating the copy doesn't
|
|
434
|
+
# mutate our variable mapping
|
|
435
|
+
return DFContainer(self.builder, self.ctx, self.locals.copy())
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
class CompilerBase(ABC):
|
|
439
|
+
"""Base class for the Guppy compiler."""
|
|
440
|
+
|
|
441
|
+
ctx: CompilerContext
|
|
442
|
+
|
|
443
|
+
def __init__(self, ctx: CompilerContext) -> None:
|
|
444
|
+
self.ctx = ctx
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def return_var(n: int) -> str:
|
|
448
|
+
"""Name of the dummy variable for the n-th return value of a function.
|
|
449
|
+
|
|
450
|
+
During compilation, we treat return statements like assignments of dummy variables.
|
|
451
|
+
For example, the statement `return e0, e1, e2` is treated like `%ret0 = e0 ; %ret1 =
|
|
452
|
+
e1 ; %ret2 = e2`. This way, we can reuse our existing mechanism for passing of live
|
|
453
|
+
variables between basic blocks."""
|
|
454
|
+
return f"%ret{n}"
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def is_return_var(x: str) -> bool:
|
|
458
|
+
"""Checks whether the given name is a dummy return variable."""
|
|
459
|
+
return x.startswith("%ret")
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def requires_monomorphization(param: Parameter) -> bool:
|
|
463
|
+
"""Checks if a type parameter must be monomorphized before compiling to Hugr.
|
|
464
|
+
|
|
465
|
+
This is required for some Guppy language features that cannot be encoded in Hugr
|
|
466
|
+
yet. Currently, this only applies to non-nat const parameters.
|
|
467
|
+
"""
|
|
468
|
+
match param:
|
|
469
|
+
case TypeParam():
|
|
470
|
+
return False
|
|
471
|
+
case ConstParam(ty=ty):
|
|
472
|
+
return ty != NumericType(NumericType.Kind.Nat)
|
|
473
|
+
case x:
|
|
474
|
+
return assert_never(x)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def partially_monomorphize_args(
|
|
478
|
+
params: Sequence[Parameter],
|
|
479
|
+
args: Inst,
|
|
480
|
+
ctx: CompilerContext,
|
|
481
|
+
) -> tuple[PartiallyMonomorphizedArgs, Inst]:
|
|
482
|
+
"""
|
|
483
|
+
Given an instantiation of all type parameters, extracts the ones that will need to
|
|
484
|
+
be monomorphized.
|
|
485
|
+
|
|
486
|
+
Also takes care of normalising bound variables w.r.t. the current monomorphization.
|
|
487
|
+
"""
|
|
488
|
+
mono_args: list[Argument | None] = []
|
|
489
|
+
rem_args = []
|
|
490
|
+
for param, arg in zip(params, args, strict=True):
|
|
491
|
+
if requires_monomorphization(param):
|
|
492
|
+
# The constant could still refer to a bound variable, so we need to
|
|
493
|
+
# instantiate it w.r.t. to the current monomorphization
|
|
494
|
+
match arg:
|
|
495
|
+
case ConstArg(const=BoundConstVar(idx=idx)):
|
|
496
|
+
assert ctx.current_mono_args is not None
|
|
497
|
+
inst = ctx.current_mono_args[idx]
|
|
498
|
+
assert inst is not None
|
|
499
|
+
mono_args.append(inst)
|
|
500
|
+
case TypeArg():
|
|
501
|
+
# TODO: Once we also have type args that require monomorphization,
|
|
502
|
+
# we'll need to downshift de Bruijn indices here as well
|
|
503
|
+
raise NotImplementedError
|
|
504
|
+
case arg:
|
|
505
|
+
mono_args.append(arg)
|
|
506
|
+
else:
|
|
507
|
+
mono_args.append(None)
|
|
508
|
+
rem_args.append(arg)
|
|
509
|
+
return tuple(mono_args), rem_args
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def compile_variable_idx(idx: int, mono_args: PartiallyMonomorphizedArgs) -> int:
|
|
513
|
+
"""Returns the Hugr index for a variable.
|
|
514
|
+
|
|
515
|
+
Takes care of shifting down Guppy's indices to account for the current partial
|
|
516
|
+
monomorphization. This avoids gaps in Hugr indices due to the fact that not all
|
|
517
|
+
Guppy parameters are lowered to Hugr (some are monomorphized away).
|
|
518
|
+
"""
|
|
519
|
+
assert mono_args[idx] is None, "Should not compile monomorphized index"
|
|
520
|
+
return sum(1 for arg in mono_args[:idx] if arg is None)
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
QUANTUM_EXTENSION = tket_exts.quantum()
|
|
524
|
+
RESULT_EXTENSION = tket_exts.result()
|
|
525
|
+
DEBUG_EXTENSION = tket_exts.debug()
|
|
526
|
+
|
|
527
|
+
#: List of extension ops that have side-effects, identified by their qualified name
|
|
528
|
+
EXTENSION_OPS_WITH_SIDE_EFFECTS: list[str] = [
|
|
529
|
+
# Results should be order w.r.t. each other but also w.r.t. panics
|
|
530
|
+
*(op_def.qualified_name() for op_def in RESULT_EXTENSION.operations.values()),
|
|
531
|
+
PRELUDE.get_op("panic").qualified_name(),
|
|
532
|
+
PRELUDE.get_op("exit").qualified_name(),
|
|
533
|
+
DEBUG_EXTENSION.get_op("StateResult").qualified_name(),
|
|
534
|
+
# Qubit allocation and deallocation have the side-effect of changing the number of
|
|
535
|
+
# available free qubits
|
|
536
|
+
QUANTUM_EXTENSION.get_op("QAlloc").qualified_name(),
|
|
537
|
+
QUANTUM_EXTENSION.get_op("QFree").qualified_name(),
|
|
538
|
+
QUANTUM_EXTENSION.get_op("MeasureFree").qualified_name(),
|
|
539
|
+
]
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def may_have_side_effect(op: ops.Op) -> bool:
|
|
543
|
+
"""Checks whether an operation could have a side-effect.
|
|
544
|
+
|
|
545
|
+
We need to insert implicit state order edges between these kinds of nodes to ensure
|
|
546
|
+
they are executed in the correct order, even if there is no data dependency.
|
|
547
|
+
"""
|
|
548
|
+
match op:
|
|
549
|
+
case ops.ExtOp() as ext_op:
|
|
550
|
+
return ext_op.op_def().qualified_name() in EXTENSION_OPS_WITH_SIDE_EFFECTS
|
|
551
|
+
case ops.Custom(op_name=op_name, extension=extension):
|
|
552
|
+
qualified_name = f"{extension}.{op_name}" if extension else op_name
|
|
553
|
+
return qualified_name in EXTENSION_OPS_WITH_SIDE_EFFECTS
|
|
554
|
+
case ops.Call() | ops.CallIndirect():
|
|
555
|
+
# Conservative choice is to assume that all calls could have side effects.
|
|
556
|
+
# In the future we could inspect the call graph to figure out a more
|
|
557
|
+
# precise answer
|
|
558
|
+
return True
|
|
559
|
+
case _:
|
|
560
|
+
return False
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
@contextmanager
|
|
564
|
+
def track_hugr_side_effects() -> Iterator[None]:
|
|
565
|
+
"""Initialises the tracking of nodes with side-effects during Hugr building.
|
|
566
|
+
|
|
567
|
+
Ensures that state-order edges are implicitly inserted between side-effectful nodes
|
|
568
|
+
to ensure they are executed in the order they are added.
|
|
569
|
+
"""
|
|
570
|
+
# Remember original `Hugr.add_node` method that is monkey-patched below.
|
|
571
|
+
hugr_add_node = Hugr.add_node
|
|
572
|
+
# Last node with potential side effects for each dataflow parent
|
|
573
|
+
prev_node_with_side_effect: defaultdict[Node, Node | None] = defaultdict(
|
|
574
|
+
lambda: None
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
def hugr_add_node_with_order(
|
|
578
|
+
self: Hugr[OpVarCov],
|
|
579
|
+
op: ops.Op,
|
|
580
|
+
parent: ToNode | None = None,
|
|
581
|
+
num_outs: int | None = None,
|
|
582
|
+
metadata: dict[str, Any] | None = None,
|
|
583
|
+
) -> Node:
|
|
584
|
+
"""Monkey-patched version of `Hugr.add_node` that takes care of implicitly
|
|
585
|
+
inserting state order edges between operations that could have side-effects.
|
|
586
|
+
"""
|
|
587
|
+
new_node = hugr_add_node(self, op, parent, num_outs, metadata)
|
|
588
|
+
if may_have_side_effect(op):
|
|
589
|
+
handle_side_effect(new_node, self)
|
|
590
|
+
return new_node
|
|
591
|
+
|
|
592
|
+
def handle_side_effect(node: Node, hugr: Hugr[OpVarCov]) -> None:
|
|
593
|
+
"""Performs the actual order-edge insertion, assuming that `node` has a side-
|
|
594
|
+
effect."""
|
|
595
|
+
parent = hugr[node].parent
|
|
596
|
+
if parent is not None:
|
|
597
|
+
if prev := prev_node_with_side_effect[parent]:
|
|
598
|
+
hugr.add_order_link(prev, node)
|
|
599
|
+
else:
|
|
600
|
+
# If this is the first side-effectful op in this DFG, make a recursive
|
|
601
|
+
# call with the parent since the parent is also considered side-
|
|
602
|
+
# effectful now. We shouldn't walk up through function definitions
|
|
603
|
+
# or basic blocks though
|
|
604
|
+
if not isinstance(hugr[parent].op, ops.FuncDefn | ops.DataflowBlock):
|
|
605
|
+
handle_side_effect(parent, hugr)
|
|
606
|
+
prev_node_with_side_effect[parent] = node
|
|
607
|
+
|
|
608
|
+
# Monkey-patch the `add_node` method
|
|
609
|
+
Hugr.add_node = hugr_add_node_with_order # type: ignore[method-assign]
|
|
610
|
+
try:
|
|
611
|
+
yield
|
|
612
|
+
finally:
|
|
613
|
+
Hugr.add_node = hugr_add_node # type: ignore[method-assign]
|