cranelift_codegen/isa/x64/inst/
external.rs

1//! Interface with the external assembler crate.
2
3use super::{
4    regs, Amode, Gpr, Inst, LabelUse, MachBuffer, MachLabel, OperandVisitor, OperandVisitorImpl,
5    SyntheticAmode, VCodeConstant, WritableGpr, WritableXmm, Xmm,
6};
7use crate::ir::TrapCode;
8use cranelift_assembler_x64 as asm;
9use std::string::String;
10
11/// Define the types of registers Cranelift will use.
12#[derive(Clone, Debug)]
13pub struct CraneliftRegisters;
14impl asm::Registers for CraneliftRegisters {
15    type ReadGpr = Gpr;
16    type ReadWriteGpr = PairedGpr;
17    type ReadXmm = Xmm;
18    type ReadWriteXmm = PairedXmm;
19}
20
21/// A pair of registers, one for reading and one for writing.
22///
23/// Due to how Cranelift's SSA form, we must track the read and write registers
24/// separately prior to register allocation. Once register allocation is
25/// complete, we expect the hardware encoding for both `read` and `write` to be
26/// the same.
27#[derive(Clone, Copy, Debug)]
28pub struct PairedGpr {
29    pub(crate) read: Gpr,
30    pub(crate) write: WritableGpr,
31}
32
33impl asm::AsReg for PairedGpr {
34    fn enc(&self) -> u8 {
35        let PairedGpr { read, write } = self;
36        let read = enc_gpr(read);
37        let write = enc_gpr(&write.to_reg());
38        assert_eq!(read, write);
39        write
40    }
41
42    fn to_string(&self, size: Option<asm::Size>) -> String {
43        if self.read.is_real() {
44            asm::gpr::enc::to_string(self.enc(), size.unwrap()).into()
45        } else {
46            let read = self.read.to_reg();
47            let write = self.write.to_reg().to_reg();
48            format!("(%{write:?} <- %{read:?})")
49        }
50    }
51
52    fn new(_: u8) -> Self {
53        panic!("disallow creation of new assembler registers")
54    }
55}
56
57/// A pair of XMM registers, one for reading and one for writing.
58#[derive(Clone, Copy, Debug)]
59pub struct PairedXmm {
60    pub(crate) read: Xmm,
61    pub(crate) write: WritableXmm,
62}
63
64impl asm::AsReg for PairedXmm {
65    fn enc(&self) -> u8 {
66        let PairedXmm { read, write } = self;
67        let read = enc_xmm(read);
68        let write = enc_xmm(&write.to_reg());
69        assert_eq!(read, write);
70        write
71    }
72
73    fn to_string(&self, size: Option<asm::Size>) -> String {
74        assert!(size.is_none(), "XMM registers do not have size variants");
75        if self.read.is_real() {
76            asm::xmm::enc::to_string(self.enc()).into()
77        } else {
78            let read = self.read.to_reg();
79            let write = self.write.to_reg().to_reg();
80            format!("(%{write:?} <- %{read:?})")
81        }
82    }
83
84    fn new(_: u8) -> Self {
85        panic!("disallow creation of new assembler registers")
86    }
87}
88
89/// This bridges the gap between codegen and assembler for general purpose register types.
90impl asm::AsReg for Gpr {
91    fn enc(&self) -> u8 {
92        enc_gpr(self)
93    }
94
95    fn to_string(&self, size: Option<asm::Size>) -> String {
96        if self.is_real() {
97            asm::gpr::enc::to_string(self.enc(), size.unwrap()).into()
98        } else {
99            format!("%{:?}", self.to_reg())
100        }
101    }
102
103    fn new(_: u8) -> Self {
104        panic!("disallow creation of new assembler registers")
105    }
106}
107
108/// This bridges the gap between codegen and assembler for xmm register types.
109impl asm::AsReg for Xmm {
110    fn enc(&self) -> u8 {
111        enc_xmm(self)
112    }
113
114    fn to_string(&self, size: Option<asm::Size>) -> String {
115        assert!(size.is_none(), "XMM registers do not have size variants");
116        if self.is_real() {
117            asm::xmm::enc::to_string(self.enc()).into()
118        } else {
119            format!("%{:?}", self.to_reg())
120        }
121    }
122
123    fn new(_: u8) -> Self {
124        panic!("disallow creation of new assembler registers")
125    }
126}
127
128/// A helper method for extracting the hardware encoding of a general purpose register.
129#[inline]
130fn enc_gpr(gpr: &Gpr) -> u8 {
131    if let Some(real) = gpr.to_reg().to_real_reg() {
132        real.hw_enc()
133    } else {
134        unreachable!()
135    }
136}
137
138/// A helper method for extracting the hardware encoding of an xmm register.
139#[inline]
140fn enc_xmm(xmm: &Xmm) -> u8 {
141    if let Some(real) = xmm.to_reg().to_real_reg() {
142        real.hw_enc()
143    } else {
144        unreachable!()
145    }
146}
147
148/// A wrapper to implement the `cranelift-assembler-x64` register allocation trait,
149/// `RegallocVisitor`, in terms of the trait used in Cranelift,
150/// `OperandVisitor`.
151pub(crate) struct RegallocVisitor<'a, T>
152where
153    T: OperandVisitorImpl,
154{
155    pub collector: &'a mut T,
156}
157
158impl<'a, T: OperandVisitor> asm::RegisterVisitor<CraneliftRegisters> for RegallocVisitor<'a, T> {
159    fn read(&mut self, reg: &mut Gpr) {
160        self.collector.reg_use(reg);
161    }
162
163    fn read_write(&mut self, reg: &mut PairedGpr) {
164        let PairedGpr { read, write } = reg;
165        self.collector.reg_use(read);
166        self.collector.reg_reuse_def(write, 0);
167    }
168
169    fn fixed_read(&mut self, _reg: &Gpr) {
170        todo!()
171    }
172
173    fn fixed_read_write(&mut self, _reg: &PairedGpr) {
174        todo!()
175    }
176
177    fn read_xmm(&mut self, reg: &mut Xmm) {
178        self.collector.reg_use(reg);
179    }
180
181    fn read_write_xmm(&mut self, reg: &mut PairedXmm) {
182        let PairedXmm { read, write } = reg;
183        self.collector.reg_use(read);
184        self.collector.reg_reuse_def(write, 0);
185    }
186
187    fn fixed_read_xmm(&mut self, _reg: &Xmm) {
188        todo!()
189    }
190
191    fn fixed_read_write_xmm(&mut self, _reg: &PairedXmm) {
192        todo!()
193    }
194}
195
196impl Into<asm::Amode<Gpr>> for SyntheticAmode {
197    fn into(self) -> asm::Amode<Gpr> {
198        match self {
199            SyntheticAmode::Real(amode) => amode.into(),
200            SyntheticAmode::IncomingArg { offset } => asm::Amode::ImmReg {
201                base: Gpr::unwrap_new(regs::rbp()),
202                simm32: asm::AmodeOffsetPlusKnownOffset {
203                    simm32: (-i32::try_from(offset).unwrap()).into(),
204                    offset: Some(offsets::KEY_INCOMING_ARG),
205                },
206                trap: None,
207            },
208            SyntheticAmode::SlotOffset { simm32 } => asm::Amode::ImmReg {
209                base: Gpr::unwrap_new(regs::rbp()),
210                simm32: asm::AmodeOffsetPlusKnownOffset {
211                    simm32: simm32.into(),
212                    offset: Some(offsets::KEY_SLOT_OFFSET),
213                },
214                trap: None,
215            },
216            SyntheticAmode::ConstantOffset(vcode_constant) => asm::Amode::RipRelative {
217                target: asm::DeferredTarget::Constant(asm::Constant(vcode_constant.as_u32())),
218            },
219        }
220    }
221}
222
223impl Into<asm::Amode<Gpr>> for Amode {
224    fn into(self) -> asm::Amode<Gpr> {
225        match self {
226            Amode::ImmReg {
227                simm32,
228                base,
229                flags,
230            } => asm::Amode::ImmReg {
231                simm32: asm::AmodeOffsetPlusKnownOffset {
232                    simm32: simm32.into(),
233                    offset: None,
234                },
235                base: Gpr::unwrap_new(base),
236                trap: flags.trap_code().map(Into::into),
237            },
238            Amode::ImmRegRegShift {
239                simm32,
240                base,
241                index,
242                shift,
243                flags,
244            } => asm::Amode::ImmRegRegShift {
245                base,
246                index: asm::NonRspGpr::new(index),
247                scale: asm::Scale::new(shift),
248                simm32: simm32.into(),
249                trap: flags.trap_code().map(Into::into),
250            },
251            Amode::RipRelative { target } => asm::Amode::RipRelative {
252                target: asm::DeferredTarget::Label(asm::Label(target.as_u32())),
253            },
254        }
255    }
256}
257
258/// Keep track of the offset slots to fill in during emission; see
259/// `KnownOffsetTable`.
260pub mod offsets {
261    pub const KEY_INCOMING_ARG: usize = 0;
262    pub const KEY_SLOT_OFFSET: usize = 1;
263}
264
265impl asm::CodeSink for MachBuffer<Inst> {
266    fn put1(&mut self, value: u8) {
267        self.put1(value)
268    }
269
270    fn put2(&mut self, value: u16) {
271        self.put2(value)
272    }
273
274    fn put4(&mut self, value: u32) {
275        self.put4(value)
276    }
277
278    fn put8(&mut self, value: u64) {
279        self.put8(value)
280    }
281
282    fn current_offset(&self) -> u32 {
283        self.cur_offset()
284    }
285
286    fn use_label_at_offset(&mut self, offset: u32, label: asm::Label) {
287        self.use_label_at_offset(offset, label.into(), LabelUse::JmpRel32);
288    }
289
290    fn add_trap(&mut self, code: asm::TrapCode) {
291        self.add_trap(code.into());
292    }
293
294    fn get_label_for_constant(&mut self, c: asm::Constant) -> asm::Label {
295        self.get_label_for_constant(c.into()).into()
296    }
297}
298
299impl From<asm::TrapCode> for TrapCode {
300    fn from(value: asm::TrapCode) -> Self {
301        Self::from_raw(value.0)
302    }
303}
304
305impl From<TrapCode> for asm::TrapCode {
306    fn from(value: TrapCode) -> Self {
307        Self(value.as_raw())
308    }
309}
310
311impl From<asm::Label> for MachLabel {
312    fn from(value: asm::Label) -> Self {
313        Self::from_u32(value.0)
314    }
315}
316
317impl From<MachLabel> for asm::Label {
318    fn from(value: MachLabel) -> Self {
319        Self(value.as_u32())
320    }
321}
322
323impl From<asm::Constant> for VCodeConstant {
324    fn from(value: asm::Constant) -> Self {
325        Self::from_u32(value.0)
326    }
327}
328
329// Include code generated by `cranelift-codegen/meta/src/gen_asm.rs`. This file
330// contains a `isle_assembler_methods!` macro with Rust implementations of all
331// the assembler instructions exposed to ISLE.
332include!(concat!(env!("OUT_DIR"), "/assembler-isle-macro.rs"));
333pub(crate) use isle_assembler_methods;
334
335#[cfg(test)]
336mod tests {
337    use super::asm::{AsReg, Size};
338    use super::PairedGpr;
339    use crate::isa::x64::args::{FromWritableReg, Gpr, WritableGpr, WritableXmm, Xmm};
340    use crate::isa::x64::inst::external::PairedXmm;
341    use crate::{Reg, Writable};
342    use regalloc2::{RegClass, VReg};
343
344    #[test]
345    fn pretty_print_registers() {
346        // For logging, we need to be able to pretty-print the virtual registers
347        // that Cranelift uses before register allocation. This test ensures
348        // that these remain printable using the `AsReg::to_string` interface
349        // (see issue #10631).
350
351        let v200: Reg = VReg::new(200, RegClass::Int).into();
352        let gpr200 = Gpr::new(v200).unwrap();
353        assert_eq!(gpr200.to_string(Some(Size::Quadword)), "%v200");
354
355        let v300: Reg = VReg::new(300, RegClass::Int).into();
356        let wgpr300 = WritableGpr::from_writable_reg(Writable::from_reg(v300).into()).unwrap();
357        let pair = PairedGpr {
358            read: gpr200,
359            write: wgpr300,
360        };
361        assert_eq!(pair.to_string(Some(Size::Quadword)), "(%v300 <- %v200)");
362
363        let v400: Reg = VReg::new(400, RegClass::Float).into();
364        let xmm400 = Xmm::new(v400).unwrap();
365        assert_eq!(xmm400.to_string(None), "%v400");
366
367        let v500: Reg = VReg::new(500, RegClass::Float).into();
368        let wxmm500 = WritableXmm::from_writable_reg(Writable::from_reg(v500).into()).unwrap();
369        let pair = PairedXmm {
370            read: xmm400,
371            write: wxmm500,
372        };
373        assert_eq!(pair.to_string(None), "(%v500 <- %v400)");
374    }
375}