* Add initial support for fused adapter trampolines This commit lands a significant new piece of functionality to Wasmtime's implementation of the component model in the form of the implementation of fused adapter trampolines. Internally within a component core wasm modules can communicate with each other by having their exports `canon lift`'d to get `canon lower`'d into a different component. This signifies that two components are communicating through a statically known interface via the canonical ABI at this time. Previously Wasmtime was able to identify that this communication was happening but it simply panicked with `unimplemented!` upon seeing it. This commit is the beginning of filling out this panic location with an actual implementation. The implementation route chosen here for fused adapters is to use a WebAssembly module itself for the implementation. This means that, at compile time of a component, Wasmtime is generating core WebAssembly modules which then get recursively compiled within Wasmtime as well. The choice to use WebAssembly itself as the implementation of fused adapters stems from a few motivations: * This does not represent a significant increase in the "trusted compiler base" of Wasmtime. Getting the Wasm -> CLIF translation correct once is hard enough much less for an entirely different IR to CLIF. By generating WebAssembly no new interactions with Cranelift are added which drastically reduces the possibilities for mistakes. * Using WebAssembly means that component adapters are insulated from miscompilations and mistakes. If something goes wrong it's defined well within the WebAssembly specification how it goes wrong and what happens as a result. This means that the "blast zone" for a wrong adapter is the component instance but not the entire host itself. Accesses to linear memory are guaranteed to be in-bounds and otherwise handled via well-defined traps. * A fully-finished fused adapter compiler is expected to be a significant and quite complex component of Wasmtime. Functionality along these lines is expected to be needed for Web-based polyfills of the component model and by using core WebAssembly it provides the opportunity to share code between Wasmtime and these polyfills for the component model. * Finally the runtime implementation of managing WebAssembly modules is already implemented and quite easy to integrate with, so representing fused adapters with WebAssembly results in very little extra support necessary for the runtime implementation of instantiating and managing a component. The compiler added in this commit is dubbed Wasmtime's Fused Adapter Compiler of Trampolines (FACT) because who doesn't like deriving a name from an acronym. Currently the trampoline compiler is limited in its support for interface types and only supports a few primitives. I plan on filing future PRs to flesh out the support here for all the variants of `InterfaceType`. For now this PR is primarily focused on all of the other infrastructure for the addition of a trampoline compiler. With the choice to use core WebAssembly to implement fused adapters it means that adapters need to be inserted into a module. Unfortunately adapters cannot all go into a single WebAssembly module because adapters themselves have dependencies which may be provided transitively through instances that were instantiated with other adapters. This means that a significant chunk of this PR (`adapt.rs`) is dedicated to determining precisely which adapters go into precisely which adapter modules. This partitioning process attempts to make large modules wherever it can to cut down on core wasm instantiations but is likely not optimal as it's just a simple heuristic today. With all of this added together it's now possible to start writing `*.wast` tests that internally have adapted modules communicating with one another. A `fused.wast` test suite was added as part of this PR which is the beginning of tests for the support of the fused adapter compiler added in this PR. Currently this is primarily testing some various topologies of adapters along with direct/indirect modes. This will grow many more tests over time as more types are supported. Overall I'm not 100% satisfied with the testing story of this PR. When a test fails it's very difficult to debug since everything is written in the text format of WebAssembly meaning there's no "conveniences" to print out the state of the world when things go wrong and easily debug. I think this will become even more apparent as more tests are written for more types in subsequent PRs. At this time though I know of no better alternative other than leaning pretty heavily on fuzz-testing to ensure this is all exercised. * Fix an unused field warning * Fix tests in `wasmtime-runtime` * Add some more tests for compiled trampolines * Remap exports when injecting adapters The exports of a component were accidentally left unmapped which meant that they indexed the instance indexes pre-adapter module insertion. * Fix typo * Rebase conflicts
457 lines
12 KiB
Rust
457 lines
12 KiB
Rust
//! Densely numbered entity references as mapping keys.
|
|
use crate::boxed_slice::BoxedSlice;
|
|
use crate::iter::{IntoIter, Iter, IterMut};
|
|
use crate::keys::Keys;
|
|
use crate::EntityRef;
|
|
use alloc::boxed::Box;
|
|
use alloc::vec::Vec;
|
|
use core::iter::FromIterator;
|
|
use core::marker::PhantomData;
|
|
use core::ops::{Index, IndexMut};
|
|
use core::slice;
|
|
#[cfg(feature = "enable-serde")]
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// A primary mapping `K -> V` allocating dense entity references.
|
|
///
|
|
/// The `PrimaryMap` data structure uses the dense index space to implement a map with a vector.
|
|
///
|
|
/// A primary map contains the main definition of an entity, and it can be used to allocate new
|
|
/// entity references with the `push` method.
|
|
///
|
|
/// There should only be a single `PrimaryMap` instance for a given `EntityRef` type, otherwise
|
|
/// conflicting references will be created. Using unknown keys for indexing will cause a panic.
|
|
///
|
|
/// Note that `PrimaryMap` doesn't implement `Deref` or `DerefMut`, which would allow
|
|
/// `&PrimaryMap<K, V>` to convert to `&[V]`. One of the main advantages of `PrimaryMap` is
|
|
/// that it only allows indexing with the distinct `EntityRef` key type, so converting to a
|
|
/// plain slice would make it easier to use incorrectly. To make a slice of a `PrimaryMap`, use
|
|
/// `into_boxed_slice`.
|
|
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
|
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
|
|
pub struct PrimaryMap<K, V>
|
|
where
|
|
K: EntityRef,
|
|
{
|
|
elems: Vec<V>,
|
|
unused: PhantomData<K>,
|
|
}
|
|
|
|
impl<K, V> PrimaryMap<K, V>
|
|
where
|
|
K: EntityRef,
|
|
{
|
|
/// Create a new empty map.
|
|
pub fn new() -> Self {
|
|
Self {
|
|
elems: Vec::new(),
|
|
unused: PhantomData,
|
|
}
|
|
}
|
|
|
|
/// Create a new empty map with the given capacity.
|
|
pub fn with_capacity(capacity: usize) -> Self {
|
|
Self {
|
|
elems: Vec::with_capacity(capacity),
|
|
unused: PhantomData,
|
|
}
|
|
}
|
|
|
|
/// Check if `k` is a valid key in the map.
|
|
pub fn is_valid(&self, k: K) -> bool {
|
|
k.index() < self.elems.len()
|
|
}
|
|
|
|
/// Get the element at `k` if it exists.
|
|
pub fn get(&self, k: K) -> Option<&V> {
|
|
self.elems.get(k.index())
|
|
}
|
|
|
|
/// Get the element at `k` if it exists, mutable version.
|
|
pub fn get_mut(&mut self, k: K) -> Option<&mut V> {
|
|
self.elems.get_mut(k.index())
|
|
}
|
|
|
|
/// Is this map completely empty?
|
|
pub fn is_empty(&self) -> bool {
|
|
self.elems.is_empty()
|
|
}
|
|
|
|
/// Get the total number of entity references created.
|
|
pub fn len(&self) -> usize {
|
|
self.elems.len()
|
|
}
|
|
|
|
/// Iterate over all the keys in this map.
|
|
pub fn keys(&self) -> Keys<K> {
|
|
Keys::with_len(self.elems.len())
|
|
}
|
|
|
|
/// Iterate over all the values in this map.
|
|
pub fn values(&self) -> slice::Iter<V> {
|
|
self.elems.iter()
|
|
}
|
|
|
|
/// Iterate over all the values in this map, mutable edition.
|
|
pub fn values_mut(&mut self) -> slice::IterMut<V> {
|
|
self.elems.iter_mut()
|
|
}
|
|
|
|
/// Iterate over all the keys and values in this map.
|
|
pub fn iter(&self) -> Iter<K, V> {
|
|
Iter::new(self.elems.iter())
|
|
}
|
|
|
|
/// Iterate over all the keys and values in this map, mutable edition.
|
|
pub fn iter_mut(&mut self) -> IterMut<K, V> {
|
|
IterMut::new(self.elems.iter_mut())
|
|
}
|
|
|
|
/// Remove all entries from this map.
|
|
pub fn clear(&mut self) {
|
|
self.elems.clear()
|
|
}
|
|
|
|
/// Get the key that will be assigned to the next pushed value.
|
|
pub fn next_key(&self) -> K {
|
|
K::new(self.elems.len())
|
|
}
|
|
|
|
/// Append `v` to the mapping, assigning a new key which is returned.
|
|
pub fn push(&mut self, v: V) -> K {
|
|
let k = self.next_key();
|
|
self.elems.push(v);
|
|
k
|
|
}
|
|
|
|
/// Returns the last element that was inserted in the map.
|
|
pub fn last(&self) -> Option<(K, &V)> {
|
|
let len = self.elems.len();
|
|
let last = self.elems.last()?;
|
|
Some((K::new(len - 1), last))
|
|
}
|
|
|
|
/// Returns the last element that was inserted in the map.
|
|
pub fn last_mut(&mut self) -> Option<(K, &mut V)> {
|
|
let len = self.elems.len();
|
|
let last = self.elems.last_mut()?;
|
|
Some((K::new(len - 1), last))
|
|
}
|
|
|
|
/// Reserves capacity for at least `additional` more elements to be inserted.
|
|
pub fn reserve(&mut self, additional: usize) {
|
|
self.elems.reserve(additional)
|
|
}
|
|
|
|
/// Reserves the minimum capacity for exactly `additional` more elements to be inserted.
|
|
pub fn reserve_exact(&mut self, additional: usize) {
|
|
self.elems.reserve_exact(additional)
|
|
}
|
|
|
|
/// Shrinks the capacity of the `PrimaryMap` as much as possible.
|
|
pub fn shrink_to_fit(&mut self) {
|
|
self.elems.shrink_to_fit()
|
|
}
|
|
|
|
/// Consumes this `PrimaryMap` and produces a `BoxedSlice`.
|
|
pub fn into_boxed_slice(self) -> BoxedSlice<K, V> {
|
|
unsafe { BoxedSlice::<K, V>::from_raw(Box::<[V]>::into_raw(self.elems.into_boxed_slice())) }
|
|
}
|
|
|
|
/// Performs a binary search on the values with a key extraction function.
|
|
///
|
|
/// Assumes that the values are sorted by the key extracted by the function.
|
|
///
|
|
/// If the value is found then `Ok(K)` is returned, containing the entity key
|
|
/// of the matching value.
|
|
///
|
|
/// If there are multiple matches, then any one of the matches could be returned.
|
|
///
|
|
/// If the value is not found then Err(K) is returned, containing the entity key
|
|
/// where a matching element could be inserted while maintaining sorted order.
|
|
pub fn binary_search_values_by_key<'a, B, F>(&'a self, b: &B, f: F) -> Result<K, K>
|
|
where
|
|
F: FnMut(&'a V) -> B,
|
|
B: Ord,
|
|
{
|
|
self.elems
|
|
.binary_search_by_key(b, f)
|
|
.map(|i| K::new(i))
|
|
.map_err(|i| K::new(i))
|
|
}
|
|
}
|
|
|
|
impl<K, V> Default for PrimaryMap<K, V>
|
|
where
|
|
K: EntityRef,
|
|
{
|
|
fn default() -> PrimaryMap<K, V> {
|
|
PrimaryMap::new()
|
|
}
|
|
}
|
|
|
|
/// Immutable indexing into an `PrimaryMap`.
|
|
/// The indexed value must be in the map.
|
|
impl<K, V> Index<K> for PrimaryMap<K, V>
|
|
where
|
|
K: EntityRef,
|
|
{
|
|
type Output = V;
|
|
|
|
fn index(&self, k: K) -> &V {
|
|
&self.elems[k.index()]
|
|
}
|
|
}
|
|
|
|
/// Mutable indexing into an `PrimaryMap`.
|
|
impl<K, V> IndexMut<K> for PrimaryMap<K, V>
|
|
where
|
|
K: EntityRef,
|
|
{
|
|
fn index_mut(&mut self, k: K) -> &mut V {
|
|
&mut self.elems[k.index()]
|
|
}
|
|
}
|
|
|
|
impl<K, V> IntoIterator for PrimaryMap<K, V>
|
|
where
|
|
K: EntityRef,
|
|
{
|
|
type Item = (K, V);
|
|
type IntoIter = IntoIter<K, V>;
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
IntoIter::new(self.elems.into_iter())
|
|
}
|
|
}
|
|
|
|
impl<'a, K, V> IntoIterator for &'a PrimaryMap<K, V>
|
|
where
|
|
K: EntityRef,
|
|
{
|
|
type Item = (K, &'a V);
|
|
type IntoIter = Iter<'a, K, V>;
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
Iter::new(self.elems.iter())
|
|
}
|
|
}
|
|
|
|
impl<'a, K, V> IntoIterator for &'a mut PrimaryMap<K, V>
|
|
where
|
|
K: EntityRef,
|
|
{
|
|
type Item = (K, &'a mut V);
|
|
type IntoIter = IterMut<'a, K, V>;
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
IterMut::new(self.elems.iter_mut())
|
|
}
|
|
}
|
|
|
|
impl<K, V> FromIterator<V> for PrimaryMap<K, V>
|
|
where
|
|
K: EntityRef,
|
|
{
|
|
fn from_iter<T>(iter: T) -> Self
|
|
where
|
|
T: IntoIterator<Item = V>,
|
|
{
|
|
Self {
|
|
elems: Vec::from_iter(iter),
|
|
unused: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
// `EntityRef` impl for testing.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
struct E(u32);
|
|
|
|
impl EntityRef for E {
|
|
fn new(i: usize) -> Self {
|
|
E(i as u32)
|
|
}
|
|
fn index(self) -> usize {
|
|
self.0 as usize
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn basic() {
|
|
let r0 = E(0);
|
|
let r1 = E(1);
|
|
let m = PrimaryMap::<E, isize>::new();
|
|
|
|
let v: Vec<E> = m.keys().collect();
|
|
assert_eq!(v, []);
|
|
|
|
assert!(!m.is_valid(r0));
|
|
assert!(!m.is_valid(r1));
|
|
}
|
|
|
|
#[test]
|
|
fn push() {
|
|
let mut m = PrimaryMap::new();
|
|
let k0: E = m.push(12);
|
|
let k1 = m.push(33);
|
|
|
|
assert_eq!(m[k0], 12);
|
|
assert_eq!(m[k1], 33);
|
|
|
|
let v: Vec<E> = m.keys().collect();
|
|
assert_eq!(v, [k0, k1]);
|
|
}
|
|
|
|
#[test]
|
|
fn iter() {
|
|
let mut m: PrimaryMap<E, usize> = PrimaryMap::new();
|
|
m.push(12);
|
|
m.push(33);
|
|
|
|
let mut i = 0;
|
|
for (key, value) in &m {
|
|
assert_eq!(key.index(), i);
|
|
match i {
|
|
0 => assert_eq!(*value, 12),
|
|
1 => assert_eq!(*value, 33),
|
|
_ => panic!(),
|
|
}
|
|
i += 1;
|
|
}
|
|
i = 0;
|
|
for (key_mut, value_mut) in m.iter_mut() {
|
|
assert_eq!(key_mut.index(), i);
|
|
match i {
|
|
0 => assert_eq!(*value_mut, 12),
|
|
1 => assert_eq!(*value_mut, 33),
|
|
_ => panic!(),
|
|
}
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn iter_rev() {
|
|
let mut m: PrimaryMap<E, usize> = PrimaryMap::new();
|
|
m.push(12);
|
|
m.push(33);
|
|
|
|
let mut i = 2;
|
|
for (key, value) in m.iter().rev() {
|
|
i -= 1;
|
|
assert_eq!(key.index(), i);
|
|
match i {
|
|
0 => assert_eq!(*value, 12),
|
|
1 => assert_eq!(*value, 33),
|
|
_ => panic!(),
|
|
}
|
|
}
|
|
|
|
i = 2;
|
|
for (key, value) in m.iter_mut().rev() {
|
|
i -= 1;
|
|
assert_eq!(key.index(), i);
|
|
match i {
|
|
0 => assert_eq!(*value, 12),
|
|
1 => assert_eq!(*value, 33),
|
|
_ => panic!(),
|
|
}
|
|
}
|
|
}
|
|
#[test]
|
|
fn keys() {
|
|
let mut m: PrimaryMap<E, usize> = PrimaryMap::new();
|
|
m.push(12);
|
|
m.push(33);
|
|
|
|
let mut i = 0;
|
|
for key in m.keys() {
|
|
assert_eq!(key.index(), i);
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn keys_rev() {
|
|
let mut m: PrimaryMap<E, usize> = PrimaryMap::new();
|
|
m.push(12);
|
|
m.push(33);
|
|
|
|
let mut i = 2;
|
|
for key in m.keys().rev() {
|
|
i -= 1;
|
|
assert_eq!(key.index(), i);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn values() {
|
|
let mut m: PrimaryMap<E, usize> = PrimaryMap::new();
|
|
m.push(12);
|
|
m.push(33);
|
|
|
|
let mut i = 0;
|
|
for value in m.values() {
|
|
match i {
|
|
0 => assert_eq!(*value, 12),
|
|
1 => assert_eq!(*value, 33),
|
|
_ => panic!(),
|
|
}
|
|
i += 1;
|
|
}
|
|
i = 0;
|
|
for value_mut in m.values_mut() {
|
|
match i {
|
|
0 => assert_eq!(*value_mut, 12),
|
|
1 => assert_eq!(*value_mut, 33),
|
|
_ => panic!(),
|
|
}
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn values_rev() {
|
|
let mut m: PrimaryMap<E, usize> = PrimaryMap::new();
|
|
m.push(12);
|
|
m.push(33);
|
|
|
|
let mut i = 2;
|
|
for value in m.values().rev() {
|
|
i -= 1;
|
|
match i {
|
|
0 => assert_eq!(*value, 12),
|
|
1 => assert_eq!(*value, 33),
|
|
_ => panic!(),
|
|
}
|
|
}
|
|
i = 2;
|
|
for value_mut in m.values_mut().rev() {
|
|
i -= 1;
|
|
match i {
|
|
0 => assert_eq!(*value_mut, 12),
|
|
1 => assert_eq!(*value_mut, 33),
|
|
_ => panic!(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn from_iter() {
|
|
let mut m: PrimaryMap<E, usize> = PrimaryMap::new();
|
|
m.push(12);
|
|
m.push(33);
|
|
|
|
let n = m.values().collect::<PrimaryMap<E, _>>();
|
|
assert!(m.len() == n.len());
|
|
for (me, ne) in m.values().zip(n.values()) {
|
|
assert!(*me == **ne);
|
|
}
|
|
}
|
|
}
|