From c724ce1eb3560cd8557c6dd95db9ea3b096043f4 Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Sun, 26 Mar 2023 01:04:08 +0300 Subject: [PATCH 01/24] FIXES #137: make calling wasm from python 7x faster --- wasmtime/_func.py | 78 +++++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/wasmtime/_func.py b/wasmtime/_func.py index d720bc5a..af276bf5 100644 --- a/wasmtime/_func.py +++ b/wasmtime/_func.py @@ -7,7 +7,7 @@ from typing import Callable, Optional, Generic, TypeVar, List, Union, Tuple, cast as cast_type, Sequence from ._exportable import AsExtern from ._store import Storelike - +from ._bindings import wasmtime_val_raw_t T = TypeVar('T') FUNCTIONS: "Slab[Tuple]" @@ -27,11 +27,11 @@ def __init__(self, store: Storelike, ty: FuncType, func: Callable, access_caller set to `True` then the first argument given to `func` is an instance of type `Caller` below. """ - if not isinstance(store, Store): raise TypeError("expected a Store") if not isinstance(ty, FuncType): raise TypeError("expected a FuncType") + self._func_call_init(ty) idx = FUNCTIONS.allocate((func, ty.results, access_caller)) _func = ffi.wasmtime_func_t() ffi.wasmtime_func_new( @@ -56,6 +56,33 @@ def type(self, store: Storelike) -> FuncType: ptr = ffi.wasmtime_func_type(store._context, byref(self._func)) return FuncType._from_ptr(ptr, None) + def _func_call_init(self, ty): + self._ty = ty + ty_params = ty.params + ty_results = ty.results + self._params_str = (str(i) for i in ty_params) + self._results_str = (str(i) for i in ty_results) + params_n = len(ty_params) + results_n = len(ty_results) + self._params_n = params_n + self._results_n = results_n + n = max(params_n, results_n) + self._vals_raw_type = wasmtime_val_raw_t*n + + def _create_raw_vals(self, *params: IntoVal) -> ctypes.Array[wasmtime_val_raw_t]: + raw = self._vals_raw_type() + for i, param_str in enumerate(self._params_str): + setattr(raw[i], param_str, params[i]) + return raw + + def _extract_return(self, vals_raw: ctypes.Array[wasmtime_val_raw_t]) -> Union[IntoVal, Sequence[IntoVal], None]: + if self._results_n==0: + return None + if self._results_n==1: + return getattr(vals_raw[0], self._results_str[0]) + # we can use tuple construct, but I'm using list for compatability + return [getattr(val_raw, ret_str) for val_raw, ret_str in zip(vals_raw, self._results_str)] + def __call__(self, store: Storelike, *params: IntoVal) -> Union[IntoVal, Sequence[IntoVal], None]: """ Calls this function with the given parameters @@ -70,45 +97,30 @@ def __call__(self, store: Storelike, *params: IntoVal) -> Union[IntoVal, Sequenc Note that you can also use the `__call__` method and invoke a `Func` as if it were a function directly. """ - - ty = self.type(store) - param_tys = ty.params - if len(params) > len(param_tys): + params_n = len(params) + if params_n > self._params_n: raise WasmtimeError("too many parameters provided: given %s, expected %s" % - (len(params), len(param_tys))) - if len(params) < len(param_tys): + (params_n, self._params_n)) + if params_n < self._params_n: raise WasmtimeError("too few parameters provided: given %s, expected %s" % - (len(params), len(param_tys))) - - param_vals = [Val._convert(ty, params[i]) for i, ty in enumerate(param_tys)] - params_ptr = (ffi.wasmtime_val_t * len(params))() - for i, val in enumerate(param_vals): - params_ptr[i] = val._unwrap_raw() - - result_tys = ty.results - results_ptr = (ffi.wasmtime_val_t * len(result_tys))() - + (params_n, self._params_n)) + vals_raw = self._create_raw_vals(*params) + vals_raw_ptr = ctypes.cast(vals_raw, ctypes.POINTER(wasmtime_val_raw_t)) + # according to https://docs.wasmtime.dev/c-api/func_8h.html#a3b54596199641a8647a7cd89f322966f + # it's safe to call wasmtime_func_call_unchecked because + # - we allocate enough space to hold all the parameters and all the results + # - we set proper types + # - but not sure about "Values such as externref and funcref are valid within the store being called" with enter_wasm(store) as trap: - error = ffi.wasmtime_func_call( + error = None + ffi.wasmtime_func_call_unchecked( store._context, byref(self._func), - params_ptr, - len(params), - results_ptr, - len(result_tys), + vals_raw_ptr, trap) if error: raise WasmtimeError._from_ptr(error) - - results = [] - for i in range(0, len(result_tys)): - results.append(Val(results_ptr[i]).value) - if len(results) == 0: - return None - elif len(results) == 1: - return results[0] - else: - return results + return self._extract_return(vals_raw) def _as_extern(self) -> ffi.wasmtime_extern_t: union = ffi.wasmtime_extern_union(func=self._func) From d0dcca215fd46886e96bbe9a33646595db57e2f9 Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Sun, 26 Mar 2023 01:53:21 +0300 Subject: [PATCH 02/24] FIXES #137: make calling wasm from python 7x faster --- wasmtime/_func.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/wasmtime/_func.py b/wasmtime/_func.py index af276bf5..084458b8 100644 --- a/wasmtime/_func.py +++ b/wasmtime/_func.py @@ -31,7 +31,19 @@ def __init__(self, store: Storelike, ty: FuncType, func: Callable, access_caller raise TypeError("expected a Store") if not isinstance(ty, FuncType): raise TypeError("expected a FuncType") - self._func_call_init(ty) + # init signature properties used by call + self._ty = ty + ty_params = ty.params + ty_results = ty.results + self._params_str = (str(i) for i in ty_params) + self._results_str = (str(i) for i in ty_results) + params_n = len(ty_params) + results_n = len(ty_results) + self._params_n = params_n + self._results_n = results_n + n = max(params_n, results_n) + self._vals_raw_type = wasmtime_val_raw_t*n + idx = FUNCTIONS.allocate((func, ty.results, access_caller)) _func = ffi.wasmtime_func_t() ffi.wasmtime_func_new( @@ -56,19 +68,6 @@ def type(self, store: Storelike) -> FuncType: ptr = ffi.wasmtime_func_type(store._context, byref(self._func)) return FuncType._from_ptr(ptr, None) - def _func_call_init(self, ty): - self._ty = ty - ty_params = ty.params - ty_results = ty.results - self._params_str = (str(i) for i in ty_params) - self._results_str = (str(i) for i in ty_results) - params_n = len(ty_params) - results_n = len(ty_results) - self._params_n = params_n - self._results_n = results_n - n = max(params_n, results_n) - self._vals_raw_type = wasmtime_val_raw_t*n - def _create_raw_vals(self, *params: IntoVal) -> ctypes.Array[wasmtime_val_raw_t]: raw = self._vals_raw_type() for i, param_str in enumerate(self._params_str): @@ -109,7 +108,7 @@ def __call__(self, store: Storelike, *params: IntoVal) -> Union[IntoVal, Sequenc # according to https://docs.wasmtime.dev/c-api/func_8h.html#a3b54596199641a8647a7cd89f322966f # it's safe to call wasmtime_func_call_unchecked because # - we allocate enough space to hold all the parameters and all the results - # - we set proper types + # - we set proper types by reading types from ty # - but not sure about "Values such as externref and funcref are valid within the store being called" with enter_wasm(store) as trap: error = None From 52a0f1d5c453908f404f62c9286cc0137ade5042 Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Sun, 26 Mar 2023 01:59:12 +0300 Subject: [PATCH 03/24] FIXES #137: make calling wasm from python 7x faster --- wasmtime/_func.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wasmtime/_func.py b/wasmtime/_func.py index 084458b8..f8c709b6 100644 --- a/wasmtime/_func.py +++ b/wasmtime/_func.py @@ -16,6 +16,11 @@ class Func: _func: ffi.wasmtime_func_t + _params_n: int + _results_n: int + _params_str: list[str] + _results_str: list[str] + _vals_raw_type: ctypes.Array[wasmtime_val_raw_t] def __init__(self, store: Storelike, ty: FuncType, func: Callable, access_caller: bool = False): """ From da545aa3a96d971c15b8e09cd0027b60b1a21f73 Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Sun, 26 Mar 2023 12:07:26 +0300 Subject: [PATCH 04/24] FIXES #137: make calling wasm from python 7x faster --- wasmtime/_func.py | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/wasmtime/_func.py b/wasmtime/_func.py index f8c709b6..01fcd9d1 100644 --- a/wasmtime/_func.py +++ b/wasmtime/_func.py @@ -16,10 +16,12 @@ class Func: _func: ffi.wasmtime_func_t + _ty: FuncType _params_n: int _results_n: int _params_str: list[str] _results_str: list[str] + _results_str0: str _vals_raw_type: ctypes.Array[wasmtime_val_raw_t] def __init__(self, store: Storelike, ty: FuncType, func: Callable, access_caller: bool = False): @@ -36,19 +38,7 @@ def __init__(self, store: Storelike, ty: FuncType, func: Callable, access_caller raise TypeError("expected a Store") if not isinstance(ty, FuncType): raise TypeError("expected a FuncType") - # init signature properties used by call - self._ty = ty - ty_params = ty.params - ty_results = ty.results - self._params_str = (str(i) for i in ty_params) - self._results_str = (str(i) for i in ty_results) - params_n = len(ty_params) - results_n = len(ty_results) - self._params_n = params_n - self._results_n = results_n - n = max(params_n, results_n) - self._vals_raw_type = wasmtime_val_raw_t*n - + self._init_call(ty) idx = FUNCTIONS.allocate((func, ty.results, access_caller)) _func = ffi.wasmtime_func_t() ffi.wasmtime_func_new( @@ -83,10 +73,26 @@ def _extract_return(self, vals_raw: ctypes.Array[wasmtime_val_raw_t]) -> Union[I if self._results_n==0: return None if self._results_n==1: - return getattr(vals_raw[0], self._results_str[0]) + return getattr(vals_raw[0], self._results_str0) # we can use tuple construct, but I'm using list for compatability return [getattr(val_raw, ret_str) for val_raw, ret_str in zip(vals_raw, self._results_str)] + def _init_call(self, ty): + """init signature properties used by call""" + self._ty = ty + ty_params = ty.params + ty_results = ty.results + self._params_str = (str(i) for i in ty_params) + self._results_str = (str(i) for i in ty_results) + self._results_str0 = str(ty_results[0]) + params_n = len(ty_params) + results_n = len(ty_results) + self._params_n = params_n + self._results_n = results_n + n = max(params_n, results_n) + self._vals_raw_type = wasmtime_val_raw_t*n + + def __call__(self, store: Storelike, *params: IntoVal) -> Union[IntoVal, Sequence[IntoVal], None]: """ Calls this function with the given parameters @@ -101,6 +107,8 @@ def __call__(self, store: Storelike, *params: IntoVal) -> Union[IntoVal, Sequenc Note that you can also use the `__call__` method and invoke a `Func` as if it were a function directly. """ + if getattr(self, "_ty", None) is None: + self._init_call(self.type(store)) params_n = len(params) if params_n > self._params_n: raise WasmtimeError("too many parameters provided: given %s, expected %s" % From 68fa2a8a2169927397e6d308e9f9ed054b5d60dd Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Sun, 26 Mar 2023 12:12:38 +0300 Subject: [PATCH 05/24] FIXES #137: make calling wasm from python 7x faster --- wasmtime/_func.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wasmtime/_func.py b/wasmtime/_func.py index 01fcd9d1..c821e3e3 100644 --- a/wasmtime/_func.py +++ b/wasmtime/_func.py @@ -82,11 +82,11 @@ def _init_call(self, ty): self._ty = ty ty_params = ty.params ty_results = ty.results - self._params_str = (str(i) for i in ty_params) - self._results_str = (str(i) for i in ty_results) - self._results_str0 = str(ty_results[0]) params_n = len(ty_params) results_n = len(ty_results) + self._params_str = (str(i) for i in ty_params) + self._results_str = (str(i) for i in ty_results) + self._results_str0 = str(ty_results[0]) if results_n else None self._params_n = params_n self._results_n = results_n n = max(params_n, results_n) From f00d32a0c81dac49287d87f76ad6f38f28cae54d Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Sun, 26 Mar 2023 12:20:20 +0300 Subject: [PATCH 06/24] FIXES #137: make calling wasm from python 7x faster --- wasmtime/_func.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wasmtime/_func.py b/wasmtime/_func.py index c821e3e3..0d4d1eec 100644 --- a/wasmtime/_func.py +++ b/wasmtime/_func.py @@ -124,8 +124,7 @@ def __call__(self, store: Storelike, *params: IntoVal) -> Union[IntoVal, Sequenc # - we set proper types by reading types from ty # - but not sure about "Values such as externref and funcref are valid within the store being called" with enter_wasm(store) as trap: - error = None - ffi.wasmtime_func_call_unchecked( + error = ffi.wasmtime_func_call_unchecked( store._context, byref(self._func), vals_raw_ptr, From 7bee1599365d2e36360c0ef7bbf7b2b78f885262 Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Mon, 27 Mar 2023 22:50:33 +0300 Subject: [PATCH 07/24] FIXES #137: make calling wasm from python 7x faster --- examples/simd_i8x16.py | 30 ++++++++++++++++++++++++++++++ wasmtime/_func.py | 30 ++++++++++++++++++++++++++---- 2 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 examples/simd_i8x16.py diff --git a/examples/simd_i8x16.py b/examples/simd_i8x16.py new file mode 100644 index 00000000..d319070a --- /dev/null +++ b/examples/simd_i8x16.py @@ -0,0 +1,30 @@ +""" +how to call v128 SMID operations +for more details see https://github.com/WebAssembly/simd/blob/main/proposals/simd/SIMD.md#integer-addition +""" +import ctypes + +from functools import partial +from wasmtime import Store, Module, Instance + + + +store = Store() +module = Module(store.engine, """ +(module + (func $add_v128 (param $a v128) (param $b v128) (result v128) + local.get $a + local.get $b + i8x16.add + ) + (export "add_v128" (func $add_v128)) +) +""") + +instance = Instance(store, module, []) +vector_type = ctypes.c_uint8*16 +add_v128 = partial(instance.exports(store)["add_v128"], store) +a=vector_type(*(i for i in range(16))) +b=vector_type(*(40+i for i in range(16))) +c=add_v128(a, b) +print([v for v in c]) diff --git a/wasmtime/_func.py b/wasmtime/_func.py index 0d4d1eec..55396758 100644 --- a/wasmtime/_func.py +++ b/wasmtime/_func.py @@ -7,12 +7,34 @@ from typing import Callable, Optional, Generic, TypeVar, List, Union, Tuple, cast as cast_type, Sequence from ._exportable import AsExtern from ._store import Storelike -from ._bindings import wasmtime_val_raw_t +from ._bindings import wasmtime_val_raw_t, wasm_valtype_kind +from ._ffi import ( + WASMTIME_I32, + WASMTIME_I64, + WASMTIME_F32, + WASMTIME_F64, + WASMTIME_V128, + WASMTIME_FUNCREF, + WASMTIME_EXTERNREF, +) + T = TypeVar('T') FUNCTIONS: "Slab[Tuple]" LAST_EXCEPTION: Optional[Exception] = None +val_id2attr = { + WASMTIME_I32.value: 'i32', + WASMTIME_I64.value: 'i64', + WASMTIME_F32.value: 'f32', + WASMTIME_F64.value: 'f64', + WASMTIME_V128.value: 'v128', + WASMTIME_FUNCREF.value: 'funcref', + WASMTIME_EXTERNREF.value: 'externref', +} + +def get_valtype_attr(ty): + return val_id2attr[wasm_valtype_kind(ty._ptr)] class Func: _func: ffi.wasmtime_func_t @@ -84,9 +106,9 @@ def _init_call(self, ty): ty_results = ty.results params_n = len(ty_params) results_n = len(ty_results) - self._params_str = (str(i) for i in ty_params) - self._results_str = (str(i) for i in ty_results) - self._results_str0 = str(ty_results[0]) if results_n else None + self._params_str = (get_valtype_attr(i) for i in ty_params) + self._results_str = (get_valtype_attr(i) for i in ty_results) + self._results_str0 = get_valtype_attr(ty_results[0]) if results_n else None self._params_n = params_n self._results_n = results_n n = max(params_n, results_n) From d0913e8e5ab2673f82724a6c01e7291379b7bf99 Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Mon, 27 Mar 2023 23:11:42 +0300 Subject: [PATCH 08/24] FIXES #137: make calling wasm from python 7x faster --- wasmtime/_func.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wasmtime/_func.py b/wasmtime/_func.py index 55396758..343e1ceb 100644 --- a/wasmtime/_func.py +++ b/wasmtime/_func.py @@ -1,7 +1,7 @@ from contextlib import contextmanager from ctypes import POINTER, byref, CFUNCTYPE, c_void_p, cast import ctypes -from wasmtime import Store, FuncType, Val, IntoVal, Trap, WasmtimeError +from wasmtime import Store, FuncType, Val, IntoVal, Trap, WasmtimeError, ValType from . import _ffi as ffi from ._extern import wrap_extern from typing import Callable, Optional, Generic, TypeVar, List, Union, Tuple, cast as cast_type, Sequence @@ -33,7 +33,7 @@ WASMTIME_EXTERNREF.value: 'externref', } -def get_valtype_attr(ty): +def get_valtype_attr(ty: ValType): return val_id2attr[wasm_valtype_kind(ty._ptr)] class Func: @@ -99,7 +99,7 @@ def _extract_return(self, vals_raw: ctypes.Array[wasmtime_val_raw_t]) -> Union[I # we can use tuple construct, but I'm using list for compatability return [getattr(val_raw, ret_str) for val_raw, ret_str in zip(vals_raw, self._results_str)] - def _init_call(self, ty): + def _init_call(self, ty: FuncType): """init signature properties used by call""" self._ty = ty ty_params = ty.params From d383a055454f644c7a4bfe809cfe8a1679750436 Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Tue, 28 Mar 2023 11:36:23 +0300 Subject: [PATCH 09/24] FIXES #137: make calling wasm from python 7x faster --- wasmtime/_func.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/wasmtime/_func.py b/wasmtime/_func.py index 343e1ceb..a3ceb871 100644 --- a/wasmtime/_func.py +++ b/wasmtime/_func.py @@ -16,6 +16,8 @@ WASMTIME_V128, WASMTIME_FUNCREF, WASMTIME_EXTERNREF, + WASM_ANYREF, + WASM_FUNCREF, ) @@ -31,11 +33,26 @@ WASMTIME_V128.value: 'v128', WASMTIME_FUNCREF.value: 'funcref', WASMTIME_EXTERNREF.value: 'externref', + WASM_FUNCREF.value: 'funcref', + WASM_ANYREF.value: 'externref', } def get_valtype_attr(ty: ValType): return val_id2attr[wasm_valtype_kind(ty._ptr)] +def val_setter(dst, attr, val): + if attr=='externref': + # TODO: handle None + v = Val.externref(val) + casted = ctypes.addressof(v._raw.of.externref) + elif isinstance(val, Func): + # TODO: handle null_funcref + # TODO: validate same val._func.store_id + casted = val._func.index + else: + casted = val + setattr(dst, attr, casted) + class Func: _func: ffi.wasmtime_func_t _ty: FuncType @@ -88,7 +105,7 @@ def type(self, store: Storelike) -> FuncType: def _create_raw_vals(self, *params: IntoVal) -> ctypes.Array[wasmtime_val_raw_t]: raw = self._vals_raw_type() for i, param_str in enumerate(self._params_str): - setattr(raw[i], param_str, params[i]) + val_setter(raw[i], param_str, params[i]) return raw def _extract_return(self, vals_raw: ctypes.Array[wasmtime_val_raw_t]) -> Union[IntoVal, Sequence[IntoVal], None]: From 383cad6698c59124a21261c15bd3444dff1d3b9b Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Mon, 3 Apr 2023 01:16:45 +0300 Subject: [PATCH 10/24] FIXES #137: make calling wasm from python 7x faster --- wasmtime/_func.py | 49 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/wasmtime/_func.py b/wasmtime/_func.py index a3ceb871..1a43d0b7 100644 --- a/wasmtime/_func.py +++ b/wasmtime/_func.py @@ -7,7 +7,8 @@ from typing import Callable, Optional, Generic, TypeVar, List, Union, Tuple, cast as cast_type, Sequence from ._exportable import AsExtern from ._store import Storelike -from ._bindings import wasmtime_val_raw_t, wasm_valtype_kind +from ._bindings import wasmtime_val_raw_t, wasm_valtype_kind, wasmtime_val_t, wasmtime_externref_t, wasmtime_func_t +from ._value import _unintern from ._ffi import ( WASMTIME_I32, WASMTIME_I64, @@ -18,6 +19,7 @@ WASMTIME_EXTERNREF, WASM_ANYREF, WASM_FUNCREF, + wasmtime_externref_data, ) @@ -40,11 +42,41 @@ def get_valtype_attr(ty: ValType): return val_id2attr[wasm_valtype_kind(ty._ptr)] +from struct import Struct + +def val_getter(store_id, val_raw, attr): + val = getattr(val_raw, attr) + + if attr=='externref': + ptr = ctypes.POINTER(wasmtime_externref_t) + if not val: return None + ffi = ptr.from_address(val) + if not ffi: return + extern_id = wasmtime_externref_data(ffi) + ret = _unintern(extern_id) + return ret + elif attr=='funcref': + if val==0: return None + f=wasmtime_func_t() + f.store_id=store_id + f.index=val + ret=Func._from_raw(f) + return ret + return val + def val_setter(dst, attr, val): if attr=='externref': - # TODO: handle None - v = Val.externref(val) - casted = ctypes.addressof(v._raw.of.externref) + if isinstance(val, Val) and val._raw.kind==WASMTIME_EXTERNREF.value: + if val._raw.of.externref: + extern_id = wasmtime_externref_data(val._raw.of.externref) + casted = ctypes.addressof(val._raw.of.externref) + else: + v = Val.externref(val) + casted = ctypes.addressof(v._raw.of.externref) + elif attr=='funcref': + if isinstance(val, Val) and val._raw.kind==WASMTIME_FUNCREF.value: + casted = val._raw.of.funcref.index + else: raise RuntimeError("foo") elif isinstance(val, Func): # TODO: handle null_funcref # TODO: validate same val._func.store_id @@ -112,9 +144,10 @@ def _extract_return(self, vals_raw: ctypes.Array[wasmtime_val_raw_t]) -> Union[I if self._results_n==0: return None if self._results_n==1: - return getattr(vals_raw[0], self._results_str0) + ret = val_getter(self._func.store_id, vals_raw[0], self._results_str0) + return ret # we can use tuple construct, but I'm using list for compatability - return [getattr(val_raw, ret_str) for val_raw, ret_str in zip(vals_raw, self._results_str)] + return [val_getter(self._func.store_id, val_raw, ret_str) for val_raw, ret_str in zip(vals_raw, self._results_str)] def _init_call(self, ty: FuncType): """init signature properties used by call""" @@ -123,8 +156,8 @@ def _init_call(self, ty: FuncType): ty_results = ty.results params_n = len(ty_params) results_n = len(ty_results) - self._params_str = (get_valtype_attr(i) for i in ty_params) - self._results_str = (get_valtype_attr(i) for i in ty_results) + self._params_str = [get_valtype_attr(i) for i in ty_params] + self._results_str = [get_valtype_attr(i) for i in ty_results] self._results_str0 = get_valtype_attr(ty_results[0]) if results_n else None self._params_n = params_n self._results_n = results_n From dc3d69704e019b3d2a04e497cf23dd9c69d96379 Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Mon, 3 Apr 2023 01:22:08 +0300 Subject: [PATCH 11/24] FIXES #137: make calling wasm from python 7x faster --- wasmtime/_func.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wasmtime/_func.py b/wasmtime/_func.py index 1a43d0b7..5a79c02d 100644 --- a/wasmtime/_func.py +++ b/wasmtime/_func.py @@ -82,7 +82,10 @@ def val_setter(dst, attr, val): # TODO: validate same val._func.store_id casted = val._func.index else: - casted = val + if isinstance(val, Val): + casted = getattr(val._raw.of, attr) + else: + casted = val setattr(dst, attr, casted) class Func: From 1446d7961f9aec10ad0d629eab71153c099f09cd Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Wed, 5 Apr 2023 01:56:37 +0300 Subject: [PATCH 12/24] FIXES #137: make calling wasm from python 7x faster --- examples/gcd_perf.py | 20 +++++++++++ wasmtime/_func.py | 82 +++----------------------------------------- wasmtime/_value.py | 53 ++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 78 deletions(-) create mode 100644 examples/gcd_perf.py diff --git a/examples/gcd_perf.py b/examples/gcd_perf.py new file mode 100644 index 00000000..32eaef48 --- /dev/null +++ b/examples/gcd_perf.py @@ -0,0 +1,20 @@ +import wasmtime.loader +import time +from math import gcd as math_gcd +from gcd import gcd_func as wasm_gcd +from gcd_alt import gcd as wasm_gcd_alt + +def python_gcd(x, y): + while y: + x, y = y, x % y + return abs(x) + +N = 1_000 +by_name = locals() +for name in 'math_gcd', 'python_gcd', 'wasm_gcd', 'wasm_gcd_alt': + gcdf = by_name[name] + start_time = time.perf_counter() + for _ in range(N): + g = gcdf(16516842, 154654684) + total_time = time.perf_counter() - start_time + print(total_time, "\t\t", name) diff --git a/wasmtime/_func.py b/wasmtime/_func.py index 5a79c02d..cbd6c525 100644 --- a/wasmtime/_func.py +++ b/wasmtime/_func.py @@ -1,92 +1,19 @@ from contextlib import contextmanager from ctypes import POINTER, byref, CFUNCTYPE, c_void_p, cast import ctypes -from wasmtime import Store, FuncType, Val, IntoVal, Trap, WasmtimeError, ValType +from wasmtime import Store, FuncType, Val, IntoVal, Trap, WasmtimeError from . import _ffi as ffi from ._extern import wrap_extern from typing import Callable, Optional, Generic, TypeVar, List, Union, Tuple, cast as cast_type, Sequence from ._exportable import AsExtern from ._store import Storelike -from ._bindings import wasmtime_val_raw_t, wasm_valtype_kind, wasmtime_val_t, wasmtime_externref_t, wasmtime_func_t -from ._value import _unintern -from ._ffi import ( - WASMTIME_I32, - WASMTIME_I64, - WASMTIME_F32, - WASMTIME_F64, - WASMTIME_V128, - WASMTIME_FUNCREF, - WASMTIME_EXTERNREF, - WASM_ANYREF, - WASM_FUNCREF, - wasmtime_externref_data, -) - +from ._bindings import wasmtime_val_raw_t +from ._value import get_valtype_attr, val_getter, val_setter T = TypeVar('T') FUNCTIONS: "Slab[Tuple]" LAST_EXCEPTION: Optional[Exception] = None -val_id2attr = { - WASMTIME_I32.value: 'i32', - WASMTIME_I64.value: 'i64', - WASMTIME_F32.value: 'f32', - WASMTIME_F64.value: 'f64', - WASMTIME_V128.value: 'v128', - WASMTIME_FUNCREF.value: 'funcref', - WASMTIME_EXTERNREF.value: 'externref', - WASM_FUNCREF.value: 'funcref', - WASM_ANYREF.value: 'externref', -} - -def get_valtype_attr(ty: ValType): - return val_id2attr[wasm_valtype_kind(ty._ptr)] - -from struct import Struct - -def val_getter(store_id, val_raw, attr): - val = getattr(val_raw, attr) - - if attr=='externref': - ptr = ctypes.POINTER(wasmtime_externref_t) - if not val: return None - ffi = ptr.from_address(val) - if not ffi: return - extern_id = wasmtime_externref_data(ffi) - ret = _unintern(extern_id) - return ret - elif attr=='funcref': - if val==0: return None - f=wasmtime_func_t() - f.store_id=store_id - f.index=val - ret=Func._from_raw(f) - return ret - return val - -def val_setter(dst, attr, val): - if attr=='externref': - if isinstance(val, Val) and val._raw.kind==WASMTIME_EXTERNREF.value: - if val._raw.of.externref: - extern_id = wasmtime_externref_data(val._raw.of.externref) - casted = ctypes.addressof(val._raw.of.externref) - else: - v = Val.externref(val) - casted = ctypes.addressof(v._raw.of.externref) - elif attr=='funcref': - if isinstance(val, Val) and val._raw.kind==WASMTIME_FUNCREF.value: - casted = val._raw.of.funcref.index - else: raise RuntimeError("foo") - elif isinstance(val, Func): - # TODO: handle null_funcref - # TODO: validate same val._func.store_id - casted = val._func.index - else: - if isinstance(val, Val): - casted = getattr(val._raw.of, attr) - else: - casted = val - setattr(dst, attr, casted) class Func: _func: ffi.wasmtime_func_t @@ -147,8 +74,7 @@ def _extract_return(self, vals_raw: ctypes.Array[wasmtime_val_raw_t]) -> Union[I if self._results_n==0: return None if self._results_n==1: - ret = val_getter(self._func.store_id, vals_raw[0], self._results_str0) - return ret + return val_getter(self._func.store_id, vals_raw[0], self._results_str0) # we can use tuple construct, but I'm using list for compatability return [val_getter(self._func.store_id, val_raw, ret_str) for val_raw, ret_str in zip(vals_raw, self._results_str)] diff --git a/wasmtime/_value.py b/wasmtime/_value.py index f30a7e3c..65fe71ce 100644 --- a/wasmtime/_value.py +++ b/wasmtime/_value.py @@ -4,6 +4,17 @@ import ctypes import typing +val_id2attr = { + WASMTIME_I32.value: 'i32', + WASMTIME_I64.value: 'i64', + WASMTIME_F32.value: 'f32', + WASMTIME_F64.value: 'f64', + WASMTIME_V128.value: 'v128', + WASMTIME_FUNCREF.value: 'funcref', + WASMTIME_EXTERNREF.value: 'externref', + WASM_FUNCREF.value: 'funcref', + WASM_ANYREF.value: 'externref', +} @ctypes.CFUNCTYPE(None, c_void_p) def _externref_finalizer(extern_id: int) -> None: @@ -24,6 +35,48 @@ def _intern(obj: typing.Any) -> c_void_p: def _unintern(val: int) -> typing.Any: return Val._id_to_extern.get(val) +def get_valtype_attr(ty: ValType) -> str: + return val_id2attr[wasm_valtype_kind(ty._ptr)] + +def val_getter(store_id: int, val_raw: wasmtime_val_raw_t, attr: str) -> typing.Union[int, float, "wasmtime.Func", typing.Any]: + val = getattr(val_raw, attr) + + if attr=='externref': + ptr = ctypes.POINTER(wasmtime_externref_t) + if not val: return None + ffi = ptr.from_address(val) + if not ffi: return None + extern_id = wasmtime_externref_data(ffi) + return _unintern(extern_id) + elif attr=='funcref': + if val==0: return None + f = wasmtime_func_t() + f.store_id = store_id + f.index = val + return wasmtime.Func._from_raw(f) + return val + +def val_setter(dst: wasmtime_val_raw_t, attr: str, val: "IntoVal"): + if attr=='externref': + if isinstance(val, Val) and val._raw.kind==WASMTIME_EXTERNREF.value: + casted = ctypes.addressof(val._raw.of.externref) + else: + casted = ctypes.addressof(Val.externref(val)._raw.of.externref) + elif attr=='funcref': + if isinstance(val, Val) and val._raw.kind==WASMTIME_FUNCREF.value: + casted = val._raw.of.funcref.index + elif isinstance(val, wasmtime.Func): + # TODO: validate same val._func.store_id + casted = val._func.index + else: + raise RuntimeError("foo") + else: + if isinstance(val, Val): + casted = getattr(val._raw.of, attr) + else: + casted = val + setattr(dst, attr, casted) + class Val: # We can't let the extern values we wrap `externref`s around be GC'd, so we From 1ca73e964c97a51328deb14279a2da10534dd311 Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Wed, 5 Apr 2023 02:16:34 +0300 Subject: [PATCH 13/24] FIXES #137: make calling wasm from python 7x faster --- examples/gcd.py | 5 +++-- examples/gcd_perf.py | 4 +++- examples/simd_i8x16.py | 19 +++++++++++-------- tests/test_func.py | 27 +++++++++++++++++++++++++++ wasmtime/_func.py | 9 ++++----- wasmtime/_value.py | 27 +++++++++++++++++---------- 6 files changed, 65 insertions(+), 26 deletions(-) diff --git a/examples/gcd.py b/examples/gcd.py index 86582567..36f87429 100644 --- a/examples/gcd.py +++ b/examples/gcd.py @@ -1,10 +1,11 @@ # Example of instantiating a wasm module and calling an export on it from wasmtime import Store, Module, Instance - +from functools import partial store = Store() module = Module.from_file(store.engine, './examples/gcd.wat') instance = Instance(store, module, []) gcd = instance.exports(store)["gcd"] - +gcd_func = partial(gcd, store) print("gcd(6, 27) = %d" % gcd(store, 6, 27)) +print("gcd(6, 27) = %d" % gcd_func(6, 27)) diff --git a/examples/gcd_perf.py b/examples/gcd_perf.py index 32eaef48..eeb82ed0 100644 --- a/examples/gcd_perf.py +++ b/examples/gcd_perf.py @@ -4,14 +4,16 @@ from gcd import gcd_func as wasm_gcd from gcd_alt import gcd as wasm_gcd_alt + def python_gcd(x, y): while y: x, y = y, x % y return abs(x) + N = 1_000 by_name = locals() -for name in 'math_gcd', 'python_gcd', 'wasm_gcd', 'wasm_gcd_alt': +for name in "math_gcd", "python_gcd", "wasm_gcd", "wasm_gcd_alt": gcdf = by_name[name] start_time = time.perf_counter() for _ in range(N): diff --git a/examples/simd_i8x16.py b/examples/simd_i8x16.py index d319070a..14aa8db0 100644 --- a/examples/simd_i8x16.py +++ b/examples/simd_i8x16.py @@ -1,5 +1,5 @@ """ -how to call v128 SMID operations +how to call v128 SIMD operations for more details see https://github.com/WebAssembly/simd/blob/main/proposals/simd/SIMD.md#integer-addition """ import ctypes @@ -8,9 +8,10 @@ from wasmtime import Store, Module, Instance - store = Store() -module = Module(store.engine, """ +module = Module( + store.engine, + """ (module (func $add_v128 (param $a v128) (param $b v128) (result v128) local.get $a @@ -19,12 +20,14 @@ ) (export "add_v128" (func $add_v128)) ) -""") +""", +) instance = Instance(store, module, []) -vector_type = ctypes.c_uint8*16 +vector_type = ctypes.c_uint8 * 16 add_v128 = partial(instance.exports(store)["add_v128"], store) -a=vector_type(*(i for i in range(16))) -b=vector_type(*(40+i for i in range(16))) -c=add_v128(a, b) +a = vector_type(*(i for i in range(16))) +b = vector_type(*(40 + i for i in range(16))) +c = add_v128(a, b) print([v for v in c]) +print([v for v in c] == [i + j for i, j in zip(a, b)]) diff --git a/tests/test_func.py b/tests/test_func.py index 00be6c3f..22bedc5f 100644 --- a/tests/test_func.py +++ b/tests/test_func.py @@ -1,5 +1,7 @@ import unittest +import ctypes +from functools import partial from wasmtime import * @@ -17,6 +19,31 @@ def test_add(self): func = Func(store, ty, lambda a, b: a + b) self.assertEqual(func(store, 1, 2), 3) + def test_simd_i8x16_add(self): + # i8x16.add is SIMD 128-bit vector of i8 items of size 16 + store = Store() + module = Module( + store.engine, + """ + (module + (func $add_v128 (param $a v128) (param $b v128) (result v128) + local.get $a + local.get $b + i8x16.add + ) + (export "add_v128" (func $add_v128)) + ) + """, + ) + + instance = Instance(store, module, []) + vector_type = ctypes.c_uint8 * 16 + add_v128 = partial(instance.exports(store)["add_v128"], store) + a = vector_type(*(i for i in range(16))) + b = vector_type(*(40 + i for i in range(16))) + c = add_v128(a, b) + self.assertEqual([v for v in c], [i + j for i, j in zip(a, b)]) + def test_calls(self): store = Store() ty = FuncType([ValType.i32()], []) diff --git a/wasmtime/_func.py b/wasmtime/_func.py index cbd6c525..cdf266f6 100644 --- a/wasmtime/_func.py +++ b/wasmtime/_func.py @@ -69,11 +69,11 @@ def _create_raw_vals(self, *params: IntoVal) -> ctypes.Array[wasmtime_val_raw_t] for i, param_str in enumerate(self._params_str): val_setter(raw[i], param_str, params[i]) return raw - + def _extract_return(self, vals_raw: ctypes.Array[wasmtime_val_raw_t]) -> Union[IntoVal, Sequence[IntoVal], None]: - if self._results_n==0: + if self._results_n == 0: return None - if self._results_n==1: + if self._results_n == 1: return val_getter(self._func.store_id, vals_raw[0], self._results_str0) # we can use tuple construct, but I'm using list for compatability return [val_getter(self._func.store_id, val_raw, ret_str) for val_raw, ret_str in zip(vals_raw, self._results_str)] @@ -91,8 +91,7 @@ def _init_call(self, ty: FuncType): self._params_n = params_n self._results_n = results_n n = max(params_n, results_n) - self._vals_raw_type = wasmtime_val_raw_t*n - + self._vals_raw_type = wasmtime_val_raw_t * n def __call__(self, store: Storelike, *params: IntoVal) -> Union[IntoVal, Sequence[IntoVal], None]: """ diff --git a/wasmtime/_value.py b/wasmtime/_value.py index 65fe71ce..31a12af7 100644 --- a/wasmtime/_value.py +++ b/wasmtime/_value.py @@ -16,6 +16,7 @@ WASM_ANYREF.value: 'externref', } + @ctypes.CFUNCTYPE(None, c_void_p) def _externref_finalizer(extern_id: int) -> None: Val._id_to_ref_count[extern_id] -= 1 @@ -35,35 +36,41 @@ def _intern(obj: typing.Any) -> c_void_p: def _unintern(val: int) -> typing.Any: return Val._id_to_extern.get(val) + def get_valtype_attr(ty: ValType) -> str: return val_id2attr[wasm_valtype_kind(ty._ptr)] + def val_getter(store_id: int, val_raw: wasmtime_val_raw_t, attr: str) -> typing.Union[int, float, "wasmtime.Func", typing.Any]: val = getattr(val_raw, attr) - - if attr=='externref': + + if attr == 'externref': ptr = ctypes.POINTER(wasmtime_externref_t) - if not val: return None + if not val: + return None ffi = ptr.from_address(val) - if not ffi: return None + if not ffi: + return None extern_id = wasmtime_externref_data(ffi) return _unintern(extern_id) - elif attr=='funcref': - if val==0: return None + elif attr == 'funcref': + if val == 0: + return None f = wasmtime_func_t() f.store_id = store_id f.index = val return wasmtime.Func._from_raw(f) return val + def val_setter(dst: wasmtime_val_raw_t, attr: str, val: "IntoVal"): - if attr=='externref': - if isinstance(val, Val) and val._raw.kind==WASMTIME_EXTERNREF.value: + if attr == 'externref': + if isinstance(val, Val) and val._raw.kind == WASMTIME_EXTERNREF.value: casted = ctypes.addressof(val._raw.of.externref) else: casted = ctypes.addressof(Val.externref(val)._raw.of.externref) - elif attr=='funcref': - if isinstance(val, Val) and val._raw.kind==WASMTIME_FUNCREF.value: + elif attr == 'funcref': + if isinstance(val, Val) and val._raw.kind == WASMTIME_FUNCREF.value: casted = val._raw.of.funcref.index elif isinstance(val, wasmtime.Func): # TODO: validate same val._func.store_id From e6c054b02b345231b3d0108437c06d63bd823914 Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Wed, 5 Apr 2023 02:18:51 +0300 Subject: [PATCH 14/24] FIXES #137: make calling wasm from python 7x faster --- examples/gcd_perf.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/gcd_perf.py b/examples/gcd_perf.py index eeb82ed0..f7b09879 100644 --- a/examples/gcd_perf.py +++ b/examples/gcd_perf.py @@ -2,8 +2,6 @@ import time from math import gcd as math_gcd from gcd import gcd_func as wasm_gcd -from gcd_alt import gcd as wasm_gcd_alt - def python_gcd(x, y): while y: @@ -13,7 +11,7 @@ def python_gcd(x, y): N = 1_000 by_name = locals() -for name in "math_gcd", "python_gcd", "wasm_gcd", "wasm_gcd_alt": +for name in "math_gcd", "python_gcd", "wasm_gcd": gcdf = by_name[name] start_time = time.perf_counter() for _ in range(N): From 8bc29e6be989863a2088b0b642473caa50a8333e Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Wed, 5 Apr 2023 02:24:07 +0300 Subject: [PATCH 15/24] FIXES #137: make calling wasm from python 7x faster --- examples/simd_i8x16.py | 5 +++-- tests/test_func.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/simd_i8x16.py b/examples/simd_i8x16.py index 14aa8db0..2b49eb90 100644 --- a/examples/simd_i8x16.py +++ b/examples/simd_i8x16.py @@ -5,7 +5,7 @@ import ctypes from functools import partial -from wasmtime import Store, Module, Instance +from wasmtime import Store, Module, Instance, Func store = Store() @@ -25,7 +25,8 @@ instance = Instance(store, module, []) vector_type = ctypes.c_uint8 * 16 -add_v128 = partial(instance.exports(store)["add_v128"], store) +add_v128_f: Func = instance.exports(store)["add_v128"] +add_v128 = partial(add_v128_f, store) a = vector_type(*(i for i in range(16))) b = vector_type(*(40 + i for i in range(16))) c = add_v128(a, b) diff --git a/tests/test_func.py b/tests/test_func.py index 22bedc5f..23364453 100644 --- a/tests/test_func.py +++ b/tests/test_func.py @@ -38,7 +38,8 @@ def test_simd_i8x16_add(self): instance = Instance(store, module, []) vector_type = ctypes.c_uint8 * 16 - add_v128 = partial(instance.exports(store)["add_v128"], store) + add_v128_f: Func = instance.exports(store)["add_v128"] + add_v128 = partial(add_v128_f, store) a = vector_type(*(i for i in range(16))) b = vector_type(*(40 + i for i in range(16))) c = add_v128(a, b) From 37b6067c6e6ded8968963787d301184b9573f137 Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Wed, 5 Apr 2023 02:59:09 +0300 Subject: [PATCH 16/24] FIXES #137: make calling wasm from python 7x faster --- pytest.ini | 2 +- wasmtime/_bindings.py | 2 +- wasmtime/_func.py | 6 +++--- wasmtime/_types.py | 2 +- wasmtime/_value.py | 18 +++++++++++++----- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/pytest.ini b/pytest.ini index 87d07bf8..df3eb518 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,2 @@ [pytest] -addopts = --doctest-modules --flake8 --mypy +addopts = --doctest-modules diff --git a/wasmtime/_bindings.py b/wasmtime/_bindings.py index 537f1120..c529d66a 100644 --- a/wasmtime/_bindings.py +++ b/wasmtime/_bindings.py @@ -175,7 +175,7 @@ def wasm_valtype_new(arg0: Any) -> ctypes._Pointer: _wasm_valtype_kind = dll.wasm_valtype_kind _wasm_valtype_kind.restype = wasm_valkind_t _wasm_valtype_kind.argtypes = [POINTER(wasm_valtype_t)] -def wasm_valtype_kind(arg0: Any) -> wasm_valkind_t: +def wasm_valtype_kind(arg0: Any) -> int: return _wasm_valtype_kind(arg0) # type: ignore class wasm_functype_t(Structure): diff --git a/wasmtime/_func.py b/wasmtime/_func.py index cdf266f6..90dd403e 100644 --- a/wasmtime/_func.py +++ b/wasmtime/_func.py @@ -23,7 +23,7 @@ class Func: _params_str: list[str] _results_str: list[str] _results_str0: str - _vals_raw_type: ctypes.Array[wasmtime_val_raw_t] + _vals_raw_type: type[ctypes.Array[wasmtime_val_raw_t]] def __init__(self, store: Storelike, ty: FuncType, func: Callable, access_caller: bool = False): """ @@ -78,7 +78,7 @@ def _extract_return(self, vals_raw: ctypes.Array[wasmtime_val_raw_t]) -> Union[I # we can use tuple construct, but I'm using list for compatability return [val_getter(self._func.store_id, val_raw, ret_str) for val_raw, ret_str in zip(vals_raw, self._results_str)] - def _init_call(self, ty: FuncType): + def _init_call(self, ty: FuncType) -> None: """init signature properties used by call""" self._ty = ty ty_params = ty.params @@ -87,7 +87,7 @@ def _init_call(self, ty: FuncType): results_n = len(ty_results) self._params_str = [get_valtype_attr(i) for i in ty_params] self._results_str = [get_valtype_attr(i) for i in ty_results] - self._results_str0 = get_valtype_attr(ty_results[0]) if results_n else None + self._results_str0 = get_valtype_attr(ty_results[0]) if results_n else 'i32' self._params_n = params_n self._results_n = results_n n = max(params_n, results_n) diff --git a/wasmtime/_types.py b/wasmtime/_types.py index cd81556e..e2d1d82c 100644 --- a/wasmtime/_types.py +++ b/wasmtime/_types.py @@ -81,7 +81,7 @@ def __str__(self) -> str: return 'anyref' if kind == ffi.WASM_FUNCREF.value: return 'funcref' - return 'ValType(%d)' % kind.value + return 'ValType(%d)' % kind def __del__(self) -> None: if not hasattr(self, '_owner') or not hasattr(self, '_ptr'): diff --git a/wasmtime/_value.py b/wasmtime/_value.py index 31a12af7..5ac78e3b 100644 --- a/wasmtime/_value.py +++ b/wasmtime/_value.py @@ -63,14 +63,19 @@ def val_getter(store_id: int, val_raw: wasmtime_val_raw_t, attr: str) -> typing. return val -def val_setter(dst: wasmtime_val_raw_t, attr: str, val: "IntoVal"): +def val_setter(dst: wasmtime_val_raw_t, attr: str, val: "IntoVal") -> None: + casted: typing.Union[Any, int, float, None, wasmtime.Func] if attr == 'externref': - if isinstance(val, Val) and val._raw.kind == WASMTIME_EXTERNREF.value: + if isinstance(val, Val) and val._raw and val._raw.kind == WASMTIME_EXTERNREF.value: casted = ctypes.addressof(val._raw.of.externref) else: - casted = ctypes.addressof(Val.externref(val)._raw.of.externref) + ex = Val.externref(val)._raw + if ex: + casted = ctypes.addressof(ex.of.externref) + else: + casted = 0 elif attr == 'funcref': - if isinstance(val, Val) and val._raw.kind == WASMTIME_FUNCREF.value: + if isinstance(val, Val) and val._raw and val._raw.kind == WASMTIME_FUNCREF.value: casted = val._raw.of.funcref.index elif isinstance(val, wasmtime.Func): # TODO: validate same val._func.store_id @@ -79,7 +84,10 @@ def val_setter(dst: wasmtime_val_raw_t, attr: str, val: "IntoVal"): raise RuntimeError("foo") else: if isinstance(val, Val): - casted = getattr(val._raw.of, attr) + if val._raw: + casted = getattr(val._raw.of, attr) + else: + casted = 0 else: casted = val setattr(dst, attr, casted) From 2b1d2c0b153a9c75f6f06afda3b3336def17697c Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Wed, 5 Apr 2023 03:04:37 +0300 Subject: [PATCH 17/24] FIXES #137: make calling wasm from python 7x faster --- pytest.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index df3eb518..33e9aeaa 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,3 @@ [pytest] -addopts = --doctest-modules +addopts = --doctest-modules --flake8 --mypy + From 8417fba7c386aa53fbafacc363aac92f047a042b Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Wed, 5 Apr 2023 03:06:54 +0300 Subject: [PATCH 18/24] FIXES #137: make calling wasm from python 7x faster --- wasmtime/_bindings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasmtime/_bindings.py b/wasmtime/_bindings.py index c529d66a..537f1120 100644 --- a/wasmtime/_bindings.py +++ b/wasmtime/_bindings.py @@ -175,7 +175,7 @@ def wasm_valtype_new(arg0: Any) -> ctypes._Pointer: _wasm_valtype_kind = dll.wasm_valtype_kind _wasm_valtype_kind.restype = wasm_valkind_t _wasm_valtype_kind.argtypes = [POINTER(wasm_valtype_t)] -def wasm_valtype_kind(arg0: Any) -> int: +def wasm_valtype_kind(arg0: Any) -> wasm_valkind_t: return _wasm_valtype_kind(arg0) # type: ignore class wasm_functype_t(Structure): From 5bb5c3902ed904a0ea0b971f2e83d4d52b7d3d43 Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Wed, 5 Apr 2023 03:14:59 +0300 Subject: [PATCH 19/24] FIXES #137: make calling wasm from python 7x faster --- examples/gcd_perf.py | 7 ++++++- examples/simd_i8x16.py | 4 +++- tests/test_func.py | 4 +++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/examples/gcd_perf.py b/examples/gcd_perf.py index f7b09879..816f4660 100644 --- a/examples/gcd_perf.py +++ b/examples/gcd_perf.py @@ -1,14 +1,19 @@ -import wasmtime.loader import time from math import gcd as math_gcd from gcd import gcd_func as wasm_gcd + def python_gcd(x, y): while y: x, y = y, x % y return abs(x) +a = 16516842 +b = 154654684 + +print(math_gcd(a, b), python_gcd(a, b), wasm_gcd(a, b)) + N = 1_000 by_name = locals() for name in "math_gcd", "python_gcd", "wasm_gcd": diff --git a/examples/simd_i8x16.py b/examples/simd_i8x16.py index 2b49eb90..407836b5 100644 --- a/examples/simd_i8x16.py +++ b/examples/simd_i8x16.py @@ -25,7 +25,9 @@ instance = Instance(store, module, []) vector_type = ctypes.c_uint8 * 16 -add_v128_f: Func = instance.exports(store)["add_v128"] +add_v128_f = instance.exports(store)["add_v128"] +if not isinstance(add_v128_f, Func): + raise TypeError("expecting Func") add_v128 = partial(add_v128_f, store) a = vector_type(*(i for i in range(16))) b = vector_type(*(40 + i for i in range(16))) diff --git a/tests/test_func.py b/tests/test_func.py index 23364453..478c68db 100644 --- a/tests/test_func.py +++ b/tests/test_func.py @@ -38,7 +38,9 @@ def test_simd_i8x16_add(self): instance = Instance(store, module, []) vector_type = ctypes.c_uint8 * 16 - add_v128_f: Func = instance.exports(store)["add_v128"] + add_v128_f = instance.exports(store)["add_v128"] + if not isinstance(add_v128_f, Func): + raise TypeError("expecting Func") add_v128 = partial(add_v128_f, store) a = vector_type(*(i for i in range(16))) b = vector_type(*(40 + i for i in range(16))) From a67ecc0f55e48a427ccf0d38283104f6535de3e9 Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Wed, 5 Apr 2023 03:39:53 +0300 Subject: [PATCH 20/24] FIXES #137: make calling wasm from python 7x faster --- tests/test_func.py | 2 +- wasmtime/_types.py | 2 +- wasmtime/_value.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_func.py b/tests/test_func.py index 478c68db..799a6246 100644 --- a/tests/test_func.py +++ b/tests/test_func.py @@ -44,7 +44,7 @@ def test_simd_i8x16_add(self): add_v128 = partial(add_v128_f, store) a = vector_type(*(i for i in range(16))) b = vector_type(*(40 + i for i in range(16))) - c = add_v128(a, b) + c: list[int] = add_v128(a, b) # type: ignore self.assertEqual([v for v in c], [i + j for i, j in zip(a, b)]) def test_calls(self): diff --git a/wasmtime/_types.py b/wasmtime/_types.py index e2d1d82c..82de44d0 100644 --- a/wasmtime/_types.py +++ b/wasmtime/_types.py @@ -81,7 +81,7 @@ def __str__(self) -> str: return 'anyref' if kind == ffi.WASM_FUNCREF.value: return 'funcref' - return 'ValType(%d)' % kind + return 'ValType(%d)' % kind # type: ignore def __del__(self) -> None: if not hasattr(self, '_owner') or not hasattr(self, '_ptr'): diff --git a/wasmtime/_value.py b/wasmtime/_value.py index 5ac78e3b..7fc3e7d3 100644 --- a/wasmtime/_value.py +++ b/wasmtime/_value.py @@ -38,7 +38,7 @@ def _unintern(val: int) -> typing.Any: def get_valtype_attr(ty: ValType) -> str: - return val_id2attr[wasm_valtype_kind(ty._ptr)] + return val_id2attr[wasm_valtype_kind(ty._ptr)] # type: ignore def val_getter(store_id: int, val_raw: wasmtime_val_raw_t, attr: str) -> typing.Union[int, float, "wasmtime.Func", typing.Any]: From 0388b165799c6354f78dc6c8c581fc096302ecac Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Wed, 5 Apr 2023 03:44:11 +0300 Subject: [PATCH 21/24] FIXES #137: make calling wasm from python 7x faster --- examples/simd_i8x16.py | 2 +- tests/test_func.py | 2 +- wasmtime/_types.py | 2 +- wasmtime/_value.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/simd_i8x16.py b/examples/simd_i8x16.py index 407836b5..ae0fef3a 100644 --- a/examples/simd_i8x16.py +++ b/examples/simd_i8x16.py @@ -31,6 +31,6 @@ add_v128 = partial(add_v128_f, store) a = vector_type(*(i for i in range(16))) b = vector_type(*(40 + i for i in range(16))) -c = add_v128(a, b) +c: list[int] = add_v128(a, b) # type: ignore print([v for v in c]) print([v for v in c] == [i + j for i, j in zip(a, b)]) diff --git a/tests/test_func.py b/tests/test_func.py index 799a6246..2d8e4160 100644 --- a/tests/test_func.py +++ b/tests/test_func.py @@ -44,7 +44,7 @@ def test_simd_i8x16_add(self): add_v128 = partial(add_v128_f, store) a = vector_type(*(i for i in range(16))) b = vector_type(*(40 + i for i in range(16))) - c: list[int] = add_v128(a, b) # type: ignore + c: list[int] = add_v128(a, b) # type: ignore self.assertEqual([v for v in c], [i + j for i, j in zip(a, b)]) def test_calls(self): diff --git a/wasmtime/_types.py b/wasmtime/_types.py index 82de44d0..b73b662a 100644 --- a/wasmtime/_types.py +++ b/wasmtime/_types.py @@ -81,7 +81,7 @@ def __str__(self) -> str: return 'anyref' if kind == ffi.WASM_FUNCREF.value: return 'funcref' - return 'ValType(%d)' % kind # type: ignore + return 'ValType(%d)' % kind # type: ignore def __del__(self) -> None: if not hasattr(self, '_owner') or not hasattr(self, '_ptr'): diff --git a/wasmtime/_value.py b/wasmtime/_value.py index 7fc3e7d3..703ba0c8 100644 --- a/wasmtime/_value.py +++ b/wasmtime/_value.py @@ -38,7 +38,7 @@ def _unintern(val: int) -> typing.Any: def get_valtype_attr(ty: ValType) -> str: - return val_id2attr[wasm_valtype_kind(ty._ptr)] # type: ignore + return val_id2attr[wasm_valtype_kind(ty._ptr)] # type: ignore def val_getter(store_id: int, val_raw: wasmtime_val_raw_t, attr: str) -> typing.Union[int, float, "wasmtime.Func", typing.Any]: From 4cd48d3930c628b39f627deb843568db7f49a73b Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Wed, 5 Apr 2023 03:52:33 +0300 Subject: [PATCH 22/24] FIXES #137: make calling wasm from python 7x faster --- wasmtime/_value.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasmtime/_value.py b/wasmtime/_value.py index 703ba0c8..e4f8848a 100644 --- a/wasmtime/_value.py +++ b/wasmtime/_value.py @@ -81,7 +81,7 @@ def val_setter(dst: wasmtime_val_raw_t, attr: str, val: "IntoVal") -> None: # TODO: validate same val._func.store_id casted = val._func.index else: - raise RuntimeError("foo") + raise RuntimeError("expecting param of type funcref got " + type(val).__name__) else: if isinstance(val, Val): if val._raw: From 9285e6cfa27e0bcc423b0b97bddc39d621345c4a Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Wed, 5 Apr 2023 03:54:49 +0300 Subject: [PATCH 23/24] FIXES #137: make calling wasm from python 7x faster --- wasmtime/_func.py | 2 +- wasmtime/_value.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/wasmtime/_func.py b/wasmtime/_func.py index 90dd403e..b86d785d 100644 --- a/wasmtime/_func.py +++ b/wasmtime/_func.py @@ -67,7 +67,7 @@ def type(self, store: Storelike) -> FuncType: def _create_raw_vals(self, *params: IntoVal) -> ctypes.Array[wasmtime_val_raw_t]: raw = self._vals_raw_type() for i, param_str in enumerate(self._params_str): - val_setter(raw[i], param_str, params[i]) + val_setter(self._func.store_id, raw[i], param_str, params[i]) return raw def _extract_return(self, vals_raw: ctypes.Array[wasmtime_val_raw_t]) -> Union[IntoVal, Sequence[IntoVal], None]: diff --git a/wasmtime/_value.py b/wasmtime/_value.py index e4f8848a..9071df56 100644 --- a/wasmtime/_value.py +++ b/wasmtime/_value.py @@ -63,7 +63,7 @@ def val_getter(store_id: int, val_raw: wasmtime_val_raw_t, attr: str) -> typing. return val -def val_setter(dst: wasmtime_val_raw_t, attr: str, val: "IntoVal") -> None: +def val_setter(store_id: int, dst: wasmtime_val_raw_t, attr: str, val: "IntoVal") -> None: casted: typing.Union[Any, int, float, None, wasmtime.Func] if attr == 'externref': if isinstance(val, Val) and val._raw and val._raw.kind == WASMTIME_EXTERNREF.value: @@ -78,7 +78,8 @@ def val_setter(dst: wasmtime_val_raw_t, attr: str, val: "IntoVal") -> None: if isinstance(val, Val) and val._raw and val._raw.kind == WASMTIME_FUNCREF.value: casted = val._raw.of.funcref.index elif isinstance(val, wasmtime.Func): - # TODO: validate same val._func.store_id + if val._func.store_id != store_id: + raise TypeError("passed funcref does not belong to same store") casted = val._func.index else: raise RuntimeError("expecting param of type funcref got " + type(val).__name__) From 818bb8dcf0e4f732f00b6ce510c403df37386c3f Mon Sep 17 00:00:00 2001 From: Muayyad alsadi Date: Wed, 5 Apr 2023 12:36:57 +0300 Subject: [PATCH 24/24] FIXES #137: make calling wasm from python 7x faster --- examples/gcd.py | 2 ++ examples/gcd_perf.py | 8 ++--- wasmtime/_func.py | 69 +++++++++++++++++++++++++++++++++++++------- wasmtime/_value.py | 4 +-- 4 files changed, 67 insertions(+), 16 deletions(-) diff --git a/examples/gcd.py b/examples/gcd.py index 36f87429..1db8db52 100644 --- a/examples/gcd.py +++ b/examples/gcd.py @@ -7,5 +7,7 @@ instance = Instance(store, module, []) gcd = instance.exports(store)["gcd"] gcd_func = partial(gcd, store) +gcd_func_val = partial(gcd._call_val, store) print("gcd(6, 27) = %d" % gcd(store, 6, 27)) print("gcd(6, 27) = %d" % gcd_func(6, 27)) +print("gcd(6, 27) = %d" % gcd_func_val(6, 27)) diff --git a/examples/gcd_perf.py b/examples/gcd_perf.py index 816f4660..8b4ee9f6 100644 --- a/examples/gcd_perf.py +++ b/examples/gcd_perf.py @@ -1,6 +1,6 @@ import time from math import gcd as math_gcd -from gcd import gcd_func as wasm_gcd +from gcd import gcd_func as wasm_gcd, gcd_func_val as wasm_gcd_old def python_gcd(x, y): @@ -12,14 +12,14 @@ def python_gcd(x, y): a = 16516842 b = 154654684 -print(math_gcd(a, b), python_gcd(a, b), wasm_gcd(a, b)) +print(math_gcd(a, b), python_gcd(a, b), wasm_gcd(a, b), wasm_gcd_old(a, b)) N = 1_000 by_name = locals() -for name in "math_gcd", "python_gcd", "wasm_gcd": +for name in "math_gcd", "python_gcd", "wasm_gcd", "wasm_gcd_old": gcdf = by_name[name] start_time = time.perf_counter() for _ in range(N): - g = gcdf(16516842, 154654684) + g = gcdf(a, b) total_time = time.perf_counter() - start_time print(total_time, "\t\t", name) diff --git a/wasmtime/_func.py b/wasmtime/_func.py index b86d785d..a2fd99ad 100644 --- a/wasmtime/_func.py +++ b/wasmtime/_func.py @@ -93,19 +93,52 @@ def _init_call(self, ty: FuncType) -> None: n = max(params_n, results_n) self._vals_raw_type = wasmtime_val_raw_t * n - def __call__(self, store: Storelike, *params: IntoVal) -> Union[IntoVal, Sequence[IntoVal], None]: + def _call_val(self, store: Storelike, *params: IntoVal) -> Union[IntoVal, Sequence[IntoVal], None]: """ - Calls this function with the given parameters + internal implementation of calling a function that uses `wasmtime_func_call` + """ + ty = self.type(store) + param_tys = ty.params + if len(params) > len(param_tys): + raise WasmtimeError("too many parameters provided: given %s, expected %s" % + (len(params), len(param_tys))) + if len(params) < len(param_tys): + raise WasmtimeError("too few parameters provided: given %s, expected %s" % + (len(params), len(param_tys))) - Parameters can either be a `Val` or a native python value which can be - converted to a `Val` of the corresponding correct type + param_vals = [Val._convert(ty, params[i]) for i, ty in enumerate(param_tys)] + params_ptr = (ffi.wasmtime_val_t * len(params))() + for i, val in enumerate(param_vals): + params_ptr[i] = val._unwrap_raw() - Returns `None` if this func has 0 return types - Returns a single value if the func has 1 return type - Returns a list if the func has more than 1 return type + result_tys = ty.results + results_ptr = (ffi.wasmtime_val_t * len(result_tys))() - Note that you can also use the `__call__` method and invoke a `Func` as - if it were a function directly. + with enter_wasm(store) as trap: + error = ffi.wasmtime_func_call( + store._context, + byref(self._func), + params_ptr, + len(params), + results_ptr, + len(result_tys), + trap) + if error: + raise WasmtimeError._from_ptr(error) + + results = [] + for i in range(0, len(result_tys)): + results.append(Val(results_ptr[i]).value) + if len(results) == 0: + return None + elif len(results) == 1: + return results[0] + else: + return results + + def _call_raw(self, store: Storelike, *params: IntoVal) -> Union[IntoVal, Sequence[IntoVal], None]: + """ + internal implementation of calling a function that uses `wasmtime_func_call_unchecked` """ if getattr(self, "_ty", None) is None: self._init_call(self.type(store)) @@ -122,7 +155,7 @@ def __call__(self, store: Storelike, *params: IntoVal) -> Union[IntoVal, Sequenc # it's safe to call wasmtime_func_call_unchecked because # - we allocate enough space to hold all the parameters and all the results # - we set proper types by reading types from ty - # - but not sure about "Values such as externref and funcref are valid within the store being called" + # - externref and funcref are valid within the store being called with enter_wasm(store) as trap: error = ffi.wasmtime_func_call_unchecked( store._context, @@ -133,6 +166,22 @@ def __call__(self, store: Storelike, *params: IntoVal) -> Union[IntoVal, Sequenc raise WasmtimeError._from_ptr(error) return self._extract_return(vals_raw) + def __call__(self, store: Storelike, *params: IntoVal) -> Union[IntoVal, Sequence[IntoVal], None]: + """ + Calls this function with the given parameters + + Parameters can either be a `Val` or a native python value which can be + converted to a `Val` of the corresponding correct type + + Returns `None` if this func has 0 return types + Returns a single value if the func has 1 return type + Returns a list if the func has more than 1 return type + + Note that you can also use the `__call__` method and invoke a `Func` as + if it were a function directly. + """ + return self._call_raw(store, *params) + def _as_extern(self) -> ffi.wasmtime_extern_t: union = ffi.wasmtime_extern_union(func=self._func) return ffi.wasmtime_extern_t(ffi.WASMTIME_EXTERN_FUNC, union) diff --git a/wasmtime/_value.py b/wasmtime/_value.py index 9071df56..8013019b 100644 --- a/wasmtime/_value.py +++ b/wasmtime/_value.py @@ -79,10 +79,10 @@ def val_setter(store_id: int, dst: wasmtime_val_raw_t, attr: str, val: "IntoVal" casted = val._raw.of.funcref.index elif isinstance(val, wasmtime.Func): if val._func.store_id != store_id: - raise TypeError("passed funcref does not belong to same store") + raise WasmtimeError("passed funcref does not belong to same store") casted = val._func.index else: - raise RuntimeError("expecting param of type funcref got " + type(val).__name__) + raise WasmtimeError("expecting param of type funcref got " + type(val).__name__) else: if isinstance(val, Val): if val._raw: