1use super::regs;
2use crate::{
3 abi::{align_to, ABIOperand, ABIParams, ABIResults, ABISig, ParamsOrReturns, ABI},
4 codegen::CodeGenError,
5 isa::{reg::Reg, CallingConvention},
6 RegIndexEnv,
7};
8use anyhow::{bail, Result};
9use wasmtime_environ::{WasmHeapType, WasmRefType, WasmValType};
10
11#[derive(Default)]
12pub(crate) struct X64ABI;
13
14impl 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 16
34 }
35
36 fn initial_frame_size() -> u8 {
37 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 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 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 scratch_for(ty: &WasmValType) -> Reg {
102 match ty {
103 WasmValType::I32
104 | WasmValType::I64
105 | WasmValType::Ref(WasmRefType {
106 heap_type: WasmHeapType::Func,
107 ..
108 }) => regs::scratch(),
109 WasmValType::F32 | WasmValType::F64 | WasmValType::V128 => regs::scratch_xmm(),
110 _ => unimplemented!(),
111 }
112 }
113
114 fn vmctx_reg() -> Reg {
115 regs::vmctx()
116 }
117
118 fn stack_slot_size() -> u8 {
119 Self::word_bytes()
126 }
127
128 fn sizeof(ty: &WasmValType) -> u8 {
129 match ty {
130 WasmValType::Ref(rt) => match rt.heap_type {
131 WasmHeapType::Func | WasmHeapType::Extern => Self::word_bytes(),
132 ht => unimplemented!("Support for WasmHeapType: {ht}"),
133 },
134 WasmValType::F64 | WasmValType::I64 => Self::word_bytes(),
135 WasmValType::F32 | WasmValType::I32 => Self::word_bytes() / 2,
136 WasmValType::V128 => Self::word_bytes() * 2,
137 }
138 }
139}
140
141impl X64ABI {
142 fn to_abi_operand(
143 wasm_arg: &WasmValType,
144 stack_offset: u32,
145 index_env: &mut RegIndexEnv,
146 call_conv: &CallingConvention,
147 params_or_returns: ParamsOrReturns,
148 ) -> Result<(ABIOperand, u32)> {
149 let (reg, ty) = match wasm_arg {
150 ty @ WasmValType::Ref(rt) => match rt.heap_type {
151 WasmHeapType::Func | WasmHeapType::Extern => (
152 Self::int_reg_for(index_env.next_gpr(), call_conv, params_or_returns),
153 ty,
154 ),
155 _ => bail!(CodeGenError::unsupported_wasm_type()),
156 },
157
158 ty @ (WasmValType::I32 | WasmValType::I64) => (
159 Self::int_reg_for(index_env.next_gpr(), call_conv, params_or_returns),
160 ty,
161 ),
162
163 ty @ (WasmValType::F32 | WasmValType::F64 | WasmValType::V128) => (
165 Self::float_reg_for(index_env.next_fpr(), call_conv, params_or_returns),
166 ty,
167 ),
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 let next_stack = if params_or_returns == ParamsOrReturns::Params {
179 let alignment = if *ty == WasmValType::V128 {
180 ty_size
181 } else {
182 slot_size
183 };
184 align_to(stack_offset, alignment as u32) + (alignment as u32)
185 } else {
186 if call_conv.is_default() {
190 stack_offset + (ty_size as u32)
191 } else {
192 align_to(stack_offset, ty_size as u32) + (ty_size as u32)
193 }
194 };
195 (arg, next_stack)
196 };
197
198 Ok(reg.map_or_else(default, |reg| {
199 (ABIOperand::reg(reg, *ty, ty_size as u32), stack_offset)
200 }))
201 }
202
203 fn int_reg_for(
204 index: Option<u8>,
205 call_conv: &CallingConvention,
206 params_or_returns: ParamsOrReturns,
207 ) -> Option<Reg> {
208 use ParamsOrReturns::*;
209
210 let index = match index {
211 None => return None,
212 Some(index) => index,
213 };
214
215 if call_conv.is_fastcall() {
216 return match (index, params_or_returns) {
217 (0, Params) => Some(regs::rcx()),
218 (1, Params) => Some(regs::rdx()),
219 (2, Params) => Some(regs::r8()),
220 (3, Params) => Some(regs::r9()),
221 (0, Returns) => Some(regs::rax()),
222 _ => None,
223 };
224 }
225
226 if call_conv.is_systemv() || call_conv.is_default() {
227 return match (index, params_or_returns) {
228 (0, Params) => Some(regs::rdi()),
229 (1, Params) => Some(regs::rsi()),
230 (2, Params) => Some(regs::rdx()),
231 (3, Params) => Some(regs::rcx()),
232 (4, Params) => Some(regs::r8()),
233 (5, Params) => Some(regs::r9()),
234 (0, Returns) => Some(regs::rax()),
235 _ => None,
236 };
237 }
238
239 None
240 }
241
242 fn float_reg_for(
243 index: Option<u8>,
244 call_conv: &CallingConvention,
245 params_or_returns: ParamsOrReturns,
246 ) -> Option<Reg> {
247 use ParamsOrReturns::*;
248
249 let index = match index {
250 None => return None,
251 Some(index) => index,
252 };
253
254 if call_conv.is_fastcall() {
255 return match (index, params_or_returns) {
256 (0, Params) => Some(regs::xmm0()),
257 (1, Params) => Some(regs::xmm1()),
258 (2, Params) => Some(regs::xmm2()),
259 (3, Params) => Some(regs::xmm3()),
260 (0, Returns) => Some(regs::xmm0()),
261 _ => None,
262 };
263 }
264
265 if call_conv.is_systemv() || call_conv.is_default() {
266 return match (index, params_or_returns) {
267 (0, Params) => Some(regs::xmm0()),
268 (1, Params) => Some(regs::xmm1()),
269 (2, Params) => Some(regs::xmm2()),
270 (3, Params) => Some(regs::xmm3()),
271 (4, Params) => Some(regs::xmm4()),
272 (5, Params) => Some(regs::xmm5()),
273 (6, Params) => Some(regs::xmm6()),
274 (7, Params) => Some(regs::xmm7()),
275 (0, Returns) => Some(regs::xmm0()),
276 _ => None,
277 };
278 }
279
280 None
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::X64ABI;
287 use crate::{
288 abi::{ABIOperand, ABI},
289 isa::{reg::Reg, x64::regs, CallingConvention},
290 };
291
292 use anyhow::Result;
293
294 use wasmtime_environ::{
295 WasmFuncType,
296 WasmValType::{self, *},
297 };
298
299 #[test]
300 fn int_abi_sig() -> Result<()> {
301 let wasm_sig =
302 WasmFuncType::new([I32, I64, I32, I64, I32, I32, I64, I32].into(), [].into());
303
304 let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
305 let params = sig.params;
306
307 match_reg_arg(params.get(0).unwrap(), I32, regs::rdi());
308 match_reg_arg(params.get(1).unwrap(), I64, regs::rsi());
309 match_reg_arg(params.get(2).unwrap(), I32, regs::rdx());
310 match_reg_arg(params.get(3).unwrap(), I64, regs::rcx());
311 match_reg_arg(params.get(4).unwrap(), I32, regs::r8());
312 match_reg_arg(params.get(5).unwrap(), I32, regs::r9());
313 match_stack_arg(params.get(6).unwrap(), I64, 0);
314 match_stack_arg(params.get(7).unwrap(), I32, 8);
315 Ok(())
316 }
317
318 #[test]
319 fn int_abi_sig_multi_returns() -> Result<()> {
320 let wasm_sig = WasmFuncType::new(
321 [I32, I64, I32, I64, I32, I32, I64, I32].into(),
322 [I32, I32, I32].into(),
323 );
324
325 let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
326 let params = sig.params;
327 let results = sig.results;
328
329 match_reg_arg(params.get(0).unwrap(), I32, regs::rsi());
330 match_reg_arg(params.get(1).unwrap(), I64, regs::rdx());
331 match_reg_arg(params.get(2).unwrap(), I32, regs::rcx());
332 match_reg_arg(params.get(3).unwrap(), I64, regs::r8());
333 match_reg_arg(params.get(4).unwrap(), I32, regs::r9());
334 match_stack_arg(params.get(5).unwrap(), I32, 0);
335 match_stack_arg(params.get(6).unwrap(), I64, 8);
336 match_stack_arg(params.get(7).unwrap(), I32, 16);
337
338 match_stack_arg(results.get(0).unwrap(), I32, 4);
339 match_stack_arg(results.get(1).unwrap(), I32, 0);
340 match_reg_arg(results.get(2).unwrap(), I32, regs::rax());
341 Ok(())
342 }
343
344 #[test]
345 fn float_abi_sig() -> Result<()> {
346 let wasm_sig = WasmFuncType::new(
347 [F32, F64, F32, F64, F32, F32, F64, F32, F64].into(),
348 [].into(),
349 );
350
351 let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
352 let params = sig.params;
353
354 match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());
355 match_reg_arg(params.get(1).unwrap(), F64, regs::xmm1());
356 match_reg_arg(params.get(2).unwrap(), F32, regs::xmm2());
357 match_reg_arg(params.get(3).unwrap(), F64, regs::xmm3());
358 match_reg_arg(params.get(4).unwrap(), F32, regs::xmm4());
359 match_reg_arg(params.get(5).unwrap(), F32, regs::xmm5());
360 match_reg_arg(params.get(6).unwrap(), F64, regs::xmm6());
361 match_reg_arg(params.get(7).unwrap(), F32, regs::xmm7());
362 match_stack_arg(params.get(8).unwrap(), F64, 0);
363 Ok(())
364 }
365
366 #[test]
367 fn vector_abi_sig() -> Result<()> {
368 let wasm_sig = WasmFuncType::new(
369 [V128, V128, V128, V128, V128, V128, V128, V128, V128, V128].into(),
370 [].into(),
371 );
372
373 let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
374 let params = sig.params;
375
376 match_reg_arg(params.get(0).unwrap(), V128, regs::xmm0());
377 match_reg_arg(params.get(1).unwrap(), V128, regs::xmm1());
378 match_reg_arg(params.get(2).unwrap(), V128, regs::xmm2());
379 match_reg_arg(params.get(3).unwrap(), V128, regs::xmm3());
380 match_reg_arg(params.get(4).unwrap(), V128, regs::xmm4());
381 match_reg_arg(params.get(5).unwrap(), V128, regs::xmm5());
382 match_reg_arg(params.get(6).unwrap(), V128, regs::xmm6());
383 match_reg_arg(params.get(7).unwrap(), V128, regs::xmm7());
384 match_stack_arg(params.get(8).unwrap(), V128, 0);
385 match_stack_arg(params.get(9).unwrap(), V128, 16);
386 Ok(())
387 }
388
389 #[test]
390 fn vector_abi_sig_multi_returns() -> Result<()> {
391 let wasm_sig = WasmFuncType::new([].into(), [V128, V128, V128].into());
392
393 let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
394 let results = sig.results;
395
396 match_stack_arg(results.get(0).unwrap(), V128, 16);
397 match_stack_arg(results.get(1).unwrap(), V128, 0);
398 match_reg_arg(results.get(2).unwrap(), V128, regs::xmm0());
399 Ok(())
400 }
401
402 #[test]
403 fn mixed_abi_sig() -> Result<()> {
404 let wasm_sig = WasmFuncType::new(
405 [F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),
406 [].into(),
407 );
408
409 let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default)?;
410 let params = sig.params;
411
412 match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());
413 match_reg_arg(params.get(1).unwrap(), I32, regs::rdi());
414 match_reg_arg(params.get(2).unwrap(), I64, regs::rsi());
415 match_reg_arg(params.get(3).unwrap(), F64, regs::xmm1());
416 match_reg_arg(params.get(4).unwrap(), I32, regs::rdx());
417 match_reg_arg(params.get(5).unwrap(), F32, regs::xmm2());
418 match_reg_arg(params.get(6).unwrap(), F64, regs::xmm3());
419 match_reg_arg(params.get(7).unwrap(), F32, regs::xmm4());
420 match_reg_arg(params.get(8).unwrap(), F64, regs::xmm5());
421 Ok(())
422 }
423
424 #[test]
425 fn system_v_call_conv() -> Result<()> {
426 let wasm_sig = WasmFuncType::new(
427 [F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),
428 [].into(),
429 );
430
431 let sig = X64ABI::sig(&wasm_sig, &CallingConvention::SystemV)?;
432 let params = sig.params;
433
434 match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());
435 match_reg_arg(params.get(1).unwrap(), I32, regs::rdi());
436 match_reg_arg(params.get(2).unwrap(), I64, regs::rsi());
437 match_reg_arg(params.get(3).unwrap(), F64, regs::xmm1());
438 match_reg_arg(params.get(4).unwrap(), I32, regs::rdx());
439 match_reg_arg(params.get(5).unwrap(), F32, regs::xmm2());
440 match_reg_arg(params.get(6).unwrap(), F64, regs::xmm3());
441 match_reg_arg(params.get(7).unwrap(), F32, regs::xmm4());
442 match_reg_arg(params.get(8).unwrap(), F64, regs::xmm5());
443 Ok(())
444 }
445
446 #[test]
447 fn fastcall_call_conv() -> Result<()> {
448 let wasm_sig = WasmFuncType::new(
449 [F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),
450 [].into(),
451 );
452
453 let sig = X64ABI::sig(&wasm_sig, &CallingConvention::WindowsFastcall)?;
454 let params = sig.params;
455
456 match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());
457 match_reg_arg(params.get(1).unwrap(), I32, regs::rdx());
458 match_reg_arg(params.get(2).unwrap(), I64, regs::r8());
459 match_reg_arg(params.get(3).unwrap(), F64, regs::xmm3());
460 match_stack_arg(params.get(4).unwrap(), I32, 32);
461 match_stack_arg(params.get(5).unwrap(), F32, 40);
462 Ok(())
463 }
464
465 #[test]
466 fn fastcall_call_conv_multi_returns() -> Result<()> {
467 let wasm_sig = WasmFuncType::new(
468 [F32, I32, I64, F64, I32, F32, F64, F32, F64].into(),
469 [I32, F32, I32, F32, I64].into(),
470 );
471
472 let sig = X64ABI::sig(&wasm_sig, &CallingConvention::WindowsFastcall)?;
473 let params = sig.params;
474 let results = sig.results;
475
476 match_reg_arg(params.get(0).unwrap(), F32, regs::xmm1());
477 match_reg_arg(params.get(1).unwrap(), I32, regs::r8());
478 match_reg_arg(params.get(2).unwrap(), I64, regs::r9());
479 match_stack_arg(params.get(3).unwrap(), F64, 32);
481 match_stack_arg(params.get(4).unwrap(), I32, 40);
482 match_stack_arg(params.get(5).unwrap(), F32, 48);
483
484 match_reg_arg(results.get(0).unwrap(), I32, regs::rax());
485
486 match_stack_arg(results.get(1).unwrap(), F32, 0);
487 match_stack_arg(results.get(2).unwrap(), I32, 4);
488 match_stack_arg(results.get(3).unwrap(), F32, 8);
489 match_stack_arg(results.get(4).unwrap(), I64, 12);
490 Ok(())
491 }
492
493 #[track_caller]
494 fn match_reg_arg(abi_arg: &ABIOperand, expected_ty: WasmValType, expected_reg: Reg) {
495 match abi_arg {
496 &ABIOperand::Reg { reg, ty, .. } => {
497 assert_eq!(reg, expected_reg);
498 assert_eq!(ty, expected_ty);
499 }
500 stack => panic!("Expected reg argument, got {stack:?}"),
501 }
502 }
503
504 #[track_caller]
505 fn match_stack_arg(abi_arg: &ABIOperand, expected_ty: WasmValType, expected_offset: u32) {
506 match abi_arg {
507 &ABIOperand::Stack { offset, ty, .. } => {
508 assert_eq!(offset, expected_offset);
509 assert_eq!(ty, expected_ty);
510 }
511 reg => panic!("Expected stack argument, got {reg:?}"),
512 }
513 }
514}