Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/winch/codegen/src/isa/aarch64/abi.rs
3069 views
1
use super::regs;
2
use crate::abi::{ABI, ABIOperand, ABIParams, ABIResults, ABISig, ParamsOrReturns, align_to};
3
use crate::codegen::CodeGenError;
4
use crate::isa::{CallingConvention, reg::Reg};
5
use crate::{RegIndexEnv, Result, bail};
6
use wasmtime_environ::{WasmHeapType, WasmValType};
7
8
#[derive(Default)]
9
pub(crate) struct Aarch64ABI;
10
11
/// The x28 register serves as the shadow stack pointer. For further details,
12
/// please refer to [`crate::isa::aarch64::regs::shadow_sp`].
13
///
14
/// This register is designated as callee-saved to prevent corruption during
15
/// function calls. This is especially important for Wasm-to-Wasm calls in
16
/// Winch-generated code, as Winch's default calling convention does not define
17
/// any callee-saved registers.
18
///
19
/// Note that 16 bytes are used to save the shadow stack pointer register even
20
/// though only 8 are needed. 16 is used for simplicity to ensure that the
21
/// 16-byte alignment requirement for memory addressing is met at the function's
22
/// prologue and epilogue.
23
pub const SHADOW_STACK_POINTER_SLOT_SIZE: u8 = 16;
24
25
impl ABI for Aarch64ABI {
26
// TODO change to 16 once SIMD is supported
27
fn stack_align() -> u8 {
28
8
29
}
30
31
fn call_stack_align() -> u8 {
32
16
33
}
34
35
fn arg_base_offset() -> u8 {
36
// Two 8-byte slots:
37
// * One for link register
38
// * One for the frame pointer
39
//
40
// ┌──────────┬───────── Argument base
41
// │ LR │
42
// │ │
43
// ├──────────┼
44
// │ │
45
// │ FP │
46
// └──────────┴ -> 16
47
16
48
}
49
50
fn initial_frame_size() -> u8 {
51
// The initial frame size is composed of 4 8-byte slots:
52
// * Two slots for the link register and the frame pointer. See
53
// [`Self::arg_base_offset`]
54
// * Two for the shadow stack pointer register. See
55
// [`SHADOW_STACK_POINTER_SIZE`]
56
//
57
// ┌──────────┬───────── Argument base
58
// │ LR │
59
// │ │
60
// ├──────────┼
61
// │ │
62
// │ FP │
63
// ┌──────────┬
64
// │ │
65
// │ │
66
// │ │
67
// │ x28 │
68
// └──────────┴ -> 32
69
Self::arg_base_offset() + SHADOW_STACK_POINTER_SLOT_SIZE
70
}
71
72
fn word_bits() -> u8 {
73
64
74
}
75
76
fn sig_from(
77
params: &[WasmValType],
78
returns: &[WasmValType],
79
call_conv: &CallingConvention,
80
) -> Result<ABISig> {
81
assert!(call_conv.is_apple_aarch64() || call_conv.is_default());
82
// The first element tracks the general purpose register index, capped at 7 (x0-x7).
83
// The second element tracks the floating point register index, capped at 7 (v0-v7).
84
// Follows
85
// https://github.com/ARM-software/abi-aa/blob/2021Q1/aapcs64/aapcs64.rst#64parameter-passing
86
let mut params_index_env = RegIndexEnv::with_limits_per_class(8, 8);
87
let results = Self::abi_results(returns, call_conv)?;
88
let params =
89
ABIParams::from::<_, Self>(params, 0, results.on_stack(), |ty, stack_offset| {
90
Self::to_abi_operand(
91
ty,
92
stack_offset,
93
&mut params_index_env,
94
call_conv,
95
ParamsOrReturns::Params,
96
)
97
})?;
98
99
Ok(ABISig::new(*call_conv, params, results))
100
}
101
102
fn abi_results(returns: &[WasmValType], call_conv: &CallingConvention) -> Result<ABIResults> {
103
assert!(call_conv.is_apple_aarch64() || call_conv.is_default());
104
// Use absolute count for results given that for Winch's
105
// default CallingConvention only one register is used for results
106
// independent of the register class.
107
// In the case of 2+ results, the rest are passed in the stack,
108
// similar to how Wasmtime handles multi-value returns.
109
let mut returns_index_env = RegIndexEnv::with_absolute_limit(1);
110
111
ABIResults::from(returns, call_conv, |ty, stack_offset| {
112
Self::to_abi_operand(
113
ty,
114
stack_offset,
115
&mut returns_index_env,
116
call_conv,
117
ParamsOrReturns::Returns,
118
)
119
})
120
}
121
122
fn vmctx_reg() -> Reg {
123
regs::xreg(9)
124
}
125
126
fn stack_slot_size() -> u8 {
127
Self::word_bytes()
128
}
129
130
fn sizeof(ty: &WasmValType) -> u8 {
131
match ty {
132
WasmValType::Ref(rt) => match rt.heap_type {
133
WasmHeapType::Func => Self::word_bytes(),
134
ht => unimplemented!("Support for WasmHeapType: {ht}"),
135
},
136
WasmValType::F64 | WasmValType::I64 => Self::word_bytes(),
137
WasmValType::F32 | WasmValType::I32 => Self::word_bytes() / 2,
138
WasmValType::V128 => Self::word_bytes() * 2,
139
}
140
}
141
}
142
143
impl Aarch64ABI {
144
fn to_abi_operand(
145
wasm_arg: &WasmValType,
146
stack_offset: u32,
147
index_env: &mut RegIndexEnv,
148
call_conv: &CallingConvention,
149
params_or_returns: ParamsOrReturns,
150
) -> Result<(ABIOperand, u32)> {
151
let (reg, ty) = match wasm_arg {
152
ty @ (WasmValType::I32 | WasmValType::I64) => {
153
(index_env.next_gpr().map(regs::xreg), ty)
154
}
155
156
ty @ (WasmValType::F32 | WasmValType::F64) => {
157
(index_env.next_fpr().map(regs::vreg), ty)
158
}
159
160
ty @ WasmValType::Ref(rt) => match rt.heap_type {
161
WasmHeapType::Func | WasmHeapType::Extern => {
162
(index_env.next_gpr().map(regs::xreg), ty)
163
}
164
_ => bail!(CodeGenError::unsupported_wasm_type()),
165
},
166
167
_ => bail!(CodeGenError::unsupported_wasm_type()),
168
};
169
170
let ty_size = <Self as ABI>::sizeof(wasm_arg);
171
let default = || {
172
let arg = ABIOperand::stack_offset(stack_offset, *ty, ty_size as u32);
173
let slot_size = Self::stack_slot_size();
174
// Stack slots for parameters are aligned to a fixed slot size,
175
// in the case of Aarch64, 8 bytes.
176
// For the non-default calling convention, stack slots for
177
// return values are type-sized aligned.
178
// For the default calling convention, we don't type-size align,
179
// given that results on the stack must match spills generated
180
// from within the compiler, which are not type-size aligned.
181
let next_stack = if params_or_returns == ParamsOrReturns::Params {
182
align_to(stack_offset, slot_size as u32) + (slot_size as u32)
183
} else if call_conv.is_default() {
184
stack_offset + (ty_size as u32)
185
} else {
186
align_to(stack_offset, ty_size as u32) + (ty_size as u32)
187
};
188
(arg, next_stack)
189
};
190
Ok(reg.map_or_else(default, |reg| {
191
(ABIOperand::reg(reg, *ty, ty_size as u32), stack_offset)
192
}))
193
}
194
}
195
196
#[cfg(test)]
197
mod tests {
198
use super::Aarch64ABI;
199
use crate::{
200
Result,
201
abi::{ABI, ABIOperand},
202
isa::CallingConvention,
203
isa::aarch64::regs,
204
isa::reg::Reg,
205
};
206
use wasmtime_environ::{
207
WasmFuncType,
208
WasmValType::{self, *},
209
};
210
211
#[test]
212
fn xreg_abi_sig() -> Result<()> {
213
let wasm_sig = WasmFuncType::new(
214
[I32, I64, I32, I64, I32, I32, I64, I32, I64].into(),
215
[].into(),
216
);
217
218
let sig = Aarch64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
219
let params = sig.params;
220
221
match_reg_arg(params.get(0).unwrap(), I32, regs::xreg(0));
222
match_reg_arg(params.get(1).unwrap(), I64, regs::xreg(1));
223
match_reg_arg(params.get(2).unwrap(), I32, regs::xreg(2));
224
match_reg_arg(params.get(3).unwrap(), I64, regs::xreg(3));
225
match_reg_arg(params.get(4).unwrap(), I32, regs::xreg(4));
226
match_reg_arg(params.get(5).unwrap(), I32, regs::xreg(5));
227
match_reg_arg(params.get(6).unwrap(), I64, regs::xreg(6));
228
match_reg_arg(params.get(7).unwrap(), I32, regs::xreg(7));
229
match_stack_arg(params.get(8).unwrap(), I64, 0);
230
Ok(())
231
}
232
233
#[test]
234
fn vreg_abi_sig() -> Result<()> {
235
let wasm_sig = WasmFuncType::new(
236
[F32, F64, F32, F64, F32, F32, F64, F32, F64].into(),
237
[].into(),
238
);
239
240
let sig = Aarch64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
241
let params = sig.params;
242
243
match_reg_arg(params.get(0).unwrap(), F32, regs::vreg(0));
244
match_reg_arg(params.get(1).unwrap(), F64, regs::vreg(1));
245
match_reg_arg(params.get(2).unwrap(), F32, regs::vreg(2));
246
match_reg_arg(params.get(3).unwrap(), F64, regs::vreg(3));
247
match_reg_arg(params.get(4).unwrap(), F32, regs::vreg(4));
248
match_reg_arg(params.get(5).unwrap(), F32, regs::vreg(5));
249
match_reg_arg(params.get(6).unwrap(), F64, regs::vreg(6));
250
match_reg_arg(params.get(7).unwrap(), F32, regs::vreg(7));
251
match_stack_arg(params.get(8).unwrap(), F64, 0);
252
Ok(())
253
}
254
255
#[test]
256
fn mixed_abi_sig() -> Result<()> {
257
let wasm_sig = WasmFuncType::new(
258
[F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),
259
[].into(),
260
);
261
262
let sig = Aarch64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
263
let params = sig.params;
264
265
match_reg_arg(params.get(0).unwrap(), F32, regs::vreg(0));
266
match_reg_arg(params.get(1).unwrap(), I32, regs::xreg(0));
267
match_reg_arg(params.get(2).unwrap(), I64, regs::xreg(1));
268
match_reg_arg(params.get(3).unwrap(), F64, regs::vreg(1));
269
match_reg_arg(params.get(4).unwrap(), I32, regs::xreg(2));
270
match_reg_arg(params.get(5).unwrap(), F32, regs::vreg(2));
271
match_reg_arg(params.get(6).unwrap(), F64, regs::vreg(3));
272
match_reg_arg(params.get(7).unwrap(), F32, regs::vreg(4));
273
match_reg_arg(params.get(8).unwrap(), F64, regs::vreg(5));
274
Ok(())
275
}
276
277
#[test]
278
fn int_abi_sig_multi_returns() -> Result<()> {
279
let wasm_sig = WasmFuncType::new(
280
[I32, I64, I32, I64, I32, I32].into(),
281
[I32, I32, I32].into(),
282
);
283
284
let sig = Aarch64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
285
let params = sig.params;
286
let results = sig.results;
287
288
match_reg_arg(params.get(0).unwrap(), I32, regs::xreg(1));
289
match_reg_arg(params.get(1).unwrap(), I64, regs::xreg(2));
290
match_reg_arg(params.get(2).unwrap(), I32, regs::xreg(3));
291
match_reg_arg(params.get(3).unwrap(), I64, regs::xreg(4));
292
match_reg_arg(params.get(4).unwrap(), I32, regs::xreg(5));
293
match_reg_arg(params.get(5).unwrap(), I32, regs::xreg(6));
294
295
match_stack_arg(results.get(0).unwrap(), I32, 4);
296
match_stack_arg(results.get(1).unwrap(), I32, 0);
297
match_reg_arg(results.get(2).unwrap(), I32, regs::xreg(0));
298
Ok(())
299
}
300
301
#[test]
302
fn mixed_abi_sig_multi_returns() -> Result<()> {
303
let wasm_sig = WasmFuncType::new(
304
[F32, I32, I64, F64, I32].into(),
305
[I32, F32, I32, F32, I64].into(),
306
);
307
308
let sig = Aarch64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
309
let params = sig.params;
310
let results = sig.results;
311
312
match_reg_arg(params.get(0).unwrap(), F32, regs::vreg(0));
313
match_reg_arg(params.get(1).unwrap(), I32, regs::xreg(1));
314
match_reg_arg(params.get(2).unwrap(), I64, regs::xreg(2));
315
match_reg_arg(params.get(3).unwrap(), F64, regs::vreg(1));
316
match_reg_arg(params.get(4).unwrap(), I32, regs::xreg(3));
317
318
match_stack_arg(results.get(0).unwrap(), I32, 12);
319
match_stack_arg(results.get(1).unwrap(), F32, 8);
320
match_stack_arg(results.get(2).unwrap(), I32, 4);
321
match_stack_arg(results.get(3).unwrap(), F32, 0);
322
match_reg_arg(results.get(4).unwrap(), I64, regs::xreg(0));
323
Ok(())
324
}
325
326
#[track_caller]
327
fn match_reg_arg(abi_arg: &ABIOperand, expected_ty: WasmValType, expected_reg: Reg) {
328
match abi_arg {
329
&ABIOperand::Reg { reg, ty, .. } => {
330
assert_eq!(reg, expected_reg);
331
assert_eq!(ty, expected_ty);
332
}
333
stack => panic!("Expected reg argument, got {stack:?}"),
334
}
335
}
336
337
#[track_caller]
338
fn match_stack_arg(abi_arg: &ABIOperand, expected_ty: WasmValType, expected_offset: u32) {
339
match abi_arg {
340
&ABIOperand::Stack { offset, ty, .. } => {
341
assert_eq!(offset, expected_offset);
342
assert_eq!(ty, expected_ty);
343
}
344
reg => panic!("Expected stack argument, got {reg:?}"),
345
}
346
}
347
}
348
349