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