Path: blob/main/cranelift/codegen/src/inst_predicates.rs
1693 views
//! Instruction predicates/properties, shared by various analyses.1use crate::ir::immediates::Offset32;2use crate::ir::{self, Block, Function, Inst, InstructionData, Opcode, Type, Value};34/// Test whether the given opcode is unsafe to even consider as side-effect-free.5#[inline(always)]6fn trivially_has_side_effects(opcode: Opcode) -> bool {7opcode.is_call()8|| opcode.is_branch()9|| opcode.is_terminator()10|| opcode.is_return()11|| opcode.can_trap()12|| opcode.other_side_effects()13|| opcode.can_store()14}1516/// Load instructions without the `notrap` flag are defined to trap when17/// operating on inaccessible memory, so we can't treat them as side-effect-free even if the loaded18/// value is unused.19#[inline(always)]20fn is_load_with_defined_trapping(opcode: Opcode, data: &InstructionData) -> bool {21if !opcode.can_load() {22return false;23}24match *data {25InstructionData::StackLoad { .. } => false,26InstructionData::Load { flags, .. } => !flags.notrap(),27_ => true,28}29}3031/// Does the given instruction have any side-effect that would preclude it from being removed when32/// its value is unused?33#[inline(always)]34fn has_side_effect(func: &Function, inst: Inst) -> bool {35let data = &func.dfg.insts[inst];36let opcode = data.opcode();37trivially_has_side_effects(opcode) || is_load_with_defined_trapping(opcode, data)38}3940/// Does the given instruction behave as a "pure" node with respect to41/// aegraph semantics?42///43/// - Trivially pure nodes (bitwise arithmetic, etc)44/// - Loads with the `readonly`, `notrap`, and `can_move` flags set45pub fn is_pure_for_egraph(func: &Function, inst: Inst) -> bool {46let is_pure_load = match func.dfg.insts[inst] {47InstructionData::Load {48opcode: Opcode::Load,49flags,50..51} => flags.readonly() && flags.notrap() && flags.can_move(),52_ => false,53};5455// Multi-value results do not play nicely with much of the egraph56// infrastructure. They are in practice used only for multi-return57// calls and some other odd instructions (e.g. uadd_overflow) which,58// for now, we can afford to leave in place as opaque59// side-effecting ops. So if more than one result, then the inst60// is "not pure". Similarly, ops with zero results can be used61// only for their side-effects, so are never pure. (Or if they62// are, we can always trivially eliminate them with no effect.)63let has_one_result = func.dfg.inst_results(inst).len() == 1;6465let op = func.dfg.insts[inst].opcode();6667has_one_result && (is_pure_load || (!op.can_load() && !trivially_has_side_effects(op)))68}6970/// Can the given instruction be merged into another copy of itself?71/// These instructions may have side-effects, but as long as we retain72/// the first instance of the instruction, the second and further73/// instances are redundant if they would produce the same trap or74/// result.75pub fn is_mergeable_for_egraph(func: &Function, inst: Inst) -> bool {76let op = func.dfg.insts[inst].opcode();77// We can only merge zero- and one-result operators due to the way that GVN78// is structured in the egraph implementation.79func.dfg.inst_results(inst).len() <= 180// Loads/stores are handled by alias analysis and not81// otherwise mergeable.82&& !op.can_load()83&& !op.can_store()84// Can only have idempotent side-effects.85&& (!has_side_effect(func, inst) || op.side_effects_idempotent())86}8788/// Does the given instruction have any side-effect as per [has_side_effect], or else is a load,89/// but not the get_pinned_reg opcode?90pub fn has_lowering_side_effect(func: &Function, inst: Inst) -> bool {91let op = func.dfg.insts[inst].opcode();92op != Opcode::GetPinnedReg && (has_side_effect(func, inst) || op.can_load())93}9495/// Is the given instruction a constant value (`iconst`, `fconst`) that can be96/// represented in 64 bits?97pub fn is_constant_64bit(func: &Function, inst: Inst) -> Option<u64> {98match &func.dfg.insts[inst] {99&InstructionData::UnaryImm { imm, .. } => Some(imm.bits() as u64),100&InstructionData::UnaryIeee16 { imm, .. } => Some(imm.bits() as u64),101&InstructionData::UnaryIeee32 { imm, .. } => Some(imm.bits() as u64),102&InstructionData::UnaryIeee64 { imm, .. } => Some(imm.bits()),103_ => None,104}105}106107/// Get the address, offset, and access type from the given instruction, if any.108pub fn inst_addr_offset_type(func: &Function, inst: Inst) -> Option<(Value, Offset32, Type)> {109match &func.dfg.insts[inst] {110InstructionData::Load { arg, offset, .. } => {111let ty = func.dfg.value_type(func.dfg.inst_results(inst)[0]);112Some((*arg, *offset, ty))113}114InstructionData::LoadNoOffset { arg, .. } => {115let ty = func.dfg.value_type(func.dfg.inst_results(inst)[0]);116Some((*arg, 0.into(), ty))117}118InstructionData::Store { args, offset, .. } => {119let ty = func.dfg.value_type(args[0]);120Some((args[1], *offset, ty))121}122InstructionData::StoreNoOffset { args, .. } => {123let ty = func.dfg.value_type(args[0]);124Some((args[1], 0.into(), ty))125}126_ => None,127}128}129130/// Get the store data, if any, from an instruction.131pub fn inst_store_data(func: &Function, inst: Inst) -> Option<Value> {132match &func.dfg.insts[inst] {133InstructionData::Store { args, .. } | InstructionData::StoreNoOffset { args, .. } => {134Some(args[0])135}136_ => None,137}138}139140/// Determine whether this opcode behaves as a memory fence, i.e.,141/// prohibits any moving of memory accesses across it.142pub fn has_memory_fence_semantics(op: Opcode) -> bool {143match op {144Opcode::AtomicRmw145| Opcode::AtomicCas146| Opcode::AtomicLoad147| Opcode::AtomicStore148| Opcode::Fence149| Opcode::Debugtrap => true,150Opcode::Call | Opcode::CallIndirect | Opcode::TryCall | Opcode::TryCallIndirect => true,151op if op.can_trap() => true,152_ => false,153}154}155156/// Visit all successors of a block with a given visitor closure. The closure157/// arguments are the branch instruction that is used to reach the successor,158/// the successor block itself, and a flag indicating whether the block is159/// branched to via a table entry.160pub(crate) fn visit_block_succs<F: FnMut(Inst, Block, bool)>(161f: &Function,162block: Block,163mut visit: F,164) {165if let Some(inst) = f.layout.last_inst(block) {166match &f.dfg.insts[inst] {167ir::InstructionData::Jump {168destination: dest, ..169} => {170visit(inst, dest.block(&f.dfg.value_lists), false);171}172173ir::InstructionData::Brif {174blocks: [block_then, block_else],175..176} => {177visit(inst, block_then.block(&f.dfg.value_lists), false);178visit(inst, block_else.block(&f.dfg.value_lists), false);179}180181ir::InstructionData::BranchTable { table, .. } => {182let pool = &f.dfg.value_lists;183let table = &f.stencil.dfg.jump_tables[*table];184185// The default block is reached via a direct conditional branch,186// so it is not part of the table. We visit the default block187// first explicitly, to mirror the traversal order of188// `JumpTableData::all_branches`, and transitively the order of189// `InstructionData::branch_destination`.190//191// Additionally, this case is why we are unable to replace this192// whole function with a loop over `branch_destination`: we need193// to report which branch targets come from the table vs the194// default.195visit(inst, table.default_block().block(pool), false);196197for dest in table.as_slice() {198visit(inst, dest.block(pool), true);199}200}201202ir::InstructionData::TryCall { exception, .. }203| ir::InstructionData::TryCallIndirect { exception, .. } => {204let pool = &f.dfg.value_lists;205let exdata = &f.stencil.dfg.exception_tables[*exception];206207for dest in exdata.all_branches() {208visit(inst, dest.block(pool), false);209}210}211212inst => debug_assert!(!inst.opcode().is_branch()),213}214}215}216217218