-
Notifications
You must be signed in to change notification settings - Fork 53
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
FIXES #137: make calling wasm from python 7x faster #139
base: main
Are you sure you want to change the base?
FIXES #137: make calling wasm from python 7x faster #139
Conversation
Why I'm getting
|
the reason it failed was because it init was never called as it was constructed with |
all the test errors are in |
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.
This seems reasonable to me to add one way or another, but there's a number of soundness and safety issues to address before merging, in addition to the test failures.
wasmtime/_func.py
Outdated
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 comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that this conversion is quite correct. The IntoVal
type could also be Val
, for example, to handle things like v128/funcref/externref/etc. In such a case the raw attribute of raw[i]
can't be set to that value, and I'm not sure what happens otherwise during such a process.
Additionally it's not safe to simply set the externref
and funcref
fields. Those need to go through the methods in the Val
type.
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.
For an example you can see the logic in Value._convert
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.
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
wasmtime/_func.py
Outdated
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 comment
The reason will be displayed to describe this comment to others. Learn more.
It's not sufficient to simply call getattr
here, namely for the funcref
and externref
types. You can see what the logic in Value._value
does, for example.
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.
I'm trying to move all the logic in the constructor (or in _init_call
) so that this logic is run once the module is loaded not on every single call. and hence the speed boost.
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 comment
The reason will be displayed to describe this comment to others. Learn more.
I found the example it's test_refs.py
I'll look into it and make sure it works
Sorry I don't have a ton of time to sink into this myself. I can help out more once CI is passing perhaps, but until then I think it's probably best to fix those errors. Once CI is passing that should give a sense of everything needed to handle various edge cases correctly. |
it was very difficult to know how the code now works and passes all the tests except cosmetic tests |
9122bb4
to
1ca73e9
Compare
there seems to be an existing typing bug (not introduced by my commits) _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:
return _wasm_valtype_kind(arg0) # type: ignore the function # _types.py
class ValType:
def __str__(self) -> str:
assert(self._ptr is not None)
kind = ffi.wasm_valtype_kind(self._ptr)
if kind == ffi.WASM_I32.value:
return 'i32'
if kind == ffi.WASM_I64.value:
return 'i64'
if kind == ffi.WASM_F32.value:
return 'f32'
if kind == ffi.WASM_F64.value:
return 'f64'
if kind == ffi.WASM_ANYREF.value:
return 'anyref'
if kind == ffi.WASM_FUNCREF.value: # <--- as you can see it's not kind.value but just kind which means it's int not c_uint8
return 'funcref'
return 'ValType(%d)' % kind # type: ignore <----------- this was kind.value which is wrong If I change |
I think I'm done and you can take it from here |
I've kept old method of calling so that we can compare the performance of the two |
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.
I think there are still some correctness issues related to funcref/externref. The C API has wasmtime_{func,externref}_{to,from}_raw
which need to be used for the various conversions and should avoid the need to pass around a store_id
which should be an internal implementation detail.
) | ||
|
||
instance = Instance(store, module, []) | ||
vector_type = ctypes.c_uint8 * 16 |
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.
This is not intended to be a public detail of this library that ctypes can be used to pass in data. Currently there's no support for v128
but I think that adding it should be done in a "first class" way that doesn't accidentally expose the internal representation at this time. For example I don't know that this array-based representation is the best for v128.
@@ -1,10 +1,13 @@ | |||
# Example of instantiating a wasm module and calling an export on it | |||
|
|||
from wasmtime import Store, Module, Instance | |||
|
|||
from functools import partial |
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.
How come this is being imported and used in the examples? Is this required? If not I think it'd be best to leave this out.
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) |
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.
Can this avoid duplicating Val._as_externref
?
if val == 0: | ||
return None | ||
f = wasmtime_func_t() | ||
f.store_id = store_id | ||
f.index = val | ||
return wasmtime.Func._from_raw(f) |
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.
Can this avoid duplicating Val._as_func
?
@@ -25,6 +37,63 @@ 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)] # type: ignore |
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.
How come this is type: ignore
?
_ty: FuncType | ||
_params_n: int | ||
_results_n: int | ||
_params_str: list[str] |
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.
_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 comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would be best stored as Optional[str]
to avoid having a dummy value listed for empty results.
return None | ||
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 |
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.
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.
elif isinstance(val, wasmtime.Func): | ||
if val._func.store_id != store_id: | ||
raise WasmtimeError("passed funcref does not belong to same store") | ||
casted = val._func.index |
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.
Passing the index
here is not correct. This needs to invoke wasmtime_func_to_raw
. This also shows that this needs to have tests passing funcref, for example.
else: | ||
ex = Val.externref(val)._raw | ||
if ex: | ||
casted = ctypes.addressof(ex.of.externref) |
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.
Like below for funcref I don't think that this is correct. This needs to call wasmtime_externref_to_raw
.
this is a great enhancement from 43 ms to 6 ms in @alexprengere question in #96