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