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 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}