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}