Skip to main content

wasmtime/runtime/component/
matching.rs

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