1 //! Instruction predicates/properties, shared by various analyses.
2 use crate::ir::immediates::Offset32;
3 use crate::ir::{self, Block, Function, Inst, InstructionData, Opcode, Type, Value};
4
5 /// Test whether the given opcode is unsafe to even consider as side-effect-free.
6 #[inline(always)]
trivially_has_side_effects(opcode: Opcode) -> bool7 fn trivially_has_side_effects(opcode: Opcode) -> bool {
8 opcode.is_call()
9 || opcode.is_branch()
10 || opcode.is_terminator()
11 || opcode.is_return()
12 || opcode.can_trap()
13 || opcode.other_side_effects()
14 || opcode.can_store()
15 }
16
17 /// Load instructions without the `notrap` flag are defined to trap when
18 /// operating on inaccessible memory, so we can't treat them as side-effect-free even if the loaded
19 /// value is unused.
20 #[inline(always)]
is_load_with_defined_trapping(opcode: Opcode, data: &InstructionData) -> bool21 fn is_load_with_defined_trapping(opcode: Opcode, data: &InstructionData) -> bool {
22 if !opcode.can_load() {
23 return false;
24 }
25 match *data {
26 InstructionData::StackLoad { .. } => false,
27 InstructionData::Load { flags, .. } => !flags.notrap(),
28 _ => true,
29 }
30 }
31
32 /// Does the given instruction have any side-effect that would preclude it from being removed when
33 /// its value is unused?
34 #[inline(always)]
has_side_effect(func: &Function, inst: Inst) -> bool35 fn has_side_effect(func: &Function, inst: Inst) -> bool {
36 let data = &func.dfg.insts[inst];
37 let opcode = data.opcode();
38 trivially_has_side_effects(opcode) || is_load_with_defined_trapping(opcode, data)
39 }
40
41 /// Does the given instruction behave as a "pure" node with respect to
42 /// aegraph semantics?
43 ///
44 /// - Trivially pure nodes (bitwise arithmetic, etc)
45 /// - Loads with the `readonly`, `notrap`, and `can_move` flags set
is_pure_for_egraph(func: &Function, inst: Inst) -> bool46 pub fn is_pure_for_egraph(func: &Function, inst: Inst) -> bool {
47 let is_pure_load = match func.dfg.insts[inst] {
48 InstructionData::Load {
49 opcode: Opcode::Load,
50 flags,
51 ..
52 } => flags.readonly() && flags.notrap() && flags.can_move(),
53 _ => false,
54 };
55
56 // Multi-value results do not play nicely with much of the egraph
57 // infrastructure. They are in practice used only for multi-return
58 // calls and some other odd instructions (e.g. uadd_overflow) which,
59 // for now, we can afford to leave in place as opaque
60 // side-effecting ops. So if more than one result, then the inst
61 // is "not pure". Similarly, ops with zero results can be used
62 // only for their side-effects, so are never pure. (Or if they
63 // are, we can always trivially eliminate them with no effect.)
64 let has_one_result = func.dfg.inst_results(inst).len() == 1;
65
66 let op = func.dfg.insts[inst].opcode();
67
68 has_one_result && (is_pure_load || (!op.can_load() && !trivially_has_side_effects(op)))
69 }
70
71 /// Can the given instruction be merged into another copy of itself?
72 /// These instructions may have side-effects, but as long as we retain
73 /// the first instance of the instruction, the second and further
74 /// instances are redundant if they would produce the same trap or
75 /// result.
is_mergeable_for_egraph(func: &Function, inst: Inst) -> bool76 pub fn is_mergeable_for_egraph(func: &Function, inst: Inst) -> bool {
77 let op = func.dfg.insts[inst].opcode();
78 // We can only merge zero- and one-result operators due to the way that GVN
79 // is structured in the egraph implementation.
80 func.dfg.inst_results(inst).len() <= 1
81 // Loads/stores are handled by alias analysis and not
82 // otherwise mergeable.
83 && !op.can_load()
84 && !op.can_store()
85 // Can only have idempotent side-effects.
86 && (!has_side_effect(func, inst) || op.side_effects_idempotent())
87 }
88
89 /// Does the given instruction have any side-effect as per [has_side_effect], or else is a load,
90 /// but not the get_pinned_reg opcode?
has_lowering_side_effect(func: &Function, inst: Inst) -> bool91 pub fn has_lowering_side_effect(func: &Function, inst: Inst) -> bool {
92 let op = func.dfg.insts[inst].opcode();
93 op != Opcode::GetPinnedReg && (has_side_effect(func, inst) || op.can_load())
94 }
95
96 /// Is the given instruction a constant value (`iconst`, `fconst`) that can be
97 /// represented in 64 bits?
is_constant_64bit(func: &Function, inst: Inst) -> Option<u64>98 pub fn is_constant_64bit(func: &Function, inst: Inst) -> Option<u64> {
99 match &func.dfg.insts[inst] {
100 &InstructionData::UnaryImm { imm, .. } => Some(imm.bits() as u64),
101 &InstructionData::UnaryIeee16 { imm, .. } => Some(imm.bits() as u64),
102 &InstructionData::UnaryIeee32 { imm, .. } => Some(imm.bits() as u64),
103 &InstructionData::UnaryIeee64 { imm, .. } => Some(imm.bits()),
104 _ => None,
105 }
106 }
107
108 /// Get the address, offset, and access type from the given instruction, if any.
inst_addr_offset_type(func: &Function, inst: Inst) -> Option<(Value, Offset32, Type)>109 pub fn inst_addr_offset_type(func: &Function, inst: Inst) -> Option<(Value, Offset32, Type)> {
110 match &func.dfg.insts[inst] {
111 InstructionData::Load { arg, offset, .. } => {
112 let ty = func.dfg.value_type(func.dfg.inst_results(inst)[0]);
113 Some((*arg, *offset, ty))
114 }
115 InstructionData::LoadNoOffset { arg, .. } => {
116 let ty = func.dfg.value_type(func.dfg.inst_results(inst)[0]);
117 Some((*arg, 0.into(), ty))
118 }
119 InstructionData::Store { args, offset, .. } => {
120 let ty = func.dfg.value_type(args[0]);
121 Some((args[1], *offset, ty))
122 }
123 InstructionData::StoreNoOffset { args, .. } => {
124 let ty = func.dfg.value_type(args[0]);
125 Some((args[1], 0.into(), ty))
126 }
127 _ => None,
128 }
129 }
130
131 /// Get the store data, if any, from an instruction.
inst_store_data(func: &Function, inst: Inst) -> Option<Value>132 pub fn inst_store_data(func: &Function, inst: Inst) -> Option<Value> {
133 match &func.dfg.insts[inst] {
134 InstructionData::Store { args, .. } | InstructionData::StoreNoOffset { args, .. } => {
135 Some(args[0])
136 }
137 _ => None,
138 }
139 }
140
141 /// Determine whether this opcode behaves as a memory fence, i.e.,
142 /// prohibits any moving of memory accesses across it.
has_memory_fence_semantics(op: Opcode) -> bool143 pub fn has_memory_fence_semantics(op: Opcode) -> bool {
144 match op {
145 Opcode::AtomicRmw
146 | Opcode::AtomicCas
147 | Opcode::AtomicLoad
148 | Opcode::AtomicStore
149 | Opcode::Fence
150 | Opcode::Debugtrap
151 | Opcode::SequencePoint => true,
152 Opcode::Call | Opcode::CallIndirect | Opcode::TryCall | Opcode::TryCallIndirect => true,
153 op if op.can_trap() => true,
154 _ => false,
155 }
156 }
157
158 /// Visit all successors of a block with a given visitor closure. The closure
159 /// arguments are the branch instruction that is used to reach the successor,
160 /// the successor block itself, and a flag indicating whether the block is
161 /// branched to via a table entry.
visit_block_succs<F: FnMut(Inst, Block, bool)>( f: &Function, block: Block, mut visit: F, )162 pub(crate) fn visit_block_succs<F: FnMut(Inst, Block, bool)>(
163 f: &Function,
164 block: Block,
165 mut visit: F,
166 ) {
167 if let Some(inst) = f.layout.last_inst(block) {
168 match &f.dfg.insts[inst] {
169 ir::InstructionData::Jump {
170 destination: dest, ..
171 } => {
172 visit(inst, dest.block(&f.dfg.value_lists), false);
173 }
174
175 ir::InstructionData::Brif {
176 blocks: [block_then, block_else],
177 ..
178 } => {
179 visit(inst, block_then.block(&f.dfg.value_lists), false);
180 visit(inst, block_else.block(&f.dfg.value_lists), false);
181 }
182
183 ir::InstructionData::BranchTable { table, .. } => {
184 let pool = &f.dfg.value_lists;
185 let table = &f.stencil.dfg.jump_tables[*table];
186
187 // The default block is reached via a direct conditional branch,
188 // so it is not part of the table. We visit the default block
189 // first explicitly, to mirror the traversal order of
190 // `JumpTableData::all_branches`, and transitively the order of
191 // `InstructionData::branch_destination`.
192 //
193 // Additionally, this case is why we are unable to replace this
194 // whole function with a loop over `branch_destination`: we need
195 // to report which branch targets come from the table vs the
196 // default.
197 visit(inst, table.default_block().block(pool), false);
198
199 for dest in table.as_slice() {
200 visit(inst, dest.block(pool), true);
201 }
202 }
203
204 ir::InstructionData::TryCall { exception, .. }
205 | ir::InstructionData::TryCallIndirect { exception, .. } => {
206 let pool = &f.dfg.value_lists;
207 let exdata = &f.stencil.dfg.exception_tables[*exception];
208
209 for dest in exdata.all_branches() {
210 visit(inst, dest.block(pool), false);
211 }
212 }
213
214 inst => debug_assert!(!inst.opcode().is_branch()),
215 }
216 }
217 }
218