Next Generation WASM Microkernel Operating System
at trap_handler 568 lines 20 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::Func; 9use crate::wasm::store::StoreOpaque; 10use crate::wasm::types::{HeapType, HeapTypeInner, RefType, ValType}; 11use crate::wasm::utils::enum_accessors; 12use crate::wasm::vm::{TableElement, VMVal}; 13use anyhow::bail; 14use core::ptr; 15 16#[derive(Debug, Clone, Copy)] 17pub enum Val { 18 I32(i32), 19 I64(i64), 20 F32(u32), 21 F64(u64), 22 V128(u128), 23 /// A first-class reference to a WebAssembly function. 24 /// 25 /// The host, or the Wasm guest, can invoke this function. 26 /// 27 /// The host can create function references via [`Func::wrap`]. 28 /// 29 /// The Wasm guest can create non-null function references via the 30 /// `ref.func` instruction, or null references via the `ref.null func` 31 /// instruction. 32 FuncRef(Option<Func>), 33} 34 35impl Val { 36 /// Returns the null reference for the given heap type. 37 #[inline] 38 pub fn null_ref(heap_type: &HeapType) -> Val { 39 Ref::null(heap_type).into() 40 } 41 42 /// Returns the null function reference value. 43 /// 44 /// The return value has type `(ref null nofunc)` aka `nullfuncref` and is a 45 /// subtype of all function references. 46 #[inline] 47 pub const fn null_func_ref() -> Val { 48 Val::FuncRef(None) 49 } 50 51 // /// Returns the null function reference value. 52 // /// 53 // /// The return value has type `(ref null extern)` aka `nullexternref` and is 54 // /// a subtype of all external references. 55 // #[inline] 56 // pub const fn null_extern_ref() -> Val { 57 // Val::ExternRef(None) 58 // } 59 // 60 // /// Returns the null function reference value. 61 // /// 62 // /// The return value has type `(ref null any)` aka `nullref` and is a 63 // /// subtype of all internal references. 64 // #[inline] 65 // pub const fn null_any_ref() -> Val { 66 // Val::AnyRef(None) 67 // } 68 69 /// Returns the default value for the given type, if any exists. 70 /// 71 /// Returns `None` if there is no default value for the given type (for 72 /// example, non-nullable reference types do not have a default value). 73 pub fn default_for_ty(ty: &ValType) -> Option<Val> { 74 match ty { 75 ValType::I32 => Some(Val::I32(0)), 76 ValType::I64 => Some(Val::I64(0)), 77 ValType::F32 => Some(Val::F32(0)), 78 ValType::F64 => Some(Val::F64(0)), 79 ValType::V128 => Some(Val::V128(0)), 80 ValType::Ref(ref_ty) => { 81 if ref_ty.is_nullable() { 82 Some(Val::null_ref(ref_ty.heap_type())) 83 } else { 84 None 85 } 86 } 87 } 88 } 89 90 /// Returns the corresponding [`ValType`] for this `Val`. 91 /// 92 /// # Errors 93 /// 94 /// Returns an error if this value is a GC reference that has since been 95 /// unrooted. 96 /// 97 /// # Panics 98 /// 99 /// Panics if this value is associated with a different store. 100 #[inline] 101 #[expect(clippy::unnecessary_wraps, reason = "TODO")] 102 pub fn ty(&self, store: &StoreOpaque) -> crate::Result<ValType> { 103 Ok(match self { 104 Val::I32(_) => ValType::I32, 105 Val::I64(_) => ValType::I64, 106 Val::F32(_) => ValType::F32, 107 Val::F64(_) => ValType::F64, 108 Val::V128(_) => ValType::V128, 109 Val::FuncRef(None) => ValType::NULLFUNCREF, 110 Val::FuncRef(Some(f)) => { 111 ValType::Ref(RefType::new(false, HeapType::concrete_func(f.ty(store)))) 112 } // Val::ExternRef(Some(_)) => ValType::EXTERNREF, 113 // Val::ExternRef(None) => ValType::NULLFUNCREF, 114 // Val::AnyRef(None) => ValType::NULLREF, 115 // Val::AnyRef(Some(a)) => ValType::Ref(RefType::new(false, a._ty(store)?)), 116 }) 117 } 118 119 /// Does this value match the given type? 120 /// 121 /// Returns an error is an underlying `Rooted` has been unrooted. 122 /// 123 /// # Panics 124 /// 125 /// Panics if this value is not associated with the given store. 126 pub fn matches_ty(&self, store: &StoreOpaque, ty: &ValType) -> crate::Result<bool> { 127 assert!(self.comes_from_same_store(store)); 128 assert!(ty.comes_from_same_engine(store.engine())); 129 Ok(match (self, ty) { 130 (Val::I32(_), ValType::I32) 131 | (Val::I64(_), ValType::I64) 132 | (Val::F32(_), ValType::F32) 133 | (Val::F64(_), ValType::F64) 134 | (Val::V128(_), ValType::V128) => true, 135 136 (Val::FuncRef(f), ValType::Ref(ref_ty)) => Ref::from(*f).matches_ty(store, ref_ty)?, 137 138 ( 139 Val::I32(_) 140 | Val::I64(_) 141 | Val::F32(_) 142 | Val::F64(_) 143 | Val::V128(_) 144 | Val::FuncRef(_), 145 _, 146 ) => false, 147 }) 148 } 149 150 pub(crate) fn ensure_matches_ty(&self, store: &StoreOpaque, ty: &ValType) -> crate::Result<()> { 151 if !self.comes_from_same_store(store) { 152 bail!("value used with wrong store") 153 } 154 if !ty.comes_from_same_engine(store.engine()) { 155 bail!("type used with wrong engine") 156 } 157 if self.matches_ty(store, ty)? { 158 Ok(()) 159 } else { 160 let actual_ty = self.ty(store)?; 161 bail!("type mismatch: expected {ty}, found {actual_ty}") 162 } 163 } 164 165 /// Convenience method to convert this [`Val`] into a [`VMVal`]. 166 /// 167 /// Returns an error if this value is a GC reference and the GC reference 168 /// has been unrooted. 169 /// 170 /// # Unsafety 171 /// 172 /// This method is unsafe for the reasons that 173 /// [`Func::to_vmval`] are unsafe. 174 #[expect(clippy::unnecessary_wraps, reason = "TODO")] 175 pub(super) unsafe fn to_vmval(self, store: &mut StoreOpaque) -> crate::Result<VMVal> { 176 // Safety: ensured by caller 177 unsafe { 178 match self { 179 Val::I32(i) => Ok(VMVal::i32(i)), 180 Val::I64(i) => Ok(VMVal::i64(i)), 181 Val::F32(u) => Ok(VMVal::f32(u)), 182 Val::F64(u) => Ok(VMVal::f64(u)), 183 Val::V128(b) => Ok(VMVal::v128(b)), 184 Val::FuncRef(f) => Ok(VMVal::funcref(match f { 185 None => ptr::null_mut(), 186 Some(e) => e.to_vmval(store), 187 })), 188 } 189 } 190 } 191 192 /// Convenience method to convert a [`VMVal`] into a [`Val`]. 193 /// 194 /// # Unsafety 195 /// 196 /// This method is unsafe for the reasons that 197 /// [`Func::from_vmval`] are unsafe. Additionally there's no guarantee 198 /// otherwise that `raw` should have the type `ty` specified. 199 pub(super) unsafe fn from_vmval(store: &mut StoreOpaque, vmval: VMVal, ty: ValType) -> Val { 200 // Safety: ensured by caller 201 unsafe { 202 match ty { 203 ValType::I32 => Val::I32(vmval.get_i32()), 204 ValType::I64 => Val::I64(vmval.get_i64()), 205 ValType::F32 => Val::F32(vmval.get_f32()), 206 ValType::F64 => Val::F64(vmval.get_f64()), 207 ValType::V128 => Val::V128(vmval.get_v128()), 208 ValType::Ref(ref_ty) => { 209 let ref_ = match ref_ty.heap_type().inner { 210 HeapTypeInner::Func | HeapTypeInner::ConcreteFunc(_) => { 211 Func::from_vmval(store, vmval.get_funcref()).into() 212 } 213 214 HeapTypeInner::NoFunc => Ref::Func(None), 215 216 HeapTypeInner::Extern => todo!(), 217 218 HeapTypeInner::NoExtern => todo!(), 219 220 HeapTypeInner::Any 221 | HeapTypeInner::Eq 222 | HeapTypeInner::I31 223 | HeapTypeInner::Array 224 | HeapTypeInner::ConcreteArray(_) 225 | HeapTypeInner::Struct 226 | HeapTypeInner::ConcreteStruct(_) => { 227 todo!() 228 } 229 HeapTypeInner::None => todo!(), 230 231 HeapTypeInner::Exn | HeapTypeInner::NoExn => todo!(), 232 HeapTypeInner::Cont | HeapTypeInner::NoCont => todo!(), 233 }; 234 assert!( 235 ref_ty.is_nullable() || !ref_.is_null(), 236 "if the type is not nullable, we shouldn't get null; got \ 237 type = {ref_ty}, ref = {ref_:?}" 238 ); 239 ref_.into() 240 } 241 } 242 } 243 } 244 245 enum_accessors! { 246 e 247 (I32(i32) i32 get_i32 unwrap_i32 *e) 248 (I64(i64) i64 get_i64 unwrap_i64 *e) 249 (F32(f32) f32 get_f32 unwrap_f32 f32::from_bits(*e)) 250 (F64(f64) f64 get_f64 unwrap_f64 f64::from_bits(*e)) 251 (V128(u128) v128 get_v128 unwrap_v128 *e) 252 (FuncRef(Option<&Func>) func_ref get_func_ref unwrap_func_ref e.as_ref()) 253 // (ExternRef(Option<&Rooted<ExternRef>>) extern_ref unwrap_extern_ref e.as_ref()) 254 // (AnyRef(Option<&Rooted<AnyRef>>) any_ref unwrap_any_ref e.as_ref()) 255 } 256 257 #[inline] 258 pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool { 259 match self { 260 Val::FuncRef(Some(f)) => f.comes_from_same_store(store), 261 Val::FuncRef(None) => true, 262 263 // Val::ExternRef(Some(x)) => x.comes_from_same_store(store), 264 // Val::ExternRef(None) => true, 265 // 266 // Val::AnyRef(Some(a)) => a.comes_from_same_store(store), 267 // Val::AnyRef(None) => true, 268 269 // Integers, floats, and vectors have no association with any 270 // particular store, so they're always considered as "yes I came 271 // from that store", 272 Val::I32(_) | Val::I64(_) | Val::F32(_) | Val::F64(_) | Val::V128(_) => true, 273 } 274 } 275} 276 277impl From<i32> for Val { 278 #[inline] 279 fn from(val: i32) -> Val { 280 Val::I32(val) 281 } 282} 283 284impl From<i64> for Val { 285 #[inline] 286 fn from(val: i64) -> Val { 287 Val::I64(val) 288 } 289} 290 291impl From<f32> for Val { 292 #[inline] 293 fn from(val: f32) -> Val { 294 Val::F32(val.to_bits()) 295 } 296} 297 298impl From<f64> for Val { 299 #[inline] 300 fn from(val: f64) -> Val { 301 Val::F64(val.to_bits()) 302 } 303} 304 305impl From<Ref> for Val { 306 #[inline] 307 fn from(val: Ref) -> Val { 308 match val { 309 Ref::Func(f) => Val::FuncRef(f), 310 // Ref::Extern(e) => Val::ExternRef(e), 311 // Ref::Any(a) => Val::AnyRef(a), 312 } 313 } 314} 315 316impl From<Func> for Val { 317 #[inline] 318 fn from(val: Func) -> Val { 319 Val::FuncRef(Some(val)) 320 } 321} 322 323impl From<Option<Func>> for Val { 324 #[inline] 325 fn from(val: Option<Func>) -> Val { 326 Val::FuncRef(val) 327 } 328} 329 330impl From<u128> for Val { 331 #[inline] 332 fn from(val: u128) -> Val { 333 Val::V128(val) 334 } 335} 336 337#[derive(Debug)] 338pub enum Ref { 339 // NB: We have a variant for each of the type hierarchies defined in Wasm, 340 // and push the `Option` that provides nullability into each variant. This 341 // allows us to get the most-precise type of any reference value, whether it 342 // is null or not, without any additional metadata. 343 // 344 // Consider if we instead had the nullability inside `Val::Ref` and each of 345 // the `Ref` variants did not have an `Option`: 346 // 347 // enum Val { 348 // Ref(Option<Ref>), 349 // // Etc... 350 // } 351 // enum Ref { 352 // Func(Func), 353 // External(ExternRef), 354 // // Etc... 355 // } 356 // 357 // In this scenario, what type would we return from `Val::ty` for 358 // `Val::Ref(None)`? Because Wasm has multiple separate type hierarchies, 359 // there is no single common bottom type for all the different kinds of 360 // references. So in this scenario, `Val::Ref(None)` doesn't have enough 361 // information to reconstruct the value's type. That's a problem for us 362 // because we need to get a value's type at various times all over the code 363 // base. 364 // 365 /// A first-class reference to a WebAssembly function. 366 /// 367 /// The host, or the Wasm guest, can invoke this function. 368 /// 369 /// The host can create function references via [`Func::wrap`]. 370 /// 371 /// The Wasm guest can create non-null function references via the 372 /// `ref.func` instruction, or null references via the `ref.null func` 373 /// instruction. 374 Func(Option<Func>), 375} 376 377impl Ref { 378 /// Create a null reference to the given heap type. 379 #[inline] 380 pub fn null(heap_type: &HeapType) -> Self { 381 match heap_type.top().inner { 382 // HeapType::Any => Ref::Any(None), 383 // HeapType::Extern => Ref::Extern(None), 384 HeapTypeInner::Func => Ref::Func(None), 385 ty => unreachable!("not a heap type: {ty:?}"), 386 } 387 } 388 389 /// Is this a null reference? 390 #[inline] 391 pub fn is_null(&self) -> bool { 392 match self { 393 Ref::Func(None) => true, 394 Ref::Func(Some(_)) => false, 395 // 396 // Ref::Any(None) | Ref::Extern(None) | Ref::Func(None) => true, 397 // Ref::Any(Some(_)) | Ref::Extern(Some(_)) | Ref::Func(Some(_)) => false, 398 } 399 } 400 401 /// Is this a non-null reference? 402 #[inline] 403 pub fn is_non_null(&self) -> bool { 404 !self.is_null() 405 } 406 407 /// Get the type of this reference. 408 /// 409 /// # Errors 410 /// 411 /// Return an error if this reference has been unrooted. 412 /// 413 /// # Panics 414 /// 415 /// Panics if this reference is associated with a different store. 416 #[expect(clippy::unnecessary_wraps, reason = "TODO")] 417 pub fn ty(&self, store: &StoreOpaque) -> crate::Result<RefType> { 418 assert!(self.comes_from_same_store(store)); 419 Ok(RefType::new( 420 self.is_null(), 421 // NB: We choose the most-specific heap type we can here and let 422 // subtyping do its thing if callers are matching against a 423 // `HeapType::Func`. 424 match self { 425 // Ref::Extern(None) => HeapType::NoExtern, 426 // Ref::Extern(Some(_)) => HeapType::Extern, 427 Ref::Func(None) => HeapType { 428 shared: false, 429 inner: HeapTypeInner::NoFunc, 430 }, 431 Ref::Func(Some(f)) => HeapType { 432 shared: false, 433 inner: HeapTypeInner::ConcreteFunc(f.ty(store)), 434 }, 435 // Ref::Any(None) => HeapType::None, 436 // Ref::Any(Some(a)) => a._ty(store)?, 437 }, 438 )) 439 } 440 441 #[expect(clippy::unnecessary_wraps, reason = "TODO")] 442 pub fn matches_ty(&self, store: &StoreOpaque, ty: &RefType) -> crate::Result<bool> { 443 assert!(self.comes_from_same_store(store)); 444 assert!(ty.comes_from_same_engine(store.engine())); 445 if self.is_null() && !ty.is_nullable() { 446 return Ok(false); 447 } 448 Ok(match (self, &ty.heap_type().inner) { 449 // (Ref::Extern(_), HeapType::Extern) => true, 450 // (Ref::Extern(None), HeapType::NoExtern) => true, 451 // (Ref::Extern(_), _) => false, 452 (Ref::Func(_), HeapTypeInner::Func) => true, 453 (Ref::Func(None), HeapTypeInner::NoFunc | HeapTypeInner::ConcreteFunc(_)) => true, 454 (Ref::Func(Some(f)), HeapTypeInner::ConcreteFunc(func_ty)) => { 455 f.matches_ty(store, func_ty.clone()) 456 } 457 (Ref::Func(_), _) => false, 458 // (Ref::Any(_), HeapType::Any) => true, 459 // (Ref::Any(Some(a)), HeapType::I31) => a._is_i31(store)?, 460 // (Ref::Any(Some(a)), HeapType::Struct) => a._is_struct(store)?, 461 // (Ref::Any(Some(a)), HeapType::ConcreteStruct(_ty)) => match a._as_struct(store)? { 462 // None => false, 463 // #[cfg_attr(not(feature = "gc"), allow(unreachable_patterns))] 464 // Some(s) => s._matches_ty(store, _ty)?, 465 // }, 466 // (Ref::Any(Some(a)), HeapType::Eq) => a._is_eqref(store)?, 467 // (Ref::Any(Some(a)), HeapType::Array) => a._is_array(store)?, 468 // (Ref::Any(Some(a)), HeapType::ConcreteArray(_ty)) => match a._as_array(store)? { 469 // None => false, 470 // #[cfg_attr(not(feature = "gc"), allow(unreachable_patterns))] 471 // Some(a) => a._matches_ty(store, _ty)?, 472 // }, 473 // ( 474 // Ref::Any(None), 475 // HeapType::None 476 // | HeapType::I31 477 // | HeapType::ConcreteStruct(_) 478 // | HeapType::Struct 479 // | HeapType::ConcreteArray(_) 480 // | HeapType::Array 481 // | HeapType::Eq, 482 // ) => true, 483 // (Ref::Any(_), _) => false, 484 }) 485 } 486 487 pub fn ensure_matches_ty(&self, store: &StoreOpaque, ty: &RefType) -> crate::Result<()> { 488 if !self.comes_from_same_store(store) { 489 bail!("reference used with wrong store") 490 } 491 if !ty.comes_from_same_engine(store.engine()) { 492 bail!("type used with wrong engine") 493 } 494 if self.matches_ty(store, ty)? { 495 Ok(()) 496 } else { 497 let actual_ty = self.ty(store)?; 498 bail!("type mismatch: expected {ty}, found {actual_ty}") 499 } 500 } 501 502 pub(super) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool { 503 match self { 504 Ref::Func(Some(f)) => f.comes_from_same_store(store), 505 Ref::Func(None) => true, 506 // Ref::Extern(Some(x)) => x.comes_from_same_store(store), 507 // Ref::Extern(None) => true, 508 // Ref::Any(Some(a)) => a.comes_from_same_store(store), 509 // Ref::Any(None) => true, 510 } 511 } 512 513 pub(crate) fn into_table_element( 514 self, 515 store: &mut StoreOpaque, 516 ty: &RefType, 517 ) -> crate::Result<TableElement> { 518 let heap_top_ty = ty.heap_type().top(); 519 match (self, heap_top_ty) { 520 ( 521 Ref::Func(None), 522 HeapType { 523 inner: HeapTypeInner::NoFunc, 524 shared: _, 525 }, 526 ) => { 527 assert!(ty.is_nullable()); 528 Ok(TableElement::FuncRef(None)) 529 } 530 ( 531 Ref::Func(Some(f)), 532 HeapType { 533 inner: HeapTypeInner::Func, 534 shared: _, 535 }, 536 ) => { 537 debug_assert!( 538 f.comes_from_same_store(store), 539 "checked in `ensure_matches_ty`" 540 ); 541 Ok(TableElement::FuncRef(Some(f.vm_func_ref(store)))) 542 } 543 _ => unimplemented!(), 544 } 545 } 546} 547 548#[expect(irrefutable_let_patterns, reason = "there is only one variant rn")] 549impl Ref { 550 enum_accessors! { 551 e 552 (Func(Option<&Func>) func_ref get_func_ref unwrap_func_ref e.as_ref()) 553 } 554} 555 556impl From<Func> for Ref { 557 #[inline] 558 fn from(f: Func) -> Ref { 559 Ref::Func(Some(f)) 560 } 561} 562 563impl From<Option<Func>> for Ref { 564 #[inline] 565 fn from(f: Option<Func>) -> Ref { 566 Ref::Func(f) 567 } 568}