soroban_wasmi/
externref.rs

1use crate::{
2    collections::arena::ArenaIndex,
3    core::UntypedVal,
4    reftype::Transposer,
5    store::Stored,
6    AsContextMut,
7    StoreContext,
8};
9use core::{any::Any, num::NonZeroU32};
10use std::boxed::Box;
11
12/// A raw index to a function entity.
13#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
14pub struct ExternObjectIdx(NonZeroU32);
15
16impl ArenaIndex for ExternObjectIdx {
17    fn into_usize(self) -> usize {
18        self.0.get().wrapping_sub(1) as usize
19    }
20
21    fn from_usize(index: usize) -> Self {
22        index
23            .try_into()
24            .ok()
25            .map(|index: u32| index.wrapping_add(1))
26            .and_then(NonZeroU32::new)
27            .map(Self)
28            .unwrap_or_else(|| panic!("out of bounds extern object index {index}"))
29    }
30}
31
32/// An externally defined object.
33#[derive(Debug)]
34pub struct ExternObjectEntity {
35    inner: Box<dyn 'static + Any + Send + Sync>,
36}
37
38impl ExternObjectEntity {
39    /// Creates a new instance of `ExternRef` wrapping the given value.
40    pub fn new<T>(object: T) -> Self
41    where
42        T: 'static + Any + Send + Sync,
43    {
44        Self {
45            inner: Box::new(object),
46        }
47    }
48
49    /// Returns a shared reference to the external object.
50    pub fn data(&self) -> &dyn Any {
51        &*self.inner
52    }
53}
54
55/// Represents an opaque reference to any data within WebAssembly.
56#[derive(Debug, Copy, Clone)]
57#[repr(transparent)]
58pub struct ExternObject(Stored<ExternObjectIdx>);
59
60impl ExternObject {
61    /// Creates a new [`ExternObject`] reference from its raw representation.
62    pub(crate) fn from_inner(stored: Stored<ExternObjectIdx>) -> Self {
63        Self(stored)
64    }
65
66    /// Returns the raw representation of the [`ExternObject`].
67    pub(crate) fn as_inner(&self) -> &Stored<ExternObjectIdx> {
68        &self.0
69    }
70
71    /// Creates a new instance of `ExternRef` wrapping the given value.
72    pub fn new<T>(mut ctx: impl AsContextMut, object: T) -> Self
73    where
74        T: 'static + Any + Send + Sync,
75    {
76        ctx.as_context_mut()
77            .store
78            .inner
79            .alloc_extern_object(ExternObjectEntity::new(object))
80    }
81
82    /// Returns a shared reference to the underlying data for this [`ExternRef`].
83    ///
84    /// # Panics
85    ///
86    /// Panics if `ctx` does not own this [`ExternObject`].
87    pub fn data<'a, T: 'a>(&self, ctx: impl Into<StoreContext<'a, T>>) -> &'a dyn Any {
88        ctx.into().store.inner.resolve_external_object(self).data()
89    }
90}
91
92/// Represents a nullable opaque reference to any data within WebAssembly.
93#[derive(Debug, Default, Copy, Clone)]
94#[repr(transparent)]
95pub struct ExternRef {
96    inner: Option<ExternObject>,
97}
98
99#[test]
100fn externref_sizeof() {
101    // These assertions are important in order to convert `FuncRef`
102    // from and to 64-bit `UntypedValue` instances.
103    //
104    // The following equation must be true:
105    //     size_of(ExternRef) == size_of(ExternObject) == size_of(UntypedValue)
106    use core::mem::size_of;
107    assert_eq!(size_of::<ExternRef>(), size_of::<u64>());
108    assert_eq!(size_of::<ExternRef>(), size_of::<UntypedVal>());
109    assert_eq!(size_of::<ExternRef>(), size_of::<ExternObject>());
110}
111
112#[test]
113fn externref_null_to_zero() {
114    assert_eq!(UntypedVal::from(ExternRef::null()), UntypedVal::from(0));
115    assert!(ExternRef::from(UntypedVal::from(0)).is_null());
116}
117
118impl From<UntypedVal> for ExternRef {
119    fn from(untyped: UntypedVal) -> Self {
120        // Safety: This operation is safe since there are no invalid
121        //         bit patterns for [`ExternRef`] instances. Therefore
122        //         this operation cannot produce invalid [`ExternRef`]
123        //         instances even though the input [`UntypedVal`]
124        //         was modified arbitrarily.
125        unsafe { <Transposer<Self>>::from(untyped).reftype }.canonicalize()
126    }
127}
128
129impl From<ExternRef> for UntypedVal {
130    fn from(externref: ExternRef) -> Self {
131        let externref = externref.canonicalize();
132        // Safety: This operation is safe since there are no invalid
133        //         bit patterns for [`UntypedVal`] instances. Therefore
134        //         this operation cannot produce invalid [`UntypedVal`]
135        //         instances even if it was possible to arbitrarily modify
136        //         the input [`ExternRef`] instance.
137        Self::from(unsafe { <Transposer<ExternRef>>::new(externref).value })
138    }
139}
140
141impl ExternRef {
142    /// Creates a new [`ExternRef`] wrapping the given value.
143    pub fn new<T>(ctx: impl AsContextMut, object: impl Into<Option<T>>) -> Self
144    where
145        T: 'static + Any + Send + Sync,
146    {
147        object
148            .into()
149            .map(|object| ExternObject::new(ctx, object))
150            .map(Self::from_object)
151            .unwrap_or_else(Self::null)
152            .canonicalize()
153    }
154
155    /// Canonicalize `self` so that all `null` values have the same representation.
156    ///
157    /// # Note
158    ///
159    /// The underlying issue is that `ExternRef` has many possible values for the
160    /// `null` value. However, to simplify operating on encoded `ExternRef` instances
161    /// (encoded as `UntypedValue`) we want it to encode to exactly one `null`
162    /// value. The most trivial of all possible `null` values is `0_u64`, therefore
163    /// we canonicalize all `null` values to be represented by `0_u64`.
164    fn canonicalize(self) -> Self {
165        if self.is_null() {
166            // Safety: This is safe since `0u64` can be bit
167            //         interpreted as a valid `ExternRef` value.
168            return unsafe { <Transposer<Self>>::null().reftype };
169        }
170        self
171    }
172
173    /// Creates a new [`ExternRef`] to the given [`ExternObject`].
174    fn from_object(object: ExternObject) -> Self {
175        Self {
176            inner: Some(object),
177        }
178    }
179
180    /// Creates a new [`ExternRef`] which is `null`.
181    pub fn null() -> Self {
182        Self { inner: None }.canonicalize()
183    }
184
185    /// Returns `true` if [`ExternRef`] is `null`.
186    pub fn is_null(&self) -> bool {
187        self.inner.is_none()
188    }
189
190    /// Returns a shared reference to the underlying data for this [`ExternRef`].
191    ///
192    /// # Panics
193    ///
194    /// Panics if `ctx` does not own this [`ExternRef`].
195    pub fn data<'a, T: 'a>(&self, ctx: impl Into<StoreContext<'a, T>>) -> Option<&'a dyn Any> {
196        self.inner.map(|object| object.data(ctx))
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203    use crate::{Engine, Store};
204
205    #[test]
206    fn it_works() {
207        let engine = Engine::default();
208        let mut store = <Store<()>>::new(&engine, ());
209        let value = 42_i32;
210        let obj = ExternObject::new::<i32>(&mut store, value);
211        assert_eq!(obj.data(&store).downcast_ref::<i32>(), Some(&value),);
212    }
213}