Next Generation WASM Microkernel Operating System
1// Copyright 2025 Jonas Kruckenberg
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8use crate::wasm::{
9 ConstExprEvaluator, Engine, Extern, Instance, Linker, Module, PlaceholderAllocatorDontUse,
10 Store, Val,
11};
12use alloc::string::ToString;
13use alloc::sync::Arc;
14use alloc::vec::Vec;
15use alloc::{format, vec};
16use anyhow::{Context, anyhow, bail};
17use core::fmt::{Display, LowerHex};
18use spin::Mutex;
19use wasmparser::Validator;
20use wast::core::{EncodeOptions, NanPattern, V128Pattern, WastArgCore, WastRetCore};
21use wast::parser::ParseBuffer;
22use wast::token::{F32, F64};
23use wast::{
24 Error, QuoteWat, Wast, WastArg, WastDirective, WastExecute, WastInvoke, WastRet, Wat, parser,
25};
26
27macro_rules! wast_tests {
28 ($($names:ident $paths:literal,)*) => {
29 $(
30 #[ktest::test]
31 async fn $names() {
32 let mut ctx = $crate::tests::wast::WastContext::new_default().unwrap();
33
34 ctx.run($paths, include_str!($paths)).await.unwrap();
35 }
36 )*
37 };
38}
39pub(crate) use wast_tests;
40
41enum Outcome<T = Vec<Val>> {
42 Ok(T),
43 Trap(anyhow::Error),
44}
45
46impl<T> Outcome<T> {
47 fn map<U>(self, map: impl FnOnce(T) -> U) -> Outcome<U> {
48 match self {
49 Outcome::Ok(t) => Outcome::Ok(map(t)),
50 Outcome::Trap(t) => Outcome::Trap(t),
51 }
52 }
53
54 fn into_result(self) -> anyhow::Result<T> {
55 match self {
56 Outcome::Ok(t) => Ok(t),
57 Outcome::Trap(t) => Err(t),
58 }
59 }
60}
61
62pub struct WastContext(Arc<Mutex<WastContextInner>>);
63pub struct WastContextInner {
64 engine: Engine,
65 store: Store<()>,
66 linker: Linker<()>,
67 const_eval: ConstExprEvaluator,
68 validator: Validator,
69 current: Option<Instance>,
70}
71
72impl WastContext {
73 pub fn new_default() -> crate::Result<Self> {
74 let engine = Engine::default();
75 let mut linker = Linker::new(&engine);
76 let store = Store::new(&engine, &PlaceholderAllocatorDontUse, ());
77
78 linker.func_wrap("spectest", "print", || {})?;
79 linker.func_wrap("spectest", "print_i32", move |val: i32| {
80 tracing::debug!("{val}: i32")
81 })?;
82 linker.func_wrap("spectest", "print_i64", move |val: i64| {
83 tracing::debug!("{val}: i64")
84 })?;
85 linker.func_wrap("spectest", "print_f32", move |val: f32| {
86 tracing::debug!("{val}: f32")
87 })?;
88 linker.func_wrap("spectest", "print_f64", move |val: f64| {
89 tracing::debug!("{val}: f64")
90 })?;
91 linker.func_wrap("spectest", "print_i32_f32", move |i: i32, f: f32| {
92 tracing::debug!("{i}: i32");
93 tracing::debug!("{f}: f32");
94 })?;
95 linker.func_wrap("spectest", "print_f64_f64", move |f1: f64, f2: f64| {
96 tracing::debug!("{f1}: f64");
97 tracing::debug!("{f2}: f64");
98 })?;
99
100 // used in the hostfunc smoke tests
101 linker.func_wrap("k23", "roundtrip_i64", |arg: u64| -> u64 {
102 tracing::debug!("Hello World from hostfunc!");
103 arg
104 })?;
105
106 // let ty = GlobalType {
107 // content_type: ValType::I32,
108 // mutable: false,
109 // shared: false,
110 // };
111 // ctx.linker.define(
112 // ctx.store,
113 // "spectest",
114 // "global_i32",
115 // Global::new(ty, Value::I32(666)),
116 // )?;
117
118 // let ty = GlobalType {
119 // content_type: ValType::I64,
120 // mutable: false,
121 // shared: false,
122 // };
123 // ctx.linker.define(
124 // ctx.store,
125 // "spectest",
126 // "global_i64",
127 // Global::new(ty, Value::I64(666)),
128 // )?;
129
130 // let ty = GlobalType {
131 // content_type: ValType::F32,
132 // mutable: false,
133 // shared: false,
134 // };
135 // ctx.linker.define(
136 // ctx.store,
137 // "spectest",
138 // "global_f32",
139 // Global::new(ty, Value::F32(f32::from_bits(0x4426_a666u32))),
140 // )?;
141
142 // let ty = GlobalType {
143 // content_type: ValType::F64,
144 // mutable: false,
145 // shared: false,
146 // };
147 // ctx.linker.define(
148 // ctx.store,
149 // "spectest",
150 // "global_f64",
151 // Global::new(ty, Value::F64(f64::from_bits(0x4084_d4cc_cccc_cccd))),
152 // )?;
153
154 // let ty = TableType {
155 // element_type: RefType::FUNCREF,
156 // table64: false,
157 // initial: 10,
158 // maximum: Some(20),
159 // shared: false,
160 // };
161 // ctx.linker.define(
162 // ctx.store,
163 // "spectest",
164 // "table",
165 // Table::new(ty, Ref::Func(None)),
166 // )?;
167
168 // let ty = MemoryType {
169 // memory64: false,
170 // shared: false,
171 // initial: 1,
172 // maximum: Some(2),
173 // page_size_log2: None,
174 // };
175 // ctx.linker
176 // .define(&mut ctx.store, "spectest", "memory", Memory::new(ty))?;
177
178 Ok(Self(Arc::new(Mutex::new(WastContextInner {
179 engine,
180 linker,
181 store,
182 const_eval: ConstExprEvaluator::default(),
183 validator: Validator::new(),
184 current: None,
185 }))))
186 }
187
188 pub async fn run(&mut self, path: &str, wat: &str) -> crate::Result<()> {
189 let buf = ParseBuffer::new(&wat)?;
190 let wast = parser::parse::<Wast>(&buf)?;
191 for directive in wast.directives {
192 let span = directive.span();
193 let (line, col) = span.linecol_in(wat);
194 self.run_directive(directive, path, &wat)
195 .await
196 .with_context(|| format!("location ({path}:{line}:{col})"))?;
197 }
198 Ok(())
199 }
200
201 async fn run_directive(
202 &mut self,
203 directive: WastDirective<'_>,
204 path: &str,
205 wat: &str,
206 ) -> crate::Result<()> {
207 tracing::trace!("{directive:?}");
208 match directive {
209 WastDirective::Module(module) => self.module(module, path, wat)?,
210 WastDirective::Register { name, module, .. } => {
211 self.register(module.map(|s| s.name()), name)?;
212 }
213 WastDirective::Invoke(i) => {
214 self.perform_invoke(i).await?;
215 }
216 WastDirective::AssertMalformed { module, .. } => {
217 if let Ok(()) = self.module(module, path, wat) {
218 bail!("expected malformed module to fail to instantiate");
219 }
220 }
221 WastDirective::AssertInvalid {
222 module, message, ..
223 } => {
224 let err = match self.module(module, path, wat) {
225 Ok(()) => {
226 tracing::error!("expected module to fail to build");
227 return Ok(());
228 }
229 Err(e) => e,
230 };
231 let error_message = format!("{err:?}");
232
233 if !error_message.contains(message) {
234 bail!(
235 "assert_invalid: expected {}, got {}",
236 message,
237 error_message
238 )
239 }
240 }
241 WastDirective::AssertUnlinkable {
242 module, message, ..
243 } => {
244 let err = match self.module(QuoteWat::Wat(module), path, wat) {
245 Ok(()) => bail!("expected module to fail to link"),
246 Err(e) => e,
247 };
248 let error_message = format!("{err:?}");
249 if !error_message.contains(message) {
250 bail!(
251 "assert_unlinkable: expected {}, got {}",
252 message,
253 error_message
254 )
255 }
256 }
257 WastDirective::AssertTrap { exec, message, .. } => {
258 let result = self.perform_execute(exec).await?;
259 self.assert_trap(result, message)?;
260 }
261 WastDirective::AssertReturn { exec, results, .. } => {
262 let result = self.perform_execute(exec).await?;
263 self.assert_return(result, &results)?;
264 }
265 WastDirective::AssertExhaustion { call, message, .. } => {
266 let result = self.perform_invoke(call).await?;
267 self.assert_trap(result, message)?;
268 }
269 WastDirective::ModuleDefinition(_) => {}
270 WastDirective::ModuleInstance { .. } => {}
271 WastDirective::AssertException { .. } => {}
272 WastDirective::AssertSuspension { .. } => {}
273 WastDirective::Thread(_) => {}
274 WastDirective::Wait { .. } => {}
275 }
276
277 Ok(())
278 }
279
280 fn inner_mut(&mut self) -> &mut WastContextInner {
281 Mutex::get_mut(Arc::get_mut(&mut self.0).unwrap())
282 }
283
284 fn module(&mut self, mut wat: QuoteWat, _path: &str, _raw: &str) -> anyhow::Result<()> {
285 let encode_wat = |wat: &mut Wat<'_>| -> anyhow::Result<Vec<u8>> {
286 Ok(EncodeOptions::default()
287 // TODO .dwarf(path, raw, GenerateDwarf::Full)
288 .encode_wat(wat)?)
289 };
290
291 let bytes = match &mut wat {
292 QuoteWat::Wat(wat) => encode_wat(wat)?,
293 QuoteWat::QuoteModule(_, source) => {
294 let mut text = Vec::new();
295 for (_, src) in source {
296 text.extend_from_slice(src);
297 text.push(b' ');
298 }
299 let text = core::str::from_utf8(&text).map_err(|_| {
300 let span = wat.span();
301 Error::new(span, "malformed UTF-8 encoding".to_string())
302 })?;
303 let buf = ParseBuffer::new(text)?;
304 let mut wat = parser::parse::<Wat<'_>>(&buf)?;
305 encode_wat(&mut wat)?
306 }
307 QuoteWat::QuoteComponent(_, _) => unimplemented!(),
308 };
309
310 let instance = match self.instantiate_module(&bytes)? {
311 Outcome::Ok(i) => i,
312 Outcome::Trap(e) => return Err(e).context("instantiation failed"),
313 };
314
315 let inner = self.inner_mut();
316 if let Some(name) = wat.name() {
317 inner
318 .linker
319 .define_instance(&mut inner.store, name.name(), instance)?;
320 }
321 inner.current.replace(instance);
322
323 Ok(())
324 }
325
326 fn register(&mut self, name: Option<&str>, as_name: &str) -> anyhow::Result<()> {
327 let inner = self.inner_mut();
328 if let Some(name) = name {
329 inner.linker.alias_module(name, as_name)?
330 } else {
331 let current = inner.current.as_ref().context("no previous instance")?;
332 inner
333 .linker
334 .define_instance(&mut inner.store, as_name, *current)?
335 };
336
337 Ok(())
338 }
339
340 async fn perform_invoke(&mut self, exec: WastInvoke<'_>) -> anyhow::Result<Outcome> {
341 let export = self.get_export(exec.module.map(|i| i.name()), exec.name)?;
342 let func = export
343 .into_func()
344 .ok_or_else(|| anyhow!("no function named `{}`", exec.name))?;
345
346 let values = exec
347 .args
348 .iter()
349 .map(|v| match v {
350 WastArg::Core(v) => wast_arg_to_val(v),
351 // WastArg::Component(_) => bail!("expected component function, found core"),
352 _ => unreachable!(),
353 })
354 .collect::<anyhow::Result<Vec<_>>>()?;
355
356 let inner = self.inner_mut();
357 let ty = func.ty(&mut inner.store);
358 let this = self.0.clone();
359
360 // FIXME the virtual memory subsystem trap handling code will look for a current task
361 // in order to find the current address space to resole page faults against. This is why
362 // we need to wrap this call in a `spawn` that we immediately await (so the scheduling
363 // subsystem tracks it as a task). Ideally we would get rid of this and have some other
364 // mechanism of tracking the current address space...
365 // scheduler()
366 // .spawn(async move {
367 let mut results = vec![Val::I32(0); ty.results().len()];
368
369 match func.call(&mut this.lock().store, &values, &mut results) {
370 Ok(()) => Ok(Outcome::Ok(results)),
371 Err(e) => Ok(Outcome::Trap(e.into())),
372 }
373 // })
374 // .await
375 // .unwrap()
376 }
377
378 async fn perform_execute(&mut self, exec: WastExecute<'_>) -> anyhow::Result<Outcome> {
379 match exec {
380 WastExecute::Invoke(invoke) => self.perform_invoke(invoke).await,
381 WastExecute::Wat(mut module) => Ok(match &mut module {
382 Wat::Module(m) => self.instantiate_module(&m.encode()?)?.map(|_| Vec::new()),
383 _ => unimplemented!(),
384 }),
385 WastExecute::Get { module, global, .. } => {
386 self.get_global(module.map(|s| s.name()), global)
387 }
388 }
389 }
390
391 fn assert_return(&mut self, result: Outcome, results: &[WastRet<'_>]) -> anyhow::Result<()> {
392 let values = result.into_result()?;
393 if values.len() != results.len() {
394 bail!("expected {} results found {}", results.len(), values.len());
395 }
396 for (v, e) in values.iter().zip(results) {
397 let e = match e {
398 WastRet::Core(core) => core,
399 // WastRet::Component(_) => {
400 // bail!("expected component value found core value")
401 // }
402 _ => unreachable!(),
403 };
404
405 let inner = self.inner_mut();
406 match_val(&mut inner.store, v, e)?;
407 }
408
409 Ok(())
410 }
411
412 fn assert_trap(&self, result: Outcome, expected: &str) -> anyhow::Result<()> {
413 let trap = match result {
414 Outcome::Ok(values) => bail!("expected trap, got {:?}", values),
415 Outcome::Trap(t) => t,
416 };
417 let actual = format!("{trap:?}");
418 if actual.contains(expected)
419 // `bulk-memory-operations/bulk.wast` checks for a message that
420 // specifies which element is uninitialized, but our trap_handling don't
421 // shepherd that information out.
422 || (expected.contains("uninitialized element 2") && actual.contains("uninitialized element"))
423 // function references call_ref
424 || (expected.contains("null function") && (actual.contains("uninitialized element") || actual.contains("null reference")))
425 {
426 return Ok(());
427 }
428 bail!("expected '{}', got '{}'", expected, actual)
429 }
430
431 fn instantiate_module(&mut self, module: &[u8]) -> anyhow::Result<Outcome<Instance>> {
432 let inner = self.inner_mut();
433 let module = Module::from_bytes(&inner.engine, &mut inner.validator, module)?;
434
435 Ok(
436 match inner
437 .linker
438 .instantiate(&mut inner.store, &mut inner.const_eval, &module)
439 {
440 Ok(i) => Outcome::Ok(i),
441 Err(e) => Outcome::Trap(e.into()),
442 },
443 )
444 }
445
446 /// Get the value of an exported global from an instance.
447 fn get_global(&mut self, instance_name: Option<&str>, field: &str) -> anyhow::Result<Outcome> {
448 let ext = self.get_export(instance_name, field)?;
449 let global = ext
450 .into_global()
451 .ok_or_else(|| anyhow!("no global named `{field}`"))?;
452
453 let inner = self.inner_mut();
454 Ok(Outcome::Ok(vec![global.get(&mut inner.store)]))
455 }
456
457 fn get_export(&mut self, module: Option<&str>, name: &str) -> anyhow::Result<Extern> {
458 let inner = self.inner_mut();
459 if let Some(module) = module {
460 return inner
461 .linker
462 .get(&mut inner.store, module, name)
463 .clone()
464 .ok_or_else(|| anyhow!("no item named `{}::{}` found", module, name));
465 }
466
467 let cur = inner
468 .current
469 .as_ref()
470 .ok_or_else(|| anyhow!("no previous instance found"))?;
471
472 cur.get_export(&mut inner.store, name)
473 .ok_or_else(|| anyhow!("no item named `{}` found", name))
474 }
475}
476
477fn wast_arg_to_val(arg: &WastArgCore) -> anyhow::Result<Val> {
478 match arg {
479 WastArgCore::I32(v) => Ok(Val::I32(*v)),
480 WastArgCore::I64(v) => Ok(Val::I64(*v)),
481 WastArgCore::F32(v) => Ok(Val::F32(v.bits)),
482 WastArgCore::F64(v) => Ok(Val::F64(v.bits)),
483 WastArgCore::V128(v) => Ok(Val::V128(u128::from_le_bytes(v.to_le_bytes()))),
484 // WastArgCore::RefNull(HeapType::Abstract {
485 // ty: AbstractHeapType::Extern,
486 // shared: false,
487 // }) => Ok(VMVal::ExternRef(None)),
488 // WastArgCore::RefNull(HeapType::Abstract {
489 // ty: AbstractHeapType::Func,
490 // shared: false,
491 // }) => Ok(Value::FuncRef(None)),
492 // WastArgCore::RefExtern(x) => Ok(Value::ExternRef(Some(*x))),
493 other => bail!("couldn't convert {:?} to a runtime value", other),
494 }
495}
496
497pub fn match_val(store: &Store<()>, actual: &Val, expected: &WastRetCore) -> anyhow::Result<()> {
498 match (actual, expected) {
499 (_, WastRetCore::Either(expected)) => {
500 for expected in expected {
501 if match_val(store, actual, expected).is_ok() {
502 return Ok(());
503 }
504 }
505 match_val(store, actual, &expected[0])
506 }
507
508 (Val::I32(a), WastRetCore::I32(b)) => match_int(a, b),
509 (Val::I64(a), WastRetCore::I64(b)) => match_int(a, b),
510
511 // Note that these float comparisons are comparing bits, not float
512 // values, so we're testing for bit-for-bit equivalence
513 (Val::F32(a), WastRetCore::F32(b)) => match_f32(*a, b),
514 (Val::F64(a), WastRetCore::F64(b)) => match_f64(*a, b),
515 (Val::V128(a), WastRetCore::V128(b)) => match_v128(*a, b),
516
517 // Null references.
518 // (
519 // Val::FuncRef(None) | Val::ExternRef(None), /* | Value::AnyRef(None) */
520 // WastRetCore::RefNull(_),
521 // )
522 // | (Val::ExternRef(None), WastRetCore::RefExtern(None)) => Ok(()),
523 //
524 // // Null and non-null mismatches.
525 // (Val::ExternRef(None), WastRetCore::RefExtern(Some(_))) => {
526 // bail!("expected non-null reference, found null")
527 // }
528 // (
529 // Val::ExternRef(Some(x)),
530 // WastRetCore::RefNull(Some(HeapType::Abstract {
531 // ty: AbstractHeapType::Extern,
532 // shared: false,
533 // })),
534 // ) => {
535 // bail!("expected null externref, found non-null externref of {x}");
536 // }
537 // (Val::ExternRef(Some(_)) | Val::FuncRef(Some(_)), WastRetCore::RefNull(_)) => {
538 // bail!("expected null, found non-null reference: {actual:?}")
539 // }
540 //
541 // // // Non-null references.
542 // (Val::FuncRef(Some(_)), WastRetCore::RefFunc(_)) => Ok(()),
543 // (Val::ExternRef(Some(x)), WastRetCore::RefExtern(Some(y))) => {
544 // ensure!(x == y, "expected {} found {}", y, x);
545 // Ok(())
546 // // let x = x
547 // // .data(store)?
548 // // .downcast_ref::<u32>()
549 // // .expect("only u32 externrefs created in wast test suites");
550 // // if x == y {
551 // // Ok(())
552 // // } else {
553 // // bail!();
554 // // }
555 // }
556
557 // (Value::AnyRef(Some(x)), WastRetCore::RefI31) => {
558 // if x.is_i31(store)? {
559 // Ok(())
560 // } else {
561 // bail!("expected a `(ref i31)`, found {x:?}");
562 // }
563 // }
564 _ => bail!(
565 "don't know how to compare {:?} and {:?} yet",
566 actual,
567 expected
568 ),
569 }
570}
571
572pub fn match_int<T>(actual: &T, expected: &T) -> anyhow::Result<()>
573where
574 T: Eq + Display + LowerHex,
575{
576 if actual == expected {
577 Ok(())
578 } else {
579 bail!(
580 "expected {:18} / {0:#018x}\n\
581 actual {:18} / {1:#018x}",
582 expected,
583 actual
584 )
585 }
586}
587
588pub fn match_f32(actual: u32, expected: &NanPattern<F32>) -> anyhow::Result<()> {
589 match expected {
590 // Check if an f32 (as u32 bits to avoid possible quieting when moving values in registers, e.g.
591 // https://developer.arm.com/documentation/ddi0344/i/neon-and-vfp-programmers-model/modes-of-operation/default-nan-mode?lang=en)
592 // is a canonical NaN:
593 // - the sign bit is unspecified,
594 // - the 8-bit exponent is set to all 1s
595 // - the MSB of the payload is set to 1 (a quieted NaN) and all others to 0.
596 // See https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
597 NanPattern::CanonicalNan => {
598 let canon_nan = 0x7fc0_0000;
599 if (actual & 0x7fff_ffff) == canon_nan {
600 Ok(())
601 } else {
602 bail!(
603 "expected {:10} / {:#010x}\n\
604 actual {:10} / {:#010x}",
605 "canon-nan",
606 canon_nan,
607 f32::from_bits(actual),
608 actual,
609 )
610 }
611 }
612
613 // Check if an f32 (as u32, see comments above) is an arithmetic NaN.
614 // This is the same as a canonical NaN including that the payload MSB is
615 // set to 1, but one or more of the remaining payload bits MAY BE set to
616 // 1 (a canonical NaN specifies all 0s). See
617 // https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
618 NanPattern::ArithmeticNan => {
619 const AF32_NAN: u32 = 0x7f80_0000;
620 let is_nan = actual & AF32_NAN == AF32_NAN;
621 const AF32_PAYLOAD_MSB: u32 = 0x0040_0000;
622 let is_msb_set = actual & AF32_PAYLOAD_MSB == AF32_PAYLOAD_MSB;
623 if is_nan && is_msb_set {
624 Ok(())
625 } else {
626 bail!(
627 "expected {:>10} / {:>10}\n\
628 actual {:10} / {:#010x}",
629 "arith-nan",
630 "0x7fc*****",
631 f32::from_bits(actual),
632 actual,
633 )
634 }
635 }
636 NanPattern::Value(expected_value) => {
637 if actual == expected_value.bits {
638 Ok(())
639 } else {
640 bail!(
641 "expected {:10} / {:#010x}\n\
642 actual {:10} / {:#010x}",
643 f32::from_bits(expected_value.bits),
644 expected_value.bits,
645 f32::from_bits(actual),
646 actual,
647 )
648 }
649 }
650 }
651}
652
653pub fn match_f64(actual: u64, expected: &NanPattern<F64>) -> anyhow::Result<()> {
654 match expected {
655 // Check if an f64 (as u64 bits to avoid possible quieting when moving values in registers, e.g.
656 // https://developer.arm.com/documentation/ddi0344/i/neon-and-vfp-programmers-model/modes-of-operation/default-nan-mode?lang=en)
657 // is a canonical NaN:
658 // - the sign bit is unspecified,
659 // - the 11-bit exponent is set to all 1s
660 // - the MSB of the payload is set to 1 (a quieted NaN) and all others to 0.
661 // See https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
662 NanPattern::CanonicalNan => {
663 let canon_nan = 0x7ff8_0000_0000_0000;
664 if (actual & 0x7fff_ffff_ffff_ffff) == canon_nan {
665 Ok(())
666 } else {
667 bail!(
668 "expected {:18} / {:#018x}\n\
669 actual {:18} / {:#018x}",
670 "canon-nan",
671 canon_nan,
672 f64::from_bits(actual),
673 actual,
674 )
675 }
676 }
677
678 // Check if an f64 (as u64, see comments above) is an arithmetic NaN. This is the same as a
679 // canonical NaN including that the payload MSB is set to 1, but one or more of the remaining
680 // payload bits MAY BE set to 1 (a canonical NaN specifies all 0s). See
681 // https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
682 NanPattern::ArithmeticNan => {
683 const AF64_NAN: u64 = 0x7ff0_0000_0000_0000;
684 let is_nan = actual & AF64_NAN == AF64_NAN;
685 const AF64_PAYLOAD_MSB: u64 = 0x0008_0000_0000_0000;
686 let is_msb_set = actual & AF64_PAYLOAD_MSB == AF64_PAYLOAD_MSB;
687 if is_nan && is_msb_set {
688 Ok(())
689 } else {
690 bail!(
691 "expected {:>18} / {:>18}\n\
692 actual {:18} / {:#018x}",
693 "arith-nan",
694 "0x7ff8************",
695 f64::from_bits(actual),
696 actual,
697 )
698 }
699 }
700 NanPattern::Value(expected_value) => {
701 if actual == expected_value.bits {
702 Ok(())
703 } else {
704 bail!(
705 "expected {:18} / {:#018x}\n\
706 actual {:18} / {:#018x}",
707 f64::from_bits(expected_value.bits),
708 expected_value.bits,
709 f64::from_bits(actual),
710 actual,
711 )
712 }
713 }
714 }
715}
716
717fn match_v128(actual: u128, expected: &V128Pattern) -> anyhow::Result<()> {
718 match expected {
719 V128Pattern::I8x16(expected) => {
720 let actual = [
721 extract_lane_as_i8(actual, 0),
722 extract_lane_as_i8(actual, 1),
723 extract_lane_as_i8(actual, 2),
724 extract_lane_as_i8(actual, 3),
725 extract_lane_as_i8(actual, 4),
726 extract_lane_as_i8(actual, 5),
727 extract_lane_as_i8(actual, 6),
728 extract_lane_as_i8(actual, 7),
729 extract_lane_as_i8(actual, 8),
730 extract_lane_as_i8(actual, 9),
731 extract_lane_as_i8(actual, 10),
732 extract_lane_as_i8(actual, 11),
733 extract_lane_as_i8(actual, 12),
734 extract_lane_as_i8(actual, 13),
735 extract_lane_as_i8(actual, 14),
736 extract_lane_as_i8(actual, 15),
737 ];
738 if actual == *expected {
739 return Ok(());
740 }
741 bail!(
742 "expected {:4?}\n\
743 actual {:4?}\n\
744 \n\
745 expected (hex) {0:02x?}\n\
746 actual (hex) {1:02x?}",
747 expected,
748 actual,
749 )
750 }
751 V128Pattern::I16x8(expected) => {
752 let actual = [
753 extract_lane_as_i16(actual, 0),
754 extract_lane_as_i16(actual, 1),
755 extract_lane_as_i16(actual, 2),
756 extract_lane_as_i16(actual, 3),
757 extract_lane_as_i16(actual, 4),
758 extract_lane_as_i16(actual, 5),
759 extract_lane_as_i16(actual, 6),
760 extract_lane_as_i16(actual, 7),
761 ];
762 if actual == *expected {
763 return Ok(());
764 }
765 bail!(
766 "expected {:6?}\n\
767 actual {:6?}\n\
768 \n\
769 expected (hex) {0:04x?}\n\
770 actual (hex) {1:04x?}",
771 expected,
772 actual,
773 )
774 }
775 V128Pattern::I32x4(expected) => {
776 let actual = [
777 extract_lane_as_i32(actual, 0),
778 extract_lane_as_i32(actual, 1),
779 extract_lane_as_i32(actual, 2),
780 extract_lane_as_i32(actual, 3),
781 ];
782 if actual == *expected {
783 return Ok(());
784 }
785 bail!(
786 "expected {:11?}\n\
787 actual {:11?}\n\
788 \n\
789 expected (hex) {0:08x?}\n\
790 actual (hex) {1:08x?}",
791 expected,
792 actual,
793 )
794 }
795 V128Pattern::I64x2(expected) => {
796 let actual = [
797 extract_lane_as_i64(actual, 0),
798 extract_lane_as_i64(actual, 1),
799 ];
800 if actual == *expected {
801 return Ok(());
802 }
803 bail!(
804 "expected {:20?}\n\
805 actual {:20?}\n\
806 \n\
807 expected (hex) {0:016x?}\n\
808 actual (hex) {1:016x?}",
809 expected,
810 actual,
811 )
812 }
813 V128Pattern::F32x4(expected) => {
814 for (i, expected) in expected.iter().enumerate() {
815 let a = extract_lane_as_i32(actual, i) as u32;
816 match_f32(a, expected).with_context(|| format!("difference in lane {i}"))?;
817 }
818 Ok(())
819 }
820 V128Pattern::F64x2(expected) => {
821 for (i, expected) in expected.iter().enumerate() {
822 let a = extract_lane_as_i64(actual, i) as u64;
823 match_f64(a, expected).with_context(|| format!("difference in lane {i}"))?;
824 }
825 Ok(())
826 }
827 }
828}
829
830fn extract_lane_as_i8(bytes: u128, lane: usize) -> i8 {
831 (bytes >> (lane * 8)) as i8
832}
833
834fn extract_lane_as_i16(bytes: u128, lane: usize) -> i16 {
835 (bytes >> (lane * 16)) as i16
836}
837
838fn extract_lane_as_i32(bytes: u128, lane: usize) -> i32 {
839 (bytes >> (lane * 32)) as i32
840}
841
842fn extract_lane_as_i64(bytes: u128, lane: usize) -> i64 {
843 (bytes >> (lane * 64)) as i64
844}