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