1 | use crate::{Function, Handle, Int, Resolve, Type, TypeDefKind}; |
2 | |
3 | /// A core WebAssembly signature with params and results. |
4 | #[derive (Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] |
5 | pub struct WasmSignature { |
6 | /// The WebAssembly parameters of this function. |
7 | pub params: Vec<WasmType>, |
8 | |
9 | /// The WebAssembly results of this function. |
10 | pub results: Vec<WasmType>, |
11 | |
12 | /// Whether or not this signature is passing all of its parameters |
13 | /// indirectly through a pointer within `params`. |
14 | /// |
15 | /// Note that `params` still reflects the true wasm paramters of this |
16 | /// function, this is auxiliary information for code generators if |
17 | /// necessary. |
18 | pub indirect_params: bool, |
19 | |
20 | /// Whether or not this signature is using a return pointer to store the |
21 | /// result of the function, which is reflected either in `params` or |
22 | /// `results` depending on the context this function is used (e.g. an import |
23 | /// or an export). |
24 | pub retptr: bool, |
25 | } |
26 | |
27 | /// Enumerates wasm types used by interface types when lowering/lifting. |
28 | #[derive (Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] |
29 | pub enum WasmType { |
30 | I32, |
31 | I64, |
32 | F32, |
33 | F64, |
34 | |
35 | /// A pointer type. In core Wasm this typically lowers to either `i32` or |
36 | /// `i64` depending on the index type of the exported linear memory, |
37 | /// however bindings can use different source-level types to preserve |
38 | /// provenance. |
39 | /// |
40 | /// Users that don't do anything special for pointers can treat this as |
41 | /// `i32`. |
42 | Pointer, |
43 | |
44 | /// A type for values which can be either pointers or 64-bit integers. |
45 | /// This occurs in variants, when pointers and non-pointers are unified. |
46 | /// |
47 | /// Users that don't do anything special for pointers can treat this as |
48 | /// `i64`. |
49 | PointerOrI64, |
50 | |
51 | /// An array length type. In core Wasm this lowers to either `i32` or `i64` |
52 | /// depending on the index type of the exported linear memory. |
53 | /// |
54 | /// Users that don't do anything special for pointers can treat this as |
55 | /// `i32`. |
56 | Length, |
57 | // NOTE: we don't lower interface types to any other Wasm type, |
58 | // e.g. externref, so we don't need to define them here. |
59 | } |
60 | |
61 | fn join(a: WasmType, b: WasmType) -> WasmType { |
62 | use WasmType::*; |
63 | |
64 | match (a, b) { |
65 | (I32, I32) |
66 | | (I64, I64) |
67 | | (F32, F32) |
68 | | (F64, F64) |
69 | | (Pointer, Pointer) |
70 | | (PointerOrI64, PointerOrI64) |
71 | | (Length, Length) => a, |
72 | |
73 | (I32, F32) | (F32, I32) => I32, |
74 | |
75 | // A length is at least an `i32`, maybe more, so it wins over |
76 | // 32-bit types. |
77 | (Length, I32 | F32) => Length, |
78 | (I32 | F32, Length) => Length, |
79 | |
80 | // A length might be an `i64`, but might not be, so if we have |
81 | // 64-bit types, they win. |
82 | (Length, I64 | F64) => I64, |
83 | (I64 | F64, Length) => I64, |
84 | |
85 | // Pointers have provenance and are at least an `i32`, so they |
86 | // win over 32-bit and length types. |
87 | (Pointer, I32 | F32 | Length) => Pointer, |
88 | (I32 | F32 | Length, Pointer) => Pointer, |
89 | |
90 | // If we need 64 bits and provenance, we need to use the special |
91 | // `PointerOrI64`. |
92 | (Pointer, I64 | F64) => PointerOrI64, |
93 | (I64 | F64, Pointer) => PointerOrI64, |
94 | |
95 | // PointerOrI64 wins over everything. |
96 | (PointerOrI64, _) => PointerOrI64, |
97 | (_, PointerOrI64) => PointerOrI64, |
98 | |
99 | // Otherwise, `i64` wins. |
100 | (_, I64 | F64) | (I64 | F64, _) => I64, |
101 | } |
102 | } |
103 | |
104 | impl From<Int> for WasmType { |
105 | fn from(i: Int) -> WasmType { |
106 | match i { |
107 | Int::U8 | Int::U16 | Int::U32 => WasmType::I32, |
108 | Int::U64 => WasmType::I64, |
109 | } |
110 | } |
111 | } |
112 | |
113 | /// We use a different ABI for wasm importing functions exported by the host |
114 | /// than for wasm exporting functions imported by the host. |
115 | /// |
116 | /// Note that this reflects the flavor of ABI we generate, and not necessarily |
117 | /// the way the resulting bindings will be used by end users. See the comments |
118 | /// on the `Direction` enum in gen-core for details. |
119 | /// |
120 | /// The bindings ABI has a concept of a "guest" and a "host". There are two |
121 | /// variants of the ABI, one specialized for the "guest" importing and calling |
122 | /// a function defined and exported in the "host", and the other specialized for |
123 | /// the "host" importing and calling a function defined and exported in the "guest". |
124 | #[derive (Clone, Copy, PartialEq, Eq, Debug, Hash)] |
125 | pub enum AbiVariant { |
126 | /// The guest is importing and calling the function. |
127 | GuestImport, |
128 | /// The guest is defining and exporting the function. |
129 | GuestExport, |
130 | GuestImportAsync, |
131 | GuestExportAsync, |
132 | GuestExportAsyncStackful, |
133 | } |
134 | |
135 | impl Resolve { |
136 | /// Get the WebAssembly type signature for this interface function |
137 | /// |
138 | /// The first entry returned is the list of parameters and the second entry |
139 | /// is the list of results for the wasm function signature. |
140 | pub fn wasm_signature(&self, variant: AbiVariant, func: &Function) -> WasmSignature { |
141 | if let AbiVariant::GuestImportAsync = variant { |
142 | return WasmSignature { |
143 | params: vec![WasmType::Pointer; 2], |
144 | indirect_params: true, |
145 | results: vec![WasmType::I32], |
146 | retptr: true, |
147 | }; |
148 | } |
149 | |
150 | const MAX_FLAT_PARAMS: usize = 16; |
151 | const MAX_FLAT_RESULTS: usize = 1; |
152 | |
153 | let mut params = Vec::new(); |
154 | let mut indirect_params = false; |
155 | for (_, param) in func.params.iter() { |
156 | self.push_flat(param, &mut params); |
157 | } |
158 | |
159 | if params.len() > MAX_FLAT_PARAMS { |
160 | params.truncate(0); |
161 | params.push(WasmType::Pointer); |
162 | indirect_params = true; |
163 | } else { |
164 | if matches!( |
165 | (&func.kind, variant), |
166 | ( |
167 | crate::FunctionKind::Method(_), |
168 | AbiVariant::GuestExport |
169 | | AbiVariant::GuestExportAsync |
170 | | AbiVariant::GuestExportAsyncStackful |
171 | ) |
172 | ) { |
173 | // Guest exported methods always receive resource rep as first argument |
174 | // |
175 | // TODO: Ideally you would distinguish between imported and exported |
176 | // resource Handles and then use either I32 or Pointer in abi::push_flat(). |
177 | // But this contextual information isn't available, yet. |
178 | // See https://github.com/bytecodealliance/wasm-tools/pull/1438 for more details. |
179 | assert!(matches!(params[0], WasmType::I32)); |
180 | params[0] = WasmType::Pointer; |
181 | } |
182 | } |
183 | |
184 | match variant { |
185 | AbiVariant::GuestExportAsync => { |
186 | return WasmSignature { |
187 | params, |
188 | indirect_params, |
189 | results: vec![WasmType::Pointer], |
190 | retptr: false, |
191 | }; |
192 | } |
193 | AbiVariant::GuestExportAsyncStackful => { |
194 | return WasmSignature { |
195 | params, |
196 | indirect_params, |
197 | results: Vec::new(), |
198 | retptr: false, |
199 | }; |
200 | } |
201 | _ => {} |
202 | } |
203 | |
204 | let mut results = Vec::new(); |
205 | for ty in func.results.iter_types() { |
206 | self.push_flat(ty, &mut results) |
207 | } |
208 | |
209 | let mut retptr = false; |
210 | |
211 | // Rust/C don't support multi-value well right now, so if a function |
212 | // would have multiple results then instead truncate it. Imports take a |
213 | // return pointer to write into and exports return a pointer they wrote |
214 | // into. |
215 | if results.len() > MAX_FLAT_RESULTS { |
216 | retptr = true; |
217 | results.truncate(0); |
218 | match variant { |
219 | AbiVariant::GuestImport => { |
220 | params.push(WasmType::Pointer); |
221 | } |
222 | AbiVariant::GuestExport => { |
223 | results.push(WasmType::Pointer); |
224 | } |
225 | _ => unreachable!(), |
226 | } |
227 | } |
228 | |
229 | WasmSignature { |
230 | params, |
231 | indirect_params, |
232 | results, |
233 | retptr, |
234 | } |
235 | } |
236 | |
237 | /// Appends the flat wasm types representing `ty` onto the `result` |
238 | /// list provided. |
239 | pub fn push_flat(&self, ty: &Type, result: &mut Vec<WasmType>) { |
240 | match ty { |
241 | Type::Bool |
242 | | Type::S8 |
243 | | Type::U8 |
244 | | Type::S16 |
245 | | Type::U16 |
246 | | Type::S32 |
247 | | Type::U32 |
248 | | Type::Char => result.push(WasmType::I32), |
249 | |
250 | Type::U64 | Type::S64 => result.push(WasmType::I64), |
251 | Type::F32 => result.push(WasmType::F32), |
252 | Type::F64 => result.push(WasmType::F64), |
253 | Type::String => { |
254 | result.push(WasmType::Pointer); |
255 | result.push(WasmType::Length); |
256 | } |
257 | |
258 | Type::Id(id) => match &self.types[*id].kind { |
259 | TypeDefKind::Type(t) => self.push_flat(t, result), |
260 | |
261 | TypeDefKind::Handle(Handle::Own(_) | Handle::Borrow(_)) => { |
262 | result.push(WasmType::I32); |
263 | } |
264 | |
265 | TypeDefKind::Resource => todo!(), |
266 | |
267 | TypeDefKind::Record(r) => { |
268 | for field in r.fields.iter() { |
269 | self.push_flat(&field.ty, result); |
270 | } |
271 | } |
272 | |
273 | TypeDefKind::Tuple(t) => { |
274 | for ty in t.types.iter() { |
275 | self.push_flat(ty, result); |
276 | } |
277 | } |
278 | |
279 | TypeDefKind::Flags(r) => { |
280 | for _ in 0..r.repr().count() { |
281 | result.push(WasmType::I32); |
282 | } |
283 | } |
284 | |
285 | TypeDefKind::List(_) => { |
286 | result.push(WasmType::Pointer); |
287 | result.push(WasmType::Length); |
288 | } |
289 | |
290 | TypeDefKind::Variant(v) => { |
291 | result.push(v.tag().into()); |
292 | self.push_flat_variants(v.cases.iter().map(|c| c.ty.as_ref()), result); |
293 | } |
294 | |
295 | TypeDefKind::Enum(e) => result.push(e.tag().into()), |
296 | |
297 | TypeDefKind::Option(t) => { |
298 | result.push(WasmType::I32); |
299 | self.push_flat_variants([None, Some(t)], result); |
300 | } |
301 | |
302 | TypeDefKind::Result(r) => { |
303 | result.push(WasmType::I32); |
304 | self.push_flat_variants([r.ok.as_ref(), r.err.as_ref()], result); |
305 | } |
306 | |
307 | TypeDefKind::Future(_) => { |
308 | result.push(WasmType::I32); |
309 | } |
310 | |
311 | TypeDefKind::Stream(_) => { |
312 | result.push(WasmType::I32); |
313 | } |
314 | |
315 | TypeDefKind::ErrorContext => { |
316 | result.push(WasmType::I32); |
317 | } |
318 | |
319 | TypeDefKind::Unknown => unreachable!(), |
320 | }, |
321 | } |
322 | } |
323 | |
324 | fn push_flat_variants<'a>( |
325 | &self, |
326 | tys: impl IntoIterator<Item = Option<&'a Type>>, |
327 | result: &mut Vec<WasmType>, |
328 | ) { |
329 | let mut temp = Vec::new(); |
330 | let start = result.len(); |
331 | |
332 | // Push each case's type onto a temporary vector, and then |
333 | // merge that vector into our final list starting at |
334 | // `start`. Note that this requires some degree of |
335 | // "unification" so we can handle things like `Result<i32, |
336 | // f32>` where that turns into `[i32 i32]` where the second |
337 | // `i32` might be the `f32` bitcasted. |
338 | for ty in tys { |
339 | if let Some(ty) = ty { |
340 | self.push_flat(ty, &mut temp); |
341 | |
342 | for (i, ty) in temp.drain(..).enumerate() { |
343 | match result.get_mut(start + i) { |
344 | Some(prev) => *prev = join(*prev, ty), |
345 | None => result.push(ty), |
346 | } |
347 | } |
348 | } |
349 | } |
350 | } |
351 | } |
352 | |