# `printf` function for Python Web Assembly runtime

THis project is intended as a small helper package for running Web Assembly files generated by
[c4wa compiler](https://github.com/kign/c4wa) in Python. 
However, it could be used with any Web Assembly project provided that 
[variable arguments calling convention](https://github.com/kign/c4wa/blob/master/etc/doc/language.md#printf) 
defined in `c4wa` spec is followed.

Consider the following WAT file, with comments (save it as file `add.wat`):

```wat
(module
  ;; function printf (imported): int, int -> void
  (import "c4wa" "printf" (func $printf (param i32) (param i32)))
  ;; memory (exported)
  (memory (export "memory") 1)
  ;; "%d + %d = %d\n" is written at address 1024
  (data (i32.const 1024) "%d + %d = %d\0A\00")
  ;; function add (exported): int, int -> void
  (func $add (export "add") (param $a i32) (param $b i32)
    ;; memory[0-7] = $a
    (i64.store (i32.const 0) (i64.extend_i32_s (get_local $a)))
    ;; memory[8-15] = $b
    (i64.store (i32.const 8) (i64.extend_i32_s (get_local $b)))
    ;; memory[16-23] = $a + $b
    (i64.store (i32.const 16) (i64.extend_i32_s (i32.add (get_local $a) (get_local $b))))
    ;; printf(1024, 0)
    (call $printf (i32.const 1024) (i32.const 0))))
```

It exports function `add`, linear memory as `memory` and imports function `printf`, which expects two `i32` arguments.

To run it using [wasmer](https://wasmer.io/), we can use the following Python script 
(save it as file `add.py`:

```python
import sys
from wasm_import import sprintf

def main (num1 : int, num2 : int) :
    from wasmer import engine, Store, Module, Instance, Function, FunctionType, Type, ImportObject
    from wasmer_compiler_llvm import Compiler
    store = Store(engine.Native(Compiler))

    module = Module(store, open("add.wat", 'r').read())
    import_object : ImportObject = ImportObject()

    def printf(p_fmt, offset):
        mem = instance.exports.memory.uint8_view()
        res = sprintf(p_fmt, mem, offset)
        print(res, end='')

    import_object.register("c4wa", {"printf" : Function(store, printf,
                                    FunctionType(params=[Type.I32, Type.I32], results=[]))})

    instance = Instance(module, import_object)
    instance.exports.add(num1, num2)


if __name__ == "__main__" :
    if len(sys.argv) != 3 :
        print(f"USAGE: {sys.argv[0]} <num 1> <num 2>")
        exit(0)
    main(int(sys.argv[1]), int(sys.argv[2]))
```

Then install `wasmer` packages and execute:

```bash
python3 -m pip install --upgrade wasmer wasmer_compiler_llvm
python3 add.py 13 17
# 13 + 17 = 30
```

To use [wasmtime](https://wasmtime.dev/) instead, replace `main` function above with this:

```python
def main (num1 : int, num2 : int) :
    from wasmtime import Store, Module, Instance, Func, FuncType, ValType
    store = Store()

    module = Module.from_file(store.engine, "add.wat")

    def printf(p_fmt, offset):
        mem = instance.exports(store)["memory"].data_ptr(store)
        res = sprintf(p_fmt, mem, offset)
        print(res, end='')

    instance = Instance(store, module, [Func(store, FuncType([ValType.i32(), ValType.i32()], []), printf)])
    instance.exports(store)["add"](store, num1, num2)
```



