Add support for wasm
See https://github.com/CQCL/tket2/pull/737 for proposed extension spec.
- Model stateful WASM context with affine type.
- WASM functions can take have form:
E = int | bool | float
T = E | array[E, N]
def foo(*args: T) -> E: ...
def void(*args: T) -> None: ...
Require standard library guppy.std.wasm with functions:
@guppy.struct
class WASMContext:
"""Affine type representing state of environment."""
def new_wasm_checked() -> Option[WASMContext]:
"""Returns a new WASM environment if available else None."""
def new_wasm() -> WASMContext:
"""Panicking version of `new_wasm_checked`""".
new = new_wasm_checked()
if new.is_some():
return new.unwrap()
panic("No more WASM environments available")
Example:
# all declared wasm signatures are treated as belonging to single wasm module
@guppy.wasm
def add_one(x: int) -> int: ...
@guppy.wasm
def add_syndrome(syndrome: array[bool, 3]) -> None: ...
@guppy.wasm
def decode() -> int: ...
@guppy
def main() -> None:
wasm1 = new_wasm()
wasm2 = new_wasm()
y = wasm1.add_one(x) # all methods take WASMContext as inout
wasm2.add_syndrome(array(True, False, False))
decoded = wasm2.decode()
wasm2 = new_wasm()
Hi @doug-q, is the intention in the example here to panic at this line?
Never mind, I misunderstood.
Require standard library
guppy.std.wasm
Don't we want this library to be under qsystem? cc: @ss2165
@doug-q it seems there is no operation exposed in "tket2.wasm" extension to load a WASM module that's needed for lookup. From https://github.com/CQCL/tket2/commit/34bdc218b5e9bf334830873e847935dea0053242#diff-8491011655eac2b0c86da0880c3c6527df0c4237bc97bd45145663c518e9aed3R646-R653:
/// A Constant identifying a WebAssembly module.
/// Loading this is the only way to obtain a value of `tket2.wasm.module` type.
pub struct ConstWasmModule {
/// The name of the module.
pub name: String,
/// The hash of the module.
pub hash: u64,
}
How do we intend to load an external wasm module from guppy? Or where in the above guppy example can a user provide the wasm module? Perhaps I am missing something.
@doug-q it seems there is no operation exposed in "tket2.wasm" extension to load a WASM module that's needed for lookup. From CQCL/tket2@34bdc21#diff-8491011655eac2b0c86da0880c3c6527df0c4237bc97bd45145663c518e9aed3R646-R653:
/// A Constant identifying a WebAssembly module. /// Loading this is the only way to obtain a value of
tket2.wasm.moduletype. pub struct ConstWasmModule { /// The name of the module. pub name: String, /// The hash of the module. pub hash: u64, } How do we intend to load an external wasm module from guppy? Or where in the above guppy example can a user provide the wasm module? Perhaps I am missing something.
The ConstWasmModule goes in a Const node, and you load it with LoadConstant. does this make sense.
The module would then be provided to eldarion out-of-line, eldarion would check the hash.
There's no guppy mechanism specified to provide the module, this has to be designed
Let's continue at https://github.com/quantinuum-dev/hugrverse/issues/107#issuecomment-2821834689
From the Guppy side, the following interface might be nicer than the detached new_wasm method:
@guppy.wasm_module(file=".../wasm_decoder.wasm")
class MyWasmDecoder:
@guppy.wasm
def add_one(self, x: int) -> int: ...
@guppy.wasm
def add_syndrome(self, syndrome: array[bool, 3]) -> None: ...
@guppy.wasm
def decode(self) -> int: ...
@guppy
def other_method(self) -> int:
# Could even allow to define regular Guppy methods??
self.add_one(1)
self.add_one(10)
return self.decode()
@guppy
def main() -> int:
decoder1 = MyWasmDecoder()
decoder2 = MyWasmDecoder()
decoder1.add_syndrome(...)
decoder2.other_method()
....
@mark-koch generally like the idea but one downside seems to be that the WASM function signatures will then not match what's shown here (because of the necessary self parameter) ~~as they carry no state~~ (Update: I am told they can hold state).
Perhaps we can keep the good parts by allowing the wasm methods to be packaged inside a class as static methods? Unsure if guppy allows that.
We currently don't support static methods, but it wouldn't be hard to add them.
But I think the self argument is actually one of the benefits since it makes the "state carrying through semantics" consistent with other guppy functions.
With the
@guppy.wasm
def add_one(x: int) -> int: ...
@guppy
def main() -> None:
wasm = new_wasm()
y = wasm1.add_one(x)
approach, add_one(x) also implicitly becomes a method since you call it on an instance, but imo this feels less clear
I see your point. In any case, guppified versions of these wasm functions indeed take and return the linear wasm context.
Don't we want this library to be under qsystem
@qartik yes I think this is a good idea
See more discussion at https://github.com/quantinuum-dev/hugrverse/issues/107#issuecomment-2856121674