cranelift_codegen/isa/x64/inst/
external.rs

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