Skip to main content

submilli_engine/runtime/
gc_singleton.rs

1//! Helpers that declare one struct/array/func type as its own singleton rec group
2//! via wasmtime's `RecGroupBuilder`. WasmGC structural canonicalization makes the
3//! returned handle share the engine type-index a module emitting the same bytes
4//! uses, so a host-built type flows into a guest slot of that type without trapping.
5
6use crate::runtime::host::fatal_host_error;
7
8use wasmtime::{
9    ArrayType, Engine, FieldType, Finality, FuncType, RecGroupBuilder, StructType, ValType,
10};
11
12pub(crate) fn singleton_struct(
13    engine: &Engine,
14    finality: Finality,
15    supertype: Option<StructType>,
16    fields: Vec<FieldType>,
17) -> wasmtime::Result<StructType> {
18    let mut builder = RecGroupBuilder::new(engine);
19    let id = builder.declare_struct();
20    let mut def = builder.define_struct(id);
21    def.finality(finality);
22    if let Some(supertype) = supertype {
23        def.supertype(supertype);
24    }
25    for field in fields {
26        def.field(field);
27    }
28    def.finish();
29    // This builder declared and defined id with this kind. Successful build
30    // preserves its ID and kind, with no intervening mutation.
31    Ok(builder
32        .build()
33        .map_err(fatal_host_error)?
34        .get_struct(id)
35        .expect("declared singleton struct retains its kind"))
36}
37
38pub(crate) fn singleton_array(
39    engine: &Engine,
40    finality: Finality,
41    field: FieldType,
42) -> wasmtime::Result<ArrayType> {
43    let mut builder = RecGroupBuilder::new(engine);
44    let id = builder.declare_array();
45    let mut def = builder.define_array(id);
46    def.finality(finality);
47    def.element(field);
48    def.finish();
49    // This builder declared and defined id with this kind. Successful build
50    // preserves its ID and kind, with no intervening mutation.
51    Ok(builder
52        .build()
53        .map_err(fatal_host_error)?
54        .get_array(id)
55        .expect("declared singleton array retains its kind"))
56}
57
58/// Declare a non-final function type with no supertype — the shape codegen emits
59/// for closure and method func signatures.
60pub(crate) fn singleton_func(
61    engine: &Engine,
62    params: Vec<ValType>,
63    results: Vec<ValType>,
64) -> wasmtime::Result<FuncType> {
65    let mut builder = RecGroupBuilder::new(engine);
66    let id = builder.declare_func();
67    let mut def = builder.define_func(id);
68    def.finality(Finality::NonFinal);
69    for param in params {
70        def.param(param);
71    }
72    for result in results {
73        def.result(result);
74    }
75    def.finish();
76    // This builder declared and defined id with this kind. Successful build
77    // preserves its ID and kind, with no intervening mutation.
78    Ok(builder
79        .build()
80        .map_err(fatal_host_error)?
81        .get_func(id)
82        .expect("declared singleton func retains its kind"))
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use crate::runtime::host::FatalHostError;
89
90    #[test]
91    fn invalid_singleton_layout_is_fatal_and_does_not_break_next_build() {
92        let engine = crate::RuntimeConfig::default().engine().unwrap();
93        let parent = singleton_struct(&engine, Finality::Final, None, vec![]).unwrap();
94        let error =
95            singleton_struct(&engine, Finality::NonFinal, Some(parent), vec![]).unwrap_err();
96        assert!(error.is::<FatalHostError>(), "{error:#}");
97        singleton_struct(&engine, Finality::NonFinal, None, vec![]).unwrap();
98        singleton_array(
99            &engine,
100            Finality::Final,
101            FieldType::new(wasmtime::Mutability::Var, wasmtime::StorageType::I16),
102        )
103        .unwrap();
104        singleton_func(&engine, vec![ValType::I32], vec![]).unwrap();
105    }
106}