1 //! Interface with the external assembler crate. 2 3 use super::{ 4 Amode, Gpr, Inst, LabelUse, MachBuffer, MachLabel, OperandVisitor, OperandVisitorImpl, 5 SyntheticAmode, VCodeConstant, WritableGpr, WritableXmm, Xmm, args::FromWritableReg, 6 }; 7 use crate::{Reg, Writable, ir::TrapCode}; 8 use cranelift_assembler_x64 as asm; 9 use regalloc2::{PReg, RegClass}; 10 use std::string::String; 11 12 /// Define the types of registers Cranelift will use. 13 #[derive(Clone, Debug)] 14 pub struct CraneliftRegisters; 15 impl asm::Registers for CraneliftRegisters { 16 type ReadGpr = Gpr; 17 type ReadWriteGpr = PairedGpr; 18 type WriteGpr = WritableGpr; 19 type ReadXmm = Xmm; 20 type ReadWriteXmm = PairedXmm; 21 type WriteXmm = WritableXmm; 22 } 23 24 /// Convenience type alias of `asm::inst::Inst` with `R = CraneliftRegisters` 25 /// filled in. 26 pub type AsmInst = asm::inst::Inst<CraneliftRegisters>; 27 28 /// A pair of registers, one for reading and one for writing. 29 /// 30 /// Due to how Cranelift's SSA form, we must track the read and write registers 31 /// separately prior to register allocation. Once register allocation is 32 /// complete, we expect the hardware encoding for both `read` and `write` to be 33 /// the same. 34 #[derive(Clone, Copy, Debug, PartialEq)] 35 #[expect(missing_docs, reason = "self-describing variants")] 36 pub struct PairedGpr { 37 pub read: Gpr, 38 pub write: WritableGpr, 39 } 40 41 impl From<WritableGpr> for PairedGpr { 42 fn from(wgpr: WritableGpr) -> Self { 43 let read = wgpr.to_reg(); 44 let write = wgpr; 45 Self { read, write } 46 } 47 } 48 49 /// For ABI ergonomics. 50 impl From<WritableGpr> for asm::Gpr<PairedGpr> { 51 fn from(wgpr: WritableGpr) -> Self { 52 asm::Gpr::new(wgpr.into()) 53 } 54 } 55 56 // For ABI ergonomics. 57 impl From<Writable<Reg>> for asm::GprMem<PairedGpr, Gpr> { 58 fn from(wgpr: Writable<Reg>) -> Self { 59 assert!(wgpr.to_reg().class() == RegClass::Int); 60 let wgpr = WritableGpr::from_writable_reg(wgpr).unwrap(); 61 Self::Gpr(wgpr.into()) 62 } 63 } 64 65 // For ABI ergonomics. 66 impl From<Reg> for asm::GprMem<Gpr, Gpr> { 67 fn from(gpr: Reg) -> Self { 68 assert!(gpr.class() == RegClass::Int); 69 let gpr = Gpr::unwrap_new(gpr); 70 Self::Gpr(gpr) 71 } 72 } 73 74 // For ABI ergonomics. 75 impl From<Writable<Reg>> for asm::GprMem<Gpr, Gpr> { 76 fn from(wgpr: Writable<Reg>) -> Self { 77 wgpr.to_reg().into() 78 } 79 } 80 81 // For ABI ergonomics. 82 impl From<Writable<Reg>> for asm::Gpr<PairedGpr> { 83 fn from(wgpr: Writable<Reg>) -> Self { 84 assert!(wgpr.to_reg().class() == RegClass::Int); 85 let wgpr = WritableGpr::from_writable_reg(wgpr).unwrap(); 86 Self::new(wgpr.into()) 87 } 88 } 89 90 impl From<Writable<Reg>> for asm::Gpr<WritableGpr> { 91 fn from(wgpr: Writable<Reg>) -> Self { 92 assert!(wgpr.to_reg().class() == RegClass::Int); 93 let wgpr = WritableGpr::from_writable_reg(wgpr).unwrap(); 94 Self::new(wgpr) 95 } 96 } 97 98 impl asm::AsReg for PairedGpr { 99 fn enc(&self) -> u8 { 100 let PairedGpr { read, write } = self; 101 let read = enc_gpr(read); 102 let write = enc_gpr(&write.to_reg()); 103 assert_eq!(read, write); 104 write 105 } 106 107 fn to_string(&self, size: Option<asm::Size>) -> String { 108 if self.read.is_real() { 109 asm::gpr::enc::to_string(self.enc(), size.unwrap()).into() 110 } else { 111 let read = self.read.to_reg(); 112 let write = self.write.to_reg().to_reg(); 113 format!("(%{write:?} <- %{read:?})") 114 } 115 } 116 117 fn new(_: u8) -> Self { 118 panic!("disallow creation of new assembler registers") 119 } 120 } 121 122 /// A pair of XMM registers, one for reading and one for writing. 123 #[derive(Clone, Copy, Debug, PartialEq)] 124 #[expect(missing_docs, reason = "self-describing variants")] 125 pub struct PairedXmm { 126 pub read: Xmm, 127 pub write: WritableXmm, 128 } 129 130 impl From<WritableXmm> for PairedXmm { 131 fn from(wxmm: WritableXmm) -> Self { 132 let read = wxmm.to_reg(); 133 let write = wxmm; 134 Self { read, write } 135 } 136 } 137 138 /// For ABI ergonomics. 139 impl From<WritableXmm> for asm::Xmm<PairedXmm> { 140 fn from(wgpr: WritableXmm) -> Self { 141 asm::Xmm::new(wgpr.into()) 142 } 143 } 144 145 // For emission ergonomics. 146 impl From<Writable<Reg>> for asm::Xmm<PairedXmm> { 147 fn from(wxmm: Writable<Reg>) -> Self { 148 assert!(wxmm.to_reg().class() == RegClass::Float); 149 let wxmm = WritableXmm::from_writable_reg(wxmm).unwrap(); 150 Self::new(wxmm.into()) 151 } 152 } 153 154 // For emission ergonomics. 155 impl From<Reg> for asm::Xmm<Xmm> { 156 fn from(xmm: Reg) -> Self { 157 assert!(xmm.class() == RegClass::Float); 158 let xmm = Xmm::unwrap_new(xmm); 159 Self::new(xmm) 160 } 161 } 162 163 // For emission ergonomics. 164 impl From<Reg> for asm::XmmMem<Xmm, Gpr> { 165 fn from(xmm: Reg) -> Self { 166 assert!(xmm.class() == RegClass::Float); 167 let xmm = Xmm::unwrap_new(xmm); 168 Self::Xmm(xmm) 169 } 170 } 171 172 impl asm::AsReg for PairedXmm { 173 fn enc(&self) -> u8 { 174 let PairedXmm { read, write } = self; 175 let read = enc_xmm(read); 176 let write = enc_xmm(&write.to_reg()); 177 assert_eq!(read, write); 178 write 179 } 180 181 fn to_string(&self, size: Option<asm::Size>) -> String { 182 assert!(size.is_none(), "XMM registers do not have size variants"); 183 if self.read.is_real() { 184 asm::xmm::enc::to_string(self.enc()).into() 185 } else { 186 let read = self.read.to_reg(); 187 let write = self.write.to_reg().to_reg(); 188 format!("(%{write:?} <- %{read:?})") 189 } 190 } 191 192 fn new(_: u8) -> Self { 193 panic!("disallow creation of new assembler registers") 194 } 195 } 196 197 /// This bridges the gap between codegen and assembler for general purpose register types. 198 impl asm::AsReg for Gpr { 199 fn enc(&self) -> u8 { 200 enc_gpr(self) 201 } 202 203 fn to_string(&self, size: Option<asm::Size>) -> String { 204 if self.is_real() { 205 asm::gpr::enc::to_string(self.enc(), size.unwrap()).into() 206 } else { 207 format!("%{:?}", self.to_reg()) 208 } 209 } 210 211 fn new(_: u8) -> Self { 212 panic!("disallow creation of new assembler registers") 213 } 214 } 215 216 /// This bridges the gap between codegen and assembler for xmm register types. 217 impl asm::AsReg for Xmm { 218 fn enc(&self) -> u8 { 219 enc_xmm(self) 220 } 221 222 fn to_string(&self, size: Option<asm::Size>) -> String { 223 assert!(size.is_none(), "XMM registers do not have size variants"); 224 if self.is_real() { 225 asm::xmm::enc::to_string(self.enc()).into() 226 } else { 227 format!("%{:?}", self.to_reg()) 228 } 229 } 230 231 fn new(_: u8) -> Self { 232 panic!("disallow creation of new assembler registers") 233 } 234 } 235 236 /// A helper method for extracting the hardware encoding of a general purpose register. 237 #[inline] 238 fn enc_gpr(gpr: &Gpr) -> u8 { 239 if let Some(real) = gpr.to_reg().to_real_reg() { 240 real.hw_enc() 241 } else { 242 unreachable!() 243 } 244 } 245 246 /// A helper method for extracting the hardware encoding of an xmm register. 247 #[inline] 248 fn enc_xmm(xmm: &Xmm) -> u8 { 249 if let Some(real) = xmm.to_reg().to_real_reg() { 250 real.hw_enc() 251 } else { 252 unreachable!() 253 } 254 } 255 256 /// A wrapper to implement the `cranelift-assembler-x64` register allocation trait, 257 /// `RegallocVisitor`, in terms of the trait used in Cranelift, 258 /// `OperandVisitor`. 259 pub(crate) struct RegallocVisitor<'a, T> 260 where 261 T: OperandVisitorImpl, 262 { 263 pub collector: &'a mut T, 264 } 265 266 impl<'a, T: OperandVisitor> asm::RegisterVisitor<CraneliftRegisters> for RegallocVisitor<'a, T> { 267 fn read_gpr(&mut self, reg: &mut Gpr) { 268 self.collector.reg_use(reg); 269 } 270 271 fn read_write_gpr(&mut self, reg: &mut PairedGpr) { 272 let PairedGpr { read, write } = reg; 273 self.collector.reg_use(read); 274 self.collector.reg_reuse_def(write, 0); 275 } 276 277 fn write_gpr(&mut self, reg: &mut WritableGpr) { 278 self.collector.reg_def(reg); 279 } 280 281 fn fixed_read_gpr(&mut self, reg: &mut Gpr, enc: u8) { 282 self.collector 283 .reg_fixed_use(reg, fixed_reg(enc, RegClass::Int)); 284 } 285 286 fn fixed_read_write_gpr(&mut self, reg: &mut PairedGpr, enc: u8) { 287 let PairedGpr { read, write } = reg; 288 self.collector 289 .reg_fixed_use(read, fixed_reg(enc, RegClass::Int)); 290 self.collector 291 .reg_fixed_def(write, fixed_reg(enc, RegClass::Int)); 292 } 293 294 fn fixed_write_gpr(&mut self, reg: &mut WritableGpr, enc: u8) { 295 self.collector 296 .reg_fixed_def(reg, fixed_reg(enc, RegClass::Int)); 297 } 298 299 fn read_xmm(&mut self, reg: &mut Xmm) { 300 self.collector.reg_use(reg); 301 } 302 303 fn read_write_xmm(&mut self, reg: &mut PairedXmm) { 304 let PairedXmm { read, write } = reg; 305 self.collector.reg_use(read); 306 self.collector.reg_reuse_def(write, 0); 307 } 308 309 fn write_xmm(&mut self, reg: &mut WritableXmm) { 310 self.collector.reg_def(reg); 311 } 312 313 fn fixed_read_xmm(&mut self, reg: &mut Xmm, enc: u8) { 314 self.collector 315 .reg_fixed_use(reg, fixed_reg(enc, RegClass::Float)); 316 } 317 318 fn fixed_read_write_xmm(&mut self, reg: &mut PairedXmm, enc: u8) { 319 let PairedXmm { read, write } = reg; 320 self.collector 321 .reg_fixed_use(read, fixed_reg(enc, RegClass::Float)); 322 self.collector 323 .reg_fixed_def(write, fixed_reg(enc, RegClass::Float)); 324 } 325 326 fn fixed_write_xmm(&mut self, reg: &mut WritableXmm, enc: u8) { 327 self.collector 328 .reg_fixed_def(reg, fixed_reg(enc, RegClass::Float)); 329 } 330 } 331 332 /// A helper for building a fixed register from its hardware encoding. 333 fn fixed_reg(enc: u8, class: RegClass) -> Reg { 334 let preg = PReg::new(usize::from(enc), class); 335 Reg::from_real_reg(preg) 336 } 337 338 impl From<SyntheticAmode> for asm::Amode<Gpr> { 339 fn from(amode: SyntheticAmode) -> asm::Amode<Gpr> { 340 match amode { 341 SyntheticAmode::Real(amode) => amode.into(), 342 SyntheticAmode::IncomingArg { offset } => asm::Amode::ImmReg { 343 base: Gpr::RBP, 344 simm32: asm::AmodeOffsetPlusKnownOffset { 345 simm32: (-i32::try_from(offset).unwrap()).into(), 346 offset: Some(offsets::KEY_INCOMING_ARG), 347 }, 348 trap: None, 349 }, 350 SyntheticAmode::SlotOffset { simm32 } => asm::Amode::ImmReg { 351 base: Gpr::RSP, 352 simm32: asm::AmodeOffsetPlusKnownOffset { 353 simm32: simm32.into(), 354 offset: Some(offsets::KEY_SLOT_OFFSET), 355 }, 356 trap: None, 357 }, 358 SyntheticAmode::ConstantOffset(vcode_constant) => asm::Amode::RipRelative { 359 target: asm::DeferredTarget::Constant(asm::Constant(vcode_constant.as_u32())), 360 }, 361 } 362 } 363 } 364 365 impl From<Amode> for asm::Amode<Gpr> { 366 fn from(amode: Amode) -> asm::Amode<Gpr> { 367 match amode { 368 Amode::ImmReg { 369 simm32, 370 base, 371 flags, 372 } => asm::Amode::ImmReg { 373 simm32: asm::AmodeOffsetPlusKnownOffset { 374 simm32: simm32.into(), 375 offset: None, 376 }, 377 base: Gpr::unwrap_new(base), 378 trap: flags.trap_code().map(Into::into), 379 }, 380 Amode::ImmRegRegShift { 381 simm32, 382 base, 383 index, 384 shift, 385 flags, 386 } => asm::Amode::ImmRegRegShift { 387 base, 388 index: asm::NonRspGpr::new(index), 389 scale: asm::Scale::new(shift), 390 simm32: simm32.into(), 391 trap: flags.trap_code().map(Into::into), 392 }, 393 Amode::RipRelative { target } => asm::Amode::RipRelative { 394 target: asm::DeferredTarget::Label(asm::Label(target.as_u32())), 395 }, 396 } 397 } 398 } 399 400 impl<R: asm::AsReg> From<SyntheticAmode> for asm::XmmMem<R, Gpr> { 401 fn from(amode: SyntheticAmode) -> Self { 402 asm::XmmMem::Mem(amode.into()) 403 } 404 } 405 406 impl<R: asm::AsReg> From<SyntheticAmode> for asm::GprMem<R, Gpr> { 407 fn from(amode: SyntheticAmode) -> Self { 408 asm::GprMem::Mem(amode.into()) 409 } 410 } 411 412 impl<R: asm::AsReg> From<Amode> for asm::XmmMem<R, Gpr> { 413 fn from(amode: Amode) -> Self { 414 asm::XmmMem::Mem(amode.into()) 415 } 416 } 417 418 impl<R: asm::AsReg> From<Amode> for asm::GprMem<R, Gpr> { 419 fn from(amode: Amode) -> Self { 420 asm::GprMem::Mem(amode.into()) 421 } 422 } 423 424 /// Keep track of the offset slots to fill in during emission; see 425 /// `KnownOffsetTable`. 426 #[expect(missing_docs, reason = "self-describing keys")] 427 pub mod offsets { 428 pub const KEY_INCOMING_ARG: u8 = 0; 429 pub const KEY_SLOT_OFFSET: u8 = 1; 430 } 431 432 /// Implementor of the [`asm::CodeSink`] trait. 433 pub struct AsmCodeSink<'a> { 434 /// The buffer this is emitting into. 435 pub sink: &'a mut MachBuffer<Inst>, 436 /// The value of `KEY_INCOMING_ARG`. 437 pub incoming_arg_offset: i32, 438 /// The value of `KEY_SLOT_OFFSET`. 439 pub slot_offset: i32, 440 } 441 442 impl asm::CodeSink for AsmCodeSink<'_> { 443 fn put1(&mut self, value: u8) { 444 self.sink.put1(value) 445 } 446 447 fn put2(&mut self, value: u16) { 448 self.sink.put2(value) 449 } 450 451 fn put4(&mut self, value: u32) { 452 self.sink.put4(value) 453 } 454 455 fn put8(&mut self, value: u64) { 456 self.sink.put8(value) 457 } 458 459 fn add_trap(&mut self, code: asm::TrapCode) { 460 self.sink.add_trap(code.into()); 461 } 462 463 fn use_target(&mut self, target: asm::DeferredTarget) { 464 let offset = self.sink.cur_offset(); 465 match target { 466 asm::DeferredTarget::Label(label) => { 467 self.sink 468 .use_label_at_offset(offset, label.into(), LabelUse::JmpRel32); 469 } 470 asm::DeferredTarget::Constant(constant) => { 471 let label = self.sink.get_label_for_constant(constant.into()); 472 self.sink 473 .use_label_at_offset(offset, label, LabelUse::JmpRel32); 474 } 475 asm::DeferredTarget::None => {} 476 } 477 } 478 479 fn known_offset(&self, offset: asm::KnownOffset) -> i32 { 480 match offset { 481 offsets::KEY_INCOMING_ARG => self.incoming_arg_offset, 482 offsets::KEY_SLOT_OFFSET => self.slot_offset, 483 other => panic!("unknown \"known\" offset {other}"), 484 } 485 } 486 } 487 488 impl From<asm::TrapCode> for TrapCode { 489 fn from(value: asm::TrapCode) -> Self { 490 Self::from_raw(value.0) 491 } 492 } 493 494 impl From<TrapCode> for asm::TrapCode { 495 fn from(value: TrapCode) -> Self { 496 Self(value.as_raw()) 497 } 498 } 499 500 impl From<asm::Label> for MachLabel { 501 fn from(value: asm::Label) -> Self { 502 Self::from_u32(value.0) 503 } 504 } 505 506 impl From<MachLabel> for asm::Label { 507 fn from(value: MachLabel) -> Self { 508 Self(value.as_u32()) 509 } 510 } 511 512 impl From<asm::Constant> for VCodeConstant { 513 fn from(value: asm::Constant) -> Self { 514 Self::from_u32(value.0) 515 } 516 } 517 518 // Include code generated by `cranelift-codegen/meta/src/gen_asm.rs`. This file 519 // contains a `isle_assembler_methods!` macro with Rust implementations of all 520 // the assembler instructions exposed to ISLE. 521 include!(concat!(env!("OUT_DIR"), "/assembler-isle-macro.rs")); 522 pub(crate) use isle_assembler_methods; 523 524 #[cfg(test)] 525 mod tests { 526 use super::PairedGpr; 527 use super::asm::{AsReg, Size}; 528 use crate::isa::x64::args::{FromWritableReg, Gpr, WritableGpr, WritableXmm, Xmm}; 529 use crate::isa::x64::inst::external::PairedXmm; 530 use crate::{Reg, Writable}; 531 use regalloc2::{RegClass, VReg}; 532 533 #[test] 534 fn pretty_print_registers() { 535 // For logging, we need to be able to pretty-print the virtual registers 536 // that Cranelift uses before register allocation. This test ensures 537 // that these remain printable using the `AsReg::to_string` interface 538 // (see issue #10631). 539 540 let v200: Reg = VReg::new(200, RegClass::Int).into(); 541 let gpr200 = Gpr::new(v200).unwrap(); 542 assert_eq!(gpr200.to_string(Some(Size::Quadword)), "%v200"); 543 544 let v300: Reg = VReg::new(300, RegClass::Int).into(); 545 let wgpr300 = WritableGpr::from_writable_reg(Writable::from_reg(v300)).unwrap(); 546 let pair = PairedGpr { 547 read: gpr200, 548 write: wgpr300, 549 }; 550 assert_eq!(pair.to_string(Some(Size::Quadword)), "(%v300 <- %v200)"); 551 552 let v400: Reg = VReg::new(400, RegClass::Float).into(); 553 let xmm400 = Xmm::new(v400).unwrap(); 554 assert_eq!(xmm400.to_string(None), "%v400"); 555 556 let v500: Reg = VReg::new(500, RegClass::Float).into(); 557 let wxmm500 = WritableXmm::from_writable_reg(Writable::from_reg(v500)).unwrap(); 558 let pair = PairedXmm { 559 read: xmm400, 560 write: wxmm500, 561 }; 562 assert_eq!(pair.to_string(None), "(%v500 <- %v400)"); 563 } 564 } 565