backend_lang/moves.rs
1//! The parallel-move resolver: turns a parallel copy into a sequence of
2//! moves (and swaps) that a machine can execute one at a time.
3//!
4//! A parallel copy `d0, d1, ... := s0, s1, ...` reads every source before it
5//! writes any destination. Control-flow edges with block arguments, register
6//! allocation (moving values between registers and spill slots at block
7//! boundaries), and call lowering (placing arguments in ABI registers) all
8//! produce them, and all three share this resolver. Getting it wrong is a
9//! silent miscompile: a loop back edge `jump head(b, a)` that swaps its two
10//! parameters turns into `a ← b; b ← a`, and both end up holding `b`
11//! (codegen-lang's H01).
12//!
13//! # Algorithm
14//!
15//! Locations (registers and stack slots) are numbered densely by sorting the
16//! moves' locations, then the moves form a graph in which every location has
17//! at most one writer (destinations are distinct) but any number of readers
18//! (one source may feed several destinations). Such a graph is a set of
19//! trees, each hanging off at most one cycle.
20//!
21//! 1. A move is **ready** when no pending move still reads its destination.
22//! Ready moves are emitted; emitting one can make the move that writes its
23//! source ready. This drains every tree, leaves first.
24//! 2. What remains is disjoint simple cycles. Each is broken in the cheapest
25//! of three ways:
26//! - **Alias:** if a value on the cycle was already copied to a tree
27//! destination (a fan-out), that destination holds it and will not
28//! change, so the cycle reads it from there: no extra move at all.
29//! - **Swap:** if every location on the cycle is a register and the
30//! target can exchange two registers (x86-64 `xchg`), a cycle of `k`
31//! registers takes `k - 1` swaps.
32//! - **Scratch:** otherwise one value is saved to a scratch location (a
33//! free register of its class, else the caller's scratch stack slot),
34//! the cycle drains as a chain, and the saved value lands last: `k + 1`
35//! moves.
36//!
37//! The result is minimal in the number of instructions under the rules
38//! above: every destination is written once, a cycle costs nothing extra
39//! when it can be aliased, `k - 1` swaps when it can be swapped, and one
40//! extra move otherwise.
41//!
42//! Then every move is made legal for the target: a memory-to-memory move,
43//! or a store of an immediate the target cannot store directly, goes through
44//! a temporary register: a free register of the class if the caller named
45//! one, else a **borrowed** register, saved to the caller's victim slot,
46//! used, and restored. Borrowing is always sound because the borrowed
47//! register is restored and the move it serves touches only memory.
48//!
49//! Numbering costs `O(n log n)` for `n` moves; everything after it is
50//! linear. Buffers live in the [`MoveResolver`] and are reused, so a
51//! resolver that has seen its largest copy allocates nothing more.
52
53use alloc::vec::Vec;
54use core::fmt;
55
56use crate::mir::StackSlot;
57use crate::regs::{PReg, RegClass, RegSet};
58use crate::target::Target;
59
60/// A place a value lives: a physical register or a stack slot.
61///
62/// # Examples
63///
64/// ```
65/// use backend_lang::mir::StackSlot;
66/// use backend_lang::moves::Loc;
67/// use backend_lang::x86_64::RAX;
68///
69/// assert_eq!(Loc::from(RAX), Loc::Reg(RAX));
70/// assert_eq!(Loc::from(StackSlot::new(1)), Loc::Slot(StackSlot::new(1)));
71/// ```
72#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
73pub enum Loc {
74 /// A physical register.
75 Reg(PReg),
76 /// A whole stack slot (sized for the move's class).
77 Slot(StackSlot),
78}
79
80impl Loc {
81 fn key(self) -> u64 {
82 match self {
83 Loc::Reg(r) => r.index() as u64,
84 Loc::Slot(s) => (1 << 32) | s.index() as u64,
85 }
86 }
87
88 /// Whether this is a stack slot.
89 ///
90 /// # Examples
91 ///
92 /// ```
93 /// use backend_lang::mir::StackSlot;
94 /// use backend_lang::moves::Loc;
95 ///
96 /// assert!(Loc::Slot(StackSlot::new(0)).is_mem());
97 /// ```
98 #[inline]
99 #[must_use]
100 pub const fn is_mem(self) -> bool {
101 matches!(self, Loc::Slot(_))
102 }
103}
104
105impl From<PReg> for Loc {
106 #[inline]
107 fn from(r: PReg) -> Loc {
108 Loc::Reg(r)
109 }
110}
111
112impl From<StackSlot> for Loc {
113 #[inline]
114 fn from(s: StackSlot) -> Loc {
115 Loc::Slot(s)
116 }
117}
118
119/// Where a moved value comes from: a location or an immediate.
120///
121/// # Examples
122///
123/// ```
124/// use backend_lang::moves::{Loc, Source};
125/// use backend_lang::x86_64::RCX;
126///
127/// assert_eq!(Source::from(RCX), Source::Loc(Loc::Reg(RCX)));
128/// assert_eq!(Source::from(7), Source::Imm(7));
129/// ```
130#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
131pub enum Source {
132 /// The value in a location.
133 Loc(Loc),
134 /// A constant.
135 Imm(i64),
136}
137
138impl From<Loc> for Source {
139 #[inline]
140 fn from(l: Loc) -> Source {
141 Source::Loc(l)
142 }
143}
144
145impl From<PReg> for Source {
146 #[inline]
147 fn from(r: PReg) -> Source {
148 Source::Loc(Loc::Reg(r))
149 }
150}
151
152impl From<StackSlot> for Source {
153 #[inline]
154 fn from(s: StackSlot) -> Source {
155 Source::Loc(Loc::Slot(s))
156 }
157}
158
159impl From<i64> for Source {
160 #[inline]
161 fn from(v: i64) -> Source {
162 Source::Imm(v)
163 }
164}
165
166/// One copy of a parallel copy: `dst := src`, of register class `class`.
167///
168/// # Examples
169///
170/// ```
171/// use backend_lang::moves::Move;
172/// use backend_lang::x86_64::{GPR, RAX, RBX};
173///
174/// let m = Move::new(RAX, RBX, GPR);
175/// assert_eq!(m.class, GPR);
176/// ```
177#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
178pub struct Move {
179 /// The destination.
180 pub dst: Loc,
181 /// The source.
182 pub src: Source,
183 /// The class of the value moved, which picks scratch and temporary
184 /// registers and the width of the move.
185 pub class: RegClass,
186}
187
188impl Move {
189 /// `dst := src` of `class`.
190 ///
191 /// # Examples
192 ///
193 /// ```
194 /// use backend_lang::mir::StackSlot;
195 /// use backend_lang::moves::{Loc, Move, Source};
196 /// use backend_lang::x86_64::{GPR, RSI};
197 ///
198 /// let spill = Move::new(StackSlot::new(0), RSI, GPR);
199 /// assert_eq!(spill.dst, Loc::Slot(StackSlot::new(0)));
200 /// assert_eq!(spill.src, Source::from(RSI));
201 /// ```
202 #[must_use]
203 pub fn new(dst: impl Into<Loc>, src: impl Into<Source>, class: RegClass) -> Move {
204 Move {
205 dst: dst.into(),
206 src: src.into(),
207 class,
208 }
209 }
210}
211
212/// One step of the resolved sequence, each executable by one target
213/// instruction.
214///
215/// # Examples
216///
217/// ```
218/// use backend_lang::moves::{Move, MoveEnv, MoveResolver, Step};
219/// use backend_lang::x86_64::{self, GPR, RAX, RBX};
220///
221/// // Swap two registers: one xchg on x86-64.
222/// let swap = [Move::new(RAX, RBX, GPR), Move::new(RBX, RAX, GPR)];
223/// let mut out = Vec::new();
224/// MoveResolver::new().resolve(&x86_64::TARGET, &swap, &MoveEnv::new(), &mut out)?;
225/// assert_eq!(out, [Step::Swap { a: RAX, b: RBX, class: GPR }]);
226/// # Ok::<(), backend_lang::moves::MoveError>(())
227/// ```
228#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
229pub enum Step {
230 /// `dst := src`. Never memory to memory on a target that cannot, and
231 /// never a store of an immediate the target cannot store.
232 Move {
233 /// The destination.
234 dst: Loc,
235 /// The source.
236 src: Source,
237 /// The class of the value.
238 class: RegClass,
239 },
240 /// Exchange two registers of `class`.
241 Swap {
242 /// One register.
243 a: PReg,
244 /// The other.
245 b: PReg,
246 /// Their class.
247 class: RegClass,
248 },
249}
250
251/// What the resolver may use besides the moves' own locations.
252///
253/// # Examples
254///
255/// ```
256/// use backend_lang::RegSet;
257/// use backend_lang::mir::StackSlot;
258/// use backend_lang::moves::MoveEnv;
259/// use backend_lang::x86_64::R11;
260///
261/// let env = MoveEnv::new()
262/// .with_free(RegSet::of(&[R11]))
263/// .with_scratch_slot(StackSlot::new(4))
264/// .with_victim_slot(StackSlot::new(5));
265/// assert!(env.free.contains(R11));
266/// ```
267#[derive(Clone, Copy, Debug, PartialEq, Eq)]
268pub struct MoveEnv {
269 /// Registers the caller guarantees are dead here and may be overwritten.
270 /// Registers that are a source or destination of a move are ignored.
271 pub free: RegSet,
272 /// A stack slot (of the largest class's spill size) for a cycle's saved
273 /// value when no free register of its class exists.
274 pub scratch_slot: Option<StackSlot>,
275 /// A stack slot to save a borrowed register in, for a memory-to-memory
276 /// move when no free register exists.
277 pub victim_slot: Option<StackSlot>,
278 /// Whether register cycles may be resolved with swaps (honoured only on
279 /// targets that have them). On by default.
280 pub swap: bool,
281}
282
283impl Default for MoveEnv {
284 fn default() -> MoveEnv {
285 MoveEnv::new()
286 }
287}
288
289impl MoveEnv {
290 /// No free registers, no slots, swaps allowed.
291 ///
292 /// # Examples
293 ///
294 /// ```
295 /// use backend_lang::moves::MoveEnv;
296 ///
297 /// assert!(MoveEnv::new().free.is_empty());
298 /// assert!(MoveEnv::new().swap);
299 /// ```
300 #[must_use]
301 pub const fn new() -> MoveEnv {
302 MoveEnv {
303 free: RegSet::EMPTY,
304 scratch_slot: None,
305 victim_slot: None,
306 swap: true,
307 }
308 }
309
310 /// With `free` as the free registers.
311 ///
312 /// # Examples
313 ///
314 /// ```
315 /// use backend_lang::RegSet;
316 /// use backend_lang::moves::MoveEnv;
317 /// use backend_lang::x86_64::R10;
318 ///
319 /// assert!(MoveEnv::new().with_free(RegSet::of(&[R10])).free.contains(R10));
320 /// ```
321 #[must_use]
322 pub const fn with_free(self, free: RegSet) -> MoveEnv {
323 MoveEnv { free, ..self }
324 }
325
326 /// With `slot` as the scratch slot.
327 ///
328 /// # Examples
329 ///
330 /// ```
331 /// use backend_lang::mir::StackSlot;
332 /// use backend_lang::moves::MoveEnv;
333 ///
334 /// let env = MoveEnv::new().with_scratch_slot(StackSlot::new(0));
335 /// assert_eq!(env.scratch_slot, Some(StackSlot::new(0)));
336 /// ```
337 #[must_use]
338 pub const fn with_scratch_slot(self, slot: StackSlot) -> MoveEnv {
339 MoveEnv {
340 scratch_slot: Some(slot),
341 ..self
342 }
343 }
344
345 /// With `slot` as the victim slot.
346 ///
347 /// # Examples
348 ///
349 /// ```
350 /// use backend_lang::mir::StackSlot;
351 /// use backend_lang::moves::MoveEnv;
352 ///
353 /// let env = MoveEnv::new().with_victim_slot(StackSlot::new(1));
354 /// assert_eq!(env.victim_slot, Some(StackSlot::new(1)));
355 /// ```
356 #[must_use]
357 pub const fn with_victim_slot(self, slot: StackSlot) -> MoveEnv {
358 MoveEnv {
359 victim_slot: Some(slot),
360 ..self
361 }
362 }
363
364 /// With swaps allowed or not.
365 ///
366 /// # Examples
367 ///
368 /// ```
369 /// use backend_lang::moves::MoveEnv;
370 ///
371 /// assert!(!MoveEnv::new().with_swap(false).swap);
372 /// ```
373 #[must_use]
374 pub const fn with_swap(self, swap: bool) -> MoveEnv {
375 MoveEnv { swap, ..self }
376 }
377}
378
379/// Why a parallel copy could not be resolved.
380///
381/// # Examples
382///
383/// ```
384/// use backend_lang::moves::{Move, MoveEnv, MoveError, MoveResolver};
385/// use backend_lang::x86_64::{self, GPR, RAX, RBX};
386///
387/// let twice = [Move::new(RAX, RBX, GPR), Move::new(RAX, 1, GPR)];
388/// let err = MoveResolver::new()
389/// .resolve(&x86_64::TARGET, &twice, &MoveEnv::new(), &mut Vec::new())
390/// .unwrap_err();
391/// assert_eq!(err, MoveError::DuplicateDestination { index: 1 });
392/// ```
393#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
394#[non_exhaustive]
395pub enum MoveError {
396 /// Two moves write the same location; a parallel copy's destinations are
397 /// distinct. `index` is the later of the two.
398 DuplicateDestination {
399 /// The move.
400 index: usize,
401 },
402 /// A register in move `index` is not in the move's class (or the target
403 /// has no such class or register).
404 ClassMismatch {
405 /// The move.
406 index: usize,
407 },
408 /// A cycle of class `class` needs a scratch location: no free register
409 /// of the class, no swap, and no scratch slot was provided.
410 NoScratch {
411 /// The class.
412 class: RegClass,
413 },
414 /// A memory-to-memory move (or an immediate store) of class `class`
415 /// needs a temporary register: no free register of the class, and no
416 /// victim slot to borrow one with (or the class has no register to
417 /// borrow).
418 NoTemp {
419 /// The class.
420 class: RegClass,
421 },
422 /// More than `u32::MAX - 2` moves in one parallel copy.
423 TooMany,
424}
425
426impl fmt::Display for MoveError {
427 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428 match self {
429 MoveError::DuplicateDestination { index } => write!(
430 f,
431 "move {index} writes a destination an earlier move already writes"
432 ),
433 MoveError::ClassMismatch { index } => {
434 write!(f, "move {index} names a register outside its class")
435 }
436 MoveError::NoScratch { class } => write!(
437 f,
438 "a cycle of class {} needs a free register or a scratch slot",
439 class.index()
440 ),
441 MoveError::NoTemp { class } => write!(
442 f,
443 "a memory-to-memory move of class {} needs a free register or a victim slot",
444 class.index()
445 ),
446 MoveError::TooMany => f.write_str("too many moves in one parallel copy"),
447 }
448 }
449}
450
451impl core::error::Error for MoveError {}
452
453/// What a resolution used beyond the moves' own locations.
454///
455/// # Examples
456///
457/// ```
458/// use backend_lang::mir::StackSlot;
459/// use backend_lang::moves::{Move, MoveEnv, MoveResolver};
460/// use backend_lang::x86_64::{self, GPR};
461///
462/// let (a, b) = (StackSlot::new(0), StackSlot::new(1));
463/// let swap = [Move::new(a, b, GPR), Move::new(b, a, GPR)];
464/// let env = MoveEnv::new().with_scratch_slot(StackSlot::new(2)).with_victim_slot(StackSlot::new(3));
465/// let used = MoveResolver::new().resolve(&x86_64::TARGET, &swap, &env, &mut Vec::new())?;
466/// assert!(used.scratch_slot && used.victim_slot);
467/// # Ok::<(), backend_lang::moves::MoveError>(())
468/// ```
469#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
470#[non_exhaustive]
471pub struct Resolved {
472 /// Steps appended.
473 pub steps: usize,
474 /// Whether the scratch slot was used.
475 pub scratch_slot: bool,
476 /// Whether the victim slot was used.
477 pub victim_slot: bool,
478}
479
480const NONE: u32 = u32::MAX;
481/// A move's source is the scratch location.
482const SCRATCH: u32 = u32::MAX - 1;
483/// A move's source is its immediate.
484const IMM: u32 = u32::MAX - 2;
485
486/// Resolves parallel copies. Keeps its buffers between calls.
487///
488/// # Examples
489///
490/// A rotation of three registers and a register into a slot, with a free
491/// register to break the cycle when swaps are off:
492///
493/// ```
494/// use backend_lang::RegSet;
495/// use backend_lang::mir::StackSlot;
496/// use backend_lang::moves::{Loc, Move, MoveEnv, MoveResolver, Source, Step};
497/// use backend_lang::x86_64::{self, GPR, RAX, RBX, RCX, R11};
498///
499/// let moves = [
500/// Move::new(RAX, RBX, GPR),
501/// Move::new(RBX, RCX, GPR),
502/// Move::new(RCX, RAX, GPR),
503/// Move::new(StackSlot::new(0), RAX, GPR),
504/// ];
505/// let env = MoveEnv::new().with_free(RegSet::of(&[R11])).with_swap(false);
506/// let mut steps = Vec::new();
507/// MoveResolver::new().resolve(&x86_64::TARGET, &moves, &env, &mut steps)?;
508/// // The slot copy of rax doubles as the cycle's saved value: four moves.
509/// assert_eq!(steps.len(), 4);
510/// assert_eq!(steps[0], Step::Move { dst: Loc::Slot(StackSlot::new(0)), src: Source::from(RAX), class: GPR });
511/// # Ok::<(), backend_lang::moves::MoveError>(())
512/// ```
513#[derive(Clone, Debug, Default)]
514pub struct MoveResolver {
515 keys: Vec<(u64, u64)>,
516 locs: Vec<Loc>,
517 writer: Vec<u32>,
518 readers: Vec<u32>,
519 alias: Vec<u32>,
520 dst: Vec<u32>,
521 src: Vec<u32>,
522 imm: Vec<i64>,
523 class: Vec<RegClass>,
524 origin: Vec<u32>,
525 ready: Vec<u32>,
526 cycle: Vec<u32>,
527}
528
529/// The state of one resolution that the legalizer needs.
530struct Legal<'a> {
531 target: &'a Target,
532 env: &'a MoveEnv,
533 /// Registers touched by the moves; never used as free registers.
534 involved: RegSet,
535 /// The scratch location while it holds a cycle's value.
536 scratch: Option<Loc>,
537 used: Resolved,
538}
539
540impl Legal<'_> {
541 /// A free register of `class`, not involved and not the busy scratch.
542 fn free_reg(&self, class: RegClass) -> Option<PReg> {
543 let mut free = self
544 .env
545 .free
546 .intersect(self.target.class(class).regs())
547 .difference(self.involved);
548 if let Some(Loc::Reg(r)) = self.scratch {
549 free = free.without(r);
550 }
551 free.first()
552 }
553
554 fn push(&mut self, out: &mut Vec<Step>, step: Step) {
555 out.push(step);
556 self.used.steps += 1;
557 }
558
559 /// Emits `dst := src` legally for the target.
560 fn emit(
561 &mut self,
562 out: &mut Vec<Step>,
563 dst: Loc,
564 src: Source,
565 class: RegClass,
566 ) -> Result<(), MoveError> {
567 let caps = self.target.move_caps();
568 let needs_temp = dst.is_mem()
569 && match src {
570 Source::Loc(l) => l.is_mem() && !caps.mem_to_mem(),
571 Source::Imm(v) => !caps.store_imm().fits(v),
572 };
573 if !needs_temp {
574 self.push(out, Step::Move { dst, src, class });
575 return Ok(());
576 }
577 if let Some(t) = self.free_reg(class) {
578 self.push(
579 out,
580 Step::Move {
581 dst: Loc::Reg(t),
582 src,
583 class,
584 },
585 );
586 self.push(
587 out,
588 Step::Move {
589 dst,
590 src: Source::Loc(Loc::Reg(t)),
591 class,
592 },
593 );
594 return Ok(());
595 }
596 // Borrow a register: any allocatable one but the busy scratch. Its
597 // value is saved and restored around a move that touches only
598 // memory, so it is preserved whatever it held.
599 let slot = self.env.victim_slot.ok_or(MoveError::NoTemp { class })?;
600 let mut pool = self.target.class(class).allocatable();
601 if let Some(Loc::Reg(r)) = self.scratch {
602 pool = pool.without(r);
603 }
604 let v = pool.first().ok_or(MoveError::NoTemp { class })?;
605 let (vl, sl) = (Loc::Reg(v), Loc::Slot(slot));
606 self.used.victim_slot = true;
607 self.push(
608 out,
609 Step::Move {
610 dst: sl,
611 src: Source::Loc(vl),
612 class,
613 },
614 );
615 self.push(
616 out,
617 Step::Move {
618 dst: vl,
619 src,
620 class,
621 },
622 );
623 self.push(
624 out,
625 Step::Move {
626 dst,
627 src: Source::Loc(vl),
628 class,
629 },
630 );
631 self.push(
632 out,
633 Step::Move {
634 dst: vl,
635 src: Source::Loc(sl),
636 class,
637 },
638 );
639 Ok(())
640 }
641}
642
643impl MoveResolver {
644 /// A resolver with empty buffers.
645 ///
646 /// # Examples
647 ///
648 /// ```
649 /// use backend_lang::moves::MoveResolver;
650 ///
651 /// let _r = MoveResolver::new();
652 /// ```
653 #[must_use]
654 pub fn new() -> MoveResolver {
655 MoveResolver::default()
656 }
657
658 /// Resolves the parallel copy `moves` on `target`, appending the steps to
659 /// `out`.
660 ///
661 /// Moves whose source is their destination are dropped. Executing the
662 /// appended steps in order leaves every destination holding its source's
663 /// value from before the copy, and every other location unchanged except
664 /// `env.free` registers (which are dead) and the scratch and victim
665 /// slots.
666 ///
667 /// # Errors
668 ///
669 /// [`MoveError::DuplicateDestination`], [`MoveError::ClassMismatch`],
670 /// [`MoveError::NoScratch`] or [`MoveError::NoTemp`] when a scratch or
671 /// temporary is needed and `env` provides none, [`MoveError::TooMany`].
672 /// On error, `out` may hold a partial sequence; discard it.
673 ///
674 /// # Examples
675 ///
676 /// ```
677 /// use backend_lang::mir::StackSlot;
678 /// use backend_lang::moves::{Loc, Move, MoveEnv, MoveResolver, Source, Step};
679 /// use backend_lang::x86_64::{self, GPR, RAX, R11};
680 /// use backend_lang::RegSet;
681 ///
682 /// // Slot to slot on x86-64 goes through a register.
683 /// let copy = [Move::new(StackSlot::new(1), StackSlot::new(0), GPR)];
684 /// let env = MoveEnv::new().with_free(RegSet::of(&[R11]));
685 /// let mut out = Vec::new();
686 /// MoveResolver::new().resolve(&x86_64::TARGET, ©, &env, &mut out)?;
687 /// assert_eq!(out, [
688 /// Step::Move { dst: Loc::Reg(R11), src: Source::from(StackSlot::new(0)), class: GPR },
689 /// Step::Move { dst: Loc::Slot(StackSlot::new(1)), src: Source::from(R11), class: GPR },
690 /// ]);
691 /// # Ok::<(), backend_lang::moves::MoveError>(())
692 /// ```
693 pub fn resolve(
694 &mut self,
695 target: &Target,
696 moves: &[Move],
697 env: &MoveEnv,
698 out: &mut Vec<Step>,
699 ) -> Result<Resolved, MoveError> {
700 if moves.len() > (u32::MAX - 3) as usize {
701 return Err(MoveError::TooMany);
702 }
703 let involved = self.number(target, moves)?;
704 let mut legal = Legal {
705 target,
706 env,
707 involved,
708 scratch: None,
709 used: Resolved::default(),
710 };
711 let count = self.dst.len();
712 // Seed in reverse so the stack pops in input order: a copy with no
713 // conflicts comes out exactly as written.
714 self.ready.clear();
715 for m in (0..count).rev() {
716 if self.readers[self.dst[m] as usize] == 0 {
717 self.ready.push(m as u32);
718 }
719 }
720 let mut cursor = 0usize;
721 loop {
722 self.drain(&mut legal, out)?;
723 while cursor < count && self.writer[self.dst[cursor] as usize] != cursor as u32 {
724 cursor += 1;
725 }
726 if cursor == count {
727 break;
728 }
729 self.break_cycle(cursor as u32, &mut legal, out)?;
730 }
731 Ok(legal.used)
732 }
733
734 /// Numbers the locations of the non-trivial moves and builds the graph.
735 /// Returns the registers involved.
736 fn number(&mut self, target: &Target, moves: &[Move]) -> Result<RegSet, MoveError> {
737 self.keys.clear();
738 self.dst.clear();
739 self.src.clear();
740 self.imm.clear();
741 self.class.clear();
742 self.origin.clear();
743 let mut involved = RegSet::EMPTY;
744 for (i, m) in moves.iter().enumerate() {
745 if !target.has_class(m.class) {
746 return Err(MoveError::ClassMismatch { index: i });
747 }
748 let regs = target.class(m.class).regs();
749 let in_class = |l: Loc| match l {
750 Loc::Reg(r) => target.has_reg(r) && regs.contains(r),
751 Loc::Slot(_) => true,
752 };
753 if !in_class(m.dst) || matches!(m.src, Source::Loc(l) if !in_class(l)) {
754 return Err(MoveError::ClassMismatch { index: i });
755 }
756 if m.src == Source::Loc(m.dst) {
757 // Still a destination: two writers of it are an error even if
758 // one of them is a no-op. And its value is live, so it is
759 // never a free register.
760 self.keys.push((m.dst.key(), (i as u64) << 1));
761 if let Loc::Reg(r) = m.dst {
762 involved = involved.with(r);
763 }
764 continue;
765 }
766 let k = self.dst.len() as u32;
767 self.keys.push((m.dst.key(), (i as u64) << 1));
768 self.dst.push(NONE);
769 self.class.push(m.class);
770 self.origin.push(i as u32);
771 match m.src {
772 Source::Loc(l) => {
773 self.keys.push((l.key(), (u64::from(k) << 1) | 1));
774 self.src.push(NONE);
775 self.imm.push(0);
776 }
777 Source::Imm(v) => {
778 self.src.push(IMM);
779 self.imm.push(v);
780 }
781 }
782 for l in [
783 Some(m.dst),
784 if let Source::Loc(l) = m.src {
785 Some(l)
786 } else {
787 None
788 },
789 ]
790 .into_iter()
791 .flatten()
792 {
793 if let Loc::Reg(r) = l {
794 involved = involved.with(r);
795 }
796 }
797 }
798 // Destination entries carry the input index (bit 0 clear), source
799 // entries the compact move index (bit 0 set). Sorting groups each
800 // location's entries together; a second destination entry in a group
801 // is a duplicate, and the sort puts the later input second.
802 self.keys.sort_unstable();
803 // Input index -> compact index (`origin` is ascending).
804 let compact_of =
805 |input: u32, origin: &[u32]| -> Option<usize> { origin.binary_search(&input).ok() };
806 self.locs.clear();
807 let mut prev_key = u64::MAX;
808 let mut prev_was_dst = false;
809 for &(key, tag) in &self.keys {
810 if key != prev_key || self.locs.is_empty() {
811 prev_key = key;
812 prev_was_dst = false;
813 self.locs.push(if key >> 32 == 0 {
814 Loc::Reg(PReg::from_index(key as u8))
815 } else {
816 Loc::Slot(StackSlot::from_index((key & 0xFFFF_FFFF) as usize))
817 });
818 }
819 let id = (self.locs.len() - 1) as u32;
820 if tag & 1 == 0 {
821 let input = (tag >> 1) as u32;
822 if prev_was_dst {
823 return Err(MoveError::DuplicateDestination {
824 index: input as usize,
825 });
826 }
827 prev_was_dst = true;
828 if let Some(m) = compact_of(input, &self.origin) {
829 self.dst[m] = id;
830 }
831 } else {
832 self.src[(tag >> 1) as usize] = id;
833 }
834 }
835 let n = self.locs.len();
836 self.writer.clear();
837 self.writer.resize(n, NONE);
838 self.readers.clear();
839 self.readers.resize(n, 0);
840 self.alias.clear();
841 self.alias.resize(n, NONE);
842 for m in 0..self.dst.len() {
843 self.writer[self.dst[m] as usize] = m as u32;
844 let s = self.src[m];
845 if s < IMM {
846 self.readers[s as usize] += 1;
847 }
848 }
849 Ok(involved)
850 }
851
852 /// Emits every ready move, and every move each emission makes ready.
853 fn drain(&mut self, legal: &mut Legal<'_>, out: &mut Vec<Step>) -> Result<(), MoveError> {
854 while let Some(m) = self.ready.pop() {
855 let m = m as usize;
856 let d = self.dst[m];
857 let class = self.class[m];
858 let src = match self.src[m] {
859 IMM => Source::Imm(self.imm[m]),
860 SCRATCH => match legal.scratch.take() {
861 Some(l) => Source::Loc(l),
862 None => return Err(MoveError::NoScratch { class }),
863 },
864 s => Source::Loc(self.locs[s as usize]),
865 };
866 legal.emit(out, self.locs[d as usize], src, class)?;
867 self.writer[d as usize] = NONE;
868 let s = self.src[m];
869 if s < IMM {
870 // `d` now holds `s`'s original value and nothing will write
871 // it again: remember it, preferring a register copy.
872 let a = &mut self.alias[s as usize];
873 if *a == NONE
874 || (self.locs[*a as usize].is_mem() && !self.locs[d as usize].is_mem())
875 {
876 *a = d;
877 }
878 self.release(s);
879 }
880 }
881 Ok(())
882 }
883
884 /// One pending read of `s` is done; if it was the last, the pending move
885 /// writing `s` becomes ready.
886 fn release(&mut self, s: u32) {
887 let r = &mut self.readers[s as usize];
888 *r -= 1;
889 if *r == 0 {
890 let w = self.writer[s as usize];
891 if w != NONE {
892 self.ready.push(w);
893 }
894 }
895 }
896
897 /// Breaks the cycle through pending move `start`. Every pending move is
898 /// on a cycle here (trees were drained), each location on it has exactly
899 /// one pending reader, and following a move's source to that source's
900 /// writer walks the cycle.
901 fn break_cycle(
902 &mut self,
903 start: u32,
904 legal: &mut Legal<'_>,
905 out: &mut Vec<Step>,
906 ) -> Result<(), MoveError> {
907 self.cycle.clear();
908 let mut m = start;
909 loop {
910 self.cycle.push(m);
911 let s = self.src[m as usize];
912 let next = self.writer.get(s as usize).copied().unwrap_or(NONE);
913 if next == start || next == NONE {
914 break;
915 }
916 m = next;
917 }
918 // Alias: a fan-out copy of a cycle value already sits in a location
919 // nothing will overwrite. Read it from there instead.
920 for &m in &self.cycle {
921 let s = self.src[m as usize];
922 let a = self.alias[s as usize];
923 if a != NONE {
924 self.src[m as usize] = a;
925 self.readers[a as usize] += 1;
926 self.release(s);
927 return Ok(());
928 }
929 }
930 let class = self.class[start as usize];
931 let all_regs_one_class = self.cycle.iter().all(|&m| {
932 !self.locs[self.dst[m as usize] as usize].is_mem() && self.class[m as usize] == class
933 });
934 if all_regs_one_class && legal.env.swap && legal.target.move_caps().swap() {
935 // cycle[i]: D_i := D_{i+1}; swap(D_0, D_1), swap(D_1, D_2), ...
936 for w in 0..self.cycle.len().saturating_sub(1) {
937 let a = self.locs[self.dst[self.cycle[w] as usize] as usize];
938 let b = self.locs[self.dst[self.cycle[w + 1] as usize] as usize];
939 if let (Loc::Reg(a), Loc::Reg(b)) = (a, b) {
940 legal.push(out, Step::Swap { a, b, class });
941 }
942 }
943 for &m in &self.cycle {
944 let d = self.dst[m as usize] as usize;
945 self.writer[d] = NONE;
946 self.readers[d] = 0;
947 }
948 return Ok(());
949 }
950 // Scratch: save one source, prefer a move whose source and
951 // destination are registers (cheapest if the scratch is a slot).
952 let pick = self
953 .cycle
954 .iter()
955 .copied()
956 .find(|&m| {
957 !self.locs[self.src[m as usize] as usize].is_mem()
958 && !self.locs[self.dst[m as usize] as usize].is_mem()
959 })
960 .unwrap_or(start);
961 let class = self.class[pick as usize];
962 let s = self.src[pick as usize];
963 let scratch = match legal.free_reg(class) {
964 Some(r) => Loc::Reg(r),
965 None => {
966 let slot = legal
967 .env
968 .scratch_slot
969 .ok_or(MoveError::NoScratch { class })?;
970 legal.used.scratch_slot = true;
971 Loc::Slot(slot)
972 }
973 };
974 legal.emit(out, scratch, Source::Loc(self.locs[s as usize]), class)?;
975 legal.scratch = Some(scratch);
976 self.src[pick as usize] = SCRATCH;
977 self.release(s);
978 Ok(())
979 }
980}
981
982/// Resolves `moves` with a fresh [`MoveResolver`]: the one-call path.
983///
984/// # Errors
985///
986/// As for [`MoveResolver::resolve`].
987///
988/// # Examples
989///
990/// ```
991/// use backend_lang::moves::{self, Move, MoveEnv, Step};
992/// use backend_lang::x86_64::{self, GPR, RAX, RBX, RCX};
993///
994/// // rax <- rbx <- rcx <- rax: a 3-cycle is two swaps.
995/// let rot = [Move::new(RAX, RBX, GPR), Move::new(RBX, RCX, GPR), Move::new(RCX, RAX, GPR)];
996/// let steps = moves::resolve(&x86_64::TARGET, &rot, &MoveEnv::new())?;
997/// assert_eq!(steps, [
998/// Step::Swap { a: RAX, b: RBX, class: GPR },
999/// Step::Swap { a: RBX, b: RCX, class: GPR },
1000/// ]);
1001/// # Ok::<(), backend_lang::moves::MoveError>(())
1002/// ```
1003pub fn resolve(target: &Target, moves: &[Move], env: &MoveEnv) -> Result<Vec<Step>, MoveError> {
1004 let mut out = Vec::with_capacity(moves.len() + 1);
1005 let _used = MoveResolver::new().resolve(target, moves, env, &mut out)?;
1006 Ok(out)
1007}