-
Notifications
You must be signed in to change notification settings - Fork 294
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
Add Store::call_hook
API
#1144
Add Store::call_hook
API
#1144
Conversation
* Add the `Store::call_hook` function, which stores a call hook in `Store` * Create tests to verify call hook behavior
* Changed signature of call hooks to return a `wasmi::Error` instead of a `TrapCode`. * Use `assert_eq!` instead of `assert!` for tests, cosmetic change
* An `invoke_call_hook` function in Store to avoid problems with the borrow checker - we need a reference to the underlying data and the stored call hook * Invoke call hook on host -> wasm and wasm -> host calls
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1144 +/- ##
==========================================
+ Coverage 79.71% 79.78% +0.07%
==========================================
Files 295 296 +1
Lines 25309 25422 +113
==========================================
+ Hits 20176 20284 +108
- Misses 5133 5138 +5 ☔ View full report in Codecov by Sentry. |
@emiltayl Thanks a lot for this PR! Code looks pretty clean, too. :) Also great to have tests! I will check it out locally and see how it performs and see if we can improve it. If we cannot fix the performance regressions we might have to put this feature behind a feature gate just like Wasmtime before merging. |
I toyed a bit around and the fastest codegen I received was with this particular implementation: /// Executes the callback set by [`Store::call_hook`] if any has been set.
///
/// # Note
///
/// - Returns the value returned by the call hook.
/// - Returns `Ok(())` if no call hook exists.
#[inline]
pub(crate) fn invoke_call_hook(&mut self, call_type: CallHook) -> Result<(), Error> {
match self.call_hook.as_mut() {
None => Ok(()),
Some(call_hook) => Self::invoke_call_hook_impl(&mut self.data, call_type, call_hook),
}
}
/// Invokes the [`Store::call_hook`] that is asserted to be available in this case.
#[cold]
fn invoke_call_hook_impl(
data: &mut T,
call_type: CallHook,
call_hook: &mut CallHookWrapper<T>,
) -> Result<(), Error> {
call_hook.0(data, call_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.
All in all LGTM.
Clippy warning needs to be fixed and some formatting itches resolves. :)
Also it would be great to base this on current main
branch before we merge and run a perf comparison with my proposed changes to the invoke_call_hook
implementation to see if it is beneficial on your system, too.
So far I couldn't see major performance regressions so I conclude that for now no crate feature is needed to guard this feature in Wasmi.
crates/wasmi/src/store.rs
Outdated
#[derive(Debug)] | ||
/// Argument to the callback set by [`Store::call_hook`] to indicate why the | ||
/// callback was invoked. | ||
pub enum CallHook { |
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.
Not important but it itches me a bit to see #[derive(Debug)]
above doc comments. 🙃 Rest of the codebase has doc comments first.
crates/wasmi/src/store.rs
Outdated
/// Executes the callback set by [`Store::call_hook`] if any has been set | ||
/// and returns the value returned by the callback. If no callback has been | ||
/// set, `Ok(())` is returned. | ||
pub(crate) fn invoke_call_hook(&mut self, call_type: CallHook) -> Result<(), Error> { | ||
match &mut self.call_hook { | ||
None => Ok(()), | ||
Some(call_hook) => call_hook.0(&mut self.data, call_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.
Please see my non-review comment for improvements (optimizations) here. Also could you please see how the suggested code performs on your machine? I only have an M2 macbook to run benchmarks on.
#[derive(Default)] | ||
/// Number of times different callback events have fired. | ||
struct TimesCallbacksFired { |
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.
🙃
Thanks for the review! I did not realize that clippy didn't check the tests when I executed it locally. I will fix the clippy errors and reposition the attributes appearing above doc comments. I will also try out your suggested changes and be back with benchmark results. I do not have the time today, but should get this done before the weekend. |
* Placed attributes after doc comments * Added hint to allow complex type for `generate_error_after_n_calls`
* Inline `Store::invoke_call_hook` to avoid extra function call * Add a `Store::invoke_call_hook_impl` function that is `#[cold]` to hint that the compiler should optimize for the scenario that there are no call hooks
I tried your suggested changes. My benchmark numbers were quite similar prior to the changes, I still had a slight regression in I'm not too happy with the fix I did in the tests to work around |
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.
All in all LGTM! Just one super minor nit.
Thanks for the tons of tests.
I'm not too happy with the fix I did in the tests to work around WasmTy not being implemented for ! (and could therefore not be "returned" from functions), but it does work and I do not think it is too bad.
Can you please point me to the code that you mean? I seem to fail to find it.
I'd be fine with merging your PR.
Co-authored-by: Robin Freyler <[email protected]>
Great! Thank you for the help and reviews. As for the changes I did that I am not that satisfied with myself (I still think the code is good to go, though), it is related to https://github.com/wasmi-labs/wasmi/actions/runs/10148134369/job/28090510038#step:8:84. let should_not_run = Func::wrap(&mut store, |_: Caller<TimesCallbacksFired>| {
panic!("Host function that should not run due to trap from call hook executed");
}); From what I understand (which may be flawed/incomplete), with the upcoming changes in Rust 2024, this would fail to compile. This is because the closure's return type would be The new |
My attempt at resolving #1083. I have tried to keep the public API similar to Wasmtime, and everything else as simple as possible.
This adds the function
Store::call_hook
that accepts aFnMut(&mut T, CallHook) -> Result<(), Error>
, which is then called whenever a wasm function is called from the host, or a host function is called from wasm.Let me know if you have any questions or you need me to explain or change something. Benchmarking on my own computer indicated a performance regresssion in the execute/br_table and execute/tiny_keccak benchmarks. If these regressions are unacceptable, a possible solution could be to add this behind a feature flag. This should be quite simple, so let me know if you would prefer that.
I did add a
#[allow(clippy::type_complexity)]
to please clippy to avoid having to create a type alias that is not used anywhere else, but it should be quite easy to fix that as well.