-
Notifications
You must be signed in to change notification settings - Fork 60
FIXES #137: make calling wasm from python 7x faster #139
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
c724ce1
d0dcca2
52a0f1d
da545aa
68fa2a8
f00d32a
7bee159
d0913e8
d383a05
383cad6
dc3d697
1446d79
1ca73e9
e6c054b
8bc29e6
37b6067
2b1d2c0
8417fba
5bb5c39
a67ecc0
0388b16
4cd48d3
9285e6c
818bb8d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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]" | ||
|
@@ -16,6 +16,13 @@ | |
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this would be best stored as |
||
_vals_raw_type: ctypes.Array[wasmtime_val_raw_t] | ||
|
||
def __init__(self, store: Storelike, ty: FuncType, func: Callable, access_caller: bool = False): | ||
""" | ||
|
@@ -27,11 +34,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._init_call(ty) | ||
idx = FUNCTIONS.allocate((func, ty.results, access_caller)) | ||
_func = ffi.wasmtime_func_t() | ||
ffi.wasmtime_func_new( | ||
|
@@ -56,6 +63,36 @@ def type(self, store: Storelike) -> FuncType: | |
ptr = ffi.wasmtime_func_type(store._context, byref(self._func)) | ||
return FuncType._from_ptr(ptr, None) | ||
|
||
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]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that this conversion is quite correct. The Additionally it's not safe to simply set the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For an example you can see the logic in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have no idea about externref and funcref the code related to v128 says it's not implemented elif kind == WASMTIME_V128.value:
raise Exception("unimplemented v128 type") but no problem I've just pushed an example and make sure it works https://github.com/muayyad-alsadi/wasmtime-py/blob/wip-speed-call/examples/simd_i8x16.py#L15 would you please give me examples about externref and funcref |
||
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_str0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not sufficient to simply call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm trying to move all the logic in the constructor (or in I need a minimal reproducible example about the two missing types, in a way similar to my SIMD example. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found the example it's |
||
# we can use tuple construct, but I'm using list for compatability | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment can be removed. Either the result should continue to be a list in which case there's no need for the comment, or the result should change to a tuple, in which case there's no need for the comment. |
||
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 | ||
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) | ||
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 | ||
|
@@ -70,45 +107,31 @@ 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): | ||
if getattr(self, "_ty", None) is None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of falling back to |
||
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" % | ||
(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 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 = ffi.wasmtime_func_call( | ||
error = ffi.wasmtime_func_call_unchecked( | ||
alexcrichton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it still performant to store strings here? I would expect naively that storing the integer kinds would be more efficient since then checking for funcref/externref would be integer comparisons instead of string comparisons.