Skip to main content

rucc_regalloc/
moves.rs

1//! Putting a set of moves that happen at once into an order they can happen in one at a time.
2//!
3//! Design: `spec/10-backend.md` section 10.4.
4//!
5//! The parameters of a block arrive from every edge into it, and after allocation each of them is
6//! a place and each argument is a place, so an edge becomes a set of moves. They all happen at
7//! once: every argument is read as it was at the end of the predecessor, and nothing an edge
8//! writes is visible to anything else the edge writes. A machine does not have that instruction.
9//! It has one move, and the moves have to go in an order.
10//!
11//! Most of the time any order will do, but not always. Two parameters that swap two values are
12//! two moves that each destroy what the other wants to read, and no order of the two is right.
13//! The way out is a third place to keep one of the values in, which is the scratch, and the
14//! algorithm below is the one that finds out when one is needed and writes as few extra moves as
15//! it can. `spec/10-backend.md` says this is a small algorithm that is wrong in a startling number
16//! of compilers, which is why it is written here on its own and tested on its own rather than
17//! being a loop inside the allocator.
18//!
19//! Nothing here knows what a place is. A place is a register after allocation, or a stack slot
20//! for a value that was spilled, and the algorithm is the same either way, so the caller says what
21//! its places are and gets the same kind of thing back.
22
23/// One move: what is written, and what is read.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct Move<T> {
26    /// The place written.
27    pub to: T,
28    /// The place read.
29    pub from: T,
30}
31
32impl<T> Move<T> {
33    /// A move from one place to another.
34    pub const fn new(to: T, from: T) -> Self {
35        Self { to, from }
36    }
37}
38
39/// The moves in an order they can be made in, one at a time.
40///
41/// The result writes exactly the places the input writes, and every one of them ends up holding
42/// what the input said it should, except that the scratch may hold anything afterwards. A move
43/// from a place to itself is not in the result, because it is nothing to do.
44///
45/// The scratch is written only when a set of moves is a cycle, which is the case no order can
46/// answer on its own. Nothing else in the moves may name it, since it is the one place the
47/// algorithm is free to destroy.
48///
49/// # Panics
50///
51/// Panics if two moves write the same place. That is not a set of parallel moves, it is a
52/// question about which of two values a place ends up holding, and the caller has to answer it
53/// before asking for an order.
54#[must_use]
55pub fn sequence<T: Copy + PartialEq>(moves: &[Move<T>], scratch: T) -> Vec<Move<T>> {
56    let mut pending: Vec<Move<T>> =
57        moves.iter().copied().filter(|one| one.to != one.from).collect();
58    for (index, one) in pending.iter().enumerate() {
59        assert!(
60            !pending[..index].iter().any(|earlier| earlier.to == one.to),
61            "two parallel moves write the same place"
62        );
63    }
64
65    let mut order = Vec::with_capacity(pending.len());
66    while !pending.is_empty() {
67        // A move whose destination nothing else still has to read is one that can go now, and
68        // taking those first is what keeps a chain of moves a chain of moves.
69        let ready = pending.iter().position(|one| {
70            !pending.iter().any(|other| other.from == one.to && other.to != one.to)
71        });
72        match ready {
73            Some(index) => order.push(pending.remove(index)),
74            None => break_cycle(&mut pending, &mut order, scratch),
75        }
76    }
77    order
78}
79
80/// Puts one value of a cycle somewhere safe, which leaves the rest of it an ordinary chain.
81fn break_cycle<T: Copy + PartialEq>(pending: &mut [Move<T>], order: &mut Vec<Move<T>>, scratch: T) {
82    // Every move left is in a cycle, so any of them will do. Reading the first one's source into
83    // the scratch means nothing wants that source any more, so the move that writes it is free to
84    // go, and the move that wanted it reads the scratch instead.
85    let source = pending[0].from;
86    order.push(Move::new(scratch, source));
87    for one in pending.iter_mut().filter(|one| one.from == source) {
88        one.from = scratch;
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    /// Moves between places named by a letter, which is what a test can read.
97    fn moves(pairs: &[(char, char)]) -> Vec<Move<char>> {
98        pairs.iter().map(|&(to, from)| Move::new(to, from)).collect()
99    }
100
101    /// What the places hold after the moves are made in that order, starting from each place
102    /// holding its own name.
103    fn run(order: &[Move<char>]) -> Vec<(char, char)> {
104        let mut held: Vec<(char, char)> = ('a'..='z').map(|place| (place, place)).collect();
105        for one in order {
106            let value = held.iter().find(|&&(place, _)| place == one.from).expect("a place").1;
107            held.iter_mut().find(|(place, _)| *place == one.to).expect("a place").1 = value;
108        }
109        held
110    }
111
112    /// Whether an order leaves every place holding what the parallel moves said it should.
113    fn correct(parallel: &[Move<char>], order: &[Move<char>]) -> bool {
114        let held = run(order);
115        parallel.iter().all(|one| {
116            held.iter().find(|&&(place, _)| place == one.to).expect("a place").1 == one.from
117        })
118    }
119
120    #[test]
121    fn moves_that_get_in_nobody_s_way_are_made_in_any_order() {
122        let parallel = moves(&[('a', 'b'), ('c', 'd')]);
123        let order = sequence(&parallel, 'z');
124        assert_eq!(order.len(), 2);
125        assert!(correct(&parallel, &order));
126    }
127
128    #[test]
129    fn a_chain_is_made_from_the_end_of_it() {
130        // `a` gets what is in `b` and `b` gets what is in `c`, so `b` has to be read before it is
131        // written and no scratch is needed to see that.
132        let parallel = moves(&[('b', 'c'), ('a', 'b')]);
133        let order = sequence(&parallel, 'z');
134        assert_eq!(order, moves(&[('a', 'b'), ('b', 'c')]));
135        assert!(correct(&parallel, &order));
136    }
137
138    #[test]
139    fn two_values_that_swap_need_somewhere_to_put_one_of_them() {
140        let parallel = moves(&[('a', 'b'), ('b', 'a')]);
141        let order = sequence(&parallel, 'z');
142        assert!(correct(&parallel, &order));
143        assert_eq!(order.len(), 3);
144        assert!(order.iter().any(|one| one.to == 'z'));
145    }
146
147    #[test]
148    fn a_longer_cycle_costs_the_same_one_extra_move() {
149        // Three values going round: `a` takes `b`'s, `b` takes `c`'s and `c` takes `a`'s.
150        let parallel = moves(&[('a', 'b'), ('b', 'c'), ('c', 'a')]);
151        let order = sequence(&parallel, 'z');
152        assert!(correct(&parallel, &order));
153        assert_eq!(order.len(), 4);
154    }
155
156    #[test]
157    fn two_cycles_are_broken_one_at_a_time() {
158        let parallel = moves(&[('a', 'b'), ('b', 'a'), ('c', 'd'), ('d', 'c')]);
159        let order = sequence(&parallel, 'z');
160        assert!(correct(&parallel, &order));
161        // The scratch is reused, because it is free again as soon as the first cycle is closed.
162        assert_eq!(order.len(), 6);
163    }
164
165    #[test]
166    fn a_value_wanted_in_two_places_is_read_twice() {
167        let parallel = moves(&[('a', 'c'), ('b', 'c')]);
168        let order = sequence(&parallel, 'z');
169        assert!(correct(&parallel, &order));
170        assert_eq!(order.len(), 2);
171        assert!(!order.iter().any(|one| one.to == 'z'));
172    }
173
174    #[test]
175    fn a_cycle_with_a_tail_hanging_off_it_is_still_one_extra_move() {
176        // `d` also wants what is in `a`, which is not part of the cycle and has to be read before
177        // the cycle overwrites it.
178        let parallel = moves(&[('a', 'b'), ('b', 'a'), ('d', 'a')]);
179        let order = sequence(&parallel, 'z');
180        assert!(correct(&parallel, &order));
181        assert_eq!(order.len(), 4);
182    }
183
184    #[test]
185    fn a_move_from_a_place_to_itself_is_nothing_to_do() {
186        let order = sequence(&moves(&[('a', 'a'), ('b', 'c')]), 'z');
187        assert_eq!(order, moves(&[('b', 'c')]));
188    }
189
190    #[test]
191    fn nothing_to_move_is_nothing_to_do() {
192        assert_eq!(sequence::<char>(&[], 'z'), []);
193    }
194
195    #[test]
196    #[should_panic(expected = "two parallel moves write the same place")]
197    fn two_moves_that_write_one_place_are_not_a_question_this_can_answer() {
198        let _ = sequence(&moves(&[('a', 'b'), ('a', 'c')]), 'z');
199    }
200}