| f2e689cd | 11-Jul-2024 |
Nick Fitzgerald <[email protected]> |
Introduce `wasmtime::StructRef` and allocating Wasm GC structs (#8933)
* Introduce `wasmtime::StructRef` and allocating Wasm GC structs
This commit introduces the `wasmtime::StructRef` type and sup
Introduce `wasmtime::StructRef` and allocating Wasm GC structs (#8933)
* Introduce `wasmtime::StructRef` and allocating Wasm GC structs
This commit introduces the `wasmtime::StructRef` type and support for allocating Wasm GC structs from the host. This commit does *not* add support for the `struct.new` family of Wasm instructions; guests still cannot allocate Wasm GC objects yet, but initial support should be pretty straightforward after this commit lands.
The `StructRef` type has everything you expect from other value types in the `wasmtime` crate:
* A method to get its type or check whether it matches a given type
* An implementation of `WasmTy` so that it can be used with `Func::wrap`-style APIs
* The ability to upcast it into an `AnyRef` and to do checked downcasts in the opposite direction
There are, additionally, methods for getting, setting, and enumerating a `StructRef`'s fields.
To allocate a `StructRef`, we need proof that the struct type we are allocating is being kept alive for the duration that the allocation may live. This is required for many reasons, but a basic example is getting a struct instance's type from the embedder API: this does a type-index-to-`StructType` lookup and conversion and if the type wasn't kept alive, then the type-index lookup will result in what is logically a use-after-free bug. This won't be a problem for Wasm guests (when we get around to implementing allocation for them) since their module defines the type, the store holds onto its instances' modules, and the allocation cannot outlive the store. For the host, we need another method of keeping the object's type alive, since it might be that the host defined the type and there is no module that also defined it, let alone such a module that is being kept alive in the store.
The solution to the struct-type-lifetime problem that this commit implements for hosts is for the store to hold a hash set of `RegisteredType`s specifically for objects which were allocated via the embedder API. But we also don't want to do a hash lookup on every allocation, so we also implement a `StructRefPre` type. A `StructRefPre` is proof that the embedder has inserted a `StructType`'s inner `RegisteredType` into a store. Structurally, it is a pair of the struct type and a store id. All `StructRef` allocation methods require a `StructRefPre` argument, which does a fast store id check, rather than a whole hash table insertion.
I opted to require `StructRefPre` in all allocation cases -- even though this has the downside of always forcing callers to create one before they allocate, even if they are only allocating a single object -- because of two reasons. First, this avoids needing to define duplicate methods, with and without a `StructRefPre` argument. Second, this avoids a performance footgun in the API where users don't realize that they *can* avoid extra work by creating a single `StructRefPre` and then using it multiple times. Anecdotally, I've heard multiple people complain about instantiation being slower than advertised but it turns out they weren't using `InstancePre`, and I'd like to avoid that situation for allocation if we can.
* Move `allow(missing_docs)` up to `gc::disabled` module instead of each `impl`
* Rename `cast` to `unchecked_cast`
* fix `GcHeapOutOfMemory` error example in doc example
* document additional error case for `StructRef::new`
* Use `unpack` method instead of open-coding it
* deallocate on failed initialization
* Refactor field access methods to share more code
And define `fields()` in terms of `field()` rather than the other way around.
* Add upcast methods from structref to anyref
* Remove duplicate type checking and add clarifying comments about initializing vs writing fields
* make the `PodValType` trait safe
* fix benchmarks build
* prtest:full
* add miri ignores to new tests that call into wasm
show more ...
|
| ff93bce0 | 20-Feb-2024 |
Nick Fitzgerald <[email protected]> |
Wasmtime: Finish support for the typed function references proposal (#7943)
* Wasmtime: Finish support for the typed function references proposal
While we supported the function references proposal
Wasmtime: Finish support for the typed function references proposal (#7943)
* Wasmtime: Finish support for the typed function references proposal
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 `Ref`s rather than `Val`s.
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
* Fix WAT in test to be valid
* Check that dependent features are enabled for function-references and GC
* Remove unnecessary engine parameters from a few methods
Ever since `FuncType`'s internal `RegisteredType` holds onto its own `Engine`, we don't need these anymore.
Still useful to keep the `Engine` parameter around for the `ensure_matches` methods because that can be used to check correct store/engine usage for embedders.
* Add missing dependent feature enabling for some tests
* Remove copy-paste bit from test
* match self to show it is uninhabited
* Add a missing `is_v128` method
* Short circuit a few func type comparisons
* Turn comment into part of doc comment
* Add test for `Global::new` and subtyping
* Add tests for embedder API, tables, and subtyping
* Add an embedder API test for setting globals and subtyping
* Construct realloc's type from its index, rather than from scratch
* Help LLVM better optimize our dynamic type checks in `TypedFunc::call_raw`
* Fix call benchmark compilation
* Change `WasmParams::into_abi` to take the whole func type instead of iter of params
* Fix doc links
prtest:full
* Fix size assertion on s390x
show more ...
|