/rust/registry/src/index.crates.io-6f17d22bba15001f/cranelift-codegen-0.91.1/src/simple_gvn.rs
Line | Count | Source (jump to first uncovered line) |
1 | | //! A simple GVN pass. |
2 | | |
3 | | use crate::cursor::{Cursor, FuncCursor}; |
4 | | use crate::dominator_tree::DominatorTree; |
5 | | use crate::ir::{Function, Inst, InstructionData, Opcode, Type}; |
6 | | use crate::scoped_hash_map::ScopedHashMap; |
7 | | use crate::timing; |
8 | | use alloc::vec::Vec; |
9 | | use core::cell::{Ref, RefCell}; |
10 | | use core::hash::{Hash, Hasher}; |
11 | | |
12 | | /// Test whether the given opcode is unsafe to even consider for GVN. |
13 | 3.98M | fn trivially_unsafe_for_gvn(opcode: Opcode) -> bool { |
14 | 3.98M | opcode.is_call() |
15 | 3.77M | || opcode.is_branch() |
16 | 3.39M | || opcode.is_terminator() |
17 | 3.06M | || opcode.is_return() |
18 | 3.06M | || opcode.can_trap() |
19 | 3.01M | || opcode.other_side_effects() |
20 | 3.00M | || opcode.can_store() |
21 | 2.95M | || opcode.writes_cpu_flags() |
22 | 3.98M | } |
23 | | |
24 | | /// Test that, if the specified instruction is a load, it doesn't have the `readonly` memflag. |
25 | 2.95M | fn is_load_and_not_readonly(inst_data: &InstructionData) -> bool { |
26 | 2.95M | match *inst_data { |
27 | 454k | InstructionData::Load { flags, .. } => !flags.readonly(), |
28 | 2.49M | _ => inst_data.opcode().can_load(), |
29 | | } |
30 | 2.95M | } |
31 | | |
32 | | /// Wrapper around `InstructionData` which implements `Eq` and `Hash` |
33 | | #[derive(Clone)] |
34 | | struct HashKey<'a, 'f: 'a> { |
35 | | inst: InstructionData, |
36 | | ty: Type, |
37 | | pos: &'a RefCell<FuncCursor<'f>>, |
38 | | } |
39 | | impl<'a, 'f: 'a> Hash for HashKey<'a, 'f> { |
40 | 4.44M | fn hash<H: Hasher>(&self, state: &mut H) { |
41 | 4.44M | let pool = &self.pos.borrow().func.dfg.value_lists; |
42 | 4.44M | self.inst.hash(state, pool); |
43 | 4.44M | self.ty.hash(state); |
44 | 4.44M | } |
45 | | } |
46 | | impl<'a, 'f: 'a> PartialEq for HashKey<'a, 'f> { |
47 | 1.30M | fn eq(&self, other: &Self) -> bool { |
48 | 1.30M | let pool = &self.pos.borrow().func.dfg.value_lists; |
49 | 1.30M | self.inst.eq(&other.inst, pool) && self.ty == other.ty |
50 | 1.30M | } |
51 | | } |
52 | | impl<'a, 'f: 'a> Eq for HashKey<'a, 'f> {} |
53 | | |
54 | | /// Perform simple GVN on `func`. |
55 | | /// |
56 | 278k | pub fn do_simple_gvn(func: &mut Function, domtree: &mut DominatorTree) { |
57 | 278k | let _tt = timing::gvn(); |
58 | 278k | debug_assert!(domtree.is_valid()); |
59 | | |
60 | | // Visit blocks in a reverse post-order. |
61 | | // |
62 | | // The RefCell here is a bit ugly since the HashKeys in the ScopedHashMap |
63 | | // need a reference to the function. |
64 | 278k | let pos = RefCell::new(FuncCursor::new(func)); |
65 | 278k | |
66 | 278k | let mut visible_values: ScopedHashMap<HashKey, Inst> = ScopedHashMap::new(); |
67 | 278k | let mut scope_stack: Vec<Inst> = Vec::new(); |
68 | | |
69 | 656k | for &block in domtree.cfg_postorder().iter().rev() { |
70 | | { |
71 | | // Pop any scopes that we just exited. |
72 | 656k | let layout = &pos.borrow().func.layout; |
73 | | loop { |
74 | 792k | if let Some(current) = scope_stack.last() { |
75 | 513k | if domtree.dominates(*current, block, layout) { |
76 | 377k | break; |
77 | 136k | } |
78 | | } else { |
79 | 278k | break; |
80 | | } |
81 | 136k | scope_stack.pop(); |
82 | 136k | visible_values.decrement_depth(); |
83 | | } |
84 | | |
85 | | // Push a scope for the current block. |
86 | 656k | scope_stack.push(layout.first_inst(block).unwrap()); |
87 | 656k | visible_values.increment_depth(); |
88 | 656k | } |
89 | 656k | |
90 | 656k | pos.borrow_mut().goto_top(block); |
91 | 4.64M | while let Some(inst) = { |
92 | 4.64M | let mut pos = pos.borrow_mut(); |
93 | 4.64M | pos.next_inst() |
94 | 4.64M | } { |
95 | | // Resolve aliases, particularly aliases we created earlier. |
96 | 3.98M | pos.borrow_mut().func.dfg.resolve_aliases_in_arguments(inst); |
97 | 3.98M | |
98 | 3.98M | let func = Ref::map(pos.borrow(), |pos| &pos.func); |
99 | 3.98M | |
100 | 3.98M | let opcode = func.dfg[inst].opcode(); |
101 | 3.98M | |
102 | 3.98M | if opcode.is_branch() && !opcode.is_terminator() { |
103 | 51.1k | scope_stack.push(func.layout.next_inst(inst).unwrap()); |
104 | 51.1k | visible_values.increment_depth(); |
105 | 3.93M | } |
106 | | |
107 | 3.98M | if trivially_unsafe_for_gvn(opcode) { |
108 | 1.03M | continue; |
109 | 2.95M | } |
110 | 2.95M | |
111 | 2.95M | // These are split up to separate concerns. |
112 | 2.95M | if is_load_and_not_readonly(&func.dfg[inst]) { |
113 | 345k | continue; |
114 | 2.60M | } |
115 | 2.60M | |
116 | 2.60M | let ctrl_typevar = func.dfg.ctrl_typevar(inst); |
117 | 2.60M | let key = HashKey { |
118 | 2.60M | inst: func.dfg[inst], |
119 | 2.60M | ty: ctrl_typevar, |
120 | 2.60M | pos: &pos, |
121 | 2.60M | }; |
122 | 2.60M | use crate::scoped_hash_map::Entry::*; |
123 | 2.60M | match visible_values.entry(key) { |
124 | 1.20M | Occupied(entry) => { |
125 | 1.20M | #[allow(clippy::debug_assert_with_mut_call)] |
126 | 1.20M | { |
127 | 1.20M | // Clippy incorrectly believes `&func.layout` should not be used here: |
128 | 1.20M | // https://github.com/rust-lang/rust-clippy/issues/4737 |
129 | 1.20M | debug_assert!(domtree.dominates(*entry.get(), inst, &func.layout)); |
130 | | } |
131 | | |
132 | | // If the redundant instruction is representing the current |
133 | | // scope, pick a new representative. |
134 | 1.20M | let old = scope_stack.last_mut().unwrap(); |
135 | 1.20M | if *old == inst { |
136 | 51.6k | *old = func.layout.next_inst(inst).unwrap(); |
137 | 1.14M | } |
138 | | // Replace the redundant instruction and remove it. |
139 | 1.20M | drop(func); |
140 | 1.20M | let mut pos = pos.borrow_mut(); |
141 | 1.20M | pos.func.dfg.replace_with_aliases(inst, *entry.get()); |
142 | 1.20M | pos.remove_inst_and_step_back(); |
143 | | } |
144 | 1.40M | Vacant(entry) => { |
145 | 1.40M | entry.insert(inst); |
146 | 1.40M | } |
147 | | } |
148 | | } |
149 | | } |
150 | 278k | } |