Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Statically register host WASM functions #10394

Conversation

koute
Copy link
Contributor

@koute koute commented Nov 30, 2021

skip check-dependent-cumulus

polkadot companion: paritytech/polkadot#4471

This PR changes how we register the host WASM functions exposed to the runtime. Currently we register them dynamically with wasmtime::Func::new, while this PR makes it so that we statically register them with wasmtime::Func::wrap.

Why? Let me quote wasmtime docs:

This function [Func::wrap] will create a new Func which, when called, will execute the given Rust closure. Unlike Func::new the target function being called is known statically so the type signature can be inferred. [...] Note that when using this API, the intention is to create as thin of a layer as possible for when WebAssembly calls the function provided. With sufficient inlining and optimization the WebAssembly will call straight into func provided, with no extra fluff entailed.

And indeed, using Func::wrap is significantly more efficient than using Func::new. Granted, we probably don't make enough calls from the WASM runtime for this to make a significant difference when those functions are called, however where it makes a huge difference is when we're instantiating new WASM instances.

When using Func::new every time a host function is registered wasmtime has to JIT a new trampoline for it, and this is extremely slow; when using Func::wrap it doesn't, and from what I can see this cuts down the instantiation time by over 90%.

Of course this also doesn't make a huge difference in practice right now since we're using our own instance reuse mechanism, however this is absolutely necessary if we'd ever want to switch to wasmtime's native instance pooling. (Without this PR even with wasmtimes instance pooling enabled the cost of JITing those trampolines completely dominates the execution time.)

EDIT(pep): Closes #8959

