wasmtime_wast/
core.rs

1use anyhow::{anyhow, bail, Context, Result};
2use std::fmt::{Display, LowerHex};
3use wasmtime::{AnyRef, ExternRef, Store, Val};
4use wast::core::{AbstractHeapType, HeapType, NanPattern, V128Pattern, WastArgCore, WastRetCore};
5use wast::token::{F32, F64};
6
7/// Translate from a `script::Value` to a `RuntimeValue`.
8pub fn val<T>(store: &mut Store<T>, v: &WastArgCore<'_>) -> Result<Val> {
9    use wast::core::WastArgCore::*;
10
11    Ok(match v {
12        I32(x) => Val::I32(*x),
13        I64(x) => Val::I64(*x),
14        F32(x) => Val::F32(x.bits),
15        F64(x) => Val::F64(x.bits),
16        V128(x) => Val::V128(u128::from_le_bytes(x.to_le_bytes()).into()),
17        RefNull(HeapType::Abstract {
18            ty: AbstractHeapType::Extern,
19            shared: false,
20        }) => Val::ExternRef(None),
21        RefNull(HeapType::Abstract {
22            ty: AbstractHeapType::Func,
23            shared: false,
24        }) => Val::FuncRef(None),
25        RefNull(HeapType::Abstract {
26            ty: AbstractHeapType::Any,
27            shared: false,
28        }) => Val::AnyRef(None),
29        RefNull(HeapType::Abstract {
30            shared: false,
31            ty: AbstractHeapType::None,
32        }) => Val::AnyRef(None),
33        RefExtern(x) => Val::ExternRef(Some(ExternRef::new(store, *x)?)),
34        RefHost(x) => {
35            let x = ExternRef::new(&mut *store, *x)?;
36            let x = AnyRef::convert_extern(&mut *store, x)?;
37            Val::AnyRef(Some(x))
38        }
39        other => bail!("couldn't convert {:?} to a runtime value", other),
40    })
41}
42
43fn extract_lane_as_i8(bytes: u128, lane: usize) -> i8 {
44    (bytes >> (lane * 8)) as i8
45}
46
47fn extract_lane_as_i16(bytes: u128, lane: usize) -> i16 {
48    (bytes >> (lane * 16)) as i16
49}
50
51fn extract_lane_as_i32(bytes: u128, lane: usize) -> i32 {
52    (bytes >> (lane * 32)) as i32
53}
54
55fn extract_lane_as_i64(bytes: u128, lane: usize) -> i64 {
56    (bytes >> (lane * 64)) as i64
57}
58
59pub fn match_val<T>(store: &mut Store<T>, actual: &Val, expected: &WastRetCore) -> Result<()> {
60    match (actual, expected) {
61        (_, WastRetCore::Either(expected)) => {
62            for expected in expected {
63                if match_val(store, actual, expected).is_ok() {
64                    return Ok(());
65                }
66            }
67            match_val(store, actual, &expected[0])
68        }
69
70        (Val::I32(a), WastRetCore::I32(b)) => match_int(a, b),
71        (Val::I64(a), WastRetCore::I64(b)) => match_int(a, b),
72
73        // Note that these float comparisons are comparing bits, not float
74        // values, so we're testing for bit-for-bit equivalence
75        (Val::F32(a), WastRetCore::F32(b)) => match_f32(*a, b),
76        (Val::F64(a), WastRetCore::F64(b)) => match_f64(*a, b),
77        (Val::V128(a), WastRetCore::V128(b)) => match_v128(a.as_u128(), b),
78
79        // Null references.
80        (
81            Val::FuncRef(None) | Val::ExternRef(None) | Val::AnyRef(None),
82            WastRetCore::RefNull(_),
83        )
84        | (Val::ExternRef(None), WastRetCore::RefExtern(None)) => Ok(()),
85
86        // Null and non-null mismatches.
87        (Val::ExternRef(None), WastRetCore::RefExtern(Some(_))) => {
88            bail!("expected non-null reference, found null")
89        }
90        (
91            Val::ExternRef(Some(x)),
92            WastRetCore::RefNull(Some(HeapType::Abstract {
93                ty: AbstractHeapType::Extern,
94                shared: false,
95            })),
96        ) => {
97            match x.data(store)?.map(|x| {
98                x.downcast_ref::<u32>()
99                    .expect("only u32 externrefs created in wast test suites")
100            }) {
101                None => {
102                    bail!("expected null externref, found non-null externref without host data")
103                }
104                Some(x) => bail!("expected null externref, found non-null externref of {x}"),
105            }
106        }
107        (Val::ExternRef(Some(_)) | Val::FuncRef(Some(_)), WastRetCore::RefNull(_)) => {
108            bail!("expected null, found non-null reference: {actual:?}")
109        }
110
111        // Non-null references.
112        (Val::FuncRef(Some(_)), WastRetCore::RefFunc(_)) => Ok(()),
113        (Val::ExternRef(Some(_)), WastRetCore::RefExtern(None)) => Ok(()),
114        (Val::ExternRef(Some(x)), WastRetCore::RefExtern(Some(y))) => {
115            let x = x
116                .data(store)?
117                .ok_or_else(|| {
118                    anyhow!("expected an externref of a u32, found externref without host data")
119                })?
120                .downcast_ref::<u32>()
121                .expect("only u32 externrefs created in wast test suites");
122            if x == y {
123                Ok(())
124            } else {
125                bail!("expected {} found {}", y, x);
126            }
127        }
128
129        (Val::AnyRef(Some(_)), WastRetCore::RefAny) => Ok(()),
130        (Val::AnyRef(Some(x)), WastRetCore::RefEq) => {
131            if x.is_eqref(store)? {
132                Ok(())
133            } else {
134                bail!("expected an eqref, found {x:?}");
135            }
136        }
137        (Val::AnyRef(Some(x)), WastRetCore::RefI31) => {
138            if x.is_i31(store)? {
139                Ok(())
140            } else {
141                bail!("expected a `(ref i31)`, found {x:?}");
142            }
143        }
144        (Val::AnyRef(Some(x)), WastRetCore::RefStruct) => {
145            if x.is_struct(store)? {
146                Ok(())
147            } else {
148                bail!("expected a struct reference, found {x:?}")
149            }
150        }
151        (Val::AnyRef(Some(x)), WastRetCore::RefArray) => {
152            if x.is_array(store)? {
153                Ok(())
154            } else {
155                bail!("expected a array reference, found {x:?}")
156            }
157        }
158        (Val::AnyRef(Some(x)), WastRetCore::RefHost(y)) => {
159            let x = ExternRef::convert_any(&mut *store, *x)?;
160            let x = x
161                .data(&mut *store)?
162                .ok_or_else(|| {
163                    anyhow!(
164                        "expected anyref of externref of u32, found anyref that is \
165                         not a converted externref"
166                    )
167                })?
168                .downcast_ref::<u32>()
169                .expect("only u32 externrefs created in wast test suites");
170            if x == y {
171                Ok(())
172            } else {
173                bail!("expected anyref of externref of {y}, found anyref of externref of {x}")
174            }
175        }
176
177        _ => bail!(
178            "don't know how to compare {:?} and {:?} yet",
179            actual,
180            expected
181        ),
182    }
183}
184
185pub fn match_int<T>(actual: &T, expected: &T) -> Result<()>
186where
187    T: Eq + Display + LowerHex,
188{
189    if actual == expected {
190        Ok(())
191    } else {
192        bail!(
193            "expected {:18} / {0:#018x}\n\
194             actual   {:18} / {1:#018x}",
195            expected,
196            actual
197        )
198    }
199}
200
201pub fn match_f32(actual: u32, expected: &NanPattern<F32>) -> Result<()> {
202    match expected {
203        // Check if an f32 (as u32 bits to avoid possible quieting when moving values in registers, e.g.
204        // https://developer.arm.com/documentation/ddi0344/i/neon-and-vfp-programmers-model/modes-of-operation/default-nan-mode?lang=en)
205        // is a canonical NaN:
206        //  - the sign bit is unspecified,
207        //  - the 8-bit exponent is set to all 1s
208        //  - the MSB of the payload is set to 1 (a quieted NaN) and all others to 0.
209        // See https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
210        NanPattern::CanonicalNan => {
211            let canon_nan = 0x7fc0_0000;
212            if (actual & 0x7fff_ffff) == canon_nan {
213                Ok(())
214            } else {
215                bail!(
216                    "expected {:10} / {:#010x}\n\
217                     actual   {:10} / {:#010x}",
218                    "canon-nan",
219                    canon_nan,
220                    f32::from_bits(actual),
221                    actual,
222                )
223            }
224        }
225
226        // Check if an f32 (as u32, see comments above) is an arithmetic NaN.
227        // This is the same as a canonical NaN including that the payload MSB is
228        // set to 1, but one or more of the remaining payload bits MAY BE set to
229        // 1 (a canonical NaN specifies all 0s). See
230        // https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
231        NanPattern::ArithmeticNan => {
232            const AF32_NAN: u32 = 0x7f80_0000;
233            let is_nan = actual & AF32_NAN == AF32_NAN;
234            const AF32_PAYLOAD_MSB: u32 = 0x0040_0000;
235            let is_msb_set = actual & AF32_PAYLOAD_MSB == AF32_PAYLOAD_MSB;
236            if is_nan && is_msb_set {
237                Ok(())
238            } else {
239                bail!(
240                    "expected {:>10} / {:>10}\n\
241                     actual   {:10} / {:#010x}",
242                    "arith-nan",
243                    "0x7fc*****",
244                    f32::from_bits(actual),
245                    actual,
246                )
247            }
248        }
249        NanPattern::Value(expected_value) => {
250            if actual == expected_value.bits {
251                Ok(())
252            } else {
253                bail!(
254                    "expected {:10} / {:#010x}\n\
255                     actual   {:10} / {:#010x}",
256                    f32::from_bits(expected_value.bits),
257                    expected_value.bits,
258                    f32::from_bits(actual),
259                    actual,
260                )
261            }
262        }
263    }
264}
265
266pub fn match_f64(actual: u64, expected: &NanPattern<F64>) -> Result<()> {
267    match expected {
268        // Check if an f64 (as u64 bits to avoid possible quieting when moving values in registers, e.g.
269        // https://developer.arm.com/documentation/ddi0344/i/neon-and-vfp-programmers-model/modes-of-operation/default-nan-mode?lang=en)
270        // is a canonical NaN:
271        //  - the sign bit is unspecified,
272        //  - the 11-bit exponent is set to all 1s
273        //  - the MSB of the payload is set to 1 (a quieted NaN) and all others to 0.
274        // See https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
275        NanPattern::CanonicalNan => {
276            let canon_nan = 0x7ff8_0000_0000_0000;
277            if (actual & 0x7fff_ffff_ffff_ffff) == canon_nan {
278                Ok(())
279            } else {
280                bail!(
281                    "expected {:18} / {:#018x}\n\
282                     actual   {:18} / {:#018x}",
283                    "canon-nan",
284                    canon_nan,
285                    f64::from_bits(actual),
286                    actual,
287                )
288            }
289        }
290
291        // Check if an f64 (as u64, see comments above) is an arithmetic NaN. This is the same as a
292        // canonical NaN including that the payload MSB is set to 1, but one or more of the remaining
293        // payload bits MAY BE set to 1 (a canonical NaN specifies all 0s). See
294        // https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
295        NanPattern::ArithmeticNan => {
296            const AF64_NAN: u64 = 0x7ff0_0000_0000_0000;
297            let is_nan = actual & AF64_NAN == AF64_NAN;
298            const AF64_PAYLOAD_MSB: u64 = 0x0008_0000_0000_0000;
299            let is_msb_set = actual & AF64_PAYLOAD_MSB == AF64_PAYLOAD_MSB;
300            if is_nan && is_msb_set {
301                Ok(())
302            } else {
303                bail!(
304                    "expected {:>18} / {:>18}\n\
305                     actual   {:18} / {:#018x}",
306                    "arith-nan",
307                    "0x7ff8************",
308                    f64::from_bits(actual),
309                    actual,
310                )
311            }
312        }
313        NanPattern::Value(expected_value) => {
314            if actual == expected_value.bits {
315                Ok(())
316            } else {
317                bail!(
318                    "expected {:18} / {:#018x}\n\
319                     actual   {:18} / {:#018x}",
320                    f64::from_bits(expected_value.bits),
321                    expected_value.bits,
322                    f64::from_bits(actual),
323                    actual,
324                )
325            }
326        }
327    }
328}
329
330fn match_v128(actual: u128, expected: &V128Pattern) -> Result<()> {
331    match expected {
332        V128Pattern::I8x16(expected) => {
333            let actual = [
334                extract_lane_as_i8(actual, 0),
335                extract_lane_as_i8(actual, 1),
336                extract_lane_as_i8(actual, 2),
337                extract_lane_as_i8(actual, 3),
338                extract_lane_as_i8(actual, 4),
339                extract_lane_as_i8(actual, 5),
340                extract_lane_as_i8(actual, 6),
341                extract_lane_as_i8(actual, 7),
342                extract_lane_as_i8(actual, 8),
343                extract_lane_as_i8(actual, 9),
344                extract_lane_as_i8(actual, 10),
345                extract_lane_as_i8(actual, 11),
346                extract_lane_as_i8(actual, 12),
347                extract_lane_as_i8(actual, 13),
348                extract_lane_as_i8(actual, 14),
349                extract_lane_as_i8(actual, 15),
350            ];
351            if actual == *expected {
352                return Ok(());
353            }
354            bail!(
355                "expected {:4?}\n\
356                 actual   {:4?}\n\
357                 \n\
358                 expected (hex) {0:02x?}\n\
359                 actual (hex)   {1:02x?}",
360                expected,
361                actual,
362            )
363        }
364        V128Pattern::I16x8(expected) => {
365            let actual = [
366                extract_lane_as_i16(actual, 0),
367                extract_lane_as_i16(actual, 1),
368                extract_lane_as_i16(actual, 2),
369                extract_lane_as_i16(actual, 3),
370                extract_lane_as_i16(actual, 4),
371                extract_lane_as_i16(actual, 5),
372                extract_lane_as_i16(actual, 6),
373                extract_lane_as_i16(actual, 7),
374            ];
375            if actual == *expected {
376                return Ok(());
377            }
378            bail!(
379                "expected {:6?}\n\
380                 actual   {:6?}\n\
381                 \n\
382                 expected (hex) {0:04x?}\n\
383                 actual (hex)   {1:04x?}",
384                expected,
385                actual,
386            )
387        }
388        V128Pattern::I32x4(expected) => {
389            let actual = [
390                extract_lane_as_i32(actual, 0),
391                extract_lane_as_i32(actual, 1),
392                extract_lane_as_i32(actual, 2),
393                extract_lane_as_i32(actual, 3),
394            ];
395            if actual == *expected {
396                return Ok(());
397            }
398            bail!(
399                "expected {:11?}\n\
400                 actual   {:11?}\n\
401                 \n\
402                 expected (hex) {0:08x?}\n\
403                 actual (hex)   {1:08x?}",
404                expected,
405                actual,
406            )
407        }
408        V128Pattern::I64x2(expected) => {
409            let actual = [
410                extract_lane_as_i64(actual, 0),
411                extract_lane_as_i64(actual, 1),
412            ];
413            if actual == *expected {
414                return Ok(());
415            }
416            bail!(
417                "expected {:20?}\n\
418                 actual   {:20?}\n\
419                 \n\
420                 expected (hex) {0:016x?}\n\
421                 actual (hex)   {1:016x?}",
422                expected,
423                actual,
424            )
425        }
426        V128Pattern::F32x4(expected) => {
427            for (i, expected) in expected.iter().enumerate() {
428                let a = extract_lane_as_i32(actual, i) as u32;
429                match_f32(a, expected).with_context(|| format!("difference in lane {i}"))?;
430            }
431            Ok(())
432        }
433        V128Pattern::F64x2(expected) => {
434            for (i, expected) in expected.iter().enumerate() {
435                let a = extract_lane_as_i64(actual, i) as u64;
436                match_f64(a, expected).with_context(|| format!("difference in lane {i}"))?;
437            }
438            Ok(())
439        }
440    }
441}