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 pub fn new(engine: &Engine) -> MatchCx<'_> {
15 MatchCx { engine }
16 }
17
18 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 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
291fn match_ty(engine: &Engine, expected: WasmValType, actual: WasmValType, desc: &str) -> Result<()> {
294 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 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}