Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/winch/codegen/src/isa/x64/abi.rs
1693 views
1
use super::regs;
2
use crate::{
3
RegIndexEnv,
4
abi::{ABI, ABIOperand, ABIParams, ABIResults, ABISig, ParamsOrReturns, align_to},
5
codegen::CodeGenError,
6
isa::{CallingConvention, reg::Reg},
7
};
8
use anyhow::{Result, bail};
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
abi::{ABI, ABIOperand},
277
isa::{CallingConvention, reg::Reg, x64::regs},
278
};
279
280
use anyhow::Result;
281
282
use wasmtime_environ::{
283
WasmFuncType,
284
WasmValType::{self, *},
285
};
286
287
#[test]
288
fn int_abi_sig() -> Result<()> {
289
let wasm_sig =
290
WasmFuncType::new([I32, I64, I32, I64, I32, I32, I64, I32].into(), [].into());
291
292
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
293
let params = sig.params;
294
295
match_reg_arg(params.get(0).unwrap(), I32, regs::rdi());
296
match_reg_arg(params.get(1).unwrap(), I64, regs::rsi());
297
match_reg_arg(params.get(2).unwrap(), I32, regs::rdx());
298
match_reg_arg(params.get(3).unwrap(), I64, regs::rcx());
299
match_reg_arg(params.get(4).unwrap(), I32, regs::r8());
300
match_reg_arg(params.get(5).unwrap(), I32, regs::r9());
301
match_stack_arg(params.get(6).unwrap(), I64, 0);
302
match_stack_arg(params.get(7).unwrap(), I32, 8);
303
Ok(())
304
}
305
306
#[test]
307
fn int_abi_sig_multi_returns() -> Result<()> {
308
let wasm_sig = WasmFuncType::new(
309
[I32, I64, I32, I64, I32, I32, I64, I32].into(),
310
[I32, I32, I32].into(),
311
);
312
313
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
314
let params = sig.params;
315
let results = sig.results;
316
317
match_reg_arg(params.get(0).unwrap(), I32, regs::rsi());
318
match_reg_arg(params.get(1).unwrap(), I64, regs::rdx());
319
match_reg_arg(params.get(2).unwrap(), I32, regs::rcx());
320
match_reg_arg(params.get(3).unwrap(), I64, regs::r8());
321
match_reg_arg(params.get(4).unwrap(), I32, regs::r9());
322
match_stack_arg(params.get(5).unwrap(), I32, 0);
323
match_stack_arg(params.get(6).unwrap(), I64, 8);
324
match_stack_arg(params.get(7).unwrap(), I32, 16);
325
326
match_stack_arg(results.get(0).unwrap(), I32, 4);
327
match_stack_arg(results.get(1).unwrap(), I32, 0);
328
match_reg_arg(results.get(2).unwrap(), I32, regs::rax());
329
Ok(())
330
}
331
332
#[test]
333
fn float_abi_sig() -> Result<()> {
334
let wasm_sig = WasmFuncType::new(
335
[F32, F64, F32, F64, F32, F32, F64, F32, F64].into(),
336
[].into(),
337
);
338
339
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
340
let params = sig.params;
341
342
match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());
343
match_reg_arg(params.get(1).unwrap(), F64, regs::xmm1());
344
match_reg_arg(params.get(2).unwrap(), F32, regs::xmm2());
345
match_reg_arg(params.get(3).unwrap(), F64, regs::xmm3());
346
match_reg_arg(params.get(4).unwrap(), F32, regs::xmm4());
347
match_reg_arg(params.get(5).unwrap(), F32, regs::xmm5());
348
match_reg_arg(params.get(6).unwrap(), F64, regs::xmm6());
349
match_reg_arg(params.get(7).unwrap(), F32, regs::xmm7());
350
match_stack_arg(params.get(8).unwrap(), F64, 0);
351
Ok(())
352
}
353
354
#[test]
355
fn vector_abi_sig() -> Result<()> {
356
let wasm_sig = WasmFuncType::new(
357
[V128, V128, V128, V128, V128, V128, V128, V128, V128, V128].into(),
358
[].into(),
359
);
360
361
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
362
let params = sig.params;
363
364
match_reg_arg(params.get(0).unwrap(), V128, regs::xmm0());
365
match_reg_arg(params.get(1).unwrap(), V128, regs::xmm1());
366
match_reg_arg(params.get(2).unwrap(), V128, regs::xmm2());
367
match_reg_arg(params.get(3).unwrap(), V128, regs::xmm3());
368
match_reg_arg(params.get(4).unwrap(), V128, regs::xmm4());
369
match_reg_arg(params.get(5).unwrap(), V128, regs::xmm5());
370
match_reg_arg(params.get(6).unwrap(), V128, regs::xmm6());
371
match_reg_arg(params.get(7).unwrap(), V128, regs::xmm7());
372
match_stack_arg(params.get(8).unwrap(), V128, 0);
373
match_stack_arg(params.get(9).unwrap(), V128, 16);
374
Ok(())
375
}
376
377
#[test]
378
fn vector_abi_sig_multi_returns() -> Result<()> {
379
let wasm_sig = WasmFuncType::new([].into(), [V128, V128, V128].into());
380
381
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
382
let results = sig.results;
383
384
match_stack_arg(results.get(0).unwrap(), V128, 16);
385
match_stack_arg(results.get(1).unwrap(), V128, 0);
386
match_reg_arg(results.get(2).unwrap(), V128, regs::xmm0());
387
Ok(())
388
}
389
390
#[test]
391
fn mixed_abi_sig() -> Result<()> {
392
let wasm_sig = WasmFuncType::new(
393
[F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),
394
[].into(),
395
);
396
397
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
398
let params = sig.params;
399
400
match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());
401
match_reg_arg(params.get(1).unwrap(), I32, regs::rdi());
402
match_reg_arg(params.get(2).unwrap(), I64, regs::rsi());
403
match_reg_arg(params.get(3).unwrap(), F64, regs::xmm1());
404
match_reg_arg(params.get(4).unwrap(), I32, regs::rdx());
405
match_reg_arg(params.get(5).unwrap(), F32, regs::xmm2());
406
match_reg_arg(params.get(6).unwrap(), F64, regs::xmm3());
407
match_reg_arg(params.get(7).unwrap(), F32, regs::xmm4());
408
match_reg_arg(params.get(8).unwrap(), F64, regs::xmm5());
409
410
let wasm_sig = WasmFuncType::new(
411
[F32, F32, F32, F32, F32, F32, F32, F32, F32, V128].into(),
412
[V128].into(),
413
);
414
415
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
416
let params = sig.params;
417
418
match_stack_arg(params.get(8).unwrap(), F32, 0);
419
match_stack_arg(params.get(9).unwrap(), V128, 16);
420
Ok(())
421
}
422
423
#[test]
424
fn system_v_call_conv() -> Result<()> {
425
let wasm_sig = WasmFuncType::new(
426
[F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),
427
[].into(),
428
);
429
430
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::SystemV)?;
431
let params = sig.params;
432
433
match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());
434
match_reg_arg(params.get(1).unwrap(), I32, regs::rdi());
435
match_reg_arg(params.get(2).unwrap(), I64, regs::rsi());
436
match_reg_arg(params.get(3).unwrap(), F64, regs::xmm1());
437
match_reg_arg(params.get(4).unwrap(), I32, regs::rdx());
438
match_reg_arg(params.get(5).unwrap(), F32, regs::xmm2());
439
match_reg_arg(params.get(6).unwrap(), F64, regs::xmm3());
440
match_reg_arg(params.get(7).unwrap(), F32, regs::xmm4());
441
match_reg_arg(params.get(8).unwrap(), F64, regs::xmm5());
442
Ok(())
443
}
444
445
#[test]
446
fn fastcall_call_conv() -> Result<()> {
447
let wasm_sig = WasmFuncType::new(
448
[F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),
449
[].into(),
450
);
451
452
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::WindowsFastcall)?;
453
let params = sig.params;
454
455
match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());
456
match_reg_arg(params.get(1).unwrap(), I32, regs::rdx());
457
match_reg_arg(params.get(2).unwrap(), I64, regs::r8());
458
match_reg_arg(params.get(3).unwrap(), F64, regs::xmm3());
459
match_stack_arg(params.get(4).unwrap(), I32, 32);
460
match_stack_arg(params.get(5).unwrap(), F32, 40);
461
Ok(())
462
}
463
464
#[test]
465
fn fastcall_call_conv_multi_returns() -> Result<()> {
466
let wasm_sig = WasmFuncType::new(
467
[F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),
468
[I32, F32, I32, F32, I64].into(),
469
);
470
471
let sig = X64ABI::sig(&wasm_sig, &CallingConvention::WindowsFastcall)?;
472
let params = sig.params;
473
let results = sig.results;
474
475
match_reg_arg(params.get(0).unwrap(), F32, regs::xmm1());
476
match_reg_arg(params.get(1).unwrap(), I32, regs::r8());
477
match_reg_arg(params.get(2).unwrap(), I64, regs::r9());
478
// Each argument stack slot is 8 bytes.
479
match_stack_arg(params.get(3).unwrap(), F64, 32);
480
match_stack_arg(params.get(4).unwrap(), I32, 40);
481
match_stack_arg(params.get(5).unwrap(), F32, 48);
482
483
match_reg_arg(results.get(0).unwrap(), I32, regs::rax());
484
485
match_stack_arg(results.get(1).unwrap(), F32, 0);
486
match_stack_arg(results.get(2).unwrap(), I32, 4);
487
match_stack_arg(results.get(3).unwrap(), F32, 8);
488
match_stack_arg(results.get(4).unwrap(), I64, 16);
489
Ok(())
490
}
491
492
#[track_caller]
493
fn match_reg_arg(abi_arg: &ABIOperand, expected_ty: WasmValType, expected_reg: Reg) {
494
match abi_arg {
495
&ABIOperand::Reg { reg, ty, .. } => {
496
assert_eq!(reg, expected_reg);
497
assert_eq!(ty, expected_ty);
498
}
499
stack => panic!("Expected reg argument, got {stack:?}"),
500
}
501
}
502
503
#[track_caller]
504
fn match_stack_arg(abi_arg: &ABIOperand, expected_ty: WasmValType, expected_offset: u32) {
505
match abi_arg {
506
&ABIOperand::Stack { offset, ty, .. } => {
507
assert_eq!(offset, expected_offset);
508
assert_eq!(ty, expected_ty);
509
}
510
reg => panic!("Expected stack argument, got {reg:?}"),
511
}
512
}
513
}
514
515