wasmtime/runtime/component/matching.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
use crate::component::func::HostFunc;
use crate::component::linker::{Definition, Strings};
use crate::component::ResourceType;
use crate::runtime::vm::component::ComponentInstance;
use crate::types::matching;
use crate::Module;
use crate::{prelude::*, Engine};
use alloc::sync::Arc;
use core::any::Any;
use wasmtime_environ::component::{
ComponentTypes, NameMap, ResourceIndex, TypeComponentInstance, TypeDef, TypeFuncIndex,
TypeModule, TypeResourceTableIndex,
};
use wasmtime_environ::PrimaryMap;
pub struct TypeChecker<'a> {
pub engine: &'a Engine,
pub types: &'a Arc<ComponentTypes>,
pub strings: &'a Strings,
pub imported_resources: Arc<PrimaryMap<ResourceIndex, ResourceType>>,
}
#[derive(Copy, Clone)]
#[doc(hidden)]
pub struct InstanceType<'a> {
pub types: &'a Arc<ComponentTypes>,
pub resources: &'a Arc<PrimaryMap<ResourceIndex, ResourceType>>,
}
impl TypeChecker<'_> {
pub(crate) fn definition(
&mut self,
expected: &TypeDef,
actual: Option<&Definition>,
) -> Result<()> {
match *expected {
TypeDef::Module(t) => match actual {
Some(Definition::Module(actual)) => self.module(&self.types[t], actual),
Some(actual) => bail!("expected module found {}", actual.desc()),
None => bail!("module implementation is missing"),
},
TypeDef::ComponentInstance(t) => match actual {
Some(Definition::Instance(actual)) => self.instance(&self.types[t], Some(actual)),
None => self.instance(&self.types[t], None),
Some(actual) => bail!("expected instance found {}", actual.desc()),
},
TypeDef::ComponentFunc(t) => match actual {
Some(Definition::Func(actual)) => self.func(t, actual),
Some(actual) => bail!("expected function found {}", actual.desc()),
None => bail!("function implementation is missing"),
},
TypeDef::Component(_) => match actual {
Some(actual) => bail!("expected component found {}", actual.desc()),
None => bail!("component implementation is missing"),
},
TypeDef::Interface(_) => match actual {
Some(actual) => bail!("expected type found {}", actual.desc()),
None => bail!("type implementation is missing"),
},
TypeDef::Resource(i) => {
let i = self.types[i].ty;
let actual = match actual {
Some(Definition::Resource(actual, _dtor)) => actual,
// If a resource is imported yet nothing was supplied then
// that's only successful if the resource has itself
// already been defined. If it's already defined then that
// means that this is an `(eq ...)` import which is not
// required to be satisfied via `Linker` definitions in the
// Wasmtime API.
None if self.imported_resources.get(i).is_some() => return Ok(()),
Some(actual) => bail!("expected resource found {}", actual.desc()),
None => bail!("resource implementation is missing"),
};
match self.imported_resources.get(i) {
// If `i` hasn't been pushed onto `imported_resources` yet
// then that means that it's the first time a new resource
// was introduced, so record the type of this resource. It
// should always be the case that the next index assigned
// is equal to `i` since types should be checked in the
// same order they were assigned into the `Component` type.
//
// Note the `get_mut` here which is expected to always
// succeed since `imported_resources` has not yet been
// cloned.
None => {
let resources = Arc::get_mut(&mut self.imported_resources).unwrap();
let id = resources.push(*actual);
assert_eq!(id, i);
}
// If `i` has been defined, however, then that means that
// this is an `(eq ..)` bounded type imported because it's
// referring to a previously defined type. In this
// situation it's not required to provide a type import but
// if it's supplied then it must be equal. In this situation
// it's supplied, so test for equality.
Some(expected) => {
if expected != actual {
bail!("mismatched resource types");
}
}
}
Ok(())
}
// not possible for valid components to import
TypeDef::CoreFunc(_) => unreachable!(),
}
}
fn module(&self, expected: &TypeModule, actual: &Module) -> Result<()> {
let actual = actual.env_module();
// Every export that is expected should be in the actual module we have
for (name, expected) in expected.exports.iter() {
let idx = actual
.exports
.get(name)
.ok_or_else(|| anyhow!("module export `{name}` not defined"))?;
let actual = actual.type_of(*idx);
matching::entity_ty(self.engine, expected, &actual)
.with_context(|| format!("module export `{name}` has the wrong type"))?;
}
// Note the opposite order of checks here. Every import that the actual
// module expects should be imported by the expected module since the
// expected module has the set of items given to the actual module.
// Additionally the "matches" check is inverted here.
for (module, name, actual) in actual.imports() {
// TODO: shouldn't need a `.to_string()` here ideally
let expected = expected
.imports
.get(&(module.to_string(), name.to_string()))
.ok_or_else(|| anyhow!("module import `{module}::{name}` not defined"))?;
matching::entity_ty(self.engine, &actual, expected)
.with_context(|| format!("module import `{module}::{name}` has the wrong type"))?;
}
Ok(())
}
fn instance(
&mut self,
expected: &TypeComponentInstance,
actual: Option<&NameMap<usize, Definition>>,
) -> Result<()> {
// Like modules, every export in the expected type must be present in
// the actual type. It's ok, though, to have extra exports in the actual
// type.
for (name, expected) in expected.exports.iter() {
// Interface types may be exported from a component in order to give them a name, but
// they don't have a definition in the sense that this search is interested in, so
// ignore them.
if let TypeDef::Interface(_) = expected {
continue;
}
let actual = actual.and_then(|map| map.get(name, self.strings));
self.definition(expected, actual)
.with_context(|| format!("instance export `{name}` has the wrong type"))?;
}
Ok(())
}
fn func(&self, expected: TypeFuncIndex, actual: &HostFunc) -> Result<()> {
let instance_type = InstanceType {
types: self.types,
resources: &self.imported_resources,
};
actual.typecheck(expected, &instance_type)
}
}
impl Definition {
fn desc(&self) -> &'static str {
match self {
Definition::Module(_) => "module",
Definition::Func(_) => "func",
Definition::Instance(_) => "instance",
Definition::Resource(..) => "resource",
}
}
}
impl<'a> InstanceType<'a> {
pub fn new(instance: &'a ComponentInstance) -> InstanceType<'a> {
InstanceType {
types: instance.component_types(),
resources: downcast_arc_ref(instance.resource_types()),
}
}
pub fn resource_type(&self, index: TypeResourceTableIndex) -> ResourceType {
let index = self.types[index].ty;
self.resources
.get(index)
.copied()
.unwrap_or_else(|| ResourceType::uninstantiated(&self.types, index))
}
}
/// Small helper method to downcast an `Arc` borrow into a borrow of a concrete
/// type within the `Arc`.
///
/// Note that this is different than `downcast_ref` which projects out `&T`
/// where here we want `&Arc<T>`.
fn downcast_arc_ref<T: 'static>(arc: &Arc<dyn Any + Send + Sync>) -> &Arc<T> {
// First assert that the payload of the `Any` is indeed a `T`
let _ = arc.downcast_ref::<T>();
// Next do an unsafe pointer cast to convert the `Any` into `T` which should
// be safe given the above check.
unsafe { &*(arc as *const Arc<dyn Any + Send + Sync> as *const Arc<T>) }
}