wasmtime/runtime/types/
matching.rs

1use crate::prelude::*;
2use crate::{linker::DefinitionType, Engine};
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_attr(not(feature = "component-model"), allow(dead_code))]
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            let actual = actual.unwrap_engine_type_index();
195            let expected = expected.unwrap_engine_type_index();
196            engine.signatures().is_subtype(actual, expected)
197        }
198
199        (H::NoFunc, H::NoFunc) => true,
200        (_, H::NoFunc) => false,
201
202        (H::NoFunc, H::ConcreteFunc(_)) => true,
203        (_, H::ConcreteFunc(_)) => false,
204
205        (H::NoFunc | H::ConcreteFunc(_) | H::Func, H::Func) => true,
206        (_, H::Func) => false,
207
208        (H::Extern | H::NoExtern, H::Extern) => true,
209        (_, H::Extern) => false,
210
211        (H::NoExtern, H::NoExtern) => true,
212        (_, H::NoExtern) => false,
213
214        (
215            H::Any
216            | H::Eq
217            | H::I31
218            | H::Array
219            | H::ConcreteArray(_)
220            | H::Struct
221            | H::ConcreteStruct(_)
222            | H::None,
223            H::Any,
224        ) => true,
225        (_, H::Any) => false,
226
227        (
228            H::Eq
229            | H::I31
230            | H::Array
231            | H::ConcreteArray(_)
232            | H::Struct
233            | H::ConcreteStruct(_)
234            | H::None,
235            H::Eq,
236        ) => true,
237        (_, H::Eq) => false,
238
239        (H::I31 | H::None, H::I31) => true,
240        (_, H::I31) => false,
241
242        (H::Array | H::ConcreteArray(_) | H::None, H::Array) => true,
243        (_, H::Array) => false,
244
245        (H::None, H::ConcreteArray(_)) => true,
246        (_, H::ConcreteArray(_)) => false,
247
248        (H::Struct | H::ConcreteStruct(_) | H::None, H::Struct) => true,
249        (_, H::Struct) => false,
250
251        (H::None, H::ConcreteStruct(_)) => true,
252        (_, H::ConcreteStruct(_)) => false,
253
254        (H::NoCont | H::ConcreteCont(_) | H::Cont, H::Cont) => true,
255        (_, H::Cont) => false,
256
257        (H::NoCont, H::ConcreteCont(_)) => true,
258        (H::NoCont, H::NoCont) => true,
259
260        (_, H::NoCont) => false,
261        (_, H::ConcreteCont(_)) => false,
262
263        (H::None, H::None) => true,
264        (_, H::None) => false,
265    };
266    if result {
267        Ok(())
268    } else {
269        bail!(
270            "{desc} types incompatible: expected {desc} of type `{expected}`, \
271             found {desc} of type `{actual}`",
272        )
273    }
274}
275
276fn match_ref(
277    engine: &Engine,
278    expected: WasmRefType,
279    actual: WasmRefType,
280    desc: &str,
281) -> Result<()> {
282    if actual.nullable == expected.nullable || expected.nullable {
283        return match_heap(engine, expected.heap_type, actual.heap_type, desc);
284    }
285    bail!(
286        "{desc} types incompatible: expected {desc} of type `{expected}`, \
287         found {desc} of type `{actual}`",
288    )
289}
290
291// Checks whether actual is a subtype of expected, i.e. `actual <: expected`
292// (note the parameters are given the other way around in code).
293fn match_ty(engine: &Engine, expected: WasmValType, actual: WasmValType, desc: &str) -> Result<()> {
294    // Assert that both our types are engine-level canonicalized. We can't
295    // compare types otherwise.
296    debug_assert!(
297        expected.is_canonicalized_for_runtime_usage(),
298        "expected type should be canonicalized for runtime usage: {expected:?}"
299    );
300    debug_assert!(
301        actual.is_canonicalized_for_runtime_usage(),
302        "actual type should be canonicalized for runtime usage: {actual:?}"
303    );
304
305    match (actual, expected) {
306        (WasmValType::Ref(actual), WasmValType::Ref(expected)) => {
307            match_ref(engine, expected, actual, desc)
308        }
309        (actual, expected) => equal_ty(expected, actual, desc),
310    }
311}
312
313fn equal_ty(expected: WasmValType, actual: WasmValType, desc: &str) -> Result<()> {
314    // Assert that both our types are engine-level canonicalized. We can't
315    // compare types otherwise.
316    debug_assert!(
317        expected.is_canonicalized_for_runtime_usage(),
318        "expected type should be canonicalized for runtime usage: {expected:?}"
319    );
320    debug_assert!(
321        actual.is_canonicalized_for_runtime_usage(),
322        "actual type should be canonicalized for runtime usage: {actual:?}"
323    );
324
325    if expected == actual {
326        return Ok(());
327    }
328    bail!(
329        "{desc} types incompatible: expected {desc} of type `{expected}`, \
330         found {desc} of type `{actual}`",
331    )
332}
333
334fn match_bool(
335    expected: bool,
336    actual: bool,
337    desc: &str,
338    if_true: &str,
339    if_false: &str,
340) -> Result<()> {
341    if expected == actual {
342        return Ok(());
343    }
344    let expected = if expected { if_true } else { if_false };
345    let actual = if actual { if_true } else { if_false };
346    bail!(
347        "{desc} types incompatible: expected {expected} {desc}, \
348         found {actual} {desc}",
349    )
350}
351
352fn match_index(expected: IndexType, actual: IndexType, desc: &str) -> Result<()> {
353    if expected == actual {
354        return Ok(());
355    }
356    const S64: &str = "64-bit";
357    const S32: &str = "32-bit";
358    let expected = if matches!(expected, IndexType::I64) {
359        S64
360    } else {
361        S32
362    };
363    let actual = if matches!(actual, IndexType::I64) {
364        S64
365    } else {
366        S32
367    };
368    bail!(
369        "{desc} types incompatible: expected {expected} {desc}, \
370         found {actual} {desc}",
371    )
372}
373
374fn match_limits(
375    expected_min: u64,
376    expected_max: Option<u64>,
377    actual_min: u64,
378    actual_max: Option<u64>,
379    desc: &str,
380) -> Result<()> {
381    if expected_min <= actual_min
382        && match expected_max {
383            Some(expected) => match actual_max {
384                Some(actual) => expected >= actual,
385                None => false,
386            },
387            None => true,
388        }
389    {
390        return Ok(());
391    }
392    let limits = |min: u64, max: Option<u64>| {
393        format!(
394            "min: {}, max: {}",
395            min,
396            max.map(|s| s.to_string()).unwrap_or(String::from("none"))
397        )
398    };
399    bail!(
400        "{} types incompatible: expected {0} limits ({}) doesn't match provided {0} limits ({})",
401        desc,
402        limits(expected_min, expected_max),
403        limits(actual_min, actual_max)
404    )
405}
406
407fn entity_desc(ty: &EntityType) -> &'static str {
408    match ty {
409        EntityType::Global(_) => "global",
410        EntityType::Table(_) => "table",
411        EntityType::Memory(_) => "memory",
412        EntityType::Function(_) => "func",
413        EntityType::Tag(_) => "tag",
414    }
415}