cranelift_codegen/isa/x64/inst/
external.rs

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