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