wasmtime_environ/fact/
signature.rs

1//! Size, align, and flattening information about component model types.
2
3use crate::component::{ComponentTypesBuilder, InterfaceType, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS};
4use crate::fact::{AdapterOptions, Context, Options};
5use crate::prelude::*;
6use wasm_encoder::ValType;
7
8/// Metadata about a core wasm signature which is created for a component model
9/// signature.
10#[derive(Debug)]
11pub struct Signature {
12    /// Core wasm parameters.
13    pub params: Vec<ValType>,
14    /// Core wasm results.
15    pub results: Vec<ValType>,
16}
17
18impl ComponentTypesBuilder {
19    /// Calculates the core wasm function signature for the component function
20    /// type specified within `Context`.
21    ///
22    /// This is used to generate the core wasm signatures for functions that are
23    /// imported (matching whatever was `canon lift`'d) and functions that are
24    /// exported (matching the generated function from `canon lower`).
25    pub(super) fn signature(&self, options: &AdapterOptions, context: Context) -> Signature {
26        let ty = &self[options.ty];
27        let ptr_ty = options.options.ptr();
28
29        // The async lower ABI is always `(param i32 i32) (result i32)` (for
30        // wasm32, anyway), regardless of the component-level signature.
31        //
32        // The first param is a pointer to linear memory where the parameters have
33        // been stored by the caller, the second param is a pointer to linear
34        // memory where the results should be stored by the callee, and the
35        // result is a status code optionally ORed with a subtask ID.
36        if let (Context::Lower, true) = (&context, options.options.async_) {
37            return Signature {
38                params: vec![ptr_ty; 2],
39                results: vec![ValType::I32],
40            };
41        }
42
43        // If we're lifting async or sync, or if we're lowering sync, we can
44        // pass up to `MAX_FLAT_PARAMS` via the stack.
45        let mut params = match self.flatten_types(
46            &options.options,
47            MAX_FLAT_PARAMS,
48            self[ty.params].types.iter().copied(),
49        ) {
50            Some(list) => list,
51            None => {
52                vec![ptr_ty]
53            }
54        };
55
56        // If we're lifting async with a callback, the result is an `i32` status
57        // code, optionally ORed with a guest task identifier, and the result
58        // will be returned via `task.return`.
59        //
60        // If we're lifting async without a callback, then there's no need to return
61        // anything here since the result will be returned via `task.return` and the
62        // guest will use `task.wait` rather than return a status code in order to suspend
63        // itself, if necessary.
64        if options.options.async_ {
65            return Signature {
66                params,
67                results: if options.options.callback.is_some() {
68                    vec![ptr_ty]
69                } else {
70                    Vec::new()
71                },
72            };
73        }
74
75        // If we've reached this point, we're either lifting or lowering sync,
76        // in which case the guest will return up to `MAX_FLAT_RESULTS` via the
77        // stack or spill to linear memory otherwise.
78        let results = match self.flatten_types(
79            &options.options,
80            MAX_FLAT_RESULTS,
81            self[ty.results].types.iter().copied(),
82        ) {
83            Some(list) => list,
84            None => {
85                match context {
86                    // For a lifted function too-many-results gets translated to a
87                    // returned pointer where results are read from. The callee
88                    // allocates space here.
89                    Context::Lift => vec![ptr_ty],
90                    // For a lowered function too-many-results becomes a return
91                    // pointer which is passed as the last argument. The caller
92                    // allocates space here.
93                    Context::Lower => {
94                        params.push(ptr_ty);
95                        Vec::new()
96                    }
97                }
98            }
99        };
100        Signature { params, results }
101    }
102
103    /// Generates the signature for a function to be exported by the adapter
104    /// module and called by the host to lift the parameters from the caller and
105    /// lower them to the callee.
106    ///
107    /// This allows the host to delay copying the parameters until the callee
108    /// signals readiness by clearing its backpressure flag.
109    ///
110    /// Note that this function uses multi-value return to return up to
111    /// `MAX_FLAT_PARAMS` _results_ via the stack, allowing the host to pass
112    /// them directly to the callee with no additional effort.
113    pub(super) fn async_start_signature(
114        &self,
115        lower: &AdapterOptions,
116        lift: &AdapterOptions,
117    ) -> Signature {
118        let lower_ty = &self[lower.ty];
119        let lower_ptr_ty = lower.options.ptr();
120        let params = if lower.options.async_ {
121            vec![lower_ptr_ty]
122        } else {
123            match self.flatten_types(
124                &lower.options,
125                MAX_FLAT_PARAMS,
126                self[lower_ty.params].types.iter().copied(),
127            ) {
128                Some(list) => list,
129                None => {
130                    vec![lower_ptr_ty]
131                }
132            }
133        };
134
135        let lift_ty = &self[lift.ty];
136        let lift_ptr_ty = lift.options.ptr();
137        let results = match self.flatten_types(
138            &lift.options,
139            // Both sync- and async-lifted functions accept up to this many core
140            // parameters via the stack.  The host will call the `async-start`
141            // function (possibly after a backpressure delay), which will
142            // _return_ that many values (using a multi-value return, if
143            // necessary); the host will then pass them directly to the callee.
144            MAX_FLAT_PARAMS,
145            self[lift_ty.params].types.iter().copied(),
146        ) {
147            Some(list) => list,
148            None => {
149                vec![lift_ptr_ty]
150            }
151        };
152
153        Signature { params, results }
154    }
155
156    pub(super) fn flatten_lowering_types(
157        &self,
158        options: &Options,
159        tys: impl IntoIterator<Item = InterfaceType>,
160    ) -> Option<Vec<ValType>> {
161        if options.async_ {
162            // When lowering an async function, we always spill parameters to
163            // linear memory.
164            None
165        } else {
166            self.flatten_types(options, MAX_FLAT_RESULTS, tys)
167        }
168    }
169
170    pub(super) fn flatten_lifting_types(
171        &self,
172        options: &Options,
173        tys: impl IntoIterator<Item = InterfaceType>,
174    ) -> Option<Vec<ValType>> {
175        self.flatten_types(
176            options,
177            if options.async_ {
178                // Async functions return results by calling `task.return`,
179                // which accepts up to `MAX_FLAT_PARAMS` parameters via the
180                // stack.
181                MAX_FLAT_PARAMS
182            } else {
183                // Sync functions return results directly (at least until we add
184                // a `always-task-return` canonical option) and so are limited
185                // to returning up to `MAX_FLAT_RESULTS` results via the stack.
186                MAX_FLAT_RESULTS
187            },
188            tys,
189        )
190    }
191
192    /// Generates the signature for a function to be exported by the adapter
193    /// module and called by the host to lift the results from the callee and
194    /// lower them to the caller.
195    ///
196    /// Given that async-lifted exports return their results via the
197    /// `task.return` intrinsic, the host will need to copy the results from
198    /// callee to caller when that intrinsic is called rather than when the
199    /// callee task fully completes (which may happen much later).
200    pub(super) fn async_return_signature(
201        &self,
202        lower: &AdapterOptions,
203        lift: &AdapterOptions,
204    ) -> Signature {
205        let lift_ty = &self[lift.ty];
206        let lift_ptr_ty = lift.options.ptr();
207        let mut params = match self
208            .flatten_lifting_types(&lift.options, self[lift_ty.results].types.iter().copied())
209        {
210            Some(list) => list,
211            None => {
212                vec![lift_ptr_ty]
213            }
214        };
215
216        let lower_ty = &self[lower.ty];
217        let results = if lower.options.async_ {
218            // Add return pointer
219            params.push(lift_ptr_ty);
220            Vec::new()
221        } else {
222            match self.flatten_types(
223                &lower.options,
224                MAX_FLAT_RESULTS,
225                self[lower_ty.results].types.iter().copied(),
226            ) {
227                Some(list) => list,
228                None => {
229                    // Add return pointer
230                    params.push(lift_ptr_ty);
231                    Vec::new()
232                }
233            }
234        };
235
236        Signature { params, results }
237    }
238
239    /// Pushes the flat version of a list of component types into a final result
240    /// list.
241    pub(super) fn flatten_types(
242        &self,
243        opts: &Options,
244        max: usize,
245        tys: impl IntoIterator<Item = InterfaceType>,
246    ) -> Option<Vec<ValType>> {
247        let mut dst = Vec::new();
248        for ty in tys {
249            for ty in opts.flat_types(&ty, self)? {
250                if dst.len() == max {
251                    return None;
252                }
253                dst.push((*ty).into());
254            }
255        }
256        Some(dst)
257    }
258
259    pub(super) fn align(&self, opts: &Options, ty: &InterfaceType) -> u32 {
260        self.size_align(opts, ty).1
261    }
262
263    /// Returns a (size, align) pair corresponding to the byte-size and
264    /// byte-alignment of the type specified.
265    //
266    // TODO: this is probably inefficient to entire recalculate at all phases,
267    // seems like it would be best to intern this in some sort of map somewhere.
268    pub(super) fn size_align(&self, opts: &Options, ty: &InterfaceType) -> (u32, u32) {
269        let abi = self.canonical_abi(ty);
270        if opts.memory64 {
271            (abi.size64, abi.align64)
272        } else {
273            (abi.size32, abi.align32)
274        }
275    }
276
277    /// Tests whether the type signature for `options` contains a borrowed
278    /// resource anywhere.
279    pub(super) fn contains_borrow_resource(&self, options: &AdapterOptions) -> bool {
280        let ty = &self[options.ty];
281
282        // Only parameters need to be checked since results should never have
283        // borrowed resources.
284        debug_assert!(!self[ty.results]
285            .types
286            .iter()
287            .any(|t| self.ty_contains_borrow_resource(t)));
288        self[ty.params]
289            .types
290            .iter()
291            .any(|t| self.ty_contains_borrow_resource(t))
292    }
293}