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}