1use std::collections::BTreeSet;
4use std::collections::VecDeque;
5use std::fmt;
6use std::hash::Hash;
7
8use fxhash::{FxHashMap, FxHashSet};
9
10mod live_interval;
11mod parallel_copy;
12mod stack_color;
13
14pub use live_interval::{
15 LiveInterval, LiveIntervalError, LiveIntervals, LiveSegment, analyze_live_intervals,
16};
17pub use parallel_copy::{
18 CopyDestination, CopyOperation, CopyResolution, CopyResolutionError, CopyResolutionWork,
19 CopySource, ParallelCopy, resolve_parallel_copies,
20};
21pub use stack_color::{StackColorError, StackSlotColoring, color_stack_slots};
22
23pub trait MachineRegister: Copy + Eq + Hash + Ord + fmt::Debug {
25 fn index(self) -> u8;
27}
28
29#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
31pub struct RegisterSet(u64);
32
33impl RegisterSet {
34 pub const fn new() -> Self {
35 Self(0)
36 }
37
38 pub fn insert<R: MachineRegister>(&mut self, register: R) {
39 self.0 |= register_bit(register);
40 }
41
42 pub fn remove<R: MachineRegister>(&mut self, register: R) {
43 self.0 &= !register_bit(register);
44 }
45
46 pub fn contains<R: MachineRegister>(&self, register: &R) -> bool {
47 self.0 & register_bit(*register) != 0
48 }
49
50 pub const fn is_empty(self) -> bool {
51 self.0 == 0
52 }
53}
54
55fn register_bit<R: MachineRegister>(register: R) -> u64 {
56 1_u64
57 .checked_shl(u32::from(register.index()))
58 .expect("physical register index must be below 64")
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum RegConstraint<R> {
64 Any,
65 Fixed(R),
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum ValueLocation<R> {
71 Register(R),
72 Stack(i32),
73 Immediate(u64),
74}
75
76#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct InstructionConstraints<V, R> {
79 pub fixed_uses: Vec<(V, R)>,
80 pub fixed_defs: Vec<(V, R)>,
81 pub clobbers: Vec<R>,
82}
83
84impl<V, R> Default for InstructionConstraints<V, R> {
85 fn default() -> Self {
86 Self {
87 fixed_uses: Vec::new(),
88 fixed_defs: Vec::new(),
89 clobbers: Vec::new(),
90 }
91 }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct InstructionAllocationFacts<V, R> {
101 pub uses: Vec<V>,
102 pub defs: Vec<V>,
103 pub constraints: InstructionConstraints<V, R>,
104 pub is_copy: bool,
106}
107
108impl<V, R> Default for InstructionAllocationFacts<V, R> {
109 fn default() -> Self {
110 Self {
111 uses: Vec::new(),
112 defs: Vec::new(),
113 constraints: InstructionConstraints::default(),
114 is_copy: false,
115 }
116 }
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub struct PhiSource<V> {
123 pub predecessor: usize,
124 pub value: V,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
129pub struct PhiAllocationFacts<V> {
130 pub destination: V,
131 pub sources: Vec<PhiSource<V>>,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
136pub struct BlockAllocationFacts<V, R> {
137 pub successors: Vec<usize>,
138 pub phis: Vec<PhiAllocationFacts<V>>,
139 pub instructions: Vec<InstructionAllocationFacts<V, R>>,
140}
141
142#[derive(Debug, Clone, PartialEq, Eq)]
148pub struct FunctionAllocationFacts<V, R> {
149 pub entry: usize,
150 pub blocks: Vec<BlockAllocationFacts<V, R>>,
151}
152
153#[derive(Debug, Clone, PartialEq, Eq)]
155pub enum AllocationFactsError<V> {
156 MissingEntry {
157 entry: usize,
158 block_count: usize,
159 },
160 MissingSuccessor {
161 block: usize,
162 successor: usize,
163 block_count: usize,
164 },
165 MissingPhiPredecessor {
166 block: usize,
167 predecessor: usize,
168 value: V,
169 },
170 FixedUseIsNotUse {
171 block: usize,
172 instruction: usize,
173 value: V,
174 },
175 FixedDefIsNotDef {
176 block: usize,
177 instruction: usize,
178 value: V,
179 },
180}
181
182impl<V> fmt::Display for AllocationFactsError<V>
183where
184 V: fmt::Debug,
185{
186 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
187 match self {
188 Self::MissingEntry { entry, block_count } => write!(
189 formatter,
190 "allocation entry block {entry} is outside {block_count} blocks"
191 ),
192 Self::MissingSuccessor {
193 block,
194 successor,
195 block_count,
196 } => write!(
197 formatter,
198 "allocation block {block} has successor {successor} outside {block_count} blocks"
199 ),
200 Self::MissingPhiPredecessor {
201 block,
202 predecessor,
203 value,
204 } => write!(
205 formatter,
206 "allocation phi for {value:?} in block {block} names non-predecessor block {predecessor}"
207 ),
208 Self::FixedUseIsNotUse {
209 block,
210 instruction,
211 value,
212 } => write!(
213 formatter,
214 "fixed-use value {value:?} is not a use of allocation block {block} instruction {instruction}"
215 ),
216 Self::FixedDefIsNotDef {
217 block,
218 instruction,
219 value,
220 } => write!(
221 formatter,
222 "fixed-def value {value:?} is not a definition of allocation block {block} instruction {instruction}"
223 ),
224 }
225 }
226}
227
228impl<V> std::error::Error for AllocationFactsError<V> where V: fmt::Debug {}
229
230impl<V, R> FunctionAllocationFacts<V, R>
231where
232 V: Copy + Eq + fmt::Debug,
233{
234 pub fn verify(&self) -> Result<(), AllocationFactsError<V>> {
237 let block_count = self.blocks.len();
238 if self.entry >= block_count {
239 return Err(AllocationFactsError::MissingEntry {
240 entry: self.entry,
241 block_count,
242 });
243 }
244
245 let mut predecessors = vec![Vec::new(); block_count];
246 for (block_index, block) in self.blocks.iter().enumerate() {
247 for &successor in &block.successors {
248 let Some(successor_predecessors) = predecessors.get_mut(successor) else {
249 return Err(AllocationFactsError::MissingSuccessor {
250 block: block_index,
251 successor,
252 block_count,
253 });
254 };
255 successor_predecessors.push(block_index);
256 }
257 }
258
259 for (block_index, block) in self.blocks.iter().enumerate() {
260 for phi in &block.phis {
261 for source in &phi.sources {
262 if !predecessors[block_index].contains(&source.predecessor) {
263 return Err(AllocationFactsError::MissingPhiPredecessor {
264 block: block_index,
265 predecessor: source.predecessor,
266 value: source.value,
267 });
268 }
269 }
270 }
271 for (instruction_index, instruction) in block.instructions.iter().enumerate() {
272 for &(value, _) in &instruction.constraints.fixed_uses {
273 if !instruction.uses.contains(&value) {
274 return Err(AllocationFactsError::FixedUseIsNotUse {
275 block: block_index,
276 instruction: instruction_index,
277 value,
278 });
279 }
280 }
281 for &(value, _) in &instruction.constraints.fixed_defs {
282 if !instruction.defs.contains(&value) {
283 return Err(AllocationFactsError::FixedDefIsNotDef {
284 block: block_index,
285 instruction: instruction_index,
286 value,
287 });
288 }
289 }
290 }
291 }
292 Ok(())
293 }
294}
295
296#[derive(Debug, Clone, PartialEq, Eq)]
301pub struct NextUseDistances<V> {
302 entries: Vec<(V, u32)>,
303}
304
305impl<V> Default for NextUseDistances<V> {
306 fn default() -> Self {
307 Self {
308 entries: Vec::new(),
309 }
310 }
311}
312
313impl<V: Ord> NextUseDistances<V> {
314 fn from_min_entries(mut entries: Vec<(V, u32)>) -> Self {
315 entries.sort_unstable_by(|left, right| left.0.cmp(&right.0));
316 let mut merged: Vec<(V, u32)> = Vec::with_capacity(entries.len());
317 for (value, distance) in entries {
318 if let Some((previous_value, previous_distance)) = merged.last_mut()
319 && *previous_value == value
320 {
321 *previous_distance = (*previous_distance).min(distance);
322 } else {
323 merged.push((value, distance));
324 }
325 }
326 Self { entries: merged }
327 }
328
329 pub fn get(&self, value: &V) -> Option<&u32> {
330 self.entries
331 .binary_search_by(|(candidate, _)| candidate.cmp(value))
332 .ok()
333 .map(|index| &self.entries[index].1)
334 }
335
336 pub fn contains_key(&self, value: &V) -> bool {
337 self.get(value).is_some()
338 }
339
340 pub fn len(&self) -> usize {
341 self.entries.len()
342 }
343
344 pub fn is_empty(&self) -> bool {
345 self.entries.is_empty()
346 }
347
348 pub fn iter(&self) -> NextUseDistanceIter<'_, V> {
349 NextUseDistanceIter(self.entries.iter())
350 }
351
352 pub fn keys(&self) -> NextUseDistanceKeys<'_, V> {
353 NextUseDistanceKeys(self.entries.iter())
354 }
355}
356
357impl<V: Ord> std::ops::Index<&V> for NextUseDistances<V> {
358 type Output = u32;
359
360 fn index(&self, value: &V) -> &Self::Output {
361 self.get(value).expect("next-use distance key must exist")
362 }
363}
364
365pub struct NextUseDistanceIter<'a, V>(std::slice::Iter<'a, (V, u32)>);
366
367impl<'a, V> Iterator for NextUseDistanceIter<'a, V> {
368 type Item = (&'a V, &'a u32);
369
370 fn next(&mut self) -> Option<Self::Item> {
371 self.0.next().map(|(value, distance)| (value, distance))
372 }
373}
374
375pub struct NextUseDistanceKeys<'a, V>(std::slice::Iter<'a, (V, u32)>);
376
377impl<'a, V> Iterator for NextUseDistanceKeys<'a, V> {
378 type Item = &'a V;
379
380 fn next(&mut self) -> Option<Self::Item> {
381 self.0.next().map(|(value, _)| value)
382 }
383}
384
385impl<'a, V: Ord> IntoIterator for &'a NextUseDistances<V> {
386 type Item = (&'a V, &'a u32);
387 type IntoIter = NextUseDistanceIter<'a, V>;
388
389 fn into_iter(self) -> Self::IntoIter {
390 self.iter()
391 }
392}
393
394#[derive(Debug, Clone)]
395pub struct NextUseAnalysis<V> {
396 pub entry_distances: Vec<NextUseDistances<V>>,
397 pub exit_distances: Vec<NextUseDistances<V>>,
398 pub predecessors: Vec<Vec<usize>>,
399 pub backedge_successors: Vec<Vec<usize>>,
400}
401
402const LOOP_EXIT_LENGTH: u32 = 100_000;
406
407pub fn analyze_next_uses<V, R>(
409 facts: &FunctionAllocationFacts<V, R>,
410) -> Result<NextUseAnalysis<V>, AllocationFactsError<V>>
411where
412 V: Copy + Eq + Hash + Ord + fmt::Debug,
413{
414 facts.verify()?;
415 let block_count = facts.blocks.len();
416 let successors = facts
417 .blocks
418 .iter()
419 .map(|block| block.successors.clone())
420 .collect::<Vec<_>>();
421 let mut predecessors = vec![Vec::new(); block_count];
422 for (block, block_successors) in successors.iter().enumerate() {
423 for &successor in block_successors {
424 predecessors[successor].push(block);
425 }
426 }
427 let backedge_successors = compute_backedge_successors(&successors);
428 let backedge_edges = successors
429 .iter()
430 .enumerate()
431 .map(|(block, block_successors)| {
432 block_successors
433 .iter()
434 .map(|successor| backedge_successors[block].contains(successor))
435 .collect::<Vec<_>>()
436 })
437 .collect::<Vec<_>>();
438 let phi_edge_uses = compute_phi_edge_uses(facts, &successors);
439 let transfers = compute_block_transfers(facts);
440
441 let mut entry_distances = vec![NextUseDistances::default(); block_count];
442 let mut exit_distances = vec![NextUseDistances::default(); block_count];
443 let mut worklist = (0..block_count).rev().collect::<VecDeque<_>>();
444 let mut in_worklist = vec![true; block_count];
445 while let Some(block) = worklist.pop_front() {
446 in_worklist[block] = false;
447 let (new_entry, new_exit) = compute_block_distances(
448 block,
449 &successors,
450 &backedge_edges,
451 &phi_edge_uses,
452 &transfers,
453 &entry_distances,
454 );
455 if new_entry != entry_distances[block] || new_exit != exit_distances[block] {
456 entry_distances[block] = new_entry;
457 exit_distances[block] = new_exit;
458 for &predecessor in &predecessors[block] {
459 if !in_worklist[predecessor] {
460 worklist.push_back(predecessor);
461 in_worklist[predecessor] = true;
462 }
463 }
464 }
465 }
466
467 Ok(NextUseAnalysis {
468 entry_distances,
469 exit_distances,
470 predecessors,
471 backedge_successors,
472 })
473}
474
475struct BlockTransfer<V> {
476 block_len: u32,
477 defs: FxHashSet<V>,
478 local_uses: Vec<(V, u32)>,
479}
480
481fn compute_block_transfers<V, R>(facts: &FunctionAllocationFacts<V, R>) -> Vec<BlockTransfer<V>>
482where
483 V: Copy + Eq + Hash + Ord,
484{
485 facts
486 .blocks
487 .iter()
488 .map(|block| {
489 let mut defs = FxHashSet::default();
490 defs.reserve(block.phis.len() + block.instructions.len());
491 defs.extend(block.phis.iter().map(|phi| phi.destination));
492 let mut local_uses = Vec::new();
493 for (instruction_index, instruction) in block.instructions.iter().enumerate() {
494 for &definition in &instruction.defs {
495 defs.insert(definition);
496 }
497 let position = instruction_index as u32;
498 for &used in &instruction.uses {
499 if !defs.contains(&used) {
500 local_uses.push((used, position));
501 }
502 }
503 }
504 local_uses.sort_unstable_by_key(|(value, position)| (*value, *position));
505 local_uses.dedup_by_key(|(value, _)| *value);
506 BlockTransfer {
507 block_len: block.instructions.len() as u32,
508 defs,
509 local_uses,
510 }
511 })
512 .collect()
513}
514
515fn compute_phi_edge_uses<V, R>(
516 facts: &FunctionAllocationFacts<V, R>,
517 successors: &[Vec<usize>],
518) -> Vec<Vec<Vec<V>>>
519where
520 V: Copy,
521{
522 successors
523 .iter()
524 .enumerate()
525 .map(|(predecessor, block_successors)| {
526 block_successors
527 .iter()
528 .map(|&successor| {
529 facts.blocks[successor]
530 .phis
531 .iter()
532 .flat_map(|phi| {
533 phi.sources
534 .iter()
535 .filter(move |source| source.predecessor == predecessor)
536 .map(|source| source.value)
537 })
538 .collect()
539 })
540 .collect()
541 })
542 .collect()
543}
544
545fn compute_block_distances<V>(
546 block: usize,
547 successors: &[Vec<usize>],
548 backedge_edges: &[Vec<bool>],
549 phi_edge_uses: &[Vec<Vec<V>>],
550 transfers: &[BlockTransfer<V>],
551 entry_distances: &[NextUseDistances<V>],
552) -> (NextUseDistances<V>, NextUseDistances<V>)
553where
554 V: Copy + Hash + Ord,
555{
556 let transfer = &transfers[block];
557 let exit_capacity = successors[block]
558 .iter()
559 .map(|&successor| entry_distances[successor].len())
560 .sum::<usize>()
561 + phi_edge_uses[block].iter().map(Vec::len).sum::<usize>();
562 let mut new_exit_entries = Vec::with_capacity(exit_capacity);
563 for (edge, &successor) in successors[block].iter().enumerate() {
564 let edge_length = if backedge_edges[block][edge] {
565 LOOP_EXIT_LENGTH
566 } else {
567 0
568 };
569 for (&value, &distance) in &entry_distances[successor] {
570 let distance = distance.saturating_add(edge_length);
571 new_exit_entries.push((value, distance));
572 }
573 for &value in &phi_edge_uses[block][edge] {
574 new_exit_entries.push((value, edge_length));
575 }
576 }
577 let new_exit = NextUseDistances::from_min_entries(new_exit_entries);
578
579 let mut new_entry_entries = Vec::with_capacity(new_exit.len() + transfer.local_uses.len());
580 for (&value, &distance) in &new_exit {
581 if !transfer.defs.contains(&value) {
582 new_entry_entries.push((value, transfer.block_len.saturating_add(distance)));
583 }
584 }
585 new_entry_entries.extend(transfer.local_uses.iter().copied());
586 let new_entry = NextUseDistances::from_min_entries(new_entry_entries);
587 (new_entry, new_exit)
588}
589
590fn compute_backedge_successors(successors: &[Vec<usize>]) -> Vec<Vec<usize>> {
591 #[derive(Clone, Copy, PartialEq, Eq)]
592 enum Color {
593 White,
594 Gray,
595 Black,
596 }
597
598 let mut colors = vec![Color::White; successors.len()];
599 let mut backedges = vec![Vec::new(); successors.len()];
600 for root in 0..successors.len() {
601 if colors[root] != Color::White {
602 continue;
603 }
604 colors[root] = Color::Gray;
605 let mut stack = vec![(root, 0usize)];
606 while let Some((node, next_successor)) = stack.last_mut() {
607 if *next_successor == successors[*node].len() {
608 colors[*node] = Color::Black;
609 stack.pop();
610 continue;
611 }
612 let successor = successors[*node][*next_successor];
613 *next_successor += 1;
614 match colors[successor] {
615 Color::White => {
616 colors[successor] = Color::Gray;
617 stack.push((successor, 0));
618 }
619 Color::Gray => backedges[*node].push(successor),
620 Color::Black => {}
621 }
622 }
623 }
624 backedges
625}
626
627#[derive(Debug, Clone, Copy, PartialEq, Eq)]
629pub struct LiveRange<V> {
630 pub value: V,
631 pub start: u32,
632 pub end: u32,
633}
634
635#[derive(Debug, Clone, PartialEq, Eq)]
637pub struct Allocation<V: Eq + Hash, R> {
638 assignments: FxHashMap<V, R>,
639}
640
641impl<V: Eq + Hash, R: Copy> Allocation<V, R> {
642 pub fn get(&self, value: V) -> Option<R> {
643 self.assignments.get(&value).copied()
644 }
645
646 pub fn iter(&self) -> impl Iterator<Item = (&V, &R)> {
647 self.assignments.iter()
648 }
649}
650
651#[derive(Debug, Clone, PartialEq, Eq)]
653pub enum LinearScanError<V> {
654 EmptyRegisterFile,
655 DuplicateValue(V),
656 InvalidRange(LiveRange<V>),
657 RegisterPressure { value: V, point: u32 },
658}
659
660impl<V: fmt::Debug> fmt::Display for LinearScanError<V> {
661 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
662 match self {
663 Self::EmptyRegisterFile => formatter.write_str("target has no allocatable registers"),
664 Self::DuplicateValue(value) => write!(formatter, "duplicate live range for {value:?}"),
665 Self::InvalidRange(range) => write!(
666 formatter,
667 "invalid live range {:?}: {}..{}",
668 range.value, range.start, range.end
669 ),
670 Self::RegisterPressure { value, point } => write!(
671 formatter,
672 "no register available for {value:?} at program point {point}"
673 ),
674 }
675 }
676}
677
678impl<V: fmt::Debug> std::error::Error for LinearScanError<V> {}
679
680pub fn allocate_linear_scan<V, R>(
686 ranges: &[LiveRange<V>],
687 allocatable: &[R],
688) -> Result<Allocation<V, R>, LinearScanError<V>>
689where
690 V: Copy + Eq + Hash + Ord + fmt::Debug,
691 R: MachineRegister,
692{
693 if allocatable.is_empty() {
694 return Err(LinearScanError::EmptyRegisterFile);
695 }
696
697 let mut ordered = ranges.to_vec();
698 ordered.sort_unstable_by_key(|range| (range.start, range.end, range.value));
699 let mut seen = BTreeSet::new();
700 for range in &ordered {
701 if range.start > range.end {
702 return Err(LinearScanError::InvalidRange(*range));
703 }
704 if !seen.insert(range.value) {
705 return Err(LinearScanError::DuplicateValue(range.value));
706 }
707 }
708
709 let mut active = Vec::<(u32, V, R)>::new();
710 let mut assignments = FxHashMap::with_capacity_and_hasher(ordered.len(), Default::default());
711 for range in ordered {
712 active.retain(|(end, _, _)| *end >= range.start);
713 let register = allocatable
714 .iter()
715 .copied()
716 .find(|candidate| active.iter().all(|(_, _, used)| used != candidate))
717 .ok_or(LinearScanError::RegisterPressure {
718 value: range.value,
719 point: range.start,
720 })?;
721 assignments.insert(range.value, register);
722 active.push((range.end, range.value, register));
723 active.sort_unstable_by_key(|(end, value, register)| (*end, *value, register.index()));
724 }
725
726 Ok(Allocation { assignments })
727}
728
729#[cfg(test)]
730mod tests {
731 use super::*;
732
733 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
734 struct Reg(u8);
735
736 impl MachineRegister for Reg {
737 fn index(self) -> u8 {
738 self.0
739 }
740 }
741
742 #[test]
743 fn register_set_supports_registers_above_x86s_range() {
744 let mut set = RegisterSet::new();
745 set.insert(Reg(30));
746 assert!(set.contains(&Reg(30)));
747 set.remove(Reg(30));
748 assert!(set.is_empty());
749 }
750
751 #[test]
752 fn linear_scan_reuses_register_after_last_use() {
753 let ranges = [
754 LiveRange {
755 value: 0,
756 start: 0,
757 end: 1,
758 },
759 LiveRange {
760 value: 1,
761 start: 2,
762 end: 3,
763 },
764 ];
765 let allocation = allocate_linear_scan(&ranges, &[Reg(9)]).unwrap();
766 assert_eq!(allocation.get(0), Some(Reg(9)));
767 assert_eq!(allocation.get(1), Some(Reg(9)));
768 }
769
770 #[test]
771 fn linear_scan_reports_pressure_without_hidden_spills() {
772 let ranges = [
773 LiveRange {
774 value: 0,
775 start: 0,
776 end: 2,
777 },
778 LiveRange {
779 value: 1,
780 start: 1,
781 end: 2,
782 },
783 ];
784 assert_eq!(
785 allocate_linear_scan(&ranges, &[Reg(9)]),
786 Err(LinearScanError::RegisterPressure { value: 1, point: 1 })
787 );
788 }
789
790 #[test]
791 fn allocation_facts_verify_without_knowing_target_opcodes() {
792 let facts = FunctionAllocationFacts {
793 entry: 0,
794 blocks: vec![
795 BlockAllocationFacts {
796 successors: vec![1],
797 phis: Vec::new(),
798 instructions: vec![InstructionAllocationFacts {
799 uses: vec![0],
800 defs: vec![1],
801 constraints: InstructionConstraints {
802 fixed_uses: vec![(0, Reg(3))],
803 fixed_defs: vec![(1, Reg(4))],
804 clobbers: vec![Reg(5)],
805 },
806 is_copy: false,
807 }],
808 },
809 BlockAllocationFacts {
810 successors: Vec::new(),
811 phis: vec![PhiAllocationFacts {
812 destination: 2,
813 sources: vec![PhiSource {
814 predecessor: 0,
815 value: 1,
816 }],
817 }],
818 instructions: Vec::new(),
819 },
820 ],
821 };
822
823 assert_eq!(facts.verify(), Ok(()));
824 let analysis = analyze_next_uses(&facts).unwrap();
825 assert_eq!(analysis.exit_distances[0].get(&1), Some(&0));
826 assert_eq!(analysis.entry_distances[0].get(&0), Some(&0));
827 }
828
829 #[test]
830 fn allocation_facts_reject_constraints_detached_from_operands() {
831 let facts = FunctionAllocationFacts {
832 entry: 0,
833 blocks: vec![BlockAllocationFacts {
834 successors: Vec::new(),
835 phis: Vec::new(),
836 instructions: vec![InstructionAllocationFacts {
837 uses: vec![0],
838 defs: Vec::new(),
839 constraints: InstructionConstraints {
840 fixed_uses: vec![(1, Reg(3))],
841 ..InstructionConstraints::default()
842 },
843 is_copy: false,
844 }],
845 }],
846 };
847
848 assert_eq!(
849 facts.verify(),
850 Err(AllocationFactsError::FixedUseIsNotUse {
851 block: 0,
852 instruction: 0,
853 value: 1,
854 })
855 );
856 }
857}