Next Generation WASM Microkernel Operating System
at main 846 lines 30 kB view raw
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}