Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/cranelift/codegen/src/inst_predicates.rs
1693 views
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)]
7
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)]
21
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)]
35
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
46
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.
76
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?
91
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?
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.
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.
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.
143
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 => true,
151
Opcode::Call | Opcode::CallIndirect | Opcode::TryCall | Opcode::TryCallIndirect => true,
152
op if op.can_trap() => true,
153
_ => false,
154
}
155
}
156
157
/// Visit all successors of a block with a given visitor closure. The closure
158
/// arguments are the branch instruction that is used to reach the successor,
159
/// the successor block itself, and a flag indicating whether the block is
160
/// branched to via a table entry.
161
pub(crate) fn visit_block_succs<F: FnMut(Inst, Block, bool)>(
162
f: &Function,
163
block: Block,
164
mut visit: F,
165
) {
166
if let Some(inst) = f.layout.last_inst(block) {
167
match &f.dfg.insts[inst] {
168
ir::InstructionData::Jump {
169
destination: dest, ..
170
} => {
171
visit(inst, dest.block(&f.dfg.value_lists), false);
172
}
173
174
ir::InstructionData::Brif {
175
blocks: [block_then, block_else],
176
..
177
} => {
178
visit(inst, block_then.block(&f.dfg.value_lists), false);
179
visit(inst, block_else.block(&f.dfg.value_lists), false);
180
}
181
182
ir::InstructionData::BranchTable { table, .. } => {
183
let pool = &f.dfg.value_lists;
184
let table = &f.stencil.dfg.jump_tables[*table];
185
186
// The default block is reached via a direct conditional branch,
187
// so it is not part of the table. We visit the default block
188
// first explicitly, to mirror the traversal order of
189
// `JumpTableData::all_branches`, and transitively the order of
190
// `InstructionData::branch_destination`.
191
//
192
// Additionally, this case is why we are unable to replace this
193
// whole function with a loop over `branch_destination`: we need
194
// to report which branch targets come from the table vs the
195
// default.
196
visit(inst, table.default_block().block(pool), false);
197
198
for dest in table.as_slice() {
199
visit(inst, dest.block(pool), true);
200
}
201
}
202
203
ir::InstructionData::TryCall { exception, .. }
204
| ir::InstructionData::TryCallIndirect { exception, .. } => {
205
let pool = &f.dfg.value_lists;
206
let exdata = &f.stencil.dfg.exception_tables[*exception];
207
208
for dest in exdata.all_branches() {
209
visit(inst, dest.block(pool), false);
210
}
211
}
212
213
inst => debug_assert!(!inst.opcode().is_branch()),
214
}
215
}
216
}
217
218