Coverage Report

Created: 2026-09-28 08:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wasm-tools/crates/wit-parser/src/abi.rs
Line
Count
Source
1
use crate::{Function, Handle, Int, Resolve, Type, TypeDefKind};
2
use alloc::vec::Vec;
3
4
/// A core WebAssembly signature with params and results.
5
#[derive(Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
6
pub 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)]
30
pub 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
62
2.72k
fn join(a: WasmType, b: WasmType) -> WasmType {
63
    use WasmType::*;
64
65
2.72k
    match (a, b) {
66
        (I32, I32)
67
        | (I64, I64)
68
        | (F32, F32)
69
        | (F64, F64)
70
        | (Pointer, Pointer)
71
        | (PointerOrI64, PointerOrI64)
72
2.48k
        | (Length, Length) => a,
73
74
39
        (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
4
        (Length, I32 | F32) => Length,
79
4
        (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
0
        (Length, I64 | F64) => I64,
84
0
        (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
10
        (Pointer, I32 | F32 | Length) => Pointer,
89
12
        (I32 | F32 | Length, Pointer) => Pointer,
90
91
        // If we need 64 bits and provenance, we need to use the special
92
        // `PointerOrI64`.
93
0
        (Pointer, I64 | F64) => PointerOrI64,
94
4
        (I64 | F64, Pointer) => PointerOrI64,
95
96
        // PointerOrI64 wins over everything.
97
0
        (PointerOrI64, _) => PointerOrI64,
98
0
        (_, PointerOrI64) => PointerOrI64,
99
100
        // Otherwise, `i64` wins.
101
165
        (_, I64 | F64) | (I64 | F64, _) => I64,
102
    }
103
2.72k
}
104
105
impl From<Int> for WasmType {
106
186
    fn from(i: Int) -> WasmType {
107
186
        match i {
108
186
            Int::U8 | Int::U16 | Int::U32 => WasmType::I32,
109
0
            Int::U64 => WasmType::I64,
110
        }
111
186
    }
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)]
126
pub 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
136
impl AbiVariant {
137
0
    pub fn is_async(&self) -> bool {
138
0
        match self {
139
0
            Self::GuestImport | Self::GuestExport => false,
140
            Self::GuestImportAsync | Self::GuestExportAsync | Self::GuestExportAsyncStackful => {
141
0
                true
142
            }
143
        }
144
0
    }
145
}
146
147
pub struct FlatTypes<'a> {
148
    types: &'a mut [WasmType],
149
    cur: usize,
150
    overflow: bool,
151
}
152
153
impl<'a> FlatTypes<'a> {
154
40.2k
    pub fn new(types: &'a mut [WasmType]) -> FlatTypes<'a> {
155
40.2k
        FlatTypes {
156
40.2k
            types,
157
40.2k
            cur: 0,
158
40.2k
            overflow: false,
159
40.2k
        }
160
40.2k
    }
161
162
100k
    pub fn push(&mut self, ty: WasmType) -> bool {
163
100k
        match self.types.get_mut(self.cur) {
164
96.8k
            Some(next) => {
165
96.8k
                *next = ty;
166
96.8k
                self.cur += 1;
167
96.8k
                true
168
            }
169
            None => {
170
3.81k
                self.overflow = true;
171
3.81k
                false
172
            }
173
        }
174
100k
    }
175
176
34.1k
    pub fn to_vec(&self) -> Vec<WasmType> {
177
34.1k
        self.types[..self.cur].to_vec()
178
34.1k
    }
179
}
180
181
impl 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
17.0k
    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
17.0k
        let mut storage = [WasmType::I32; Self::MAX_FLAT_PARAMS + 1];
194
17.0k
        let mut params = FlatTypes::new(&mut storage);
195
17.0k
        let ok = self.push_flat_list(func.params.iter().map(|p| &p.ty), &mut params);
196
17.0k
        assert_eq!(ok, !params.overflow);
197
198
17.0k
        let max = match variant {
199
            AbiVariant::GuestImport
200
            | AbiVariant::GuestExport
201
            | AbiVariant::GuestExportAsync
202
15.9k
            | AbiVariant::GuestExportAsyncStackful => Self::MAX_FLAT_PARAMS,
203
1.08k
            AbiVariant::GuestImportAsync => Self::MAX_FLAT_ASYNC_PARAMS,
204
        };
205
206
17.0k
        let indirect_params = !ok || params.cur > max;
207
17.0k
        if indirect_params {
208
1.24k
            params.types[0] = WasmType::Pointer;
209
1.24k
            params.cur = 1;
210
1.24k
        } else {
211
14.1k
            if matches!(
212
15.8k
                (&func.kind, variant),
213
                (
214
                    crate::FunctionKind::Method(_)
215
                        | crate::FunctionKind::AsyncMethod(_)
216
                        | crate::FunctionKind::MethodGetter(_)
217
                        | crate::FunctionKind::MethodSetter(_),
218
                    AbiVariant::GuestExport
219
                        | AbiVariant::GuestExportAsync
220
                        | AbiVariant::GuestExportAsyncStackful
221
                )
222
            ) {
223
                // Guest exported methods always receive resource rep as first argument
224
                //
225
                // TODO: Ideally you would distinguish between imported and exported
226
                // resource Handles and then use either I32 or Pointer in abi::push_flat().
227
                // But this contextual information isn't available, yet.
228
                // See https://github.com/bytecodealliance/wasm-tools/pull/1438 for more details.
229
1.66k
                assert!(matches!(params.types[0], WasmType::I32));
230
1.66k
                params.types[0] = WasmType::Pointer;
231
14.1k
            }
232
        }
233
234
17.0k
        let mut storage = [WasmType::I32; Self::MAX_FLAT_RESULTS];
235
17.0k
        let mut results = FlatTypes::new(&mut storage);
236
17.0k
        let mut retptr = false;
237
17.0k
        match variant {
238
            AbiVariant::GuestImport | AbiVariant::GuestExport => {
239
15.2k
                if let Some(ty) = &func.result {
240
12.1k
                    self.push_flat(ty, &mut results);
241
12.1k
                }
242
15.2k
                retptr = results.overflow;
243
244
                // Rust/C don't support multi-value well right now, so if a
245
                // function would have multiple results then instead truncate
246
                // it. Imports take a return pointer to write into and exports
247
                // return a pointer they wrote into.
248
15.2k
                if retptr {
249
2.72k
                    results.cur = 0;
250
2.72k
                    match variant {
251
                        AbiVariant::GuestImport => {
252
647
                            assert!(params.push(WasmType::Pointer));
253
                        }
254
                        AbiVariant::GuestExport => {
255
2.07k
                            assert!(results.push(WasmType::Pointer));
256
                        }
257
0
                        _ => unreachable!(),
258
                    }
259
12.5k
                }
260
            }
261
            AbiVariant::GuestImportAsync => {
262
                // If this function has a result, a pointer must be passed to
263
                // get filled in by the async runtime.
264
1.08k
                if func.result.is_some() {
265
709
                    assert!(params.push(WasmType::Pointer));
266
709
                    retptr = true;
267
377
                }
268
269
                // The result of this function is a status code.
270
1.08k
                assert!(results.push(WasmType::I32));
271
            }
272
            AbiVariant::GuestExportAsync => {
273
                // The result of this function is a status code. Note that the
274
                // function results are entirely ignored here as they aren't
275
                // part of the ABI and are handled in the `task.return`
276
                // intrinsic.
277
567
                assert!(results.push(WasmType::I32));
278
            }
279
138
            AbiVariant::GuestExportAsyncStackful => {
280
138
                // No status code, and like async exports no result handling.
281
138
            }
282
        }
283
284
17.0k
        WasmSignature {
285
17.0k
            params: params.to_vec(),
286
17.0k
            indirect_params,
287
17.0k
            results: results.to_vec(),
288
17.0k
            retptr,
289
17.0k
        }
290
17.0k
    }
291
292
33.1k
    fn push_flat_list<'a>(
293
33.1k
        &self,
294
33.1k
        mut list: impl Iterator<Item = &'a Type>,
295
33.1k
        result: &mut FlatTypes<'_>,
296
33.1k
    ) -> bool {
297
88.4k
        list.all(|ty| self.push_flat(ty, result))
<wit_parser::resolve::Resolve>::push_flat_list::<core::slice::iter::Iter<wit_parser::Type>>::{closure#0}
Line
Count
Source
297
30.8k
        list.all(|ty| self.push_flat(ty, result))
<wit_parser::resolve::Resolve>::push_flat_list::<core::iter::adapters::map::Map<core::ops::range::Range<usize>, <wit_parser::resolve::Resolve>::push_flat::{closure#1}>>::{closure#0}
Line
Count
Source
297
31
        list.all(|ty| self.push_flat(ty, result))
<wit_parser::resolve::Resolve>::push_flat_list::<core::iter::adapters::map::Map<core::ops::range::Range<u32>, <wit_parser::resolve::Resolve>::push_flat::{closure#2}>>::{closure#0}
Line
Count
Source
297
5.22k
        list.all(|ty| self.push_flat(ty, result))
<wit_parser::resolve::Resolve>::push_flat_list::<core::iter::adapters::map::Map<core::slice::iter::Iter<wit_parser::Field>, <wit_parser::resolve::Resolve>::push_flat::{closure#0}>>::{closure#0}
Line
Count
Source
297
256
        list.all(|ty| self.push_flat(ty, result))
<wit_parser::resolve::Resolve>::push_flat_list::<core::iter::adapters::map::Map<core::slice::iter::Iter<wit_parser::Param>, <wit_parser::resolve::Resolve>::wasm_signature::{closure#0}>>::{closure#0}
Line
Count
Source
297
52.0k
        list.all(|ty| self.push_flat(ty, result))
298
33.1k
    }
<wit_parser::resolve::Resolve>::push_flat_list::<core::slice::iter::Iter<wit_parser::Type>>
Line
Count
Source
292
13.1k
    fn push_flat_list<'a>(
293
13.1k
        &self,
294
13.1k
        mut list: impl Iterator<Item = &'a Type>,
295
13.1k
        result: &mut FlatTypes<'_>,
296
13.1k
    ) -> bool {
297
13.1k
        list.all(|ty| self.push_flat(ty, result))
298
13.1k
    }
<wit_parser::resolve::Resolve>::push_flat_list::<core::iter::adapters::map::Map<core::ops::range::Range<usize>, <wit_parser::resolve::Resolve>::push_flat::{closure#1}>>
Line
Count
Source
292
31
    fn push_flat_list<'a>(
293
31
        &self,
294
31
        mut list: impl Iterator<Item = &'a Type>,
295
31
        result: &mut FlatTypes<'_>,
296
31
    ) -> bool {
297
31
        list.all(|ty| self.push_flat(ty, result))
298
31
    }
<wit_parser::resolve::Resolve>::push_flat_list::<core::iter::adapters::map::Map<core::ops::range::Range<u32>, <wit_parser::resolve::Resolve>::push_flat::{closure#2}>>
Line
Count
Source
292
2.72k
    fn push_flat_list<'a>(
293
2.72k
        &self,
294
2.72k
        mut list: impl Iterator<Item = &'a Type>,
295
2.72k
        result: &mut FlatTypes<'_>,
296
2.72k
    ) -> bool {
297
2.72k
        list.all(|ty| self.push_flat(ty, result))
298
2.72k
    }
<wit_parser::resolve::Resolve>::push_flat_list::<core::iter::adapters::map::Map<core::slice::iter::Iter<wit_parser::Field>, <wit_parser::resolve::Resolve>::push_flat::{closure#0}>>
Line
Count
Source
292
168
    fn push_flat_list<'a>(
293
168
        &self,
294
168
        mut list: impl Iterator<Item = &'a Type>,
295
168
        result: &mut FlatTypes<'_>,
296
168
    ) -> bool {
297
168
        list.all(|ty| self.push_flat(ty, result))
298
168
    }
<wit_parser::resolve::Resolve>::push_flat_list::<core::iter::adapters::map::Map<core::slice::iter::Iter<wit_parser::Param>, <wit_parser::resolve::Resolve>::wasm_signature::{closure#0}>>
Line
Count
Source
292
17.0k
    fn push_flat_list<'a>(
293
17.0k
        &self,
294
17.0k
        mut list: impl Iterator<Item = &'a Type>,
295
17.0k
        result: &mut FlatTypes<'_>,
296
17.0k
    ) -> bool {
297
17.0k
        list.all(|ty| self.push_flat(ty, result))
298
17.0k
    }
299
300
    /// Appends the flat wasm types representing `ty` onto the `result`
301
    /// list provided.
302
109k
    pub fn push_flat(&self, ty: &Type, result: &mut FlatTypes<'_>) -> bool {
303
109k
        match ty {
304
            Type::Bool
305
            | Type::S8
306
            | Type::U8
307
            | Type::S16
308
            | Type::U16
309
            | Type::S32
310
            | Type::U32
311
            | Type::Char
312
68.2k
            | Type::ErrorContext => result.push(WasmType::I32),
313
314
2.37k
            Type::U64 | Type::S64 => result.push(WasmType::I64),
315
5.94k
            Type::F32 => result.push(WasmType::F32),
316
1.04k
            Type::F64 => result.push(WasmType::F64),
317
1.89k
            Type::String => result.push(WasmType::Pointer) && result.push(WasmType::Length),
318
319
29.8k
            Type::Id(id) => match &self.types[*id].kind {
320
449
                TypeDefKind::Type(t) => self.push_flat(t, result),
321
322
                TypeDefKind::Handle(Handle::Own(_) | Handle::Borrow(_)) => {
323
2.60k
                    result.push(WasmType::I32)
324
                }
325
326
0
                TypeDefKind::Resource => todo!(),
327
328
168
                TypeDefKind::Record(r) => {
329
168
                    self.push_flat_list(r.fields.iter().map(|f| &f.ty), result)
330
                }
331
332
13.1k
                TypeDefKind::Tuple(t) => self.push_flat_list(t.types.iter(), result),
333
334
31
                TypeDefKind::Flags(r) => {
335
31
                    self.push_flat_list((0..r.repr().count()).map(|_| &Type::U32), result)
336
                }
337
338
                TypeDefKind::List(_) => {
339
933
                    result.push(WasmType::Pointer) && result.push(WasmType::Length)
340
                }
341
342
                TypeDefKind::Map(_, _) => {
343
0
                    result.push(WasmType::Pointer) && result.push(WasmType::Length)
344
                }
345
346
2.72k
                TypeDefKind::FixedLengthList(ty, size) => {
347
2.72k
                    self.push_flat_list((0..*size).map(|_| ty), result)
348
                }
349
350
25
                TypeDefKind::Variant(v) => {
351
25
                    result.push(v.tag().into())
352
156
                        && self.push_flat_variants(v.cases.iter().map(|c| c.ty.as_ref()), result)
353
                }
354
355
161
                TypeDefKind::Enum(e) => result.push(e.tag().into()),
356
357
2.76k
                TypeDefKind::Option(t) => {
358
2.76k
                    result.push(WasmType::I32) && self.push_flat_variants([None, Some(t)], result)
359
                }
360
361
3.53k
                TypeDefKind::Result(r) => {
362
3.53k
                    result.push(WasmType::I32)
363
3.42k
                        && self.push_flat_variants([r.ok.as_ref(), r.err.as_ref()], result)
364
                }
365
366
2.44k
                TypeDefKind::Future(_) => result.push(WasmType::I32),
367
831
                TypeDefKind::Stream(_) => result.push(WasmType::I32),
368
369
0
                TypeDefKind::Unknown => unreachable!(),
370
            },
371
        }
372
109k
    }
373
374
6.04k
    fn push_flat_variants<'a>(
375
6.04k
        &self,
376
6.04k
        tys: impl IntoIterator<Item = Option<&'a Type>>,
377
6.04k
        result: &mut FlatTypes<'_>,
378
6.04k
    ) -> bool {
379
6.04k
        let mut temp = result.types[result.cur..].to_vec();
380
6.04k
        let mut temp = FlatTypes::new(&mut temp);
381
6.04k
        let start = result.cur;
382
383
        // Push each case's type onto a temporary vector, and then
384
        // merge that vector into our final list starting at
385
        // `start`. Note that this requires some degree of
386
        // "unification" so we can handle things like `Result<i32,
387
        // f32>` where that turns into `[i32 i32]` where the second
388
        // `i32` might be the `f32` bitcasted.
389
12.0k
        for ty in tys {
390
12.0k
            if let Some(ty) = ty {
391
8.39k
                if !self.push_flat(ty, &mut temp) {
392
966
                    result.overflow = true;
393
966
                    return false;
394
7.42k
                }
395
396
17.3k
                for (i, ty) in temp.types[..temp.cur].iter().enumerate() {
397
17.3k
                    let i = i + start;
398
17.3k
                    if i < result.cur {
399
2.72k
                        result.types[i] = join(result.types[i], *ty);
400
14.6k
                    } else if result.cur == result.types.len() {
401
0
                        result.overflow = true;
402
0
                        return false;
403
14.6k
                    } else {
404
14.6k
                        result.types[i] = *ty;
405
14.6k
                        result.cur += 1;
406
14.6k
                    }
407
                }
408
7.42k
                temp.cur = 0;
409
3.68k
            }
410
        }
411
412
5.07k
        true
413
6.04k
    }
<wit_parser::resolve::Resolve>::push_flat_variants::<[core::option::Option<&wit_parser::Type>; 2]>
Line
Count
Source
374
6.02k
    fn push_flat_variants<'a>(
375
6.02k
        &self,
376
6.02k
        tys: impl IntoIterator<Item = Option<&'a Type>>,
377
6.02k
        result: &mut FlatTypes<'_>,
378
6.02k
    ) -> bool {
379
6.02k
        let mut temp = result.types[result.cur..].to_vec();
380
6.02k
        let mut temp = FlatTypes::new(&mut temp);
381
6.02k
        let start = result.cur;
382
383
        // Push each case's type onto a temporary vector, and then
384
        // merge that vector into our final list starting at
385
        // `start`. Note that this requires some degree of
386
        // "unification" so we can handle things like `Result<i32,
387
        // f32>` where that turns into `[i32 i32]` where the second
388
        // `i32` might be the `f32` bitcasted.
389
11.9k
        for ty in tys {
390
11.9k
            if let Some(ty) = ty {
391
8.28k
                if !self.push_flat(ty, &mut temp) {
392
966
                    result.overflow = true;
393
966
                    return false;
394
7.31k
                }
395
396
17.2k
                for (i, ty) in temp.types[..temp.cur].iter().enumerate() {
397
17.2k
                    let i = i + start;
398
17.2k
                    if i < result.cur {
399
2.63k
                        result.types[i] = join(result.types[i], *ty);
400
14.6k
                    } else if result.cur == result.types.len() {
401
0
                        result.overflow = true;
402
0
                        return false;
403
14.6k
                    } else {
404
14.6k
                        result.types[i] = *ty;
405
14.6k
                        result.cur += 1;
406
14.6k
                    }
407
                }
408
7.31k
                temp.cur = 0;
409
3.63k
            }
410
        }
411
412
5.05k
        true
413
6.02k
    }
<wit_parser::resolve::Resolve>::push_flat_variants::<core::iter::adapters::map::Map<core::slice::iter::Iter<wit_parser::Case>, <wit_parser::resolve::Resolve>::push_flat::{closure#3}>>
Line
Count
Source
374
25
    fn push_flat_variants<'a>(
375
25
        &self,
376
25
        tys: impl IntoIterator<Item = Option<&'a Type>>,
377
25
        result: &mut FlatTypes<'_>,
378
25
    ) -> bool {
379
25
        let mut temp = result.types[result.cur..].to_vec();
380
25
        let mut temp = FlatTypes::new(&mut temp);
381
25
        let start = result.cur;
382
383
        // Push each case's type onto a temporary vector, and then
384
        // merge that vector into our final list starting at
385
        // `start`. Note that this requires some degree of
386
        // "unification" so we can handle things like `Result<i32,
387
        // f32>` where that turns into `[i32 i32]` where the second
388
        // `i32` might be the `f32` bitcasted.
389
156
        for ty in tys {
390
156
            if let Some(ty) = ty {
391
108
                if !self.push_flat(ty, &mut temp) {
392
0
                    result.overflow = true;
393
0
                    return false;
394
108
                }
395
396
123
                for (i, ty) in temp.types[..temp.cur].iter().enumerate() {
397
123
                    let i = i + start;
398
123
                    if i < result.cur {
399
95
                        result.types[i] = join(result.types[i], *ty);
400
95
                    } else if result.cur == result.types.len() {
401
0
                        result.overflow = true;
402
0
                        return false;
403
28
                    } else {
404
28
                        result.types[i] = *ty;
405
28
                        result.cur += 1;
406
28
                    }
407
                }
408
108
                temp.cur = 0;
409
48
            }
410
        }
411
412
25
        true
413
25
    }
414
}