guppylang icon indicating copy to clipboard operation
guppylang copied to clipboard

Add support for wasm

Open doug-q opened this issue 1 year ago • 11 comments

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()

doug-q avatar Jan 07 '25 09:01 doug-q

wasm2 = new_wasm()

Hi @doug-q, is the intention in the example here to panic at this line?

Never mind, I misunderstood.

qartik avatar Apr 17 '25 13:04 qartik

Require standard library guppy.std.wasm

Don't we want this library to be under qsystem? cc: @ss2165

qartik avatar Apr 17 '25 14:04 qartik

@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.

qartik avatar Apr 17 '25 16:04 qartik

@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.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.

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

doug-q avatar Apr 22 '25 16:04 doug-q

Let's continue at https://github.com/quantinuum-dev/hugrverse/issues/107#issuecomment-2821834689

qartik avatar Apr 22 '25 16:04 qartik

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 avatar Apr 22 '25 16:04 mark-koch

@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.

qartik avatar Apr 22 '25 16:04 qartik

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

mark-koch avatar Apr 22 '25 16:04 mark-koch

I see your point. In any case, guppified versions of these wasm functions indeed take and return the linear wasm context.

qartik avatar Apr 22 '25 16:04 qartik

Don't we want this library to be under qsystem

@qartik yes I think this is a good idea

ss2165 avatar Apr 23 '25 08:04 ss2165

See more discussion at https://github.com/quantinuum-dev/hugrverse/issues/107#issuecomment-2856121674

qartik avatar May 07 '25 15:05 qartik