Skip to main content

hyperlight_component_util/
etypes.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025 The Hyperlight Authors.
3
4/// Elaborated component model types
5///
6/// This has the basic type definitions for the elaborated types. They
7/// correspond roughly to the "Elaborated Types" section in the
8/// specification.
9use crate::structure::*;
10
11#[derive(Debug, Clone, PartialEq, Copy)]
12pub struct Name<'a> {
13    pub name: &'a str,
14}
15
16#[derive(Debug, Clone, PartialEq, Copy)]
17pub enum IntWidth {
18    I8,
19    I16,
20    I32,
21    I64,
22}
23impl IntWidth {
24    pub fn width(self) -> u8 {
25        match self {
26            IntWidth::I8 => 8,
27            IntWidth::I16 => 16,
28            IntWidth::I32 => 32,
29            IntWidth::I64 => 64,
30        }
31    }
32}
33
34#[derive(Debug, Clone, PartialEq, Copy)]
35pub enum FloatWidth {
36    F32,
37    F64,
38}
39impl FloatWidth {
40    pub fn width(self) -> u8 {
41        match self {
42            FloatWidth::F32 => 32,
43            FloatWidth::F64 => 64,
44        }
45    }
46}
47
48/// recordfield_e in the specification
49#[derive(Debug, Clone)]
50pub struct RecordField<'a> {
51    pub name: Name<'a>,
52    pub ty: Value<'a>,
53}
54
55/// variantcase_e in the specification
56#[derive(Debug, Clone)]
57pub struct VariantCase<'a> {
58    pub name: Name<'a>,
59    pub ty: Option<Value<'a>>,
60}
61
62/// valtype_e in the specification
63#[derive(Debug, Clone)]
64pub enum Value<'a> {
65    Bool,
66    S(IntWidth),
67    U(IntWidth),
68    F(FloatWidth),
69    Char,
70    String,
71    List(Box<Value<'a>>),
72    FixList(Box<Value<'a>>, u32),
73    Record(Vec<RecordField<'a>>),
74    Tuple(Vec<Value<'a>>),
75    Flags(Vec<Name<'a>>),
76    Variant(Vec<VariantCase<'a>>),
77    Enum(Vec<Name<'a>>),
78    Option(Box<Value<'a>>),
79    Result(Box<Option<Value<'a>>>, Box<Option<Value<'a>>>),
80    Own(Handleable),
81    Borrow(Handleable),
82    /// This records that a type variable was once here, and is used
83    /// to enforce export namedness checks.
84    Var(Option<Tyvar>, Box<Value<'a>>),
85}
86
87/// Global resource identifier
88#[derive(Debug, Clone, Copy, PartialEq)]
89pub struct ResourceId {
90    pub(super) id: u32,
91}
92
93/// To make certain substitutions easier, free type variables are
94/// divided into Universal and Existential variables.  Each is
95/// represented by a pair of indices: the first index is an index into
96/// [`Ctx::parents()`], specifying parent context has the variable
97/// definition in it, and the second is an index into that context's
98/// [`Ctx::uvars`] or [`Ctx::evars`].
99#[derive(Debug, Clone)]
100pub enum FreeTyvar {
101    U(u32, u32),
102    E(u32, u32),
103}
104
105/// We explicitly distinguish between bound type variables, which are
106/// can only only present on types that are themselves inside a
107/// [`QualifiedInstance`] or [`Component`], and free type variables
108/// that are used while constructing or deconstructing such a type in
109/// a [`Ctx`].
110#[derive(Debug, Clone)]
111pub enum Tyvar {
112    /// A bound type variable as a de Bruijn index (0 is the innermost
113    /// binder)
114    Bound(u32),
115    /// A free type variable, whose bounds/other information are
116    /// stored in the context
117    Free(FreeTyvar),
118}
119
120#[derive(Debug, Clone)]
121pub struct Param<'a> {
122    pub name: Name<'a>,
123    pub ty: Value<'a>,
124}
125
126pub type Result<'a> = Option<Value<'a>>;
127
128/// functype_e in the specification
129#[derive(Debug, Clone)]
130pub struct Func<'a> {
131    pub params: Vec<Param<'a>>,
132    pub result: Result<'a>,
133}
134
135/// In the spec, this does not exist, but a validation rule ensures an
136/// invariant that certain deftype_e s are of this form.
137#[derive(Debug, Clone)]
138pub enum Handleable {
139    Var(Tyvar),
140    Resource(ResourceId),
141}
142
143/// deftype_e in the specification
144#[derive(Debug, Clone)]
145pub enum Defined<'a> {
146    Handleable(Handleable),
147    Value(Value<'a>),
148    Func(Func<'a>),
149    Instance(QualifiedInstance<'a>),
150    Component(Component<'a>),
151}
152
153/// typebound_e in the specification
154#[derive(Debug, Clone)]
155pub enum TypeBound<'a> {
156    Eq(Defined<'a>),
157    SubResource,
158}
159
160/// The name of an import or export of the current
161/// component/context. Not in the spec; only used for
162/// [`BoundedTyvar::origin`] below.
163///
164/// Any string present in one of these should also be present in an
165/// [`ExternDecl::kebab_name`] in a relevant place.
166#[derive(Debug, Clone, PartialEq)]
167pub enum ImportExport<'a> {
168    Import(&'a str),
169    Export(&'a str),
170}
171impl<'a> ImportExport<'a> {
172    pub fn name(&self) -> &'a str {
173        match self {
174            ImportExport::Import(s) => s,
175            ImportExport::Export(s) => s,
176        }
177    }
178    pub fn imported(&self) -> bool {
179        match self {
180            ImportExport::Import(_) => true,
181            ImportExport::Export(_) => false,
182        }
183    }
184}
185
186/// An (optional) path through the imports/exports of a current
187/// component/context. Not in the spec; only used for
188/// [`BoundedTyvar::origin`] below.
189#[derive(Default, Debug, Clone, PartialEq)]
190pub struct TyvarOrigin<'a> {
191    /// Note that the most recent (closest) element is last
192    pub path: Option<Vec<ImportExport<'a>>>,
193}
194
195impl<'a> TyvarOrigin<'a> {
196    pub fn new() -> Self {
197        TyvarOrigin { path: Some(vec![]) }
198    }
199    pub fn push(&self, x: Option<ImportExport<'a>>) -> Self {
200        match (&self.path, x) {
201            (None, _) => TyvarOrigin { path: None },
202            (_, None) => self.clone(),
203            (Some(xs), Some(x)) => {
204                let mut xs = xs.clone();
205                xs.push(x);
206                TyvarOrigin { path: Some(xs) }
207            }
208        }
209    }
210    pub fn matches<I: Iterator<Item = &'a ImportExport<'a>>>(&self, path: I) -> bool {
211        self.path
212            .as_ref()
213            .map(|p| p.iter().rev().eq(path))
214            .unwrap_or(false)
215    }
216    pub fn is_local<
217        I: DoubleEndedIterator<Item = &'a ImportExport<'a>>
218            + ExactSizeIterator<Item = &'a ImportExport<'a>>,
219    >(
220        &self,
221        path: I,
222    ) -> Option<Vec<ImportExport<'a>>> {
223        let other = path.rev().skip(1).rev();
224        let path = self.path.as_ref()?;
225        let path = path.iter();
226        let mut path = path.rev();
227        for elem in other {
228            match path.next() {
229                None => break,
230                Some(oe) if oe != elem => return None,
231                _ => (),
232            }
233        }
234        Some(path.cloned().collect())
235    }
236    pub fn last_name(&self) -> Option<&'a str> {
237        self.path
238            .as_ref()
239            .and_then(|x| x.first())
240            .map(|ie| ie.name())
241    }
242    pub fn is_imported(&self) -> bool {
243        let Some(p) = &self.path else {
244            return false;
245        };
246        p[p.len() - 1].imported()
247    }
248}
249
250/// boundedtyvar_e in the spec
251///
252/// Because we use a de Bruijn representation of type indices, this is
253/// only the type_bound - which variable it is binding is implicit in
254/// its position in the list.
255#[derive(Debug, Clone)]
256pub struct BoundedTyvar<'a> {
257    /// This is not important for typechecking, but is used to keep
258    /// track of where a type variable originated from in order to
259    /// decide on a canonical name to be used in bindings
260    /// generation.
261    pub origin: TyvarOrigin<'a>,
262    pub bound: TypeBound<'a>,
263}
264
265impl<'a> BoundedTyvar<'a> {
266    pub fn new(bound: TypeBound<'a>) -> Self {
267        BoundedTyvar {
268            origin: TyvarOrigin::new(),
269            bound,
270        }
271    }
272    pub fn push_origin(&self, x: Option<ImportExport<'a>>) -> Self {
273        BoundedTyvar {
274            origin: self.origin.push(x),
275            ..self.clone()
276        }
277    }
278}
279
280/// externdesc_e in the specification
281#[derive(Debug, Clone)]
282pub enum ExternDesc<'a> {
283    CoreModule(CoreModule<'a>),
284    Func(Func<'a>),
285    /* TODO: First-class values (when the spec gets them) */
286    Type(Defined<'a>),
287    /// This uses an [`Instance`] rather than a [`QualifiedInstance`]
288    /// because the instance's evars need to be propagated up to the
289    /// surrounding component/instance (so that e.g. `alias`ing them
290    /// and using them in another import/export is possible).
291    Instance(Instance<'a>),
292    Component(Component<'a>),
293}
294
295/// Merely a convenience for [`Ctx::resolve_alias`]
296#[derive(Debug, Clone)]
297pub enum CoreOrComponentExternDesc<'a> {
298    Core(CoreExternDesc),
299    Component(ExternDesc<'a>),
300}
301
302/// externdecl_e in the specification
303#[derive(Debug, Clone)]
304pub struct ExternDecl<'a> {
305    pub kebab_name: &'a str,
306    pub desc: ExternDesc<'a>,
307}
308
309/// `instancetype_e` in the specification.
310///
311/// An "opened" instance, whose existential variables are recorded in
312/// some surrounding context.
313#[derive(Debug, Clone)]
314pub struct Instance<'a> {
315    pub exports: Vec<ExternDecl<'a>>,
316}
317
318/// This is an instance together with its existential variables. This
319/// concept doesn't exist as a named syntax class in the specification, but
320/// is the payload of the instance case of `deftype_e` and the output
321/// of the instance declaration inference judgement.
322#[derive(Debug, Clone)]
323pub struct QualifiedInstance<'a> {
324    /// Existential variables produced by this instance (which may be
325    /// referred to by [`exports`](Instance::exports)). These are stored in
326    /// "outside-in" order that matches how they would be written on
327    /// paper: de Bruijn index Bound(0) in the imports is the last
328    /// element in the list, and later elements can depend on earlier
329    /// ones.
330    pub evars: Vec<BoundedTyvar<'a>>,
331    pub unqualified: Instance<'a>,
332}
333
334/// componenttype_e in the specification
335#[derive(Debug, Clone)]
336pub struct Component<'a> {
337    /// Universal variables over which this component is parameterized
338    /// (which may be referred to by `imports`). These are stored in
339    /// "outside-in" order that matches how they would be written on
340    /// paper: de Bruijn index Bound(0) in the imports is the last
341    /// element in the list, and later elements can depend on earlier
342    /// ones.
343    pub uvars: Vec<BoundedTyvar<'a>>,
344    pub imports: Vec<ExternDecl<'a>>,
345    /// Since we already have [`QualifiedInstance`], we use that to
346    /// keep track of both the evars and the actual instance, unlike
347    /// in the spec; this is quite natural, since during inference the
348    /// evars are generated by the exports. However, they conceptually
349    /// belong here as much as there: instantiating a component should
350    /// add them to the context as non-imported uvars and produce an
351    /// [`Instance`], rather than a [`QualifiedInstance`] directly.
352    pub instance: QualifiedInstance<'a>,
353}
354
355// core:importdecl in the specification is wasmparser::Import
356
357/// core:importdesc in the specification
358#[derive(Debug, Clone)]
359pub enum CoreExternDesc {
360    Func(wasmparser::FuncType),
361    Table(wasmparser::TableType),
362    Memory(wasmparser::MemoryType),
363    Global(wasmparser::GlobalType),
364}
365
366/// core:exportdecl in the specification
367#[derive(Debug, Clone)]
368pub struct CoreExportDecl<'a> {
369    pub name: Name<'a>,
370    pub desc: CoreExternDesc,
371}
372
373// core:functype is wasmparser::FuncType
374
375/// core:instancetype_e in the specification
376#[derive(Debug, Clone)]
377pub struct CoreInstance<'a> {
378    pub exports: Vec<CoreExportDecl<'a>>,
379}
380
381/// core:moduletype_e in the specification
382#[derive(Debug, Clone)]
383pub struct CoreModule<'a> {
384    pub _imports: Vec<wasmparser::Import<'a>>,
385    pub _exports: Vec<CoreExportDecl<'a>>,
386}
387
388/// core:deftype_e in the specification
389#[derive(Debug, Clone)]
390pub enum CoreDefined<'a> {
391    Func(wasmparser::FuncType),
392    Module(CoreModule<'a>),
393}
394
395/// gamma_c in the specification
396#[derive(Default, Debug, Clone)]
397pub struct CoreCtx<'a> {
398    pub types: Vec<CoreDefined<'a>>,
399    pub funcs: Vec<wasmparser::FuncType>,
400    pub modules: Vec<CoreModule<'a>>,
401    pub instances: Vec<CoreInstance<'a>>,
402    pub tables: Vec<wasmparser::TableType>,
403    pub mems: Vec<wasmparser::MemoryType>,
404    pub globals: Vec<wasmparser::GlobalType>,
405}
406
407impl<'a> CoreCtx<'a> {
408    pub fn new() -> Self {
409        CoreCtx {
410            types: Vec::new(),
411            funcs: Vec::new(),
412            modules: Vec::new(),
413            instances: Vec::new(),
414            tables: Vec::new(),
415            mems: Vec::new(),
416            globals: Vec::new(),
417        }
418    }
419}
420
421/// resourcetype_e in the specification
422#[derive(Debug, Clone)]
423pub struct Resource {
424    // One day, there will be a `rep` field here...
425    pub _dtor: Option<FuncIdx>,
426}
427
428/// gamma in the specification
429#[derive(Debug, Clone)]
430pub struct Ctx<'p, 'a> {
431    pub parent: Option<&'p Ctx<'p, 'a>>,
432    pub outer_boundary: bool,
433    pub core: CoreCtx<'a>,
434    /// Universally-quantified variables, specifying for each the
435    /// known bound and whether or not it was imported. Uvars can come
436    /// from imports or component instantiations; only the imported
437    /// ones can be allowed to escape in the type of a components
438    /// exports/imports, since only those can be named outside of the
439    /// component itself.
440    pub uvars: Vec<(BoundedTyvar<'a>, bool)>,
441    /// Existentially-quantified variables, specifying for each the
442    /// known bound and, if it was locally defined, the type which
443    /// instantiates it.
444    pub evars: Vec<(BoundedTyvar<'a>, Option<Defined<'a>>)>,
445    pub rtypes: Vec<Resource>,
446    pub types: Vec<Defined<'a>>,
447    pub components: Vec<Component<'a>>,
448    pub instances: Vec<Instance<'a>>,
449    pub funcs: Vec<Func<'a>>,
450}
451
452impl<'p, 'a> Ctx<'p, 'a> {
453    pub fn new<'c>(parent: Option<&'p Ctx<'c, 'a>>, outer_boundary: bool) -> Self {
454        Ctx {
455            parent,
456            outer_boundary,
457            core: CoreCtx::new(),
458            uvars: Vec::new(),
459            evars: Vec::new(),
460            rtypes: Vec::new(),
461            types: Vec::new(),
462            components: Vec::new(),
463            instances: Vec::new(),
464            funcs: Vec::new(),
465        }
466    }
467}
468
469pub struct CtxParentIterator<'i, 'p: 'i, 'a: 'i> {
470    ctx: Option<&'i Ctx<'p, 'a>>,
471}
472impl<'i, 'p, 'a> Iterator for CtxParentIterator<'i, 'p, 'a> {
473    type Item = &'i Ctx<'p, 'a>;
474    fn next(&mut self) -> Option<Self::Item> {
475        match self.ctx {
476            Some(ctx) => {
477                self.ctx = ctx.parent;
478                Some(ctx)
479            }
480            None => None,
481        }
482    }
483}
484
485impl<'p, 'a> Ctx<'p, 'a> {
486    pub fn parents<'i>(&'i self) -> CtxParentIterator<'i, 'p, 'a> {
487        CtxParentIterator { ctx: Some(self) }
488    }
489}