feat!: Support generic comptime functions#1630
Conversation
|
This PR contains breaking changes to the public Python API. Breaking changes summary |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1630 +/- ##
==========================================
- Coverage 92.87% 92.82% -0.05%
==========================================
Files 136 136
Lines 13167 13206 +39
==========================================
+ Hits 12229 12259 +30
- Misses 938 947 +9 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
| Branch | mk/generic-comptime |
| Testbed | Linux |
Click to view all benchmark results
| Benchmark | hugr_bytes | Benchmark Result bytes x 1e3 (Result Δ%) | Upper Boundary bytes x 1e3 (Limit %) | hugr_nodes | Benchmark Result nodes (Result Δ%) | Upper Boundary nodes (Limit %) |
|---|---|---|---|---|---|---|
| tests/benchmarks/test_big_array.py::test_big_array_compile | 📈 view plot 🚷 view threshold | 158.77 x 1e3(0.00%)Baseline: 158.77 x 1e3 | 160.36 x 1e3 (99.01%) | 📈 view plot 🚷 view threshold | 6,641.00(0.00%)Baseline: 6,641.00 | 6,707.41 (99.01%) |
| tests/benchmarks/test_ctrl_flow.py::test_many_ctrl_flow_compile | 📈 view plot 🚷 view threshold | 27.54 x 1e3(0.00%)Baseline: 27.54 x 1e3 | 27.81 x 1e3 (99.01%) | 📈 view plot 🚷 view threshold | 1,074.00(0.00%)Baseline: 1,074.00 | 1,084.74 (99.01%) |
| tests/benchmarks/test_queue_push_pop.py::test_queue_push_benchmark_compile | 📈 view plot 🚷 view threshold | 10.91 x 1e3(0.00%)Baseline: 10.91 x 1e3 | 11.02 x 1e3 (99.01%) | 📈 view plot 🚷 view threshold | 308.00(0.00%)Baseline: 308.00 | 311.08 (99.01%) |
| tests/benchmarks/test_queue_push_pop.py::test_queue_push_pop_benchmark_compile | 📈 view plot 🚷 view threshold | 14.84 x 1e3(0.00%)Baseline: 14.84 x 1e3 | 14.99 x 1e3 (99.01%) | 📈 view plot 🚷 view threshold | 435.00(0.00%)Baseline: 435.00 | 439.35 (99.01%) |
acl-cqc
left a comment
There was a problem hiding this comment.
Looks great, Mark, lots of nice examples in tests :). Comments relate only to comments/docs in the code really although I am nervous about the __closure__, __code__ and co_freevars bit....is there any alternative there?
| T = guppy.type_var("T", copyable=True, droppable=True) | ||
|
|
||
| @guppy.comptime | ||
| def foo(x: T @ comptime) -> T: |
There was a problem hiding this comment.
I admit I'm a bit confused here as to whether the @ comptime applied to the T here or the x or indeed whether it's necessary at all?
There was a problem hiding this comment.
It applies to the x. It says that foo takes an input x of type T whose value must be known at compile-time. This in turn is syntactic sugar for a generic function
@guppy.comptime
def foo[T: (Copy, Drop), x: T]() -> T: ...|
|
||
| @guppy.comptime | ||
| def foo(self) -> int: | ||
| assert isinstance(n, int) |
There was a problem hiding this comment.
Presumably this is not in any sense "required" but we are just checking that it works? (Which is good to check - i.e. that we are able to access n during comptime execution)
| assert isinstance(n, int) | ||
| assert isinstance(m, int) | ||
| assert isinstance(k, int) | ||
| return n + m + k |
There was a problem hiding this comment.
| return n + m + k | |
| return xs[k] + ys[k] |
| generic_values = { | ||
| x: const_argument_to_python_value(arg) | ||
| for x, arg in generic_args.items() | ||
| # TODO: We don't have a comptime representation of types yet, so we can only |
There was a problem hiding this comment.
This "representation of types" that we need is not a Hugr representation, right, just a python representation to provide at comptime?
In which case, we do have one: the same representation as in the rest of the compiler, namely guppylang_internals.tys...... So we could expose that. I'm not saying we should**, but maybe clarify the comment ("public representation" perhaps, or say something about not wanting to expose guppylang-internals stuff)
** I mean, how much guppylang-internals to expose, is a subjective question, but I'm not going to push you one way or the other. We do in a few places I think but not many.
| return self.ty.params | ||
|
|
||
| def check(self, type_args: Inst, globals: Globals) -> "TracedMonoFunctionDef": | ||
| """Type checks the body of the function.""" |
There was a problem hiding this comment.
| """Type checks the body of the function.""" | |
| """Type checks (and thus traces/executes) the body of the function.""" |
There was a problem hiding this comment.
Actually I'm not sure it does trace/execute?? That only happens in compile? In which case are we really doing "check the body" at all?
| call = dfg.builder.call( | ||
| self.func_def, *args, instantiation=func_ty, type_args=hugr_type_args | ||
| ) | ||
| call = dfg.builder.call(self.func_def, *args) |
There was a problem hiding this comment.
So...just checking...the ht.FunctionType has been compiled into self.func_def, right?
| val = generic_values.pop(inp.name) | ||
| else: | ||
| # Function inputs are only allowed to be mutable if they are borrowed. | ||
| # For owned arguments, mutation wouldn't be observable by the caller, |
There was a problem hiding this comment.
(driveby?) Not sure I follow this. The mutation wouldn't be observable by the caller but only because the caller has lost/handed over the object that was mutated, so that respects python semantics (in that we have statically disallowed programs in which they'd be able to see the mutations!)
| if x in generic_values: | ||
| f.__closure__[i].cell_contents = generic_values.pop(x) | ||
|
|
||
| # If all values have been assigned, we can stop here |
There was a problem hiding this comment.
nit: I think all the code below is fine (does nothing) if generic_values are empty... is it worth this? (It's nice to have only one yield)
|
|
||
|
|
||
| @contextmanager | ||
| def insert_generic_args( |
There was a problem hiding this comment.
how about add_generic_args_to_globals or ...to_function_globals? That'd make the callsite a lot clearer (I was thinking it was gonna prepend them to an existing list of generic args or something)
| instantiation of generic parameters.""" | ||
| # The values of Python 3.12+ style generics are generated using an annotation scope | ||
| # (see https://docs.python.org/3/reference/compound_stmts.html#generic-functions). | ||
| # Thus, their values will be bound in the `__closure__` table of the function and |
There was a problem hiding this comment.
Argh. This is by far the nastiest bit. I note there is no python version check, either, which might make me feel slightly happier. I didn't see this from the link you give just above, is that the best reference?
Closes #896
BREAKING CHANGE:
TracedFunctionDefis no longer aCompilableDef, but now aCheckableGenericDefthat returns a monomorphicTracedMonoFunctionDefafter checkingBREAKING CHANGE:
trace_functionnow requires the generic instantiation as an extra argument