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}