wasmtime icon indicating copy to clipboard operation
wasmtime copied to clipboard

Wasmtime: Finish support for the typed function references proposal

Open fitzgen opened this issue 6 months ago • 5 comments

While we supported the function references proposal inside Wasm, we didn't support it on the "outside" in the Wasmtime embedder APIs. So much of the work here is exposing typed function references, and their type system updates, in the embedder API. These changes include:

  • ValType::FuncRef and ValType::ExternRef are gone, replaced with the introduction of the RefType and HeapType types and a ValType::Ref(RefType) variant.

  • ValType and FuncType no longer implement Eq and PartialEq. Instead there are ValType::matches and FuncType::matches methods which check directional subtyping. I also added ValType::eq and FuncType::eq static methods for the rare case where someone needs to check precise equality, but that is almost never actually the case, 99.99% of the time you want to check subtyping.

  • There are also public Val::matches_ty predicates for checking if a value is an instance of a type, as well as internal helpers like Val::ensure_matches_ty that return a formatted error if the value does not match the given type. These helpers are used throughout Wasmtime internals now.

  • There is now a dedicated wasmtime::Ref type that represents reference values. Table operations have been updated to take and return Refs rather than Vals.

Furthermore, this commit also includes type registry changes to correctly manage lifetimes of types that reference other types. This wasn't previously an issue because the only thing that could reference types that reference other types was a Wasm module that added all the types that could reference each other at the same time and removed them all at the same time. But now that the previously discussed work to expose these things in the embedder API is done, type lifetime management in the registry becomes a little trickier because the embedder might grab a reference to a type that references another type, and then unload the Wasm module that originally defined that type, but then the user should still be able use that type and the other types it transtively references. Before, we were refcounting individual registry entries. Now, we still are refcounting individual entries, but now we are also accounting for type-to-type references and adding a new type to the registry will increment the refcounts of each of the types that it references, and removing a type from the registry will decrement the refcounts of each of the types it references, and then recursively (logically, not literally) remove any types whose refcount has now reached zero.

Additionally, this PR adds support for subtyping to Func::typed- and Func::wrap-style APIs. For result types, you can always use a supertype of the WebAssembly function's actual declared return type in Func::typed. And for param types, you can always use a subtype of the Wasm function's actual declared param type. Doing these things essentially erases information but is always correct. But additionally, for functions which take a reference to a concrete type as a parameter, you can also use the concrete type's supertype. Consider a WebAssembly function that takes a reference to a function with a concrete type: (ref null <func type index>). In this scenario, there is no static wasmtime::Foo Rust type that corresponds to that particular Wasm-defined concrete reference type because Wasm modules are loaded dynamically at runtime. You could do f.typed::<Option<NoFunc>, ()>(), and while that is correctly typed and valid, it is often overly restrictive. The only value you could call the resulting typed function with is the null function reference, but we'd like to call it with non-null function references that happen to be of the correct type. Therefore, f.typed<Option<Func>, ()>() is also allowed in this case, even though Option<Func> represents (ref null func) which is the supertype, not subtype, of (ref null <func type index>). This does imply some minimal dynamic type checks in this case, but it is supported for better ergonomics, to enable passing non-null references into the function.

We can investigate whether it is possible to use generic type parameters and combinators to define Rust types that precisely match concrete reference types in future, follow-up pull requests. But for now, we've made things usable, at least.

Finally, this also takes the first baby step towards adding support for the Wasm GC proposal. Right now the only thing that is supported is nofunc references, and this was mainly to make testing function reference subtyping easier. But that does mean that supporting nofunc references entailed also adding a wasmtime::NoFunc type as well as the Config::wasm_gc(enabled) knob. So we officially have an in-progress implementation of Wasm GC in Wasmtime after this PR lands!

Fixes https://github.com/bytecodealliance/wasmtime/issues/6455

fitzgen avatar Feb 15 '24 00:02 fitzgen

Subscribe to Label Action

cc @fitzgen, @peterhuene

This issue or pull request has been labeled: "fuzzing", "wasmtime:api", "wasmtime:c-api", "wasmtime:config"

Thus the following users have been cc'd because of the following labels:

  • fitzgen: fuzzing
  • peterhuene: wasmtime:api, wasmtime:c-api

To subscribe or unsubscribe from this label, edit the .github/subscribe-to-label.json configuration file.

Learn more.

github-actions[bot] avatar Feb 15 '24 01:02 github-actions[bot]

Label Messager: wasmtime:config

It looks like you are changing Wasmtime's configuration options. Make sure to complete this check list:

  • [ ] If you added a new Config method, you wrote extensive documentation for it.

    Our documentation should be of the following form:

    Short, simple summary sentence.
    
    More details. These details can be multiple paragraphs. There should be
    information about not just the method, but its parameters and results as
    well.
    
    Is this method fallible? If so, when can it return an error?
    
    Can this method panic? If so, when does it panic?
    
    # Example
    
    Optional example here.
    
  • [ ] If you added a new Config method, or modified an existing one, you ensured that this configuration is exercised by the fuzz targets.

    For example, if you expose a new strategy for allocating the next instance slot inside the pooling allocator, you should ensure that at least one of our fuzz targets exercises that new strategy.

    Often, all that is required of you is to ensure that there is a knob for this configuration option in wasmtime_fuzzing::Config (or one of its nested structs).

    Rarely, this may require authoring a new fuzz target to specifically test this configuration. See our docs on fuzzing for more details.

  • [ ] If you are enabling a configuration option by default, make sure that it has been fuzzed for at least two weeks before turning it on by default.


To modify this label's message, edit the .github/label-messager/wasmtime-config.md file.

To add new label messages or remove existing label messages, edit the .github/label-messager.json configuration file.

Learn more.

github-actions[bot] avatar Feb 15 '24 01:02 github-actions[bot]

I talked a bunch with @fitzgen this afternoon about this PR where we were investigating perf related to this as well. We had a reproducible 3% slowdown in rps in the 17.0.1 release vs this PR and ~5% (to be confirmed in a second bit Nick) in the call microbenchmarks. In the rps perf we couldn't find anything related to this PR that was significantly worrisome and the delta included more than just this PR as it was the 17.0.1 release vs this PR.

Given all that while there may still be some perf issues lurking here that we need to improve on we aren't really in a position to articulate what they are and actually take action on them. With that in mind I'm ok landing this and we can continue to investigate after-the-fact and refactor as necessary if anything crops up.

alexcrichton avatar Feb 16 '24 22:02 alexcrichton

We had a reproducible ... ~5% [slowdown] in the call microbenchmarks.

Actually, it seems like with a handful of tweaks related to this PR and a bunch that are not related to this PR, the call benchmark is actually even better than it used to be now:

sync/no-hook/core - host-to-wasm - typed - nop-params-and-results
                        time:   [25.909 ns 26.294 ns 26.952 ns]
                        change: [-19.268% -17.214% -14.973%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 9 outliers among 100 measurements (9.00%)
  3 (3.00%) high mild
  6 (6.00%) high severe

I'm going to split out the perf improvements that are unrelated to this PR into their own PR. Once that's done, I think this will be good to go!

fitzgen avatar Feb 16 '24 23:02 fitzgen

I'm going to split out the perf improvements that are unrelated to this PR into their own PR.

https://github.com/bytecodealliance/wasmtime/pull/7953

fitzgen avatar Feb 16 '24 23:02 fitzgen