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