Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/winch/codegen/src/isa/x64/abi.rs
3070 views
1
use super::regs;
2
use crate::{
3
RegIndexEnv, Result,
4
abi::{ABI, ABIOperand, ABIParams, ABIResults, ABISig, ParamsOrReturns, align_to},
5
bail,
6
codegen::CodeGenError,
7
isa::{CallingConvention, reg::Reg},
8
};
9
use wasmtime_environ::{WasmHeapType, WasmValType};
10
11
#[derive(Default)]
12
pub(crate) struct X64ABI;
13
14
impl ABI for X64ABI {
15
fn stack_align() -> u8 {
16
16
17
}
18
19
fn call_stack_align() -> u8 {
20
16
21
}
22
23
fn arg_base_offset() -> u8 {
24
// Two 8-byte slots, one for the return address and another
25
// one for the frame pointer.
26
// ┌──────────┬───────── Argument base
27
// │ Ret │
28
// │ Addr │
29
// ├──────────┼
30
// │ │
31
// │ FP │
32
// └──────────┴
33
16
34
}
35
36
fn initial_frame_size() -> u8 {
37
// The initial frame size is equal to the space allocated to save the
38
// return address and the frame pointer.
39
Self::arg_base_offset()
40
}
41
42
fn word_bits() -> u8 {
43
64
44
}
45
46
fn sig_from(
47
params: &[WasmValType],
48
returns: &[WasmValType],
49
call_conv: &CallingConvention,
50
) -> Result<ABISig> {
51
assert!(call_conv.is_fastcall() || call_conv.is_systemv() || call_conv.is_default());
52
let is_fastcall = call_conv.is_fastcall();
53
// In the fastcall calling convention, the callee gets a contiguous
54
// stack area of 32 bytes (4 register arguments) just before its frame.
55
// See
56
// https://learn.microsoft.com/en-us/cpp/build/stack-usage?view=msvc-170#stack-allocation
57
let (params_stack_offset, mut params_index_env) = if is_fastcall {
58
(32, RegIndexEnv::with_absolute_limit(4))
59
} else {
60
(0, RegIndexEnv::with_limits_per_class(6, 8))
61
};
62
63
let results = Self::abi_results(returns, call_conv)?;
64
let params = ABIParams::from::<_, Self>(
65
params,
66
params_stack_offset,
67
results.on_stack(),
68
|ty, stack_offset| {
69
Self::to_abi_operand(
70
ty,
71
stack_offset,
72
&mut params_index_env,
73
call_conv,
74
ParamsOrReturns::Params,
75
)
76
},
77
)?;
78
79
Ok(ABISig::new(*call_conv, params, results))
80
}
81
82
fn abi_results(returns: &[WasmValType], call_conv: &CallingConvention) -> Result<ABIResults> {
83
assert!(call_conv.is_default() || call_conv.is_fastcall() || call_conv.is_systemv());
84
// Use absolute count for results given that for Winch's
85
// default CallingConvention only one register is used for results
86
// independent of the register class.
87
// In the case of 2+ results, the rest are passed in the stack,
88
// similar to how Wasmtime handles multi-value returns.
89
let mut results_index_env = RegIndexEnv::with_absolute_limit(1);
90
ABIResults::from(returns, call_conv, |ty, offset| {
91
Self::to_abi_operand(
92
ty,
93
offset,
94
&mut results_index_env,
95
call_conv,
96
ParamsOrReturns::Returns,
97
)
98
})
99
}
100
101
fn vmctx_reg() -> Reg {
102
regs::vmctx()
103
}
104
105
fn stack_slot_size() -> u8 {
106
// Winch default calling convention follows SysV calling convention so
107
// we use one 8 byte slot for values that are smaller or equal to 8
108
// bytes in size and 2 8 byte slots for values that are 128 bits.
109
// See Section 3.2.3 in
110
// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf for further
111
// details.
112
Self::word_bytes()
113
}
114
115
fn sizeof(ty: &WasmValType) -> u8 {
116
match ty {
117
WasmValType::Ref(rt) => match rt.heap_type {
118
WasmHeapType::Func | WasmHeapType::Extern => Self::word_bytes(),
119
ht => unimplemented!("Support for WasmHeapType: {ht}"),
120
},
121
WasmValType::F64 | WasmValType::I64 => Self::word_bytes(),
122
WasmValType::F32 | WasmValType::I32 => Self::word_bytes() / 2,
123
WasmValType::V128 => Self::word_bytes() * 2,
124
}
125
}
126
}
127
128
impl X64ABI {
129
fn to_abi_operand(
130
wasm_arg: &WasmValType,
131
stack_offset: u32,
132
index_env: &mut RegIndexEnv,
133
call_conv: &CallingConvention,
134
params_or_returns: ParamsOrReturns,
135
) -> Result<(ABIOperand, u32)> {
136
let (reg, ty) = match wasm_arg {
137
ty @ WasmValType::Ref(rt) => match rt.heap_type {
138
WasmHeapType::Func | WasmHeapType::Extern => (
139
Self::int_reg_for(index_env.next_gpr(), call_conv, params_or_returns),
140
ty,
141
),
142
_ => bail!(CodeGenError::unsupported_wasm_type()),
143
},
144
145
ty @ (WasmValType::I32 | WasmValType::I64) => (
146
Self::int_reg_for(index_env.next_gpr(), call_conv, params_or_returns),
147
ty,
148
),
149
150
// v128 also uses an XMM register (that is, an fpr).
151
ty @ (WasmValType::F32 | WasmValType::F64 | WasmValType::V128) => (
152
Self::float_reg_for(index_env.next_fpr(), call_conv, params_or_returns),
153
ty,
154
),
155
};
156
157
let ty_size = <Self as ABI>::sizeof(wasm_arg);
158
let default = || {
159
let slot_size = Self::stack_slot_size();
160
if params_or_returns == ParamsOrReturns::Params {
161
// Stack slots for parameters are aligned to a fixed slot size,
162
// 8 bytes if the type size is 8 or less and type-sized aligned
163
// if the type size is greater than 8 bytes.
164
let alignment = std::cmp::max(ty_size, slot_size);
165
let offset = align_to(stack_offset, u32::from(alignment));
166
let arg = ABIOperand::stack_offset(offset, *ty, u32::from(ty_size));
167
(arg, offset + u32::from(alignment))
168
} else {
169
// For the default calling convention, we don't type-size align,
170
// given that results on the stack must match spills generated
171
// from within the compiler, which are not type-size aligned.
172
// In all other cases the results are type-sized aligned.
173
if call_conv.is_default() {
174
let arg = ABIOperand::stack_offset(stack_offset, *ty, u32::from(ty_size));
175
(arg, stack_offset + (ty_size as u32))
176
} else {
177
let offset = align_to(stack_offset, u32::from(ty_size));
178
(
179
ABIOperand::stack_offset(offset, *ty, u32::from(ty_size)),
180
offset + u32::from(ty_size),
181
)
182
}
183
}
184
};
185
186
Ok(reg.map_or_else(default, |reg| {
187
(ABIOperand::reg(reg, *ty, ty_size as u32), stack_offset)
188
}))
189
}
190
191
fn int_reg_for(
192
index: Option<u8>,
193
call_conv: &CallingConvention,
194
params_or_returns: ParamsOrReturns,
195
) -> Option<Reg> {
196
use ParamsOrReturns::*;
197
198
let index = match index {
199
None => return None,
200
Some(index) => index,
201
};
202
203
if call_conv.is_fastcall() {
204
return match (index, params_or_returns) {
205
(0, Params) => Some(regs::rcx()),
206
(1, Params) => Some(regs::rdx()),
207
(2, Params) => Some(regs::r8()),
208
(3, Params) => Some(regs::r9()),
209
(0, Returns) => Some(regs::rax()),
210
_ => None,
211
};
212
}
213
214
if call_conv.is_systemv() || call_conv.is_default() {
215
return match (index, params_or_returns) {
216
(0, Params) => Some(regs::rdi()),
217
(1, Params) => Some(regs::rsi()),
218
(2, Params) => Some(regs::rdx()),
219
(3, Params) => Some(regs::rcx()),
220
(4, Params) => Some(regs::r8()),
221
(5, Params) => Some(regs::r9()),
222
(0, Returns) => Some(regs::rax()),
223
_ => None,
224
};
225
}
226
227
None
228
}
229
230
fn float_reg_for(
231
index: Option<u8>,
232
call_conv: &CallingConvention,
233
params_or_returns: ParamsOrReturns,
234
) -> Option<Reg> {
235
use ParamsOrReturns::*;
236
237
let index = match index {
238
None => return None,
239
Some(index) => index,
240
};
241
242
if call_conv.is_fastcall() {
243
return match (index, params_or_returns) {
244
(0, Params) => Some(regs::xmm0()),
245
(1, Params) => Some(regs::xmm1()),
246
(2, Params) => Some(regs::xmm2()),
247
(3, Params) => Some(regs::xmm3()),
248
(0, Returns) => Some(regs::xmm0()),
249
_ => None,
250
};
251
}
252
253
if call_conv.is_systemv() || call_conv.is_default() {
254
return match (index, params_or_returns) {
255
(0, Params) => Some(regs::xmm0()),
256
(1, Params) => Some(regs::xmm1()),
257
(2, Params) => Some(regs::xmm2()),
258
(3, Params) => Some(regs::xmm3()),
259
(4, Params) => Some(regs::xmm4()),
260
(5, Params) => Some(regs::xmm5()),
261
(6, Params) => Some(regs::xmm6()),
262
(7, Params) => Some(regs::xmm7()),
263
(0, Returns) => Some(regs::xmm0()),
264
_ => None,
265
};
266
}
267
268
None
269
}
270
}
271
272
#[cfg(test)]
273
mod tests {
274
use super::X64ABI;
275
use crate::{
276
Result,
277
abi::{ABI, ABIOperand},
278
isa::{CallingConvention, reg::Reg, x64::regs},
279
};
280
use wasmtime_environ::{
281
WasmFuncType,
282
WasmValType::{self, *},
283
};
284
285
#[test]
286
fn int_abi_sig() -> Result<()> {
287
let wasm_sig =
288
WasmFuncType::new([I32, I64, I32, I64, I32, I32, I64, I32].into(), [].into());
289
290
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
291
let params = sig.params;
292
293
match_reg_arg(params.get(0).unwrap(), I32, regs::rdi());
294
match_reg_arg(params.get(1).unwrap(), I64, regs::rsi());
295
match_reg_arg(params.get(2).unwrap(), I32, regs::rdx());
296
match_reg_arg(params.get(3).unwrap(), I64, regs::rcx());
297
match_reg_arg(params.get(4).unwrap(), I32, regs::r8());
298
match_reg_arg(params.get(5).unwrap(), I32, regs::r9());
299
match_stack_arg(params.get(6).unwrap(), I64, 0);
300
match_stack_arg(params.get(7).unwrap(), I32, 8);
301
Ok(())
302
}
303
304
#[test]
305
fn int_abi_sig_multi_returns() -> Result<()> {
306
let wasm_sig = WasmFuncType::new(
307
[I32, I64, I32, I64, I32, I32, I64, I32].into(),
308
[I32, I32, I32].into(),
309
);
310
311
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
312
let params = sig.params;
313
let results = sig.results;
314
315
match_reg_arg(params.get(0).unwrap(), I32, regs::rsi());
316
match_reg_arg(params.get(1).unwrap(), I64, regs::rdx());
317
match_reg_arg(params.get(2).unwrap(), I32, regs::rcx());
318
match_reg_arg(params.get(3).unwrap(), I64, regs::r8());
319
match_reg_arg(params.get(4).unwrap(), I32, regs::r9());
320
match_stack_arg(params.get(5).unwrap(), I32, 0);
321
match_stack_arg(params.get(6).unwrap(), I64, 8);
322
match_stack_arg(params.get(7).unwrap(), I32, 16);
323
324
match_stack_arg(results.get(0).unwrap(), I32, 4);
325
match_stack_arg(results.get(1).unwrap(), I32, 0);
326
match_reg_arg(results.get(2).unwrap(), I32, regs::rax());
327
Ok(())
328
}
329
330
#[test]
331
fn float_abi_sig() -> Result<()> {
332
let wasm_sig = WasmFuncType::new(
333
[F32, F64, F32, F64, F32, F32, F64, F32, F64].into(),
334
[].into(),
335
);
336
337
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
338
let params = sig.params;
339
340
match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());
341
match_reg_arg(params.get(1).unwrap(), F64, regs::xmm1());
342
match_reg_arg(params.get(2).unwrap(), F32, regs::xmm2());
343
match_reg_arg(params.get(3).unwrap(), F64, regs::xmm3());
344
match_reg_arg(params.get(4).unwrap(), F32, regs::xmm4());
345
match_reg_arg(params.get(5).unwrap(), F32, regs::xmm5());
346
match_reg_arg(params.get(6).unwrap(), F64, regs::xmm6());
347
match_reg_arg(params.get(7).unwrap(), F32, regs::xmm7());
348
match_stack_arg(params.get(8).unwrap(), F64, 0);
349
Ok(())
350
}
351
352
#[test]
353
fn vector_abi_sig() -> Result<()> {
354
let wasm_sig = WasmFuncType::new(
355
[V128, V128, V128, V128, V128, V128, V128, V128, V128, V128].into(),
356
[].into(),
357
);
358
359
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
360
let params = sig.params;
361
362
match_reg_arg(params.get(0).unwrap(), V128, regs::xmm0());
363
match_reg_arg(params.get(1).unwrap(), V128, regs::xmm1());
364
match_reg_arg(params.get(2).unwrap(), V128, regs::xmm2());
365
match_reg_arg(params.get(3).unwrap(), V128, regs::xmm3());
366
match_reg_arg(params.get(4).unwrap(), V128, regs::xmm4());
367
match_reg_arg(params.get(5).unwrap(), V128, regs::xmm5());
368
match_reg_arg(params.get(6).unwrap(), V128, regs::xmm6());
369
match_reg_arg(params.get(7).unwrap(), V128, regs::xmm7());
370
match_stack_arg(params.get(8).unwrap(), V128, 0);
371
match_stack_arg(params.get(9).unwrap(), V128, 16);
372
Ok(())
373
}
374
375
#[test]
376
fn vector_abi_sig_multi_returns() -> Result<()> {
377
let wasm_sig = WasmFuncType::new([].into(), [V128, V128, V128].into());
378
379
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
380
let results = sig.results;
381
382
match_stack_arg(results.get(0).unwrap(), V128, 16);
383
match_stack_arg(results.get(1).unwrap(), V128, 0);
384
match_reg_arg(results.get(2).unwrap(), V128, regs::xmm0());
385
Ok(())
386
}
387
388
#[test]
389
fn mixed_abi_sig() -> Result<()> {
390
let wasm_sig = WasmFuncType::new(
391
[F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),
392
[].into(),
393
);
394
395
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
396
let params = sig.params;
397
398
match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());
399
match_reg_arg(params.get(1).unwrap(), I32, regs::rdi());
400
match_reg_arg(params.get(2).unwrap(), I64, regs::rsi());
401
match_reg_arg(params.get(3).unwrap(), F64, regs::xmm1());
402
match_reg_arg(params.get(4).unwrap(), I32, regs::rdx());
403
match_reg_arg(params.get(5).unwrap(), F32, regs::xmm2());
404
match_reg_arg(params.get(6).unwrap(), F64, regs::xmm3());
405
match_reg_arg(params.get(7).unwrap(), F32, regs::xmm4());
406
match_reg_arg(params.get(8).unwrap(), F64, regs::xmm5());
407
408
let wasm_sig = WasmFuncType::new(
409
[F32, F32, F32, F32, F32, F32, F32, F32, F32, V128].into(),
410
[V128].into(),
411
);
412
413
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
414
let params = sig.params;
415
416
match_stack_arg(params.get(8).unwrap(), F32, 0);
417
match_stack_arg(params.get(9).unwrap(), V128, 16);
418
Ok(())
419
}
420
421
#[test]
422
fn system_v_call_conv() -> Result<()> {
423
let wasm_sig = WasmFuncType::new(
424
[F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),
425
[].into(),
426
);
427
428
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::SystemV)?;
429
let params = sig.params;
430
431
match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());
432
match_reg_arg(params.get(1).unwrap(), I32, regs::rdi());
433
match_reg_arg(params.get(2).unwrap(), I64, regs::rsi());
434
match_reg_arg(params.get(3).unwrap(), F64, regs::xmm1());
435
match_reg_arg(params.get(4).unwrap(), I32, regs::rdx());
436
match_reg_arg(params.get(5).unwrap(), F32, regs::xmm2());
437
match_reg_arg(params.get(6).unwrap(), F64, regs::xmm3());
438
match_reg_arg(params.get(7).unwrap(), F32, regs::xmm4());
439
match_reg_arg(params.get(8).unwrap(), F64, regs::xmm5());
440
Ok(())
441
}
442
443
#[test]
444
fn fastcall_call_conv() -> Result<()> {
445
let wasm_sig = WasmFuncType::new(
446
[F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),
447
[].into(),
448
);
449
450
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::WindowsFastcall)?;
451
let params = sig.params;
452
453
match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());
454
match_reg_arg(params.get(1).unwrap(), I32, regs::rdx());
455
match_reg_arg(params.get(2).unwrap(), I64, regs::r8());
456
match_reg_arg(params.get(3).unwrap(), F64, regs::xmm3());
457
match_stack_arg(params.get(4).unwrap(), I32, 32);
458
match_stack_arg(params.get(5).unwrap(), F32, 40);
459
Ok(())
460
}
461
462
#[test]
463
fn fastcall_call_conv_multi_returns() -> Result<()> {
464
let wasm_sig = WasmFuncType::new(
465
[F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),
466
[I32, F32, I32, F32, I64].into(),
467
);
468
469
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::WindowsFastcall)?;
470
let params = sig.params;
471
let results = sig.results;
472
473
match_reg_arg(params.get(0).unwrap(), F32, regs::xmm1());
474
match_reg_arg(params.get(1).unwrap(), I32, regs::r8());
475
match_reg_arg(params.get(2).unwrap(), I64, regs::r9());
476
// Each argument stack slot is 8 bytes.
477
match_stack_arg(params.get(3).unwrap(), F64, 32);
478
match_stack_arg(params.get(4).unwrap(), I32, 40);
479
match_stack_arg(params.get(5).unwrap(), F32, 48);
480
481
match_reg_arg(results.get(0).unwrap(), I32, regs::rax());
482
483
match_stack_arg(results.get(1).unwrap(), F32, 0);
484
match_stack_arg(results.get(2).unwrap(), I32, 4);
485
match_stack_arg(results.get(3).unwrap(), F32, 8);
486
match_stack_arg(results.get(4).unwrap(), I64, 16);
487
Ok(())
488
}
489
490
#[track_caller]
491
fn match_reg_arg(abi_arg: &ABIOperand, expected_ty: WasmValType, expected_reg: Reg) {
492
match abi_arg {
493
&ABIOperand::Reg { reg, ty, .. } => {
494
assert_eq!(reg, expected_reg);
495
assert_eq!(ty, expected_ty);
496
}
497
stack => panic!("Expected reg argument, got {stack:?}"),
498
}
499
}
500
501
#[track_caller]
502
fn match_stack_arg(abi_arg: &ABIOperand, expected_ty: WasmValType, expected_offset: u32) {
503
match abi_arg {
504
&ABIOperand::Stack { offset, ty, .. } => {
505
assert_eq!(offset, expected_offset);
506
assert_eq!(ty, expected_ty);
507
}
508
reg => panic!("Expected stack argument, got {reg:?}"),
509
}
510
}
511
}
512
513