Skip to main content

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(msg: &str, expected: &WasmSubType, actual: &WasmSubType) -> crate::Error {
104    format_err!("{msg}: expected type `{expected}`, found type `{actual}`")
105}
106
107fn global_ty(engine: &Engine, expected: &Global, actual: &Global) -> Result<()> {
108    // Subtyping is only sound on immutable global
109    // references. Therefore if either type is mutable we perform a
110    // strict equality check on the types.
111    if expected.mutability || actual.mutability {
112        equal_ty(expected.wasm_ty, actual.wasm_ty, "global")?;
113    } else {
114        match_ty(engine, expected.wasm_ty, actual.wasm_ty, "global")?;
115    }
116    match_bool(
117        expected.mutability,
118        actual.mutability,
119        "global",
120        "mutable",
121        "immutable",
122    )?;
123    Ok(())
124}
125
126fn table_ty(expected: &Table, actual: &Table, actual_runtime_size: Option<u64>) -> Result<()> {
127    equal_ty(
128        WasmValType::Ref(expected.ref_type),
129        WasmValType::Ref(actual.ref_type),
130        "table",
131    )?;
132    match_index(expected.idx_type, actual.idx_type, "table")?;
133    match_limits(
134        expected.limits.min,
135        expected.limits.max,
136        actual_runtime_size.unwrap_or(actual.limits.min),
137        actual.limits.max,
138        "table",
139    )?;
140    Ok(())
141}
142
143fn memory_ty(expected: &Memory, actual: &Memory, actual_runtime_size: Option<u64>) -> Result<()> {
144    match_bool(
145        expected.shared,
146        actual.shared,
147        "memory",
148        "shared",
149        "non-shared",
150    )?;
151    match_index(expected.idx_type, actual.idx_type, "memory")?;
152    match_limits(
153        expected.limits.min,
154        expected.limits.max,
155        actual_runtime_size.unwrap_or(actual.limits.min),
156        actual.limits.max,
157        "memory",
158    )?;
159    if expected.page_size_log2 != actual.page_size_log2 {
160        bail!(
161            "memory types incompatible: expected a memory with a page size of \
162             {}, but received a memory with a page size of {}",
163            expected.page_size(),
164            actual.page_size(),
165        )
166    }
167    Ok(())
168}
169
170fn tag_ty(expected: &Tag, actual: &Tag) -> Result<()> {
171    if expected.signature == actual.signature {
172        Ok(())
173    } else {
174        bail!("incompatible tag types")
175    }
176}
177
178fn match_heap(
179    engine: &Engine,
180    expected: WasmHeapType,
181    actual: WasmHeapType,
182    desc: &str,
183) -> Result<()> {
184    use WasmHeapType as H;
185    let result = match (actual, expected) {
186        (H::ConcreteArray(actual), H::ConcreteArray(expected))
187        | (H::ConcreteFunc(actual), H::ConcreteFunc(expected))
188        | (H::ConcreteStruct(actual), H::ConcreteStruct(expected))
189        | (H::ConcreteCont(actual), H::ConcreteCont(expected))
190        | (H::ConcreteExn(actual), H::ConcreteExn(expected)) => {
191            let actual = actual.unwrap_engine_type_index();
192            let expected = expected.unwrap_engine_type_index();
193            engine.signatures().is_subtype(actual, expected)
194        }
195
196        (H::NoFunc, H::NoFunc) => true,
197        (_, H::NoFunc) => false,
198
199        (H::NoFunc, H::ConcreteFunc(_)) => true,
200        (_, H::ConcreteFunc(_)) => false,
201
202        (H::NoFunc | H::ConcreteFunc(_) | H::Func, H::Func) => true,
203        (_, H::Func) => false,
204
205        (H::Extern | H::NoExtern, H::Extern) => true,
206        (_, H::Extern) => false,
207
208        (H::NoExtern, H::NoExtern) => true,
209        (_, H::NoExtern) => false,
210
211        (
212            H::Any
213            | H::Eq
214            | H::I31
215            | H::Array
216            | H::ConcreteArray(_)
217            | H::Struct
218            | H::ConcreteStruct(_)
219            | H::None,
220            H::Any,
221        ) => true,
222        (_, H::Any) => false,
223
224        (
225            H::Eq
226            | H::I31
227            | H::Array
228            | H::ConcreteArray(_)
229            | H::Struct
230            | H::ConcreteStruct(_)
231            | H::None,
232            H::Eq,
233        ) => true,
234        (_, H::Eq) => false,
235
236        (H::I31 | H::None, H::I31) => true,
237        (_, H::I31) => false,
238
239        (H::Array | H::ConcreteArray(_) | H::None, H::Array) => true,
240        (_, H::Array) => false,
241
242        (H::None, H::ConcreteArray(_)) => true,
243        (_, H::ConcreteArray(_)) => false,
244
245        (H::Struct | H::ConcreteStruct(_) | H::None, H::Struct) => true,
246        (_, H::Struct) => false,
247
248        (H::None, H::ConcreteStruct(_)) => true,
249        (_, H::ConcreteStruct(_)) => false,
250
251        (H::NoCont | H::ConcreteCont(_) | H::Cont, H::Cont) => true,
252        (_, H::Cont) => false,
253
254        (H::NoCont, H::ConcreteCont(_)) => true,
255        (H::NoCont, H::NoCont) => true,
256
257        (_, H::NoCont) => false,
258        (_, H::ConcreteCont(_)) => false,
259
260        (H::NoExn | H::ConcreteExn(_) | H::Exn, H::Exn) => true,
261        (_, H::Exn) => false,
262
263        (H::NoExn, H::ConcreteExn(_)) => true,
264        (H::NoExn, H::NoExn) => true,
265
266        (_, H::NoExn) => false,
267        (_, H::ConcreteExn(_)) => false,
268
269        (H::None, H::None) => true,
270        (_, H::None) => false,
271    };
272    if result {
273        Ok(())
274    } else {
275        bail!(
276            "{desc} types incompatible: expected {desc} of type `{expected}`, \
277             found {desc} of type `{actual}`",
278        )
279    }
280}
281
282fn match_ref(
283    engine: &Engine,
284    expected: WasmRefType,
285    actual: WasmRefType,
286    desc: &str,
287) -> Result<()> {
288    if actual.nullable == expected.nullable || expected.nullable {
289        return match_heap(engine, expected.heap_type, actual.heap_type, desc);
290    }
291    bail!(
292        "{desc} types incompatible: expected {desc} of type `{expected}`, \
293         found {desc} of type `{actual}`",
294    )
295}
296
297// Checks whether actual is a subtype of expected, i.e. `actual <: expected`
298// (note the parameters are given the other way around in code).
299fn match_ty(engine: &Engine, expected: WasmValType, actual: WasmValType, desc: &str) -> Result<()> {
300    // Assert that both our types are engine-level canonicalized. We can't
301    // compare types otherwise.
302    debug_assert!(
303        expected.is_canonicalized_for_runtime_usage(),
304        "expected type should be canonicalized for runtime usage: {expected:?}"
305    );
306    debug_assert!(
307        actual.is_canonicalized_for_runtime_usage(),
308        "actual type should be canonicalized for runtime usage: {actual:?}"
309    );
310
311    match (actual, expected) {
312        (WasmValType::Ref(actual), WasmValType::Ref(expected)) => {
313            match_ref(engine, expected, actual, desc)
314        }
315        (actual, expected) => equal_ty(expected, actual, desc),
316    }
317}
318
319fn equal_ty(expected: WasmValType, actual: WasmValType, desc: &str) -> Result<()> {
320    // Assert that both our types are engine-level canonicalized. We can't
321    // compare types otherwise.
322    debug_assert!(
323        expected.is_canonicalized_for_runtime_usage(),
324        "expected type should be canonicalized for runtime usage: {expected:?}"
325    );
326    debug_assert!(
327        actual.is_canonicalized_for_runtime_usage(),
328        "actual type should be canonicalized for runtime usage: {actual:?}"
329    );
330
331    if expected == actual {
332        return Ok(());
333    }
334    bail!(
335        "{desc} types incompatible: expected {desc} of type `{expected}`, \
336         found {desc} of type `{actual}`",
337    )
338}
339
340fn match_bool(
341    expected: bool,
342    actual: bool,
343    desc: &str,
344    if_true: &str,
345    if_false: &str,
346) -> Result<()> {
347    if expected == actual {
348        return Ok(());
349    }
350    let expected = if expected { if_true } else { if_false };
351    let actual = if actual { if_true } else { if_false };
352    bail!(
353        "{desc} types incompatible: expected {expected} {desc}, \
354         found {actual} {desc}",
355    )
356}
357
358fn match_index(expected: IndexType, actual: IndexType, desc: &str) -> Result<()> {
359    if expected == actual {
360        return Ok(());
361    }
362    const S64: &str = "64-bit";
363    const S32: &str = "32-bit";
364    let expected = if matches!(expected, IndexType::I64) {
365        S64
366    } else {
367        S32
368    };
369    let actual = if matches!(actual, IndexType::I64) {
370        S64
371    } else {
372        S32
373    };
374    bail!(
375        "{desc} types incompatible: expected {expected} {desc}, \
376         found {actual} {desc}",
377    )
378}
379
380fn match_limits(
381    expected_min: u64,
382    expected_max: Option<u64>,
383    actual_min: u64,
384    actual_max: Option<u64>,
385    desc: &str,
386) -> Result<()> {
387    if expected_min <= actual_min
388        && match expected_max {
389            Some(expected) => match actual_max {
390                Some(actual) => expected >= actual,
391                None => false,
392            },
393            None => true,
394        }
395    {
396        return Ok(());
397    }
398    let limits = |min: u64, max: Option<u64>| {
399        format!(
400            "min: {}, max: {}",
401            min,
402            max.map(|s| s.to_string()).unwrap_or(String::from("none"))
403        )
404    };
405    bail!(
406        "{} types incompatible: expected {0} limits ({}) doesn't match provided {0} limits ({})",
407        desc,
408        limits(expected_min, expected_max),
409        limits(actual_min, actual_max)
410    )
411}
412
413#[cfg(feature = "component-model")]
414fn entity_desc(ty: &EntityType) -> &'static str {
415    match ty {
416        EntityType::Global(_) => "global",
417        EntityType::Table(_) => "table",
418        EntityType::Memory(_) => "memory",
419        EntityType::Function(_) => "func",
420        EntityType::Tag(_) => "tag",
421    }
422}