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(_)
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 assert!(matches!(params.types[0], WasmType::I32));
230 params.types[0] = WasmType::Pointer;
231 }
232 }
233
234 let mut storage = [WasmType::I32; Self::MAX_FLAT_RESULTS];
235 let mut results = FlatTypes::new(&mut storage);
236 let mut retptr = false;
237 match variant {
238 AbiVariant::GuestImport | AbiVariant::GuestExport => {
239 if let Some(ty) = &func.result {
240 self.push_flat(ty, &mut results);
241 }
242 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 if retptr {
249 results.cur = 0;
250 match variant {
251 AbiVariant::GuestImport => {
252 assert!(params.push(WasmType::Pointer));
253 }
254 AbiVariant::GuestExport => {
255 assert!(results.push(WasmType::Pointer));
256 }
257 _ => unreachable!(),
258 }
259 }
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 if func.result.is_some() {
265 assert!(params.push(WasmType::Pointer));
266 retptr = true;
267 }
268
269 // The result of this function is a status code.
270 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 assert!(results.push(WasmType::I32));
278 }
279 AbiVariant::GuestExportAsyncStackful => {
280 // No status code, and like async exports no result handling.
281 }
282 }
283
284 WasmSignature {
285 params: params.to_vec(),
286 indirect_params,
287 results: results.to_vec(),
288 retptr,
289 }
290 }
291
292 fn push_flat_list<'a>(
293 &self,
294 mut list: impl Iterator<Item = &'a Type>,
295 result: &mut FlatTypes<'_>,
296 ) -> bool {
297 list.all(|ty| self.push_flat(ty, result))
298 }
299
300 /// Appends the flat wasm types representing `ty` onto the `result`
301 /// list provided.
302 pub fn push_flat(&self, ty: &Type, result: &mut FlatTypes<'_>) -> bool {
303 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 | Type::ErrorContext => result.push(WasmType::I32),
313
314 Type::U64 | Type::S64 => result.push(WasmType::I64),
315 Type::F32 => result.push(WasmType::F32),
316 Type::F64 => result.push(WasmType::F64),
317 Type::String => result.push(WasmType::Pointer) && result.push(WasmType::Length),
318
319 Type::Id(id) => match &self.types[*id].kind {
320 TypeDefKind::Type(t) => self.push_flat(t, result),
321
322 TypeDefKind::Handle(Handle::Own(_) | Handle::Borrow(_)) => {
323 result.push(WasmType::I32)
324 }
325
326 TypeDefKind::Resource => todo!(),
327
328 TypeDefKind::Record(r) => {
329 self.push_flat_list(r.fields.iter().map(|f| &f.ty), result)
330 }
331
332 TypeDefKind::Tuple(t) => self.push_flat_list(t.types.iter(), result),
333
334 TypeDefKind::Flags(r) => {
335 self.push_flat_list((0..r.repr().count()).map(|_| &Type::U32), result)
336 }
337
338 TypeDefKind::List(_) => {
339 result.push(WasmType::Pointer) && result.push(WasmType::Length)
340 }
341
342 TypeDefKind::Map(_, _) => {
343 result.push(WasmType::Pointer) && result.push(WasmType::Length)
344 }
345
346 TypeDefKind::FixedLengthList(ty, size) => {
347 self.push_flat_list((0..*size).map(|_| ty), result)
348 }
349
350 TypeDefKind::Variant(v) => {
351 result.push(v.tag().into())
352 && self.push_flat_variants(v.cases.iter().map(|c| c.ty.as_ref()), result)
353 }
354
355 TypeDefKind::Enum(e) => result.push(e.tag().into()),
356
357 TypeDefKind::Option(t) => {
358 result.push(WasmType::I32) && self.push_flat_variants([None, Some(t)], result)
359 }
360
361 TypeDefKind::Result(r) => {
362 result.push(WasmType::I32)
363 && self.push_flat_variants([r.ok.as_ref(), r.err.as_ref()], result)
364 }
365
366 TypeDefKind::Future(_) => result.push(WasmType::I32),
367 TypeDefKind::Stream(_) => result.push(WasmType::I32),
368
369 TypeDefKind::Unknown => unreachable!(),
370 },
371 }
372 }
373
374 fn push_flat_variants<'a>(
375 &self,
376 tys: impl IntoIterator<Item = Option<&'a Type>>,
377 result: &mut FlatTypes<'_>,
378 ) -> bool {
379 let mut temp = result.types[result.cur..].to_vec();
380 let mut temp = FlatTypes::new(&mut temp);
381 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 for ty in tys {
390 if let Some(ty) = ty {
391 if !self.push_flat(ty, &mut temp) {
392 result.overflow = true;
393 return false;
394 }
395
396 for (i, ty) in temp.types[..temp.cur].iter().enumerate() {
397 let i = i + start;
398 if i < result.cur {
399 result.types[i] = join(result.types[i], *ty);
400 } else if result.cur == result.types.len() {
401 result.overflow = true;
402 return false;
403 } else {
404 result.types[i] = *ty;
405 result.cur += 1;
406 }
407 }
408 temp.cur = 0;
409 }
410 }
411
412 true
413 }
414}