wasmtime_environ/fact/
signature.rs

1//! Size, align, and flattening information about component model types.
2
3use crate::component::{
4    ComponentTypesBuilder, InterfaceType, MAX_FLAT_ASYNC_PARAMS, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS,
5};
6use crate::fact::{AdapterOptions, Options};
7use crate::{WasmValType, prelude::*};
8use wasm_encoder::ValType;
9
10use super::LinearMemoryOptions;
11
12/// Metadata about a core wasm signature which is created for a component model
13/// signature.
14#[derive(Debug)]
15pub struct Signature {
16    /// Core wasm parameters.
17    pub params: Vec<ValType>,
18    /// Core wasm results.
19    pub results: Vec<ValType>,
20}
21
22impl ComponentTypesBuilder {
23    /// Calculates the core wasm function signature for the component function
24    /// type specified within `Context`.
25    ///
26    /// This is used to generate the core wasm signatures for functions that are
27    /// imported (matching whatever was `canon lift`'d) and functions that are
28    /// exported (matching the generated function from `canon lower`).
29    pub(super) fn signature(&self, options: &AdapterOptions) -> Signature {
30        let f = &self.module_types_builder()[options.options.core_type]
31            .composite_type
32            .inner
33            .unwrap_func();
34        Signature {
35            params: f.params().iter().map(|ty| self.val_type(ty)).collect(),
36            results: f.returns().iter().map(|ty| self.val_type(ty)).collect(),
37        }
38    }
39
40    fn val_type(&self, ty: &WasmValType) -> ValType {
41        match ty {
42            WasmValType::I32 => ValType::I32,
43            WasmValType::I64 => ValType::I64,
44            WasmValType::F32 => ValType::F32,
45            WasmValType::F64 => ValType::F64,
46            WasmValType::V128 => ValType::V128,
47            WasmValType::Ref(_) => todo!("CM+GC"),
48        }
49    }
50
51    /// Generates the signature for a function to be exported by the adapter
52    /// module and called by the host to lift the parameters from the caller and
53    /// lower them to the callee.
54    ///
55    /// This allows the host to delay copying the parameters until the callee
56    /// signals readiness by clearing its backpressure flag.
57    ///
58    /// Note that this function uses multi-value return to return up to
59    /// `MAX_FLAT_PARAMS` _results_ via the stack, allowing the host to pass
60    /// them directly to the callee with no additional effort.
61    pub(super) fn async_start_signature(
62        &self,
63        lower: &AdapterOptions,
64        lift: &AdapterOptions,
65    ) -> Signature {
66        let lower_ty = &self[lower.ty];
67        let lower_ptr_ty = lower.options.data_model.unwrap_memory().ptr();
68        let max_flat_params = if lower.options.async_ {
69            MAX_FLAT_ASYNC_PARAMS
70        } else {
71            MAX_FLAT_PARAMS
72        };
73        let params = match self.flatten_types(
74            &lower.options,
75            max_flat_params,
76            self[lower_ty.params].types.iter().copied(),
77        ) {
78            Some(list) => list,
79            None => vec![lower_ptr_ty],
80        };
81
82        let lift_ty = &self[lift.ty];
83        let lift_ptr_ty = lift.options.data_model.unwrap_memory().ptr();
84        let results = match self.flatten_types(
85            &lift.options,
86            // Both sync- and async-lifted functions accept up to this many core
87            // parameters via the stack.  The host will call the `async-start`
88            // function (possibly after a backpressure delay), which will
89            // _return_ that many values (using a multi-value return, if
90            // necessary); the host will then pass them directly to the callee.
91            MAX_FLAT_PARAMS,
92            self[lift_ty.params].types.iter().copied(),
93        ) {
94            Some(list) => list,
95            None => {
96                vec![lift_ptr_ty]
97            }
98        };
99
100        Signature { params, results }
101    }
102
103    pub(super) fn flatten_lowering_types(
104        &self,
105        options: &Options,
106        tys: impl IntoIterator<Item = InterfaceType>,
107    ) -> Option<Vec<ValType>> {
108        // Async functions "use the stack" for zero return values, meaning
109        // nothing is actually passed, but otherwise if anything is returned
110        // it's always through memory.
111        let max = if options.async_ { 0 } else { MAX_FLAT_RESULTS };
112        self.flatten_types(options, max, tys)
113    }
114
115    pub(super) fn flatten_lifting_types(
116        &self,
117        options: &Options,
118        tys: impl IntoIterator<Item = InterfaceType>,
119    ) -> Option<Vec<ValType>> {
120        self.flatten_types(
121            options,
122            if options.async_ {
123                // Async functions return results by calling `task.return`,
124                // which accepts up to `MAX_FLAT_PARAMS` parameters via the
125                // stack.
126                MAX_FLAT_PARAMS
127            } else {
128                // Sync functions return results directly (at least until we add
129                // a `always-task-return` canonical option) and so are limited
130                // to returning up to `MAX_FLAT_RESULTS` results via the stack.
131                MAX_FLAT_RESULTS
132            },
133            tys,
134        )
135    }
136
137    /// Generates the signature for a function to be exported by the adapter
138    /// module and called by the host to lift the results from the callee and
139    /// lower them to the caller.
140    ///
141    /// Given that async-lifted exports return their results via the
142    /// `task.return` intrinsic, the host will need to copy the results from
143    /// callee to caller when that intrinsic is called rather than when the
144    /// callee task fully completes (which may happen much later).
145    pub(super) fn async_return_signature(
146        &self,
147        lower: &AdapterOptions,
148        lift: &AdapterOptions,
149    ) -> Signature {
150        let lift_ty = &self[lift.ty];
151        let lift_ptr_ty = lift.options.data_model.unwrap_memory().ptr();
152        let mut params = match self
153            .flatten_lifting_types(&lift.options, self[lift_ty.results].types.iter().copied())
154        {
155            Some(list) => list,
156            None => {
157                vec![lift_ptr_ty]
158            }
159        };
160
161        let lower_ty = &self[lower.ty];
162        let lower_result_tys = &self[lower_ty.results];
163        let results = if lower.options.async_ {
164            // Add return pointer
165            if !lower_result_tys.types.is_empty() {
166                params.push(lift_ptr_ty);
167            }
168            Vec::new()
169        } else {
170            match self.flatten_types(
171                &lower.options,
172                MAX_FLAT_RESULTS,
173                lower_result_tys.types.iter().copied(),
174            ) {
175                Some(list) => list,
176                None => {
177                    // Add return pointer
178                    params.push(lift_ptr_ty);
179                    Vec::new()
180                }
181            }
182        };
183
184        Signature { params, results }
185    }
186
187    /// Pushes the flat version of a list of component types into a final result
188    /// list.
189    pub(super) fn flatten_types(
190        &self,
191        opts: &Options,
192        max: usize,
193        tys: impl IntoIterator<Item = InterfaceType>,
194    ) -> Option<Vec<ValType>> {
195        let mut dst = Vec::new();
196        for ty in tys {
197            for ty in opts.flat_types(&ty, self)? {
198                if dst.len() == max {
199                    return None;
200                }
201                dst.push((*ty).into());
202            }
203        }
204        Some(dst)
205    }
206
207    pub(super) fn align(&self, opts: &LinearMemoryOptions, ty: &InterfaceType) -> u32 {
208        self.size_align(opts, ty).1
209    }
210
211    /// Returns a (size, align) pair corresponding to the byte-size and
212    /// byte-alignment of the type specified.
213    //
214    // TODO: this is probably inefficient to entire recalculate at all phases,
215    // seems like it would be best to intern this in some sort of map somewhere.
216    pub(super) fn size_align(&self, opts: &LinearMemoryOptions, ty: &InterfaceType) -> (u32, u32) {
217        let abi = self.canonical_abi(ty);
218        if opts.memory64 {
219            (abi.size64, abi.align64)
220        } else {
221            (abi.size32, abi.align32)
222        }
223    }
224
225    /// Tests whether the type signature for `options` contains a borrowed
226    /// resource anywhere.
227    pub(super) fn contains_borrow_resource(&self, options: &AdapterOptions) -> bool {
228        let ty = &self[options.ty];
229
230        // Only parameters need to be checked since results should never have
231        // borrowed resources.
232        debug_assert!(
233            !self[ty.results]
234                .types
235                .iter()
236                .any(|t| self.ty_contains_borrow_resource(t))
237        );
238        self[ty.params]
239            .types
240            .iter()
241            .any(|t| self.ty_contains_borrow_resource(t))
242    }
243}