Skip to main content

wit_parser/
abi.rs

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