Skip to main content

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, &copy, &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}