/src/wasm-tools/crates/wit-parser/src/abi.rs
Line | Count | Source (jump to first uncovered line) |
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 | 7.13k | fn join(a: WasmType, b: WasmType) -> WasmType { |
62 | | use WasmType::*; |
63 | | |
64 | 7.13k | match (a, b) { |
65 | | (I32, I32) |
66 | | | (I64, I64) |
67 | | | (F32, F32) |
68 | | | (F64, F64) |
69 | | | (Pointer, Pointer) |
70 | | | (PointerOrI64, PointerOrI64) |
71 | 5.40k | | (Length, Length) => a, |
72 | | |
73 | 291 | (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 | 58 | (Length, I32 | F32) => Length, |
78 | 83 | (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 | 0 | (Length, I64 | F64) => I64, |
83 | 12 | (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 | 157 | (Pointer, I32 | F32 | Length) => Pointer, |
88 | 205 | (I32 | F32 | Length, Pointer) => Pointer, |
89 | | |
90 | | // If we need 64 bits and provenance, we need to use the special |
91 | | // `PointerOrI64`. |
92 | 0 | (Pointer, I64 | F64) => PointerOrI64, |
93 | 7 | (I64 | F64, Pointer) => PointerOrI64, |
94 | | |
95 | | // PointerOrI64 wins over everything. |
96 | 0 | (PointerOrI64, _) => PointerOrI64, |
97 | 0 | (_, PointerOrI64) => PointerOrI64, |
98 | | |
99 | | // Otherwise, `i64` wins. |
100 | 666 | (_, I64 | F64) | (I64 | F64, _) => I64, |
101 | | } |
102 | 6.88k | } |
103 | | |
104 | | impl From<Int> for WasmType { |
105 | 2.61k | fn from(i: Int) -> WasmType { |
106 | 2.61k | match i { |
107 | 2.61k | Int::U8 | Int::U16 | Int::U32 => WasmType::I32, |
108 | 0 | Int::U64 => WasmType::I64, |
109 | | } |
110 | 2.61k | } |
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 | 138k | pub fn wasm_signature(&self, variant: AbiVariant, func: &Function) -> WasmSignature { |
141 | 138k | if let AbiVariant::GuestImportAsync = variant { |
142 | 17.9k | return WasmSignature { |
143 | 17.9k | params: vec![WasmType::Pointer; 2], |
144 | 17.9k | indirect_params: true, |
145 | 17.9k | results: vec![WasmType::I32], |
146 | 17.9k | retptr: true, |
147 | 17.9k | }; |
148 | 120k | } |
149 | | |
150 | | const MAX_FLAT_PARAMS: usize = 16; |
151 | | const MAX_FLAT_RESULTS: usize = 1; |
152 | | |
153 | 120k | let mut params = Vec::new(); |
154 | 120k | let mut indirect_params = false; |
155 | 267k | for (_, param) in func.params.iter() { |
156 | 267k | self.push_flat(param, &mut params); |
157 | 267k | } |
158 | | |
159 | 120k | if params.len() > MAX_FLAT_PARAMS { |
160 | 3.58k | params.truncate(0); |
161 | 3.58k | params.push(WasmType::Pointer); |
162 | 3.58k | indirect_params = true; |
163 | 3.58k | } else { |
164 | 103k | if matches!( |
165 | 116k | (&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 | 13.0k | assert!(matches!(params[0], WasmType::I32)); |
180 | 13.0k | params[0] = WasmType::Pointer; |
181 | 103k | } |
182 | | } |
183 | | |
184 | 120k | match variant { |
185 | | AbiVariant::GuestExportAsync => { |
186 | 11.8k | return WasmSignature { |
187 | 11.8k | params, |
188 | 11.8k | indirect_params, |
189 | 11.8k | results: vec![WasmType::Pointer], |
190 | 11.8k | retptr: false, |
191 | 11.8k | }; |
192 | | } |
193 | | AbiVariant::GuestExportAsyncStackful => { |
194 | 2.61k | return WasmSignature { |
195 | 2.61k | params, |
196 | 2.61k | indirect_params, |
197 | 2.61k | results: Vec::new(), |
198 | 2.61k | retptr: false, |
199 | 2.61k | }; |
200 | | } |
201 | 105k | _ => {} |
202 | 105k | } |
203 | 105k | |
204 | 105k | let mut results = Vec::new(); |
205 | 105k | for ty in func.results.iter_types() { |
206 | 94.3k | self.push_flat(ty, &mut results) |
207 | | } |
208 | | |
209 | 105k | let mut retptr = false; |
210 | 105k | |
211 | 105k | // Rust/C don't support multi-value well right now, so if a function |
212 | 105k | // would have multiple results then instead truncate it. Imports take a |
213 | 105k | // return pointer to write into and exports return a pointer they wrote |
214 | 105k | // into. |
215 | 105k | if results.len() > MAX_FLAT_RESULTS { |
216 | 2.51k | retptr = true; |
217 | 2.51k | results.truncate(0); |
218 | 2.51k | match variant { |
219 | 1.55k | AbiVariant::GuestImport => { |
220 | 1.55k | params.push(WasmType::Pointer); |
221 | 1.55k | } |
222 | 960 | AbiVariant::GuestExport => { |
223 | 960 | results.push(WasmType::Pointer); |
224 | 960 | } |
225 | 0 | _ => unreachable!(), |
226 | | } |
227 | 103k | } |
228 | | |
229 | 105k | WasmSignature { |
230 | 105k | params, |
231 | 105k | indirect_params, |
232 | 105k | results, |
233 | 105k | retptr, |
234 | 105k | } |
235 | 138k | } |
236 | | |
237 | | /// Appends the flat wasm types representing `ty` onto the `result` |
238 | | /// list provided. |
239 | 622k | pub fn push_flat(&self, ty: &Type, result: &mut Vec<WasmType>) { |
240 | 622k | match ty { |
241 | | Type::Bool |
242 | | | Type::S8 |
243 | | | Type::U8 |
244 | | | Type::S16 |
245 | | | Type::U16 |
246 | | | Type::S32 |
247 | | | Type::U32 |
248 | 282k | | Type::Char => result.push(WasmType::I32), |
249 | | |
250 | 11.8k | Type::U64 | Type::S64 => result.push(WasmType::I64), |
251 | 8.10k | Type::F32 => result.push(WasmType::F32), |
252 | 61.5k | Type::F64 => result.push(WasmType::F64), |
253 | 2.93k | Type::String => { |
254 | 2.93k | result.push(WasmType::Pointer); |
255 | 2.93k | result.push(WasmType::Length); |
256 | 2.93k | } |
257 | | |
258 | 255k | Type::Id(id) => match &self.types[*id].kind { |
259 | 695 | TypeDefKind::Type(t) => self.push_flat(t, result), |
260 | | |
261 | 34.0k | TypeDefKind::Handle(Handle::Own(_) | Handle::Borrow(_)) => { |
262 | 34.0k | result.push(WasmType::I32); |
263 | 34.0k | } |
264 | | |
265 | 0 | TypeDefKind::Resource => todo!(), |
266 | | |
267 | 168 | TypeDefKind::Record(r) => { |
268 | 1.39k | for field in r.fields.iter() { |
269 | 1.39k | self.push_flat(&field.ty, result); |
270 | 1.39k | } |
271 | | } |
272 | | |
273 | 56.4k | TypeDefKind::Tuple(t) => { |
274 | 203k | for ty in t.types.iter() { |
275 | 203k | self.push_flat(ty, result); |
276 | 203k | } |
277 | | } |
278 | | |
279 | 165 | TypeDefKind::Flags(r) => { |
280 | 165 | for _ in 0..r.repr().count() { |
281 | 165 | result.push(WasmType::I32); |
282 | 165 | } |
283 | | } |
284 | | |
285 | 1.93k | TypeDefKind::List(_) => { |
286 | 1.93k | result.push(WasmType::Pointer); |
287 | 1.93k | result.push(WasmType::Length); |
288 | 1.93k | } |
289 | | |
290 | 458 | TypeDefKind::Variant(v) => { |
291 | 458 | result.push(v.tag().into()); |
292 | 2.06k | self.push_flat_variants(v.cases.iter().map(|c| c.ty.as_ref()), result); |
293 | 458 | } |
294 | | |
295 | 2.15k | TypeDefKind::Enum(e) => result.push(e.tag().into()), |
296 | | |
297 | 43.8k | TypeDefKind::Option(t) => { |
298 | 43.8k | result.push(WasmType::I32); |
299 | 43.8k | self.push_flat_variants([None, Some(t)], result); |
300 | 43.8k | } |
301 | | |
302 | 6.28k | TypeDefKind::Result(r) => { |
303 | 6.28k | result.push(WasmType::I32); |
304 | 6.28k | self.push_flat_variants([r.ok.as_ref(), r.err.as_ref()], result); |
305 | 6.28k | } |
306 | | |
307 | 11.4k | TypeDefKind::Future(_) => { |
308 | 11.4k | result.push(WasmType::I32); |
309 | 11.4k | } |
310 | | |
311 | 3.61k | TypeDefKind::Stream(_) => { |
312 | 3.61k | result.push(WasmType::I32); |
313 | 3.61k | } |
314 | | |
315 | 94.2k | TypeDefKind::ErrorContext => { |
316 | 94.2k | result.push(WasmType::I32); |
317 | 94.2k | } |
318 | | |
319 | 0 | TypeDefKind::Unknown => unreachable!(), |
320 | | }, |
321 | | } |
322 | 622k | } |
323 | | |
324 | 50.5k | fn push_flat_variants<'a>( |
325 | 50.5k | &self, |
326 | 50.5k | tys: impl IntoIterator<Item = Option<&'a Type>>, |
327 | 50.5k | result: &mut Vec<WasmType>, |
328 | 50.5k | ) { |
329 | 50.5k | let mut temp = Vec::new(); |
330 | 50.5k | 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 | 152k | for ty in tys { |
339 | 102k | if let Some(ty) = ty { |
340 | 55.2k | self.push_flat(ty, &mut temp); |
341 | | |
342 | 403k | for (i, ty) in temp.drain(..).enumerate() { |
343 | 403k | match result.get_mut(start + i) { |
344 | 7.13k | Some(prev) => *prev = join(*prev, ty), |
345 | 396k | None => result.push(ty), |
346 | | } |
347 | | } |
348 | 46.9k | } |
349 | | } |
350 | 50.5k | } <wit_parser::resolve::Resolve>::push_flat_variants::<[core::option::Option<&wit_parser::Type>; 2]> Line | Count | Source | 324 | 50.1k | fn push_flat_variants<'a>( | 325 | 50.1k | &self, | 326 | 50.1k | tys: impl IntoIterator<Item = Option<&'a Type>>, | 327 | 50.1k | result: &mut Vec<WasmType>, | 328 | 50.1k | ) { | 329 | 50.1k | let mut temp = Vec::new(); | 330 | 50.1k | 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 | 150k | for ty in tys { | 339 | 100k | if let Some(ty) = ty { | 340 | 53.3k | self.push_flat(ty, &mut temp); | 341 | | | 342 | 396k | for (i, ty) in temp.drain(..).enumerate() { | 343 | 396k | match result.get_mut(start + i) { | 344 | 3.95k | Some(prev) => *prev = join(*prev, ty), | 345 | 392k | None => result.push(ty), | 346 | | } | 347 | | } | 348 | 46.8k | } | 349 | | } | 350 | 50.1k | } |
<wit_parser::resolve::Resolve>::push_flat_variants::<core::iter::adapters::map::Map<core::slice::iter::Iter<wit_parser::Case>, <wit_parser::resolve::Resolve>::push_flat::{closure#0}>> Line | Count | Source | 324 | 458 | fn push_flat_variants<'a>( | 325 | 458 | &self, | 326 | 458 | tys: impl IntoIterator<Item = Option<&'a Type>>, | 327 | 458 | result: &mut Vec<WasmType>, | 328 | 458 | ) { | 329 | 458 | let mut temp = Vec::new(); | 330 | 458 | 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 | 2.51k | for ty in tys { | 339 | 2.06k | if let Some(ty) = ty { | 340 | 1.94k | self.push_flat(ty, &mut temp); | 341 | | | 342 | 7.02k | for (i, ty) in temp.drain(..).enumerate() { | 343 | 7.02k | match result.get_mut(start + i) { | 344 | 3.17k | Some(prev) => *prev = join(*prev, ty), | 345 | 3.84k | None => result.push(ty), | 346 | | } | 347 | | } | 348 | 112 | } | 349 | | } | 350 | 458 | } |
|
351 | | } |