diff --git a/crates/wasmtime/src/runtime/func.rs b/crates/wasmtime/src/runtime/func.rs index fd45ba84d31a..9620e534b3bc 100644 --- a/crates/wasmtime/src/runtime/func.rs +++ b/crates/wasmtime/src/runtime/func.rs @@ -615,6 +615,8 @@ impl Func { /// | `Rooted` | `(ref struct)` | /// | `Option>` | `(ref null array)` | /// | `Rooted` | `(ref array)` | + /// | `Option` | `nullref` aka `(ref null none)` | + /// | `NoneRef` | `(ref none)` | /// /// Note that anywhere a `Rooted` appears, a `ManuallyRooted` may also /// be used. @@ -1444,6 +1446,8 @@ impl Func { /// | `(ref struct)` | `Rooted` | /// | `arrayref` aka `(ref null array)` | `Option>` | /// | `(ref array)` | `Rooted` | + /// | `nullref` aka `(ref null none)` | `Option` | + /// | `(ref none)` | `NoneRef` | /// | `funcref` aka `(ref null func)` | `Option` | /// | `(ref func)` | `Func` | /// | `(ref null )` | `Option` | diff --git a/crates/wasmtime/src/runtime/gc.rs b/crates/wasmtime/src/runtime/gc.rs index f6697b1057a0..bcce10e76f62 100644 --- a/crates/wasmtime/src/runtime/gc.rs +++ b/crates/wasmtime/src/runtime/gc.rs @@ -11,6 +11,9 @@ pub use disabled::*; mod noextern; pub use noextern::NoExtern; +mod none_ref; +pub use none_ref::NoneRef; + use core::fmt; use core::ops::Deref; diff --git a/crates/wasmtime/src/runtime/gc/none_ref.rs b/crates/wasmtime/src/runtime/gc/none_ref.rs new file mode 100644 index 000000000000..17cd47587480 --- /dev/null +++ b/crates/wasmtime/src/runtime/gc/none_ref.rs @@ -0,0 +1,154 @@ +use crate::{ + store::{AutoAssertNoGc, StoreOpaque}, + HeapType, Ref, RefType, Result, Uninhabited, Val, ValRaw, ValType, WasmTy, +}; +use core::mem::MaybeUninit; + +/// A reference to the abstract `none` heap value. +/// +/// The are no instances of `(ref none)`: it is an uninhabited type. +/// +/// There is precisely one instance of `(ref null none)`, aka `nullref`: the +/// null reference. +/// +/// This `NoneRef` Rust type's sole purpose is for use with +/// [`Func::wrap`][crate::Func::wrap]- and +/// [`Func::typed`][crate::Func::typed]-style APIs for statically typing a +/// function as taking or returning a `(ref null none)` (aka `Option`) +/// which is always `None`. +/// +/// # Example +/// +/// ``` +/// # use wasmtime::*; +/// # fn _foo() -> Result<()> { +/// let mut config = Config::new(); +/// config.wasm_function_references(true); +/// config.wasm_gc(true); +/// let engine = Engine::new(&config)?; +/// +/// let module = Module::new( +/// &engine, +/// r#" +/// (module +/// (func (export "f") (param (ref null none)) +/// ;; If the reference is null, return. +/// local.get 0 +/// ref.is_null none +/// br_if 0 +/// +/// ;; If the reference was not null (which is impossible) +/// ;; then raise a trap. +/// unreachable +/// ) +/// ) +/// "#, +/// )?; +/// +/// let mut store = Store::new(&engine, ()); +/// let instance = Instance::new(&mut store, &module, &[])?; +/// let f = instance.get_func(&mut store, "f").unwrap(); +/// +/// // We can cast a `(ref null none)`-taking function into a typed function that +/// // takes an `Option` via the `Func::typed` method. +/// let f = f.typed::, ()>(&store)?; +/// +/// // We can call the typed function, passing the null `none` reference. +/// let result = f.call(&mut store, NoneRef::null()); +/// +/// // The function should not have trapped, because the reference we gave it was +/// // null (as it had to be, since `NoneRef` is uninhabited). +/// assert!(result.is_ok()); +/// # Ok(()) +/// # } +/// ``` +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct NoneRef { + _inner: Uninhabited, +} + +impl NoneRef { + /// Get the null `(ref null none)` (aka `nullexternref`) reference. + #[inline] + pub fn null() -> Option { + None + } + + /// Get the null `(ref null none)` (aka `nullexternref`) reference as a + /// [`Ref`]. + #[inline] + pub fn null_ref() -> Ref { + Ref::Extern(None) + } + + /// Get the null `(ref null none)` (aka `nullexternref`) reference as a + /// [`Val`]. + #[inline] + pub fn null_val() -> Val { + Val::ExternRef(None) + } +} + +unsafe impl WasmTy for NoneRef { + #[inline] + fn valtype() -> ValType { + ValType::Ref(RefType::new(false, HeapType::None)) + } + + #[inline] + fn compatible_with_store(&self, _store: &StoreOpaque) -> bool { + match self._inner {} + } + + #[inline] + fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> { + match self._inner {} + } + + #[inline] + fn is_vmgcref_and_points_to_object(&self) -> bool { + match self._inner {} + } + + fn store(self, _store: &mut AutoAssertNoGc<'_>, _ptr: &mut MaybeUninit) -> Result<()> { + match self._inner {} + } + + unsafe fn load(_store: &mut AutoAssertNoGc<'_>, _ptr: &ValRaw) -> Self { + unreachable!("NoneRef is uninhabited") + } +} + +unsafe impl WasmTy for Option { + #[inline] + fn valtype() -> ValType { + ValType::Ref(RefType::new(true, HeapType::None)) + } + + #[inline] + fn compatible_with_store(&self, _store: &StoreOpaque) -> bool { + true + } + + #[inline] + fn dynamic_concrete_type_check( + &self, + _store: &StoreOpaque, + _nullable: bool, + _ty: &HeapType, + ) -> Result<()> { + unreachable!() + } + + #[inline] + fn store(self, _store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit) -> Result<()> { + ptr.write(ValRaw::externref(0)); + Ok(()) + } + + #[inline] + unsafe fn load(_store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self { + debug_assert_eq!(ptr.get_externref(), 0); + None + } +} diff --git a/tests/all/func.rs b/tests/all/func.rs index cc92d7320060..65a8e69924db 100644 --- a/tests/all/func.rs +++ b/tests/all/func.rs @@ -413,6 +413,8 @@ fn func_constructors() { Func::wrap(&mut store, || -> Option { None }); Func::wrap(&mut store, || -> NoExtern { loop {} }); Func::wrap(&mut store, || -> Option { None }); + Func::wrap(&mut store, || -> NoneRef { loop {} }); + Func::wrap(&mut store, || -> Option { None }); Func::wrap(&mut store, || -> Result<()> { loop {} }); Func::wrap(&mut store, || -> Result { loop {} }); @@ -444,6 +446,8 @@ fn func_constructors() { Func::wrap(&mut store, || -> Result> { loop {} }); Func::wrap(&mut store, || -> Result { loop {} }); Func::wrap(&mut store, || -> Result> { loop {} }); + Func::wrap(&mut store, || -> Result { loop {} }); + Func::wrap(&mut store, || -> Result> { loop {} }); } #[test]