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