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 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)]
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 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    pub fn wasm_signature(&self, variant: AbiVariant, func: &Function) -> WasmSignature {
141        if let AbiVariant::GuestImportAsync = variant {
142            return WasmSignature {
143                params: vec![WasmType::Pointer; 2],
144                indirect_params: true,
145                results: vec![WasmType::I32],
146                retptr: true,
147            };
148        }
149
150        const MAX_FLAT_PARAMS: usize = 16;
151        const MAX_FLAT_RESULTS: usize = 1;
152
153        let mut params = Vec::new();
154        let mut indirect_params = false;
155        for (_, param) in func.params.iter() {
156            self.push_flat(param, &mut params);
157        }
158
159        if params.len() > MAX_FLAT_PARAMS {
160            params.truncate(0);
161            params.push(WasmType::Pointer);
162            indirect_params = true;
163        } else {
164            if matches!(
165                (&func.kind, variant),
166                (
167                    crate::FunctionKind::Method(_) | crate::FunctionKind::AsyncMethod(_),
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                assert!(matches!(params[0], WasmType::I32));
180                params[0] = WasmType::Pointer;
181            }
182        }
183
184        match variant {
185            AbiVariant::GuestExportAsync => {
186                return WasmSignature {
187                    params,
188                    indirect_params,
189                    results: vec![WasmType::Pointer],
190                    retptr: false,
191                };
192            }
193            AbiVariant::GuestExportAsyncStackful => {
194                return WasmSignature {
195                    params,
196                    indirect_params,
197                    results: Vec::new(),
198                    retptr: false,
199                };
200            }
201            _ => {}
202        }
203
204        let mut results = Vec::new();
205        if let Some(ty) = &func.result {
206            self.push_flat(ty, &mut results)
207        }
208
209        let mut retptr = false;
210
211        // Rust/C don't support multi-value well right now, so if a function
212        // would have multiple results then instead truncate it. Imports take a
213        // return pointer to write into and exports return a pointer they wrote
214        // into.
215        if results.len() > MAX_FLAT_RESULTS {
216            retptr = true;
217            results.truncate(0);
218            match variant {
219                AbiVariant::GuestImport => {
220                    params.push(WasmType::Pointer);
221                }
222                AbiVariant::GuestExport => {
223                    results.push(WasmType::Pointer);
224                }
225                _ => unreachable!(),
226            }
227        }
228
229        WasmSignature {
230            params,
231            indirect_params,
232            results,
233            retptr,
234        }
235    }
236
237    /// Appends the flat wasm types representing `ty` onto the `result`
238    /// list provided.
239    pub fn push_flat(&self, ty: &Type, result: &mut Vec<WasmType>) {
240        match ty {
241            Type::Bool
242            | Type::S8
243            | Type::U8
244            | Type::S16
245            | Type::U16
246            | Type::S32
247            | Type::U32
248            | Type::Char
249            | Type::ErrorContext => result.push(WasmType::I32),
250
251            Type::U64 | Type::S64 => result.push(WasmType::I64),
252            Type::F32 => result.push(WasmType::F32),
253            Type::F64 => result.push(WasmType::F64),
254            Type::String => {
255                result.push(WasmType::Pointer);
256                result.push(WasmType::Length);
257            }
258
259            Type::Id(id) => match &self.types[*id].kind {
260                TypeDefKind::Type(t) => self.push_flat(t, result),
261
262                TypeDefKind::Handle(Handle::Own(_) | Handle::Borrow(_)) => {
263                    result.push(WasmType::I32);
264                }
265
266                TypeDefKind::Resource => todo!(),
267
268                TypeDefKind::Record(r) => {
269                    for field in r.fields.iter() {
270                        self.push_flat(&field.ty, result);
271                    }
272                }
273
274                TypeDefKind::Tuple(t) => {
275                    for ty in t.types.iter() {
276                        self.push_flat(ty, result);
277                    }
278                }
279
280                TypeDefKind::Flags(r) => {
281                    for _ in 0..r.repr().count() {
282                        result.push(WasmType::I32);
283                    }
284                }
285
286                TypeDefKind::List(_) => {
287                    result.push(WasmType::Pointer);
288                    result.push(WasmType::Length);
289                }
290
291                TypeDefKind::Variant(v) => {
292                    result.push(v.tag().into());
293                    self.push_flat_variants(v.cases.iter().map(|c| c.ty.as_ref()), result);
294                }
295
296                TypeDefKind::Enum(e) => result.push(e.tag().into()),
297
298                TypeDefKind::Option(t) => {
299                    result.push(WasmType::I32);
300                    self.push_flat_variants([None, Some(t)], result);
301                }
302
303                TypeDefKind::Result(r) => {
304                    result.push(WasmType::I32);
305                    self.push_flat_variants([r.ok.as_ref(), r.err.as_ref()], result);
306                }
307
308                TypeDefKind::Future(_) => {
309                    result.push(WasmType::I32);
310                }
311
312                TypeDefKind::Stream(_) => {
313                    result.push(WasmType::I32);
314                }
315
316                TypeDefKind::Unknown => unreachable!(),
317            },
318        }
319    }
320
321    fn push_flat_variants<'a>(
322        &self,
323        tys: impl IntoIterator<Item = Option<&'a Type>>,
324        result: &mut Vec<WasmType>,
325    ) {
326        let mut temp = Vec::new();
327        let start = result.len();
328
329        // Push each case's type onto a temporary vector, and then
330        // merge that vector into our final list starting at
331        // `start`. Note that this requires some degree of
332        // "unification" so we can handle things like `Result<i32,
333        // f32>` where that turns into `[i32 i32]` where the second
334        // `i32` might be the `f32` bitcasted.
335        for ty in tys {
336            if let Some(ty) = ty {
337                self.push_flat(ty, &mut temp);
338
339                for (i, ty) in temp.drain(..).enumerate() {
340                    match result.get_mut(start + i) {
341                        Some(prev) => *prev = join(*prev, ty),
342                        None => result.push(ty),
343                    }
344                }
345            }
346        }
347    }
348}