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