wit_parser/abi.rs
1use 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)]
5pub 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 parameters 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)]
29pub 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
61fn 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
104impl 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)]
125pub 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
135impl AbiVariant {
136 pub fn is_async(&self) -> bool {
137 match self {
138 Self::GuestImport | Self::GuestExport => false,
139 Self::GuestImportAsync | Self::GuestExportAsync | Self::GuestExportAsyncStackful => {
140 true
141 }
142 }
143 }
144}
145
146pub struct FlatTypes<'a> {
147 types: &'a mut [WasmType],
148 cur: usize,
149 overflow: bool,
150}
151
152impl<'a> FlatTypes<'a> {
153 pub fn new(types: &'a mut [WasmType]) -> FlatTypes<'a> {
154 FlatTypes {
155 types,
156 cur: 0,
157 overflow: false,
158 }
159 }
160
161 pub fn push(&mut self, ty: WasmType) -> bool {
162 match self.types.get_mut(self.cur) {
163 Some(next) => {
164 *next = ty;
165 self.cur += 1;
166 true
167 }
168 None => {
169 self.overflow = true;
170 false
171 }
172 }
173 }
174
175 pub fn to_vec(&self) -> Vec<WasmType> {
176 self.types[..self.cur].to_vec()
177 }
178}
179
180impl Resolve {
181 pub const MAX_FLAT_PARAMS: usize = 16;
182 pub const MAX_FLAT_ASYNC_PARAMS: usize = 4;
183 pub const MAX_FLAT_RESULTS: usize = 1;
184
185 /// Get the WebAssembly type signature for this interface function
186 ///
187 /// The first entry returned is the list of parameters and the second entry
188 /// is the list of results for the wasm function signature.
189 pub fn wasm_signature(&self, variant: AbiVariant, func: &Function) -> WasmSignature {
190 // Note that one extra parameter is allocated in case a return pointer
191 // is needed down below for imports.
192 let mut storage = [WasmType::I32; Self::MAX_FLAT_PARAMS + 1];
193 let mut params = FlatTypes::new(&mut storage);
194 let ok = self.push_flat_list(func.params.iter().map(|(_, param)| param), &mut params);
195 assert_eq!(ok, !params.overflow);
196
197 let max = match variant {
198 AbiVariant::GuestImport
199 | AbiVariant::GuestExport
200 | AbiVariant::GuestExportAsync
201 | AbiVariant::GuestExportAsyncStackful => Self::MAX_FLAT_PARAMS,
202 AbiVariant::GuestImportAsync => Self::MAX_FLAT_ASYNC_PARAMS,
203 };
204
205 let indirect_params = !ok || params.cur > max;
206 if indirect_params {
207 params.types[0] = WasmType::Pointer;
208 params.cur = 1;
209 } else {
210 if matches!(
211 (&func.kind, variant),
212 (
213 crate::FunctionKind::Method(_) | crate::FunctionKind::AsyncMethod(_),
214 AbiVariant::GuestExport
215 | AbiVariant::GuestExportAsync
216 | AbiVariant::GuestExportAsyncStackful
217 )
218 ) {
219 // Guest exported methods always receive resource rep as first argument
220 //
221 // TODO: Ideally you would distinguish between imported and exported
222 // resource Handles and then use either I32 or Pointer in abi::push_flat().
223 // But this contextual information isn't available, yet.
224 // See https://github.com/bytecodealliance/wasm-tools/pull/1438 for more details.
225 assert!(matches!(params.types[0], WasmType::I32));
226 params.types[0] = WasmType::Pointer;
227 }
228 }
229
230 let mut storage = [WasmType::I32; Self::MAX_FLAT_RESULTS];
231 let mut results = FlatTypes::new(&mut storage);
232 let mut retptr = false;
233 match variant {
234 AbiVariant::GuestImport | AbiVariant::GuestExport => {
235 if let Some(ty) = &func.result {
236 self.push_flat(ty, &mut results);
237 }
238 retptr = results.overflow;
239
240 // Rust/C don't support multi-value well right now, so if a
241 // function would have multiple results then instead truncate
242 // it. Imports take a return pointer to write into and exports
243 // return a pointer they wrote into.
244 if retptr {
245 results.cur = 0;
246 match variant {
247 AbiVariant::GuestImport => {
248 assert!(params.push(WasmType::Pointer));
249 }
250 AbiVariant::GuestExport => {
251 assert!(results.push(WasmType::Pointer));
252 }
253 _ => unreachable!(),
254 }
255 }
256 }
257 AbiVariant::GuestImportAsync => {
258 // If this function has a result, a pointer must be passed to
259 // get filled in by the async runtime.
260 if func.result.is_some() {
261 assert!(params.push(WasmType::Pointer));
262 retptr = true;
263 }
264
265 // The result of this function is a status code.
266 assert!(results.push(WasmType::I32));
267 }
268 AbiVariant::GuestExportAsync => {
269 // The result of this function is a status code. Note that the
270 // function results are entirely ignored here as they aren't
271 // part of the ABI and are handled in the `task.return`
272 // intrinsic.
273 assert!(results.push(WasmType::I32));
274 }
275 AbiVariant::GuestExportAsyncStackful => {
276 // No status code, and like async exports no result handling.
277 }
278 }
279
280 WasmSignature {
281 params: params.to_vec(),
282 indirect_params,
283 results: results.to_vec(),
284 retptr,
285 }
286 }
287
288 fn push_flat_list<'a>(
289 &self,
290 mut list: impl Iterator<Item = &'a Type>,
291 result: &mut FlatTypes<'_>,
292 ) -> bool {
293 list.all(|ty| self.push_flat(ty, result))
294 }
295
296 /// Appends the flat wasm types representing `ty` onto the `result`
297 /// list provided.
298 pub fn push_flat(&self, ty: &Type, result: &mut FlatTypes<'_>) -> bool {
299 match ty {
300 Type::Bool
301 | Type::S8
302 | Type::U8
303 | Type::S16
304 | Type::U16
305 | Type::S32
306 | Type::U32
307 | Type::Char
308 | Type::ErrorContext => result.push(WasmType::I32),
309
310 Type::U64 | Type::S64 => result.push(WasmType::I64),
311 Type::F32 => result.push(WasmType::F32),
312 Type::F64 => result.push(WasmType::F64),
313 Type::String => result.push(WasmType::Pointer) && result.push(WasmType::Length),
314
315 Type::Id(id) => match &self.types[*id].kind {
316 TypeDefKind::Type(t) => self.push_flat(t, result),
317
318 TypeDefKind::Handle(Handle::Own(_) | Handle::Borrow(_)) => {
319 result.push(WasmType::I32)
320 }
321
322 TypeDefKind::Resource => todo!(),
323
324 TypeDefKind::Record(r) => {
325 self.push_flat_list(r.fields.iter().map(|f| &f.ty), result)
326 }
327
328 TypeDefKind::Tuple(t) => self.push_flat_list(t.types.iter(), result),
329
330 TypeDefKind::Flags(r) => {
331 self.push_flat_list((0..r.repr().count()).map(|_| &Type::U32), result)
332 }
333
334 TypeDefKind::List(_) => {
335 result.push(WasmType::Pointer) && result.push(WasmType::Length)
336 }
337
338 TypeDefKind::FixedSizeList(ty, size) => {
339 self.push_flat_list((0..*size).map(|_| ty), result)
340 }
341
342 TypeDefKind::Variant(v) => {
343 result.push(v.tag().into())
344 && self.push_flat_variants(v.cases.iter().map(|c| c.ty.as_ref()), result)
345 }
346
347 TypeDefKind::Enum(e) => result.push(e.tag().into()),
348
349 TypeDefKind::Option(t) => {
350 result.push(WasmType::I32) && self.push_flat_variants([None, Some(t)], result)
351 }
352
353 TypeDefKind::Result(r) => {
354 result.push(WasmType::I32)
355 && self.push_flat_variants([r.ok.as_ref(), r.err.as_ref()], result)
356 }
357
358 TypeDefKind::Future(_) => result.push(WasmType::I32),
359 TypeDefKind::Stream(_) => result.push(WasmType::I32),
360
361 TypeDefKind::Unknown => unreachable!(),
362 },
363 }
364 }
365
366 fn push_flat_variants<'a>(
367 &self,
368 tys: impl IntoIterator<Item = Option<&'a Type>>,
369 result: &mut FlatTypes<'_>,
370 ) -> bool {
371 let mut temp = result.types[result.cur..].to_vec();
372 let mut temp = FlatTypes::new(&mut temp);
373 let start = result.cur;
374
375 // Push each case's type onto a temporary vector, and then
376 // merge that vector into our final list starting at
377 // `start`. Note that this requires some degree of
378 // "unification" so we can handle things like `Result<i32,
379 // f32>` where that turns into `[i32 i32]` where the second
380 // `i32` might be the `f32` bitcasted.
381 for ty in tys {
382 if let Some(ty) = ty {
383 if !self.push_flat(ty, &mut temp) {
384 result.overflow = true;
385 return false;
386 }
387
388 for (i, ty) in temp.types[..temp.cur].iter().enumerate() {
389 let i = i + start;
390 if i < result.cur {
391 result.types[i] = join(result.types[i], *ty);
392 } else if result.cur == result.types.len() {
393 result.overflow = true;
394 return false;
395 } else {
396 result.types[i] = *ty;
397 result.cur += 1;
398 }
399 }
400 temp.cur = 0;
401 }
402 }
403
404 true
405 }
406}