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