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(_),
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 => result.push(WasmType::I32),
249
250            Type::U64 | Type::S64 => result.push(WasmType::I64),
251            Type::F32 => result.push(WasmType::F32),
252            Type::F64 => result.push(WasmType::F64),
253            Type::String => {
254                result.push(WasmType::Pointer);
255                result.push(WasmType::Length);
256            }
257
258            Type::Id(id) => match &self.types[*id].kind {
259                TypeDefKind::Type(t) => self.push_flat(t, result),
260
261                TypeDefKind::Handle(Handle::Own(_) | Handle::Borrow(_)) => {
262                    result.push(WasmType::I32);
263                }
264
265                TypeDefKind::Resource => todo!(),
266
267                TypeDefKind::Record(r) => {
268                    for field in r.fields.iter() {
269                        self.push_flat(&field.ty, result);
270                    }
271                }
272
273                TypeDefKind::Tuple(t) => {
274                    for ty in t.types.iter() {
275                        self.push_flat(ty, result);
276                    }
277                }
278
279                TypeDefKind::Flags(r) => {
280                    for _ in 0..r.repr().count() {
281                        result.push(WasmType::I32);
282                    }
283                }
284
285                TypeDefKind::List(_) => {
286                    result.push(WasmType::Pointer);
287                    result.push(WasmType::Length);
288                }
289
290                TypeDefKind::Variant(v) => {
291                    result.push(v.tag().into());
292                    self.push_flat_variants(v.cases.iter().map(|c| c.ty.as_ref()), result);
293                }
294
295                TypeDefKind::Enum(e) => result.push(e.tag().into()),
296
297                TypeDefKind::Option(t) => {
298                    result.push(WasmType::I32);
299                    self.push_flat_variants([None, Some(t)], result);
300                }
301
302                TypeDefKind::Result(r) => {
303                    result.push(WasmType::I32);
304                    self.push_flat_variants([r.ok.as_ref(), r.err.as_ref()], result);
305                }
306
307                TypeDefKind::Future(_) => {
308                    result.push(WasmType::I32);
309                }
310
311                TypeDefKind::Stream(_) => {
312                    result.push(WasmType::I32);
313                }
314
315                TypeDefKind::ErrorContext => {
316                    result.push(WasmType::I32);
317                }
318
319                TypeDefKind::Unknown => unreachable!(),
320            },
321        }
322    }
323
324    fn push_flat_variants<'a>(
325        &self,
326        tys: impl IntoIterator<Item = Option<&'a Type>>,
327        result: &mut Vec<WasmType>,
328    ) {
329        let mut temp = Vec::new();
330        let start = result.len();
331
332        // Push each case's type onto a temporary vector, and then
333        // merge that vector into our final list starting at
334        // `start`. Note that this requires some degree of
335        // "unification" so we can handle things like `Result<i32,
336        // f32>` where that turns into `[i32 i32]` where the second
337        // `i32` might be the `f32` bitcasted.
338        for ty in tys {
339            if let Some(ty) = ty {
340                self.push_flat(ty, &mut temp);
341
342                for (i, ty) in temp.drain(..).enumerate() {
343                    match result.get_mut(start + i) {
344                        Some(prev) => *prev = join(*prev, ty),
345                        None => result.push(ty),
346                    }
347                }
348            }
349        }
350    }
351}