1 //! Instruction predicates/properties, shared by various analyses.
2 use crate::ir::immediates::Offset32;
3 use crate::ir::instructions::BranchInfo;
4 use crate::ir::{Block, DataFlowGraph, Function, Inst, InstructionData, Opcode, Type, Value};
5 use cranelift_entity::EntityRef;
6 
7 /// Preserve instructions with used result values.
8 pub fn any_inst_results_used(inst: Inst, live: &[bool], dfg: &DataFlowGraph) -> bool {
9     dfg.inst_results(inst).iter().any(|v| live[v.index()])
10 }
11 
12 /// Test whether the given opcode is unsafe to even consider as side-effect-free.
13 #[inline(always)]
14 fn trivially_has_side_effects(opcode: Opcode) -> bool {
15     opcode.is_call()
16         || opcode.is_branch()
17         || opcode.is_terminator()
18         || opcode.is_return()
19         || opcode.can_trap()
20         || opcode.other_side_effects()
21         || opcode.can_store()
22 }
23 
24 /// Load instructions without the `notrap` flag are defined to trap when
25 /// operating on inaccessible memory, so we can't treat them as side-effect-free even if the loaded
26 /// value is unused.
27 #[inline(always)]
28 fn is_load_with_defined_trapping(opcode: Opcode, data: &InstructionData) -> bool {
29     if !opcode.can_load() {
30         return false;
31     }
32     match *data {
33         InstructionData::StackLoad { .. } => false,
34         InstructionData::Load { flags, .. } => !flags.notrap(),
35         _ => true,
36     }
37 }
38 
39 /// Does the given instruction have any side-effect that would preclude it from being removed when
40 /// its value is unused?
41 #[inline(always)]
42 pub fn has_side_effect(func: &Function, inst: Inst) -> bool {
43     let data = &func.dfg[inst];
44     let opcode = data.opcode();
45     trivially_has_side_effects(opcode) || is_load_with_defined_trapping(opcode, data)
46 }
47 
48 /// Does the given instruction have any side-effect as per [has_side_effect], or else is a load,
49 /// but not the get_pinned_reg opcode?
50 pub fn has_lowering_side_effect(func: &Function, inst: Inst) -> bool {
51     let op = func.dfg[inst].opcode();
52     op != Opcode::GetPinnedReg && (has_side_effect(func, inst) || op.can_load())
53 }
54 
55 /// Is the given instruction a constant value (`iconst`, `fconst`) that can be
56 /// represented in 64 bits?
57 pub fn is_constant_64bit(func: &Function, inst: Inst) -> Option<u64> {
58     let data = &func.dfg[inst];
59     if data.opcode() == Opcode::Null {
60         return Some(0);
61     }
62     match data {
63         &InstructionData::UnaryImm { imm, .. } => Some(imm.bits() as u64),
64         &InstructionData::UnaryIeee32 { imm, .. } => Some(imm.bits() as u64),
65         &InstructionData::UnaryIeee64 { imm, .. } => Some(imm.bits()),
66         _ => None,
67     }
68 }
69 
70 /// Get the address, offset, and access type from the given instruction, if any.
71 pub fn inst_addr_offset_type(func: &Function, inst: Inst) -> Option<(Value, Offset32, Type)> {
72     let data = &func.dfg[inst];
73     match data {
74         InstructionData::Load { arg, offset, .. } => {
75             let ty = func.dfg.value_type(func.dfg.inst_results(inst)[0]);
76             Some((*arg, *offset, ty))
77         }
78         InstructionData::LoadNoOffset { arg, .. } => {
79             let ty = func.dfg.value_type(func.dfg.inst_results(inst)[0]);
80             Some((*arg, 0.into(), ty))
81         }
82         InstructionData::Store { args, offset, .. } => {
83             let ty = func.dfg.value_type(args[0]);
84             Some((args[1], *offset, ty))
85         }
86         InstructionData::StoreNoOffset { args, .. } => {
87             let ty = func.dfg.value_type(args[0]);
88             Some((args[1], 0.into(), ty))
89         }
90         _ => None,
91     }
92 }
93 
94 /// Get the store data, if any, from an instruction.
95 pub fn inst_store_data(func: &Function, inst: Inst) -> Option<Value> {
96     let data = &func.dfg[inst];
97     match data {
98         InstructionData::Store { args, .. } | InstructionData::StoreNoOffset { args, .. } => {
99             Some(args[0])
100         }
101         _ => None,
102     }
103 }
104 
105 /// Determine whether this opcode behaves as a memory fence, i.e.,
106 /// prohibits any moving of memory accesses across it.
107 pub fn has_memory_fence_semantics(op: Opcode) -> bool {
108     match op {
109         Opcode::AtomicRmw
110         | Opcode::AtomicCas
111         | Opcode::AtomicLoad
112         | Opcode::AtomicStore
113         | Opcode::Fence
114         | Opcode::Debugtrap => true,
115         Opcode::Call | Opcode::CallIndirect => true,
116         op if op.can_trap() => true,
117         _ => false,
118     }
119 }
120 
121 /// Visit all successors of a block with a given visitor closure. The closure
122 /// arguments are the branch instruction that is used to reach the successor,
123 /// the successor block itself, and a flag indicating whether the block is
124 /// branched to via a table entry.
125 pub(crate) fn visit_block_succs<F: FnMut(Inst, Block, bool)>(
126     f: &Function,
127     block: Block,
128     mut visit: F,
129 ) {
130     for inst in f.layout.block_likely_branches(block) {
131         if f.dfg[inst].opcode().is_branch() {
132             visit_branch_targets(f, inst, &mut visit);
133         }
134     }
135 }
136 
137 fn visit_branch_targets<F: FnMut(Inst, Block, bool)>(f: &Function, inst: Inst, visit: &mut F) {
138     match f.dfg[inst].analyze_branch(&f.dfg.value_lists) {
139         BranchInfo::NotABranch => {}
140         BranchInfo::SingleDest(dest, _) => {
141             visit(inst, dest, false);
142         }
143         BranchInfo::Table(table, maybe_dest) => {
144             if let Some(dest) = maybe_dest {
145                 // The default block is reached via a direct conditional branch,
146                 // so it is not part of the table.
147                 visit(inst, dest, false);
148             }
149             for &dest in f.jump_tables[table].as_slice() {
150                 visit(inst, dest, true);
151             }
152         }
153     }
154 }
155