Path: blob/main/cranelift/codegen/src/isa/x64/inst/external.rs
1693 views
//! Interface with the external assembler crate.12use super::{3Amode, Gpr, Inst, LabelUse, MachBuffer, MachLabel, OperandVisitor, OperandVisitorImpl,4SyntheticAmode, VCodeConstant, WritableGpr, WritableXmm, Xmm, args::FromWritableReg,5};6use crate::{Reg, Writable, ir::TrapCode};7use cranelift_assembler_x64 as asm;8use regalloc2::{PReg, RegClass};9use std::string::String;1011/// Define the types of registers Cranelift will use.12#[derive(Clone, Debug)]13pub struct CraneliftRegisters;14impl asm::Registers for CraneliftRegisters {15type ReadGpr = Gpr;16type ReadWriteGpr = PairedGpr;17type WriteGpr = WritableGpr;18type ReadXmm = Xmm;19type ReadWriteXmm = PairedXmm;20type WriteXmm = WritableXmm;21}2223/// Convenience type alias of `asm::inst::Inst` with `R = CraneliftRegisters`24/// filled in.25pub type AsmInst = asm::inst::Inst<CraneliftRegisters>;2627/// A pair of registers, one for reading and one for writing.28///29/// Due to how Cranelift's SSA form, we must track the read and write registers30/// separately prior to register allocation. Once register allocation is31/// complete, we expect the hardware encoding for both `read` and `write` to be32/// the same.33#[derive(Clone, Copy, Debug, PartialEq)]34#[expect(missing_docs, reason = "self-describing variants")]35pub struct PairedGpr {36pub read: Gpr,37pub write: WritableGpr,38}3940impl From<WritableGpr> for PairedGpr {41fn from(wgpr: WritableGpr) -> Self {42let read = wgpr.to_reg();43let write = wgpr;44Self { read, write }45}46}4748/// For ABI ergonomics.49impl From<WritableGpr> for asm::Gpr<PairedGpr> {50fn from(wgpr: WritableGpr) -> Self {51asm::Gpr::new(wgpr.into())52}53}5455// For ABI ergonomics.56impl From<Writable<Reg>> for asm::GprMem<PairedGpr, Gpr> {57fn from(wgpr: Writable<Reg>) -> Self {58assert!(wgpr.to_reg().class() == RegClass::Int);59let wgpr = WritableGpr::from_writable_reg(wgpr).unwrap();60Self::Gpr(wgpr.into())61}62}6364// For ABI ergonomics.65impl From<Reg> for asm::GprMem<Gpr, Gpr> {66fn from(gpr: Reg) -> Self {67assert!(gpr.class() == RegClass::Int);68let gpr = Gpr::unwrap_new(gpr);69Self::Gpr(gpr)70}71}7273// For ABI ergonomics.74impl From<Writable<Reg>> for asm::GprMem<Gpr, Gpr> {75fn from(wgpr: Writable<Reg>) -> Self {76wgpr.to_reg().into()77}78}7980// For ABI ergonomics.81impl From<Writable<Reg>> for asm::Gpr<PairedGpr> {82fn from(wgpr: Writable<Reg>) -> Self {83assert!(wgpr.to_reg().class() == RegClass::Int);84let wgpr = WritableGpr::from_writable_reg(wgpr).unwrap();85Self::new(wgpr.into())86}87}8889impl From<Writable<Reg>> for asm::Gpr<WritableGpr> {90fn from(wgpr: Writable<Reg>) -> Self {91assert!(wgpr.to_reg().class() == RegClass::Int);92let wgpr = WritableGpr::from_writable_reg(wgpr).unwrap();93Self::new(wgpr)94}95}9697impl asm::AsReg for PairedGpr {98fn enc(&self) -> u8 {99let PairedGpr { read, write } = self;100let read = enc_gpr(read);101let write = enc_gpr(&write.to_reg());102assert_eq!(read, write);103write104}105106fn to_string(&self, size: Option<asm::Size>) -> String {107if self.read.is_real() {108asm::gpr::enc::to_string(self.enc(), size.unwrap()).into()109} else {110let read = self.read.to_reg();111let write = self.write.to_reg().to_reg();112format!("(%{write:?} <- %{read:?})")113}114}115116fn new(_: u8) -> Self {117panic!("disallow creation of new assembler registers")118}119}120121/// A pair of XMM registers, one for reading and one for writing.122#[derive(Clone, Copy, Debug, PartialEq)]123#[expect(missing_docs, reason = "self-describing variants")]124pub struct PairedXmm {125pub read: Xmm,126pub write: WritableXmm,127}128129impl From<WritableXmm> for PairedXmm {130fn from(wxmm: WritableXmm) -> Self {131let read = wxmm.to_reg();132let write = wxmm;133Self { read, write }134}135}136137/// For ABI ergonomics.138impl From<WritableXmm> for asm::Xmm<PairedXmm> {139fn from(wgpr: WritableXmm) -> Self {140asm::Xmm::new(wgpr.into())141}142}143144// For emission ergonomics.145impl From<Writable<Reg>> for asm::Xmm<PairedXmm> {146fn from(wxmm: Writable<Reg>) -> Self {147assert!(wxmm.to_reg().class() == RegClass::Float);148let wxmm = WritableXmm::from_writable_reg(wxmm).unwrap();149Self::new(wxmm.into())150}151}152153// For emission ergonomics.154impl From<Reg> for asm::Xmm<Xmm> {155fn from(xmm: Reg) -> Self {156assert!(xmm.class() == RegClass::Float);157let xmm = Xmm::unwrap_new(xmm);158Self::new(xmm)159}160}161162// For emission ergonomics.163impl From<Reg> for asm::XmmMem<Xmm, Gpr> {164fn from(xmm: Reg) -> Self {165assert!(xmm.class() == RegClass::Float);166let xmm = Xmm::unwrap_new(xmm);167Self::Xmm(xmm)168}169}170171impl asm::AsReg for PairedXmm {172fn enc(&self) -> u8 {173let PairedXmm { read, write } = self;174let read = enc_xmm(read);175let write = enc_xmm(&write.to_reg());176assert_eq!(read, write);177write178}179180fn to_string(&self, size: Option<asm::Size>) -> String {181assert!(size.is_none(), "XMM registers do not have size variants");182if self.read.is_real() {183asm::xmm::enc::to_string(self.enc()).into()184} else {185let read = self.read.to_reg();186let write = self.write.to_reg().to_reg();187format!("(%{write:?} <- %{read:?})")188}189}190191fn new(_: u8) -> Self {192panic!("disallow creation of new assembler registers")193}194}195196/// This bridges the gap between codegen and assembler for general purpose register types.197impl asm::AsReg for Gpr {198fn enc(&self) -> u8 {199enc_gpr(self)200}201202fn to_string(&self, size: Option<asm::Size>) -> String {203if self.is_real() {204asm::gpr::enc::to_string(self.enc(), size.unwrap()).into()205} else {206format!("%{:?}", self.to_reg())207}208}209210fn new(_: u8) -> Self {211panic!("disallow creation of new assembler registers")212}213}214215/// This bridges the gap between codegen and assembler for xmm register types.216impl asm::AsReg for Xmm {217fn enc(&self) -> u8 {218enc_xmm(self)219}220221fn to_string(&self, size: Option<asm::Size>) -> String {222assert!(size.is_none(), "XMM registers do not have size variants");223if self.is_real() {224asm::xmm::enc::to_string(self.enc()).into()225} else {226format!("%{:?}", self.to_reg())227}228}229230fn new(_: u8) -> Self {231panic!("disallow creation of new assembler registers")232}233}234235/// A helper method for extracting the hardware encoding of a general purpose register.236#[inline]237fn enc_gpr(gpr: &Gpr) -> u8 {238if let Some(real) = gpr.to_reg().to_real_reg() {239real.hw_enc()240} else {241unreachable!()242}243}244245/// A helper method for extracting the hardware encoding of an xmm register.246#[inline]247fn enc_xmm(xmm: &Xmm) -> u8 {248if let Some(real) = xmm.to_reg().to_real_reg() {249real.hw_enc()250} else {251unreachable!()252}253}254255/// A wrapper to implement the `cranelift-assembler-x64` register allocation trait,256/// `RegallocVisitor`, in terms of the trait used in Cranelift,257/// `OperandVisitor`.258pub(crate) struct RegallocVisitor<'a, T>259where260T: OperandVisitorImpl,261{262pub collector: &'a mut T,263}264265impl<'a, T: OperandVisitor> asm::RegisterVisitor<CraneliftRegisters> for RegallocVisitor<'a, T> {266fn read_gpr(&mut self, reg: &mut Gpr) {267self.collector.reg_use(reg);268}269270fn read_write_gpr(&mut self, reg: &mut PairedGpr) {271let PairedGpr { read, write } = reg;272self.collector.reg_use(read);273self.collector.reg_reuse_def(write, 0);274}275276fn write_gpr(&mut self, reg: &mut WritableGpr) {277self.collector.reg_def(reg);278}279280fn fixed_read_gpr(&mut self, reg: &mut Gpr, enc: u8) {281self.collector282.reg_fixed_use(reg, fixed_reg(enc, RegClass::Int));283}284285fn fixed_read_write_gpr(&mut self, reg: &mut PairedGpr, enc: u8) {286let PairedGpr { read, write } = reg;287self.collector288.reg_fixed_use(read, fixed_reg(enc, RegClass::Int));289self.collector290.reg_fixed_def(write, fixed_reg(enc, RegClass::Int));291}292293fn fixed_write_gpr(&mut self, reg: &mut WritableGpr, enc: u8) {294self.collector295.reg_fixed_def(reg, fixed_reg(enc, RegClass::Int));296}297298fn read_xmm(&mut self, reg: &mut Xmm) {299self.collector.reg_use(reg);300}301302fn read_write_xmm(&mut self, reg: &mut PairedXmm) {303let PairedXmm { read, write } = reg;304self.collector.reg_use(read);305self.collector.reg_reuse_def(write, 0);306}307308fn write_xmm(&mut self, reg: &mut WritableXmm) {309self.collector.reg_def(reg);310}311312fn fixed_read_xmm(&mut self, reg: &mut Xmm, enc: u8) {313self.collector314.reg_fixed_use(reg, fixed_reg(enc, RegClass::Float));315}316317fn fixed_read_write_xmm(&mut self, reg: &mut PairedXmm, enc: u8) {318let PairedXmm { read, write } = reg;319self.collector320.reg_fixed_use(read, fixed_reg(enc, RegClass::Float));321self.collector322.reg_fixed_def(write, fixed_reg(enc, RegClass::Float));323}324325fn fixed_write_xmm(&mut self, reg: &mut WritableXmm, enc: u8) {326self.collector327.reg_fixed_def(reg, fixed_reg(enc, RegClass::Float));328}329}330331/// A helper for building a fixed register from its hardware encoding.332fn fixed_reg(enc: u8, class: RegClass) -> Reg {333let preg = PReg::new(usize::from(enc), class);334Reg::from_real_reg(preg)335}336337impl From<SyntheticAmode> for asm::Amode<Gpr> {338fn from(amode: SyntheticAmode) -> asm::Amode<Gpr> {339match amode {340SyntheticAmode::Real(amode) => amode.into(),341SyntheticAmode::IncomingArg { offset } => asm::Amode::ImmReg {342base: Gpr::RBP,343simm32: asm::AmodeOffsetPlusKnownOffset {344simm32: (-i32::try_from(offset).unwrap()).into(),345offset: Some(offsets::KEY_INCOMING_ARG),346},347trap: None,348},349SyntheticAmode::SlotOffset { simm32 } => asm::Amode::ImmReg {350base: Gpr::RSP,351simm32: asm::AmodeOffsetPlusKnownOffset {352simm32: simm32.into(),353offset: Some(offsets::KEY_SLOT_OFFSET),354},355trap: None,356},357SyntheticAmode::ConstantOffset(vcode_constant) => asm::Amode::RipRelative {358target: asm::DeferredTarget::Constant(asm::Constant(vcode_constant.as_u32())),359},360}361}362}363364impl From<Amode> for asm::Amode<Gpr> {365fn from(amode: Amode) -> asm::Amode<Gpr> {366match amode {367Amode::ImmReg {368simm32,369base,370flags,371} => asm::Amode::ImmReg {372simm32: asm::AmodeOffsetPlusKnownOffset {373simm32: simm32.into(),374offset: None,375},376base: Gpr::unwrap_new(base),377trap: flags.trap_code().map(Into::into),378},379Amode::ImmRegRegShift {380simm32,381base,382index,383shift,384flags,385} => asm::Amode::ImmRegRegShift {386base,387index: asm::NonRspGpr::new(index),388scale: asm::Scale::new(shift),389simm32: simm32.into(),390trap: flags.trap_code().map(Into::into),391},392Amode::RipRelative { target } => asm::Amode::RipRelative {393target: asm::DeferredTarget::Label(asm::Label(target.as_u32())),394},395}396}397}398399impl<R: asm::AsReg> From<SyntheticAmode> for asm::XmmMem<R, Gpr> {400fn from(amode: SyntheticAmode) -> Self {401asm::XmmMem::Mem(amode.into())402}403}404405impl<R: asm::AsReg> From<SyntheticAmode> for asm::GprMem<R, Gpr> {406fn from(amode: SyntheticAmode) -> Self {407asm::GprMem::Mem(amode.into())408}409}410411impl<R: asm::AsReg> From<Amode> for asm::XmmMem<R, Gpr> {412fn from(amode: Amode) -> Self {413asm::XmmMem::Mem(amode.into())414}415}416417impl<R: asm::AsReg> From<Amode> for asm::GprMem<R, Gpr> {418fn from(amode: Amode) -> Self {419asm::GprMem::Mem(amode.into())420}421}422423/// Keep track of the offset slots to fill in during emission; see424/// `KnownOffsetTable`.425#[expect(missing_docs, reason = "self-describing keys")]426pub mod offsets {427pub const KEY_INCOMING_ARG: u8 = 0;428pub const KEY_SLOT_OFFSET: u8 = 1;429}430431/// Implementor of the [`asm::CodeSink`] trait.432pub struct AsmCodeSink<'a> {433/// The buffer this is emitting into.434pub sink: &'a mut MachBuffer<Inst>,435/// The value of `KEY_INCOMING_ARG`.436pub incoming_arg_offset: i32,437/// The value of `KEY_SLOT_OFFSET`.438pub slot_offset: i32,439}440441impl asm::CodeSink for AsmCodeSink<'_> {442fn put1(&mut self, value: u8) {443self.sink.put1(value)444}445446fn put2(&mut self, value: u16) {447self.sink.put2(value)448}449450fn put4(&mut self, value: u32) {451self.sink.put4(value)452}453454fn put8(&mut self, value: u64) {455self.sink.put8(value)456}457458fn add_trap(&mut self, code: asm::TrapCode) {459self.sink.add_trap(code.into());460}461462fn use_target(&mut self, target: asm::DeferredTarget) {463let offset = self.sink.cur_offset();464match target {465asm::DeferredTarget::Label(label) => {466self.sink467.use_label_at_offset(offset, label.into(), LabelUse::JmpRel32);468}469asm::DeferredTarget::Constant(constant) => {470let label = self.sink.get_label_for_constant(constant.into());471self.sink472.use_label_at_offset(offset, label, LabelUse::JmpRel32);473}474asm::DeferredTarget::None => {}475}476}477478fn known_offset(&self, offset: asm::KnownOffset) -> i32 {479match offset {480offsets::KEY_INCOMING_ARG => self.incoming_arg_offset,481offsets::KEY_SLOT_OFFSET => self.slot_offset,482other => panic!("unknown \"known\" offset {other}"),483}484}485}486487impl From<asm::TrapCode> for TrapCode {488fn from(value: asm::TrapCode) -> Self {489Self::from_raw(value.0)490}491}492493impl From<TrapCode> for asm::TrapCode {494fn from(value: TrapCode) -> Self {495Self(value.as_raw())496}497}498499impl From<asm::Label> for MachLabel {500fn from(value: asm::Label) -> Self {501Self::from_u32(value.0)502}503}504505impl From<MachLabel> for asm::Label {506fn from(value: MachLabel) -> Self {507Self(value.as_u32())508}509}510511impl From<asm::Constant> for VCodeConstant {512fn from(value: asm::Constant) -> Self {513Self::from_u32(value.0)514}515}516517// Include code generated by `cranelift-codegen/meta/src/gen_asm.rs`. This file518// contains a `isle_assembler_methods!` macro with Rust implementations of all519// the assembler instructions exposed to ISLE.520include!(concat!(env!("OUT_DIR"), "/assembler-isle-macro.rs"));521pub(crate) use isle_assembler_methods;522523#[cfg(test)]524mod tests {525use super::PairedGpr;526use super::asm::{AsReg, Size};527use crate::isa::x64::args::{FromWritableReg, Gpr, WritableGpr, WritableXmm, Xmm};528use crate::isa::x64::inst::external::PairedXmm;529use crate::{Reg, Writable};530use regalloc2::{RegClass, VReg};531532#[test]533fn pretty_print_registers() {534// For logging, we need to be able to pretty-print the virtual registers535// that Cranelift uses before register allocation. This test ensures536// that these remain printable using the `AsReg::to_string` interface537// (see issue #10631).538539let v200: Reg = VReg::new(200, RegClass::Int).into();540let gpr200 = Gpr::new(v200).unwrap();541assert_eq!(gpr200.to_string(Some(Size::Quadword)), "%v200");542543let v300: Reg = VReg::new(300, RegClass::Int).into();544let wgpr300 = WritableGpr::from_writable_reg(Writable::from_reg(v300)).unwrap();545let pair = PairedGpr {546read: gpr200,547write: wgpr300,548};549assert_eq!(pair.to_string(Some(Size::Quadword)), "(%v300 <- %v200)");550551let v400: Reg = VReg::new(400, RegClass::Float).into();552let xmm400 = Xmm::new(v400).unwrap();553assert_eq!(xmm400.to_string(None), "%v400");554555let v500: Reg = VReg::new(500, RegClass::Float).into();556let wxmm500 = WritableXmm::from_writable_reg(Writable::from_reg(v500)).unwrap();557let pair = PairedXmm {558read: xmm400,559write: wxmm500,560};561assert_eq!(pair.to_string(None), "(%v500 <- %v400)");562}563}564565566