Skip to content

Commit

Permalink
FIXES bytecodealliance#137: make calling wasm from python 7x faster
Browse files Browse the repository at this point in the history
  • Loading branch information
muayyad-alsadi committed Apr 5, 2023
1 parent 9285e6c commit 818bb8d
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 16 deletions.
2 changes: 2 additions & 0 deletions examples/gcd.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
8 changes: 4 additions & 4 deletions examples/gcd_perf.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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)
69 changes: 59 additions & 10 deletions wasmtime/_func.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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,
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions wasmtime/_value.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 818bb8d

Please sign in to comment.