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