Next Generation WASM Microkernel Operating System
at trap_handler 844 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 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}