@koute koute added A0-please_review Pull request needs code review. I7-refactor Code needs refactoring. I9-optimisation An enhancement to provide better overall performance in terms of time-to-completion for a task. B0-silent Changes should not be mentioned in any release notes C1-low PR touches the given topic and has a low impact on builders. D3-trivial 🧸 PR contains trivial changes in a runtime directory that do not require an audit labels Nov 30, 2021
@koute koute requested review from pepyakin and bkchr November 30, 2021 12:54
let mut base = Base::host_functions();
let overlay = Overlay::host_functions();
base.retain(|host_fn| {
!overlay.iter().any(|ext_host_fn| host_fn.name() == ext_host_fn.name())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to pull the overlay names into a set or do we think there will be very few of them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's how it was originally, so it's probably not a big deal? Nevertheless there aren't that many of them, and I haven't seen this pop up in any of profiles.

I can rework this to use a HashSet if you really want though.

primitives/wasm-interface/src/lib.rs Outdated Show resolved Hide resolved
primitives/wasm-interface/src/lib.rs Outdated Show resolved Hide resolved
type State;
type Error;
type FunctionContext: FunctionContext;
fn with_function_context<R>(caller: wasmtime::Caller<Self::State>, callback: impl FnOnce(&mut dyn FunctionContext) -> R) -> R;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just out of curiosity, why dynamic object and not Self::FunctionContext?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making it Self::FunctionContext significantly complicates the lifetimes everywhere, and from the top of my head I'm not even sure if it's actually possible to use a concrete type here without GATs (since Caller actually takes a lifetime which has to be passed to the Self::FunctionContext, it's just elided here). Also, rest of the traits (e.g. host::FromFFIValue) and the dynamic dispatch machinery (Function::execute) take it as &dyn FunctionContext anyway.

If we really want a concrete type here I'd suggest that we should ignore it for now and just revisit this once GATs are stabilized.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah, all is fine. Was just curious

client/executor/wasmtime/src/runtime.rs Show resolved Hide resolved
@pepyakin
Copy link
Contributor

pepyakin commented Dec 4, 2021

FWIW, this will require polkadot companion

Copy link
Contributor

@pepyakin pepyakin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good

Copy link
Member

@bkchr bkchr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, just some nitpicks here and there.

Ty for the work!

primitives/wasm-interface/src/lib.rs Outdated Show resolved Hide resolved
primitives/wasm-interface/src/lib.rs Outdated Show resolved Hide resolved
primitives/wasm-interface/src/lib.rs Show resolved Hide resolved
fn_name: &str,
func: impl wasmtime::IntoFunc<Self::State, Params, Results> + 'static,
) -> core::result::Result<(), Self::Error> {
if self.seen.contains(fn_name) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is to prevent double registration?

Maybe some debug log would be nice that says that we filtered this one out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but only in a sense that since this is explicitly used to override the functions from the Base with the functions from the Overlay it is expected that there will be some duplicates.

Yeah I guess I can add a debug log here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I've also added a little bit more strict checking here to disambiguate between duplicates in Base vs duplicates in Overlay.

type FFIType = u8;
type FFIType = u32;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be sure. This was internally passed before as u32 anyway, because wasm types don't know u8 "natively"? So, we don't change it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. WASM FFI doesn't support 8-bit and 16-bit integers natively, so it was being converted into an u32 later anyway. So I've put the extra WasmTy bound on the FFIType so that it will be guaranteed that's actually FFI-compatible and moved the conversion into the native WASM FFI type earlier.

&mut self,
fn_name: &str,
func: impl wasmtime::IntoFunc<Self::State, Params, Results>,
) -> Result<(), Self::Error> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you return a result here? To be future proof?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... good question! (:

Currently it can indeed only return an Ok; IIRC it's a vestigial remain from before one of the refactorings I did before I pushed this PR out where it could actually return an error.

@koute
Copy link
Contributor Author

koute commented Dec 14, 2021

bot merge

@paritytech-processbot
Copy link

Error: Github API says paritytech/polkadot#4471 is not mergeable

@koute
Copy link
Contributor Author

koute commented Dec 14, 2021

bot merge

@paritytech-processbot paritytech-processbot bot merged commit 49a4b18 into paritytech:master Dec 14, 2021
seunlanlege pushed a commit to seunlanlege/substrate that referenced this pull request Dec 17, 2021
* Statically register host WASM functions

* Fix `substrate-test-client` compilation

* Move `ExtendedHostFunctions` to `sp-wasm-interface`

* Fix `sp-runtime-interface` tests' compilation

* Fix `sc-executor-wasmtime` tests' compilation

* Use `runtime_interface` macro in `test-runner`

* Fix `sc-executor` tests' compilation

* Reformatting/`rustfmt`

* Add an extra comment regarding the `H` generic arg in `create_runtime`

* Even more `rustfmt`

* Depend on `wasmtime` without default features in `sp-wasm-interface`

* Bump version of `sp-wasm-interface` to 4.0.1

* Bump `sp-wasm-interface` in `Cargo.lock` too

* Bump all of the `sp-wasm-interface` requirements to 4.0.1

Maybe this will appease cargo-unleash?

* Revert "Bump all of the `sp-wasm-interface` requirements to 4.0.1"

This reverts commit 0f7ccf8.

* Make `cargo-unleash` happy (maybe)

* Use `cargo-unleash` to bump the crates' versions

* Align to review comments
grishasobol pushed a commit to gear-tech/substrate that referenced this pull request Mar 28, 2022
* Statically register host WASM functions

* Fix `substrate-test-client` compilation

* Move `ExtendedHostFunctions` to `sp-wasm-interface`

* Fix `sp-runtime-interface` tests' compilation

* Fix `sc-executor-wasmtime` tests' compilation

* Use `runtime_interface` macro in `test-runner`

* Fix `sc-executor` tests' compilation

* Reformatting/`rustfmt`

* Add an extra comment regarding the `H` generic arg in `create_runtime`

* Even more `rustfmt`

* Depend on `wasmtime` without default features in `sp-wasm-interface`

* Bump version of `sp-wasm-interface` to 4.0.1

* Bump `sp-wasm-interface` in `Cargo.lock` too

* Bump all of the `sp-wasm-interface` requirements to 4.0.1

Maybe this will appease cargo-unleash?

* Revert "Bump all of the `sp-wasm-interface` requirements to 4.0.1"

This reverts commit 0f7ccf8.

* Make `cargo-unleash` happy (maybe)

* Use `cargo-unleash` to bump the crates' versions

* Align to review comments
ark0f pushed a commit to gear-tech/substrate that referenced this pull request Feb 27, 2023
* Statically register host WASM functions

* Fix `substrate-test-client` compilation

* Move `ExtendedHostFunctions` to `sp-wasm-interface`

* Fix `sp-runtime-interface` tests' compilation

* Fix `sc-executor-wasmtime` tests' compilation

* Use `runtime_interface` macro in `test-runner`

* Fix `sc-executor` tests' compilation

* Reformatting/`rustfmt`

* Add an extra comment regarding the `H` generic arg in `create_runtime`

* Even more `rustfmt`

* Depend on `wasmtime` without default features in `sp-wasm-interface`

* Bump version of `sp-wasm-interface` to 4.0.1

* Bump `sp-wasm-interface` in `Cargo.lock` too

* Bump all of the `sp-wasm-interface` requirements to 4.0.1

Maybe this will appease cargo-unleash?

* Revert "Bump all of the `sp-wasm-interface` requirements to 4.0.1"

This reverts commit 0f7ccf8.

* Make `cargo-unleash` happy (maybe)

* Use `cargo-unleash` to bump the crates' versions

* Align to review comments
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A0-please_review Pull request needs code review. B0-silent Changes should not be mentioned in any release notes C1-low PR touches the given topic and has a low impact on builders. D3-trivial 🧸 PR contains trivial changes in a runtime directory that do not require an audit I7-refactor Code needs refactoring. I9-optimisation An enhancement to provide better overall performance in terms of time-to-completion for a task.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

wasmtime: kill the stubs
4 participants