1use std::collections::{BTreeMap, BTreeSet, VecDeque};
4use std::fmt;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
8pub enum CopyDestination<R> {
9 Register(R),
10 Stack(i32),
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub enum CopySource<R> {
16 Register(R),
17 Stack(i32),
18 Immediate(u64),
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct ParallelCopy<R> {
24 pub destination: CopyDestination<R>,
25 pub source: CopySource<R>,
26}
27
28impl<R: PartialEq> ParallelCopy<R> {
29 pub fn is_identity(&self) -> bool {
30 matches!(
31 (&self.destination, &self.source),
32 (
33 CopyDestination::Register(destination),
34 CopySource::Register(source)
35 ) if destination == source
36 ) || matches!(
37 (&self.destination, &self.source),
38 (CopyDestination::Stack(destination), CopySource::Stack(source))
39 if destination == source
40 )
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum CopyOperation<R> {
47 Move {
48 destination: CopyDestination<R>,
49 source: CopySource<R>,
50 },
51 SwapRegisters {
52 left: R,
53 right: R,
54 },
55 SaveTemporary(CopyDestination<R>),
56 RestoreTemporary(CopyDestination<R>),
57}
58
59#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
61pub struct CopyResolutionWork {
62 pub effective_copies: usize,
63 pub direct_moves: usize,
64 pub register_swaps: usize,
65 pub cycle_breaks: usize,
66 pub temporary_cycle_breaks: usize,
67 pub ready_queue_pops: usize,
68 pub dependency_releases: usize,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct CopyResolution<R> {
74 pub operations: Vec<CopyOperation<R>>,
75 pub work: CopyResolutionWork,
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct CopyResolutionError<R> {
81 pub rule: &'static str,
82 pub destination: Option<CopyDestination<R>>,
83 pub message: String,
84}
85
86impl<R> CopyResolutionError<R> {
87 fn new(rule: &'static str, message: impl Into<String>) -> Self {
88 Self {
89 rule,
90 destination: None,
91 message: message.into(),
92 }
93 }
94
95 fn at(mut self, destination: CopyDestination<R>) -> Self {
96 self.destination = Some(destination);
97 self
98 }
99}
100
101impl<R: fmt::Debug> fmt::Display for CopyResolutionError<R> {
102 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
103 write!(formatter, "parallel copy [{}]", self.rule)?;
104 if let Some(destination) = &self.destination {
105 write!(formatter, " at {destination:?}")?;
106 }
107 write!(formatter, ": {}", self.message)
108 }
109}
110
111impl<R: fmt::Debug> std::error::Error for CopyResolutionError<R> {}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114enum PendingSource<R> {
115 Value(CopySource<R>),
116 Temporary,
117}
118
119#[derive(Debug, Clone, Copy)]
120struct PendingCopy<R> {
121 destination: CopyDestination<R>,
122 source: PendingSource<R>,
123 pending: bool,
124}
125
126fn source_as_destination<R: Copy>(source: CopySource<R>) -> Option<CopyDestination<R>> {
127 match source {
128 CopySource::Register(register) => Some(CopyDestination::Register(register)),
129 CopySource::Stack(slot) => Some(CopyDestination::Stack(slot)),
130 CopySource::Immediate(_) => None,
131 }
132}
133
134fn register_cycle<R>(
135 copies: &[PendingCopy<R>],
136 destination_index: &BTreeMap<CopyDestination<R>, usize>,
137 start: usize,
138) -> Option<(Vec<usize>, Vec<(R, R)>)>
139where
140 R: Copy + Ord,
141{
142 let CopyDestination::Register(start_register) = copies.get(start)?.destination else {
143 return None;
144 };
145 let mut current = start_register;
146 let mut members = Vec::new();
147 let mut swaps = Vec::new();
148 let mut visited = BTreeSet::new();
149 loop {
150 if !visited.insert(current) {
151 return None;
152 }
153 let destination = CopyDestination::Register(current);
154 let &index = destination_index.get(&destination)?;
155 let copy = copies.get(index)?;
156 if !copy.pending {
157 return None;
158 }
159 let PendingSource::Value(CopySource::Register(source)) = copy.source else {
160 return None;
161 };
162 members.push(index);
163 if source == start_register {
164 return (members.len() >= 2).then_some((members, swaps));
165 }
166 swaps.push((current, source));
167 current = source;
168 }
169}
170
171pub fn resolve_parallel_copies<R>(
176 rows: &[ParallelCopy<R>],
177) -> Result<CopyResolution<R>, CopyResolutionError<R>>
178where
179 R: Copy + Ord + fmt::Debug,
180{
181 let mut destination_owner = BTreeSet::new();
182 for row in rows {
183 if !destination_owner.insert(row.destination) {
184 return Err(CopyResolutionError::new(
185 "PARALLEL_COPY.NON_UNIQUE_DESTINATION",
186 "parallel assignment writes one destination more than once",
187 )
188 .at(row.destination));
189 }
190 }
191
192 let mut copies = rows
193 .iter()
194 .filter(|row| !row.is_identity())
195 .map(|row| PendingCopy {
196 destination: row.destination,
197 source: PendingSource::Value(row.source),
198 pending: true,
199 })
200 .collect::<Vec<_>>();
201 let mut work = CopyResolutionWork {
202 effective_copies: copies.len(),
203 ..CopyResolutionWork::default()
204 };
205 if copies.is_empty() {
206 return Ok(CopyResolution {
207 operations: Vec::new(),
208 work,
209 });
210 }
211
212 let destination_index = copies
213 .iter()
214 .enumerate()
215 .map(|(index, copy)| (copy.destination, index))
216 .collect::<BTreeMap<_, _>>();
217 let mut readers = BTreeMap::<CopyDestination<R>, BTreeSet<usize>>::new();
218 for (index, copy) in copies.iter().enumerate() {
219 let PendingSource::Value(source) = copy.source else {
220 continue;
221 };
222 if let Some(location) = source_as_destination(source) {
223 readers.entry(location).or_default().insert(index);
224 }
225 }
226
227 let mut ready = VecDeque::new();
228 let mut queued = vec![false; copies.len()];
229 for (index, copy) in copies.iter().enumerate() {
230 if !readers.contains_key(©.destination) {
231 ready.push_back(index);
232 queued[index] = true;
233 }
234 }
235
236 let mut operations = Vec::with_capacity(copies.len());
237 let mut remaining = copies.len();
238 let mut temporary_live = false;
239 let mut cycle_search_start = 0usize;
240 while remaining != 0 {
241 while let Some(index) = ready.pop_front() {
242 queued[index] = false;
243 if !copies[index].pending {
244 continue;
245 }
246 work.ready_queue_pops += 1;
247 match copies[index].source {
248 PendingSource::Value(source) => {
249 operations.push(CopyOperation::Move {
250 destination: copies[index].destination,
251 source,
252 });
253 work.direct_moves += 1;
254 if let Some(location) = source_as_destination(source) {
255 let released = readers.get_mut(&location).is_some_and(|location_readers| {
256 location_readers.remove(&index);
257 location_readers.is_empty()
258 });
259 if released {
260 readers.remove(&location);
261 work.dependency_releases += 1;
262 if let Some(&writer) = destination_index.get(&location)
263 && copies[writer].pending
264 && !queued[writer]
265 {
266 ready.push_back(writer);
267 queued[writer] = true;
268 }
269 }
270 }
271 }
272 PendingSource::Temporary => {
273 if !temporary_live {
274 return Err(CopyResolutionError::new(
275 "PARALLEL_COPY.TEMPORARY_STATE",
276 "resolver attempted to restore an inactive temporary",
277 )
278 .at(copies[index].destination));
279 }
280 operations.push(CopyOperation::RestoreTemporary(copies[index].destination));
281 temporary_live = false;
282 }
283 }
284 copies[index].pending = false;
285 remaining -= 1;
286 }
287
288 if remaining == 0 {
289 break;
290 }
291 if temporary_live {
292 return Err(CopyResolutionError::new(
293 "PARALLEL_COPY.TEMPORARY_STATE",
294 "resolver stalled while a temporary was live",
295 ));
296 }
297 let Some(cycle) = copies
298 .iter()
299 .enumerate()
300 .skip(cycle_search_start)
301 .find_map(|(index, copy)| copy.pending.then_some(index))
302 else {
303 return Err(CopyResolutionError::new(
304 "PARALLEL_COPY.RESOLVER_STATE",
305 "nonzero pending count has no pending row",
306 ));
307 };
308 cycle_search_start = cycle + 1;
309
310 if let Some((members, swaps)) = register_cycle(&copies, &destination_index, cycle)
311 && members.len() == 2
312 {
313 for (left, right) in swaps.iter().copied() {
314 operations.push(CopyOperation::SwapRegisters { left, right });
315 }
316 for &member in &members {
317 readers.remove(&copies[member].destination);
318 copies[member].pending = false;
319 queued[member] = false;
320 }
321 remaining -= members.len();
322 work.register_swaps += swaps.len();
323 work.cycle_breaks += 1;
324 work.dependency_releases += members.len();
325 continue;
326 }
327
328 let saved = copies[cycle].destination;
329 let saved_readers = readers.remove(&saved).unwrap_or_default();
330 if saved_readers.len() != 1 {
331 return Err(CopyResolutionError::new(
332 "PARALLEL_COPY.CYCLE_SHAPE",
333 format!("stalled cycle location has {} readers", saved_readers.len()),
334 )
335 .at(saved));
336 }
337 let reader = *saved_readers
338 .iter()
339 .next()
340 .expect("one cycle reader was checked above");
341 copies[reader].source = PendingSource::Temporary;
342 operations.push(CopyOperation::SaveTemporary(saved));
343 work.cycle_breaks += 1;
344 work.temporary_cycle_breaks += 1;
345 work.dependency_releases += 1;
346 temporary_live = true;
347 if !queued[cycle] {
348 ready.push_back(cycle);
349 queued[cycle] = true;
350 }
351 }
352
353 if temporary_live {
354 return Err(CopyResolutionError::new(
355 "PARALLEL_COPY.TEMPORARY_STATE",
356 "resolver left its temporary live",
357 ));
358 }
359 Ok(CopyResolution { operations, work })
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365
366 fn register_copy(destination: u8, source: u8) -> ParallelCopy<u8> {
367 ParallelCopy {
368 destination: CopyDestination::Register(destination),
369 source: CopySource::Register(source),
370 }
371 }
372
373 #[test]
374 fn orders_an_acyclic_chain_backwards() {
375 let result = resolve_parallel_copies(&[register_copy(0, 1), register_copy(2, 0)]).unwrap();
376 assert_eq!(
377 result.operations,
378 vec![
379 CopyOperation::Move {
380 destination: CopyDestination::Register(2),
381 source: CopySource::Register(0),
382 },
383 CopyOperation::Move {
384 destination: CopyDestination::Register(0),
385 source: CopySource::Register(1),
386 },
387 ]
388 );
389 }
390
391 #[test]
392 fn swaps_a_two_register_cycle() {
393 let result = resolve_parallel_copies(&[register_copy(0, 1), register_copy(1, 0)]).unwrap();
394 assert_eq!(result.work.register_swaps, 1);
395 assert!(matches!(
396 result.operations.as_slice(),
397 [CopyOperation::SwapRegisters { .. }]
398 ));
399 }
400
401 #[test]
402 fn breaks_a_long_cycle_with_one_temporary() {
403 let result = resolve_parallel_copies(&[
404 register_copy(0, 1),
405 register_copy(1, 2),
406 register_copy(2, 0),
407 ])
408 .unwrap();
409 assert_eq!(result.work.temporary_cycle_breaks, 1);
410 assert!(matches!(
411 result.operations.first(),
412 Some(CopyOperation::SaveTemporary(_))
413 ));
414 assert!(matches!(
415 result.operations.last(),
416 Some(CopyOperation::RestoreTemporary(_))
417 ));
418 }
419
420 #[test]
421 fn rejects_duplicate_destinations_even_when_one_row_is_identity() {
422 let error =
423 resolve_parallel_copies(&[register_copy(0, 0), register_copy(0, 1)]).unwrap_err();
424 assert_eq!(error.rule, "PARALLEL_COPY.NON_UNIQUE_DESTINATION");
425 }
426}