wasmtime/runtime/types/
matching.rs

1use crate::prelude::*;
2use crate::{Engine, linker::DefinitionType};
3use wasmtime_environ::{
4    EntityType, Global, IndexType, Memory, Table, Tag, TypeTrace, VMSharedTypeIndex, WasmHeapType,
5    WasmRefType, WasmSubType, WasmValType,
6};
7
8pub struct MatchCx<'a> {
9    engine: &'a Engine,
10}
11
12impl MatchCx<'_> {
13    /// Construct a new matching context for the given module.
14    pub fn new(engine: &Engine) -> MatchCx<'_> {
15        MatchCx { engine }
16    }
17
18    /// Validates that the `expected` type matches the type of `actual`
19    pub(crate) fn definition(&self, expected: &EntityType, actual: &DefinitionType) -> Result<()> {
20        match expected {
21            EntityType::Global(expected) => match actual {
22                DefinitionType::Global(actual) => global_ty(self.engine, expected, actual),
23                _ => bail!("expected global, but found {}", actual.desc()),
24            },
25            EntityType::Table(expected) => match actual {
26                DefinitionType::Table(actual, cur_size) => {
27                    table_ty(expected, actual, Some(*cur_size))
28                }
29                _ => bail!("expected table, but found {}", actual.desc()),
30            },
31            EntityType::Memory(expected) => match actual {
32                DefinitionType::Memory(actual, cur_size) => {
33                    memory_ty(expected, actual, Some(*cur_size))
34                }
35                _ => bail!("expected memory, but found {}", actual.desc()),
36            },
37            EntityType::Function(expected) => match actual {
38                DefinitionType::Func(actual) => {
39                    type_reference(self.engine, expected.unwrap_engine_type_index(), *actual)
40                }
41                _ => bail!("expected func, but found {}", actual.desc()),
42            },
43            EntityType::Tag(expected) => match actual {
44                DefinitionType::Tag(actual) => tag_ty(expected, actual),
45                _ => bail!("expected tag, but found {}", actual.desc()),
46            },
47        }
48    }
49}
50
51fn type_reference(
52    engine: &Engine,
53    expected: VMSharedTypeIndex,
54    actual: VMSharedTypeIndex,
55) -> Result<()> {
56    if engine.signatures().is_subtype(actual, expected) {
57        return Ok(());
58    }
59
60    let msg = "types incompatible";
61    let expected = match engine.signatures().borrow(expected) {
62        Some(ty) => ty,
63        None => panic!("{expected:?} is not registered"),
64    };
65    let actual = match engine.signatures().borrow(actual) {
66        Some(ty) => ty,
67        None => panic!("{actual:?} is not registered"),
68    };
69
70    Err(concrete_type_mismatch(msg, &expected, &actual))
71}
72
73#[cfg(feature = "component-model")]
74pub fn entity_ty(engine: &Engine, expected: &EntityType, actual: &EntityType) -> Result<()> {
75    match expected {
76        EntityType::Memory(expected) => match actual {
77            EntityType::Memory(actual) => memory_ty(expected, actual, None),
78            _ => bail!("expected memory found {}", entity_desc(actual)),
79        },
80        EntityType::Global(expected) => match actual {
81            EntityType::Global(actual) => global_ty(engine, expected, actual),
82            _ => bail!("expected global found {}", entity_desc(actual)),
83        },
84        EntityType::Table(expected) => match actual {
85            EntityType::Table(actual) => table_ty(expected, actual, None),
86            _ => bail!("expected table found {}", entity_desc(actual)),
87        },
88        EntityType::Function(expected) => match actual {
89            EntityType::Function(actual) => {
90                let expected = expected.unwrap_engine_type_index();
91                let actual = actual.unwrap_engine_type_index();
92                type_reference(engine, expected, actual)
93            }
94            _ => bail!("expected func found {}", entity_desc(actual)),
95        },
96        EntityType::Tag(expected) => match actual {
97            EntityType::Tag(actual) => tag_ty(expected, actual),
98            _ => bail!("expected tag found {}", entity_desc(actual)),
99        },
100    }
101}
102
103fn concrete_type_mismatch(
104    msg: &str,
105    expected: &WasmSubType,
106    actual: &WasmSubType,
107) -> anyhow::Error {
108    anyhow!("{msg}: expected type `{expected}`, found type `{actual}`")
109}
110
111fn global_ty(engine: &Engine, expected: &Global, actual: &Global) -> Result<()> {
112    // Subtyping is only sound on immutable global
113    // references. Therefore if either type is mutable we perform a
114    // strict equality check on the types.
115    if expected.mutability || actual.mutability {
116        equal_ty(expected.wasm_ty, actual.wasm_ty, "global")?;
117    } else {
118        match_ty(engine, expected.wasm_ty, actual.wasm_ty, "global")?;
119    }
120    match_bool(
121        expected.mutability,
122        actual.mutability,
123        "global",
124        "mutable",
125        "immutable",
126    )?;
127    Ok(())
128}
129
130fn table_ty(expected: &Table, actual: &Table, actual_runtime_size: Option<u64>) -> Result<()> {
131    equal_ty(
132        WasmValType::Ref(expected.ref_type),
133        WasmValType::Ref(actual.ref_type),
134        "table",
135    )?;
136    match_index(expected.idx_type, actual.idx_type, "table")?;
137    match_limits(
138        expected.limits.min,
139        expected.limits.max,
140        actual_runtime_size.unwrap_or(actual.limits.min),
141        actual.limits.max,
142        "table",
143    )?;
144    Ok(())
145}
146
147fn memory_ty(expected: &Memory, actual: &Memory, actual_runtime_size: Option<u64>) -> Result<()> {
148    match_bool(
149        expected.shared,
150        actual.shared,
151        "memory",
152        "shared",
153        "non-shared",
154    )?;
155    match_index(expected.idx_type, actual.idx_type, "memory")?;
156    match_limits(
157        expected.limits.min,
158        expected.limits.max,
159        actual_runtime_size.unwrap_or(actual.limits.min),
160        actual.limits.max,
161        "memory",
162    )?;
163    if expected.page_size_log2 != actual.page_size_log2 {
164        bail!(
165            "memory types incompatible: expected a memory with a page size of \
166             {}, but received a memory with a page size of {}",
167            expected.page_size(),
168            actual.page_size(),
169        )
170    }
171    Ok(())
172}
173
174fn tag_ty(expected: &Tag, actual: &Tag) -> Result<()> {
175    if expected.signature == actual.signature {
176        Ok(())
177    } else {
178        bail!("incompatible tag types")
179    }
180}
181
182fn match_heap(
183    engine: &Engine,
184    expected: WasmHeapType,
185    actual: WasmHeapType,
186    desc: &str,
187) -> Result<()> {
188    use WasmHeapType as H;
189    let result = match (actual, expected) {
190        (H::ConcreteArray(actual), H::ConcreteArray(expected))
191        | (H::ConcreteFunc(actual), H::ConcreteFunc(expected))
192        | (H::ConcreteStruct(actual), H::ConcreteStruct(expected))
193        | (H::ConcreteCont(actual), H::ConcreteCont(expected))
194        | (H::ConcreteExn(actual), H::ConcreteExn(expected)) => {
195            let actual = actual.unwrap_engine_type_index();
196            let expected = expected.unwrap_engine_type_index();
197            engine.signatures().is_subtype(actual, expected)
198        }
199
200        (H::NoFunc, H::NoFunc) => true,
201        (_, H::NoFunc) => false,
202
203        (H::NoFunc, H::ConcreteFunc(_)) => true,
204        (_, H::ConcreteFunc(_)) => false,
205
206        (H::NoFunc | H::ConcreteFunc(_) | H::Func, H::Func) => true,
207        (_, H::Func) => false,
208
209        (H::Extern | H::NoExtern, H::Extern) => true,
210        (_, H::Extern) => false,
211
212        (H::NoExtern, H::NoExtern) => true,
213        (_, H::NoExtern) => false,
214
215        (
216            H::Any
217            | H::Eq
218            | H::I31
219            | H::Array
220            | H::ConcreteArray(_)
221            | H::Struct
222            | H::ConcreteStruct(_)
223            | H::None,
224            H::Any,
225        ) => true,
226        (_, H::Any) => false,
227
228        (
229            H::Eq
230            | H::I31
231            | H::Array
232            | H::ConcreteArray(_)
233            | H::Struct
234            | H::ConcreteStruct(_)
235            | H::None,
236            H::Eq,
237        ) => true,
238        (_, H::Eq) => false,
239
240        (H::I31 | H::None, H::I31) => true,
241        (_, H::I31) => false,
242
243        (H::Array | H::ConcreteArray(_) | H::None, H::Array) => true,
244        (_, H::Array) => false,
245
246        (H::None, H::ConcreteArray(_)) => true,
247        (_, H::ConcreteArray(_)) => false,
248
249        (H::Struct | H::ConcreteStruct(_) | H::None, H::Struct) => true,
250        (_, H::Struct) => false,
251
252        (H::None, H::ConcreteStruct(_)) => true,
253        (_, H::ConcreteStruct(_)) => false,
254
255        (H::NoCont | H::ConcreteCont(_) | H::Cont, H::Cont) => true,
256        (_, H::Cont) => false,
257
258        (H::NoCont, H::ConcreteCont(_)) => true,
259        (H::NoCont, H::NoCont) => true,
260
261        (_, H::NoCont) => false,
262        (_, H::ConcreteCont(_)) => false,
263
264        (H::NoExn | H::ConcreteExn(_) | H::Exn, H::Exn) => true,
265        (_, H::Exn) => false,
266
267        (H::NoExn, H::ConcreteExn(_)) => true,
268        (H::NoExn, H::NoExn) => true,
269
270        (_, H::NoExn) => false,
271        (_, H::ConcreteExn(_)) => false,
272
273        (H::None, H::None) => true,
274        (_, H::None) => false,
275    };
276    if result {
277        Ok(())
278    } else {
279        bail!(
280            "{desc} types incompatible: expected {desc} of type `{expected}`, \
281             found {desc} of type `{actual}`",
282        )
283    }
284}
285
286fn match_ref(
287    engine: &Engine,
288    expected: WasmRefType,
289    actual: WasmRefType,
290    desc: &str,
291) -> Result<()> {
292    if actual.nullable == expected.nullable || expected.nullable {
293        return match_heap(engine, expected.heap_type, actual.heap_type, desc);
294    }
295    bail!(
296        "{desc} types incompatible: expected {desc} of type `{expected}`, \
297         found {desc} of type `{actual}`",
298    )
299}
300
301// Checks whether actual is a subtype of expected, i.e. `actual <: expected`
302// (note the parameters are given the other way around in code).
303fn match_ty(engine: &Engine, expected: WasmValType, actual: WasmValType, desc: &str) -> Result<()> {
304    // Assert that both our types are engine-level canonicalized. We can't
305    // compare types otherwise.
306    debug_assert!(
307        expected.is_canonicalized_for_runtime_usage(),
308        "expected type should be canonicalized for runtime usage: {expected:?}"
309    );
310    debug_assert!(
311        actual.is_canonicalized_for_runtime_usage(),
312        "actual type should be canonicalized for runtime usage: {actual:?}"
313    );
314
315    match (actual, expected) {
316        (WasmValType::Ref(actual), WasmValType::Ref(expected)) => {
317            match_ref(engine, expected, actual, desc)
318        }
319        (actual, expected) => equal_ty(expected, actual, desc),
320    }
321}
322
323fn equal_ty(expected: WasmValType, actual: WasmValType, desc: &str) -> Result<()> {
324    // Assert that both our types are engine-level canonicalized. We can't
325    // compare types otherwise.
326    debug_assert!(
327        expected.is_canonicalized_for_runtime_usage(),
328        "expected type should be canonicalized for runtime usage: {expected:?}"
329    );
330    debug_assert!(
331        actual.is_canonicalized_for_runtime_usage(),
332        "actual type should be canonicalized for runtime usage: {actual:?}"
333    );
334
335    if expected == actual {
336        return Ok(());
337    }
338    bail!(
339        "{desc} types incompatible: expected {desc} of type `{expected}`, \
340         found {desc} of type `{actual}`",
341    )
342}
343
344fn match_bool(
345    expected: bool,
346    actual: bool,
347    desc: &str,
348    if_true: &str,
349    if_false: &str,
350) -> Result<()> {
351    if expected == actual {
352        return Ok(());
353    }
354    let expected = if expected { if_true } else { if_false };
355    let actual = if actual { if_true } else { if_false };
356    bail!(
357        "{desc} types incompatible: expected {expected} {desc}, \
358         found {actual} {desc}",
359    )
360}
361
362fn match_index(expected: IndexType, actual: IndexType, desc: &str) -> Result<()> {
363    if expected == actual {
364        return Ok(());
365    }
366    const S64: &str = "64-bit";
367    const S32: &str = "32-bit";
368    let expected = if matches!(expected, IndexType::I64) {
369        S64
370    } else {
371        S32
372    };
373    let actual = if matches!(actual, IndexType::I64) {
374        S64
375    } else {
376        S32
377    };
378    bail!(
379        "{desc} types incompatible: expected {expected} {desc}, \
380         found {actual} {desc}",
381    )
382}
383
384fn match_limits(
385    expected_min: u64,
386    expected_max: Option<u64>,
387    actual_min: u64,
388    actual_max: Option<u64>,
389    desc: &str,
390) -> Result<()> {
391    if expected_min <= actual_min
392        && match expected_max {
393            Some(expected) => match actual_max {
394                Some(actual) => expected >= actual,
395                None => false,
396            },
397            None => true,
398        }
399    {
400        return Ok(());
401    }
402    let limits = |min: u64, max: Option<u64>| {
403        format!(
404            "min: {}, max: {}",
405            min,
406            max.map(|s| s.to_string()).unwrap_or(String::from("none"))
407        )
408    };
409    bail!(
410        "{} types incompatible: expected {0} limits ({}) doesn't match provided {0} limits ({})",
411        desc,
412        limits(expected_min, expected_max),
413        limits(actual_min, actual_max)
414    )
415}
416
417#[cfg(feature = "component-model")]
418fn entity_desc(ty: &EntityType) -> &'static str {
419    match ty {
420        EntityType::Global(_) => "global",
421        EntityType::Table(_) => "table",
422        EntityType::Memory(_) => "memory",
423        EntityType::Function(_) => "func",
424        EntityType::Tag(_) => "tag",
425    }
426}