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