Skip to main content

submilli_wasm/value/
gc_type.rs

1//! GC composite-type descriptors (`StructType`/`ArrayType`/…), the host-facing surface for
2//! declaring and reflecting on GC types. `StructType`/`ArrayType` are engine-interned handles
3//! (identity by canonical id; structure materialized from the registry), matching wasmtime.
4
5use crate::canon::CanonicalTypeId;
6use crate::engine::Engine;
7use crate::value::{FuncType, HeapType, Mutability, TagType, ValType};
8use crate::Result;
9
10/// Whether a GC type may be subtyped further (`final` vs `non-final`).
11#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
12pub enum Finality {
13    Final,
14    NonFinal,
15}
16
17/// The storage type of a struct field / array element: a packed `i8`/`i16`, or a value type.
18#[derive(Clone, Debug, PartialEq, Eq, Hash)]
19pub enum StorageType {
20    I8,
21    I16,
22    ValType(ValType),
23}
24
25impl StorageType {
26    /// Whether this storage type is a (non-packed) value type.
27    pub fn is_val_type(&self) -> bool {
28        matches!(self, StorageType::ValType(_))
29    }
30}
31
32/// A struct field / array element type: mutability plus storage type.
33#[derive(Clone, Debug, PartialEq, Eq, Hash)]
34pub struct FieldType {
35    mutability: Mutability,
36    element_type: StorageType,
37}
38
39impl FieldType {
40    pub fn new(mutability: Mutability, element_type: StorageType) -> Self {
41        FieldType {
42            mutability,
43            element_type,
44        }
45    }
46
47    pub fn mutability(&self) -> Mutability {
48        self.mutability
49    }
50
51    pub fn element_type(&self) -> &StorageType {
52        &self.element_type
53    }
54}
55
56/// A GC struct type — an engine-interned handle (identity by canonical id). Refcounted (#27i):
57/// holds one registration on its rec group; `Clone`/`Drop` incref/decref (see `handle_id_traits!`).
58pub struct StructType {
59    engine: Engine,
60    id: CanonicalTypeId,
61}
62
63impl StructType {
64    pub fn new(engine: &Engine, fields: impl IntoIterator<Item = FieldType>) -> Result<Self> {
65        Self::with_finality_and_supertype(engine, Finality::Final, None, fields)
66    }
67
68    pub fn with_finality_and_supertype(
69        engine: &Engine,
70        finality: Finality,
71        supertype: Option<&Self>,
72        fields: impl IntoIterator<Item = FieldType>,
73    ) -> Result<Self> {
74        let fields: Vec<FieldType> = fields.into_iter().collect();
75        assert!(
76            supertype.is_none_or(|ty| ty.engine().same(engine)),
77            "struct supertype belongs to a different engine"
78        );
79        assert!(
80            fields
81                .iter()
82                .all(|field| field_belongs_to_engine(field, engine)),
83            "struct field contains a concrete type from a different engine"
84        );
85        let id =
86            engine.intern_struct_type(finality, supertype.map(StructType::canonical_id), &fields);
87        Ok(StructType {
88            engine: engine.clone(),
89            id,
90        })
91    }
92
93    pub(crate) fn from_id(engine: &Engine, id: CanonicalTypeId) -> Self {
94        engine.incref_type(id);
95        StructType {
96            engine: engine.clone(),
97            id,
98        }
99    }
100
101    pub(crate) fn canonical_id(&self) -> CanonicalTypeId {
102        self.id
103    }
104
105    pub(crate) fn engine(&self) -> &Engine {
106        &self.engine
107    }
108
109    pub fn field(&self, i: usize) -> Option<FieldType> {
110        self.fields().nth(i)
111    }
112
113    // This handle holds a reference on its type, so the lookup cannot miss while it lives; the
114    // `wasmtime` signature has no error to report one with.
115    pub fn fields(&self) -> impl ExactSizeIterator<Item = FieldType> {
116        self.engine
117            .struct_fields(self.id)
118            .expect("live StructType registration must retain its fields")
119            .into_iter()
120    }
121}
122
123/// A GC array type — an engine-interned handle (identity by canonical id). Refcounted (#27i),
124/// like [`StructType`].
125pub struct ArrayType {
126    engine: Engine,
127    id: CanonicalTypeId,
128}
129
130impl ArrayType {
131    pub fn new(engine: &Engine, field_type: FieldType) -> Self {
132        Self::with_finality_and_supertype(engine, Finality::Final, None, field_type)
133            .expect("array type without a supertype never fails")
134    }
135
136    pub fn with_finality_and_supertype(
137        engine: &Engine,
138        finality: Finality,
139        supertype: Option<&Self>,
140        field_type: FieldType,
141    ) -> Result<Self> {
142        assert!(
143            supertype.is_none_or(|ty| ty.engine().same(engine)),
144            "array supertype belongs to a different engine"
145        );
146        assert!(
147            field_belongs_to_engine(&field_type, engine),
148            "array field contains a concrete type from a different engine"
149        );
150        let id = engine.intern_array_type(
151            finality,
152            supertype.map(ArrayType::canonical_id),
153            &field_type,
154        );
155        Ok(ArrayType {
156            engine: engine.clone(),
157            id,
158        })
159    }
160
161    pub(crate) fn from_id(engine: &Engine, id: CanonicalTypeId) -> Self {
162        engine.incref_type(id);
163        ArrayType {
164            engine: engine.clone(),
165            id,
166        }
167    }
168
169    pub(crate) fn canonical_id(&self) -> CanonicalTypeId {
170        self.id
171    }
172
173    pub(crate) fn engine(&self) -> &Engine {
174        &self.engine
175    }
176
177    // This handle holds a reference on its type, so the lookup cannot miss while it lives; the
178    // `wasmtime` signature has no error to report one with.
179    pub fn field_type(&self) -> FieldType {
180        self.engine
181            .array_field(self.id)
182            .expect("live ArrayType registration must retain its field")
183    }
184
185    pub fn element_type(&self) -> StorageType {
186        self.field_type().element_type().clone()
187    }
188}
189
190fn field_belongs_to_engine(field: &FieldType, engine: &Engine) -> bool {
191    match field.element_type() {
192        StorageType::I8 | StorageType::I16 => true,
193        StorageType::ValType(ty) => crate::value::val_type_belongs_to_engine(ty, engine),
194    }
195}
196
197macro_rules! handle_id_traits {
198    ($ty:ident) => {
199        impl Clone for $ty {
200            fn clone(&self) -> Self {
201                self.engine.incref_type(self.id);
202                $ty {
203                    engine: self.engine.clone(),
204                    id: self.id,
205                }
206            }
207        }
208        impl Drop for $ty {
209            fn drop(&mut self) {
210                self.engine.decref_type(self.id);
211            }
212        }
213        impl PartialEq for $ty {
214            fn eq(&self, other: &Self) -> bool {
215                self.id == other.id
216            }
217        }
218        impl Eq for $ty {}
219        impl core::hash::Hash for $ty {
220            fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
221                self.id.hash(state);
222            }
223        }
224        impl core::fmt::Debug for $ty {
225            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
226                f.debug_struct(stringify!($ty))
227                    .field("id", &self.id)
228                    .finish_non_exhaustive()
229            }
230        }
231    };
232}
233
234handle_id_traits!(StructType);
235handle_id_traits!(ArrayType);
236
237impl From<StructType> for HeapType {
238    fn from(t: StructType) -> Self {
239        HeapType::ConcreteStruct(t)
240    }
241}
242
243impl From<ArrayType> for HeapType {
244    fn from(t: ArrayType) -> Self {
245        HeapType::ConcreteArray(t)
246    }
247}
248
249impl From<FuncType> for HeapType {
250    fn from(t: FuncType) -> Self {
251        HeapType::ConcreteFunc(t)
252    }
253}
254
255/// An exception type — the value types an exception carries. Internally the tag's function type
256/// (`[fields] → []`), so it shares `FuncType`'s engine-canonical identity + refcounting (#27i).
257#[derive(Clone, Debug, PartialEq, Eq, Hash)]
258pub struct ExnType {
259    func: FuncType,
260}
261
262impl ExnType {
263    pub fn new(engine: &Engine, fields: impl IntoIterator<Item = ValType>) -> Result<ExnType> {
264        Ok(ExnType {
265            func: FuncType::new(engine, fields, []),
266        })
267    }
268
269    pub fn from_tag_type(tag: &TagType) -> Result<ExnType> {
270        Ok(ExnType {
271            func: tag.ty().clone(),
272        })
273    }
274
275    /// The tag type an exception of this type is thrown with.
276    pub fn tag_type(&self) -> TagType {
277        TagType::new(self.func.clone())
278    }
279
280    pub fn field(&self, i: usize) -> Option<FieldType> {
281        self.func.params().nth(i).map(exn_field)
282    }
283
284    pub fn fields(&self) -> impl ExactSizeIterator<Item = FieldType> {
285        self.func.params().map(exn_field)
286    }
287
288    pub fn engine(&self) -> &Engine {
289        self.func.engine()
290    }
291
292    pub fn matches(&self, other: &ExnType) -> bool {
293        self.func == other.func
294    }
295
296    pub(crate) fn func(&self) -> &FuncType {
297        &self.func
298    }
299}
300
301/// An exception field is one of the tag's params: an immutable value-type slot.
302fn exn_field(ty: ValType) -> FieldType {
303    FieldType::new(Mutability::Const, StorageType::ValType(ty))
304}
305
306/// An engine-level recursion-group handle (`StructType`/`ArrayType` belong to one).
307/// Opaque stub — real canonicalized rec-groups arrive with the host rec-group builder.
308#[derive(Clone, Debug)]
309pub struct RecGroupType {
310    _private: (),
311}