jrandolf edited PR #14870.
github-actions[bot] added the label wasmtime:api on PR #14870.
jrandolf updated PR #14870.
jrandolf updated PR #14870.
jrandolf updated PR #14870.
jrandolf edited PR #14870:
This makes the typed API and
bindgen!more flexible about which Rust type amap<K, V>maps to, and fixes the handle leak from duplicate keys. One commit each:
More map types.
ComponentType/Lift/Lowerare now implemented for:
BTreeMap<K, V>, which works withoutstdand lowers entries in sorted order. That gives embedders who care about deterministic execution a deterministic lowering order, unlikeHashMap.HashMap<K, V, S>with any hasher (lifting needsS: BuildHasher + Default).- A new
MapEntries<K, V>, a newtype aroundVec<(K, V)>that keeps entries in order, including duplicate keys.All of these share one lift and one lower helper, which validate the pointer and length before allocating, as #14835 made
HashMapdo.
bindgen!map_typeoption, defaulting toBTreeMap. It picks the Rust type used for WIT maps, e.g.map_type: std::collections::HashMapormap_type: wasmtime::component::MapEntries. This mirrors the guest-side wit-bindgen option, including itsBTreeMapdefault (see the discussion on #14837).BTreeMapis available withoutstd, so the generated code no longer depends on the features Wasmtime is built with, which fixes #14830. It also makesadditional_derives: [Hash, Ord, ...]work for records containing maps.Duplicate keys with handles (#14812). Lifting a map into a type that keeps one value per key (
HashMap,BTreeMap) keeps the last value, as the spec allows. When the values contain handles, the replaced values' handles used to leak, e.g. a guest resource's destructor never ran. Now they're dropped as if the embedder had dropped them:
- The keys can't contain handles, so they're all lifted first to find which values get replaced. Those values are lifted as
Vals and discarded.- Discarded
borrows are released right away, since that runs no code.- Other handles may run a destructor in a guest, which can't happen mid-lift, so they're queued in the store. The queue is drained after post-return for the results of a call, before a host function runs for its parameters, and on the event loop for concurrent calls and stream/future reads.
MapEntriesstill gets every entry, and maps whose values don't contain handles are lifted as before. Whether the value type contains handles is computed once when the map type is built. The existing code inLinker::define_unknown_imports_as_default_valuesthat drops every handle in aValis now shared with this.Some questions I'd appreciate input on:
- Is
MapEntriesthe right shape (a public tuple field, noDeref)? Or would something like a lazyWasmMapview fit better? I didn't go with that, because re-reading a value would transferownhandles again.- The discarded handles are queued per store rather than per call, so a drain drops everything pending. Every drain point is a point where running a destructor is fine, so I think that's OK, but per-call tracking is possible if preferred, with a fair bit more plumbing.
The map snapshots from #14834 are updated for the new
BTreeMapdefault. This overlaps with #14838 in thebindgen!map type formatting, so whichever lands second will need a small rebase.Closes #14830
Closes #14832
Closes #14812
jrandolf updated PR #14870.
jrandolf edited PR #14870:
This makes the typed API and
bindgen!more flexible about which Rust type amap<K, V>maps to, and fixes the handle leak from duplicate keys. One commit each:
More map types.
ComponentType/Lift/Lowerare now implemented for:
BTreeMap<K, V>, which works withoutstdand lowers entries in sorted order. That gives embedders who care about deterministic execution a deterministic lowering order, unlikeHashMap.HashMap<K, V, S>with any hasher (lifting needsS: BuildHasher + Default).- A new
MapEntries<K, V>, a newtype aroundVec<(K, V)>that keeps entries in order, including duplicate keys.All of these share one lift and one lower helper, which validate the pointer and length before allocating, as #14835 made
HashMapdo.
bindgen!map_typeoption, defaulting toBTreeMap. It picks the Rust type used for WIT maps, e.g.map_type: std::collections::HashMapormap_type: wasmtime::component::MapEntries. This mirrors the guest-side wit-bindgen option, including itsBTreeMapdefault (see the discussion on #14837).BTreeMapis available withoutstd, so the generated code no longer depends on the features Wasmtime is built with, which fixes #14830. It also makesadditional_derives: [Hash, Ord, ...]work for records containing maps.Duplicate keys with handles (#14812). Lifting a map into a type that keeps one value per key (
HashMap,BTreeMap) keeps the last value, as the spec allows. When the values contain handles, the replaced values' handles used to leak, e.g. a guest resource's destructor never ran. Now they're dropped as if the embedder had dropped them:
- The keys can't contain handles, so they're all lifted first to find which values get replaced. Those values are lifted as
Vals and discarded.- Discarded
borrows are released right away, since that runs no code.- Other handles may run a destructor in a guest, which can't happen mid-lift, so they're queued in the store. The queue is drained after post-return for the results of a call, before a host function runs for its parameters, and on the event loop for concurrent calls and stream/future reads.
MapEntriesstill gets every entry, and maps whose values don't contain handles are lifted as before. Whether the value type contains handles is computed once when the map type is built. The existing code inLinker::define_unknown_imports_as_default_valuesthat drops every handle in aValis now shared with this.Some questions I'd appreciate input on:
- Is
MapEntriesthe right shape (a public tuple field, noDeref)? Or would something like a lazyWasmMapview fit better? I didn't go with that, because re-reading a value would transferownhandles again.- The discarded handles are queued per store rather than per call, so a drain drops everything pending. Every drain point is a point where running a destructor is fine, so I think that's OK, but per-call tracking is possible if preferred, with a fair bit more plumbing.
The map snapshots from #14834 are updated for the new
BTreeMapdefault.Closes #14830
Closes #14832
Closes #14812
Last updated: Oct 31 2026 at 23:03 UTC