wasmer_compiler/
abi.rs

1//! Shared WebAssembly return-value ABI classification.
2
3use crate::lib::std::vec::Vec;
4use itertools::Itertools;
5use wasmer_types::Type;
6
7/// How a single-register return slot is typed.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ReturnSlot {
10    /// Carried in a register of its own natural type.
11    Natural(Type),
12    /// Carried as a raw same-width integer register, bypassing its natural
13    /// register class (used on AArch64 where a float value only gets a dedicated
14    /// vector register as part of a homogeneous floating-point aggregate).
15    Raw(Type),
16}
17
18/// How two adjacent 32-bit return values share a single register.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum PairSlot {
21    /// Two `F32`s sharing a `<2 x float>` vector register.
22    F32Vector(Type, Type),
23    /// Two 32-bit values bit-concatenated into one raw integer register.
24    Raw(Type, Type),
25}
26
27/// Describes how a list of WebAssembly values is returned by a native ABI.
28///
29/// Every non-void variant retains the values it classified so signature
30/// construction, packing, and unpacking all use the same ABI rules.
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum ReturnAbi {
33    /// No return values.
34    Void,
35    /// One value returned directly.
36    Single(Type),
37    /// Two values returned independently in registers.
38    Pair(ReturnSlot, ReturnSlot),
39    /// Two 32-bit values packed into one register.
40    PackedPair(PairSlot),
41    /// The first two 32-bit values are packed into one register.
42    PackedFirst(PairSlot, ReturnSlot),
43    /// The last two 32-bit values are packed into one register.
44    PackedLast(ReturnSlot, PairSlot),
45    /// Four 32-bit values packed pairwise into two registers.
46    PackedQuads(PairSlot, PairSlot),
47    /// Values returned independently in an aggregate of registers.
48    Unpacked(Vec<Type>),
49    /// Values returned indirectly through a structure-return pointer.
50    Sret(Vec<Type>),
51}
52
53/// Two adjacent `F32`s share a vector register (SSE eightbyte); anything
54/// else bit-packs into a raw integer register.
55fn pair_slot(t0: Type, t1: Type) -> PairSlot {
56    if t0 == Type::F32 && t1 == Type::F32 {
57        PairSlot::F32Vector(t0, t1)
58    } else {
59        PairSlot::Raw(t0, t1)
60    }
61}
62
63/// Classifies x86_64 return values.
64pub fn classify_return_type_x86_64(types: &[Type]) -> ReturnAbi {
65    let widths = types.iter().map(|ty| ty.bit_size(64)).collect_vec();
66
67    match (types, widths.as_slice()) {
68        ([], []) => ReturnAbi::Void,
69        ([value], [_]) => ReturnAbi::Single(*value),
70        ([first, second], [32, 64] | [64, 32] | [64, 64]) => {
71            ReturnAbi::Pair(ReturnSlot::Natural(*first), ReturnSlot::Natural(*second))
72        }
73        ([first, second], [32, 32]) => ReturnAbi::PackedPair(pair_slot(*first, *second)),
74        ([first, second, third], [32, 32, 32 | 64]) => {
75            ReturnAbi::PackedFirst(pair_slot(*first, *second), ReturnSlot::Natural(*third))
76        }
77        ([first, second, third], [64, 32, 32]) => {
78            ReturnAbi::PackedLast(ReturnSlot::Natural(*first), pair_slot(*second, *third))
79        }
80        ([first, second, third, fourth], [32, 32, 32, 32]) => {
81            ReturnAbi::PackedQuads(pair_slot(*first, *second), pair_slot(*third, *fourth))
82        }
83        _ => ReturnAbi::Sret(types.to_vec()),
84    }
85}
86
87/// Classifies AArch64 (AAPCS64) return values.
88///
89/// A float value only gets its own vector register as part of a
90/// homogeneous floating-point aggregate (the `Unpacked` case below).
91pub fn classify_return_type_aarch64(types: &[Type]) -> ReturnAbi {
92    let widths = types.iter().map(|ty| ty.bit_size(64)).collect_vec();
93    if (2..=4).contains(&types.len())
94        && (types.iter().all(|ty| ty == &Type::F32) || types.iter().all(|ty| ty == &Type::F64))
95    {
96        return ReturnAbi::Unpacked(types.to_vec());
97    }
98
99    if let [first, second] = types
100        && matches!(
101            first,
102            Type::I32 | Type::I64 | Type::F32 | Type::F64 | Type::ExceptionRef
103        )
104        && matches!(second, Type::FuncRef | Type::ExternRef)
105    {
106        return ReturnAbi::Pair(ReturnSlot::Raw(*first), ReturnSlot::Raw(*second));
107    }
108
109    match (types, widths.as_slice()) {
110        ([], []) => ReturnAbi::Void,
111        ([value], [_]) => ReturnAbi::Single(*value),
112        ([first, second], [32, 64] | [64, 32] | [64, 64]) => {
113            ReturnAbi::Pair(ReturnSlot::Raw(*first), ReturnSlot::Raw(*second))
114        }
115        ([first, second], [32, 32]) => ReturnAbi::PackedPair(PairSlot::Raw(*first, *second)),
116        ([first, second, third], [32, 32, 32 | 64]) => {
117            ReturnAbi::PackedFirst(PairSlot::Raw(*first, *second), ReturnSlot::Raw(*third))
118        }
119        ([first, second, third], [64, 32, 32]) => {
120            ReturnAbi::PackedLast(ReturnSlot::Raw(*first), PairSlot::Raw(*second, *third))
121        }
122        ([first, second, third, fourth], [32, 32, 32, 32]) => ReturnAbi::PackedQuads(
123            PairSlot::Raw(*first, *second),
124            PairSlot::Raw(*third, *fourth),
125        ),
126        _ => ReturnAbi::Sret(types.to_vec()),
127    }
128}
129
130/// Classifies LoongArch64 return values according to the LP64D psABI.
131///
132/// Note LoongArch64 uses the same aggregate rules as the RISC-V LP64D ABI.
133pub fn classify_return_type_loongarch64(types: &[Type]) -> ReturnAbi {
134    classify_return_type_riscv64(types)
135}
136
137/// Classifies RISC-V return values according to the hard-float psABI.
138pub fn classify_return_type_riscv64(types: &[Type]) -> ReturnAbi {
139    let widths = types.iter().map(|ty| ty.bit_size(64)).collect_vec();
140
141    // The hardware floating-point calling convention flattens only aggregates
142    // with one or two fields.
143    if let [first, second] = types {
144        let is_float = |ty| matches!(ty, Type::F32 | Type::F64);
145        let eligible = |ty, width| is_float(ty) || width <= 64;
146        if (is_float(*first) || is_float(*second))
147            && eligible(*first, widths[0])
148            && eligible(*second, widths[1])
149        {
150            return ReturnAbi::Pair(ReturnSlot::Natural(*first), ReturnSlot::Natural(*second));
151        }
152    }
153
154    match (types, widths.as_slice()) {
155        ([], []) => ReturnAbi::Void,
156        ([value], [_]) => ReturnAbi::Single(*value),
157
158        // RV64 integer-convention aggregates of at most two XLEN-sized chunks.
159        ([first, second], [32, 64] | [64, 32] | [64, 64]) => {
160            ReturnAbi::Pair(ReturnSlot::Raw(*first), ReturnSlot::Raw(*second))
161        }
162        ([first, second], [32, 32]) => ReturnAbi::PackedPair(PairSlot::Raw(*first, *second)),
163        ([first, second, third], [32, 32, 32 | 64]) => {
164            ReturnAbi::PackedFirst(PairSlot::Raw(*first, *second), ReturnSlot::Raw(*third))
165        }
166        ([first, second, third], [64, 32, 32]) => {
167            ReturnAbi::PackedLast(ReturnSlot::Raw(*first), PairSlot::Raw(*second, *third))
168        }
169        ([first, second, third, fourth], [32, 32, 32, 32]) => ReturnAbi::PackedQuads(
170            PairSlot::Raw(*first, *second),
171            PairSlot::Raw(*third, *fourth),
172        ),
173        _ => ReturnAbi::Sret(types.to_vec()),
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::classify_return_type_x86_64;
180    use crate::abi::{PairSlot, ReturnAbi, ReturnSlot};
181    use wasmer_types::Type;
182
183    #[test]
184    fn classify_x86_64_return_type_abi() {
185        assert_eq!(classify_return_type_x86_64(&[]), ReturnAbi::Void);
186        assert_eq!(
187            classify_return_type_x86_64(&[Type::I64]),
188            ReturnAbi::Single(Type::I64)
189        );
190        assert_eq!(
191            classify_return_type_x86_64(&[Type::I32, Type::F64]),
192            ReturnAbi::Pair(
193                ReturnSlot::Natural(Type::I32),
194                ReturnSlot::Natural(Type::F64)
195            )
196        );
197        assert_eq!(
198            classify_return_type_x86_64(&[Type::I32, Type::F32]),
199            ReturnAbi::PackedPair(PairSlot::Raw(Type::I32, Type::F32))
200        );
201        assert_eq!(
202            classify_return_type_x86_64(&[Type::F32, Type::F32, Type::I64]),
203            ReturnAbi::PackedFirst(
204                PairSlot::F32Vector(Type::F32, Type::F32),
205                ReturnSlot::Natural(Type::I64)
206            )
207        );
208        assert_eq!(
209            classify_return_type_x86_64(&[Type::F64, Type::I32, Type::F32]),
210            ReturnAbi::PackedLast(
211                ReturnSlot::Natural(Type::F64),
212                PairSlot::Raw(Type::I32, Type::F32)
213            )
214        );
215        assert_eq!(
216            classify_return_type_x86_64(&[Type::I32, Type::F32, Type::F32, Type::I32]),
217            ReturnAbi::PackedQuads(
218                PairSlot::Raw(Type::I32, Type::F32),
219                PairSlot::Raw(Type::F32, Type::I32)
220            )
221        );
222        assert_eq!(
223            classify_return_type_x86_64(&[Type::V128, Type::I32]),
224            ReturnAbi::Sret(vec![Type::V128, Type::I32])
225        );
226    }
227}