//! Verify value locations. use crate::flowgraph::ControlFlowGraph; use crate::ir; use crate::isa; use crate::regalloc::liveness::Liveness; use crate::regalloc::RegDiversions; use crate::timing; use crate::verifier::{VerifierErrors, VerifierStepResult}; /// Verify value locations for `func`. /// /// After register allocation, every value must be assigned to a location - either a register or a /// stack slot. These locations must be compatible with the constraints described by the /// instruction encoding recipes. /// /// Values can be temporarily diverted to a different location by using the `regmove`, `regspill`, /// and `regfill` instructions, but only inside a block. /// /// If a liveness analysis is provided, it is used to verify that there are no active register /// diversions across control flow edges. pub fn verify_locations( isa: &dyn isa::TargetIsa, func: &ir::Function, cfg: &ControlFlowGraph, liveness: Option<&Liveness>, errors: &mut VerifierErrors, ) -> VerifierStepResult<()> { let _tt = timing::verify_locations(); let verifier = LocationVerifier { isa, func, reginfo: isa.register_info(), encinfo: isa.encoding_info(), cfg, liveness, }; verifier.check_constraints(errors)?; Ok(()) } struct LocationVerifier<'a> { isa: &'a dyn isa::TargetIsa, func: &'a ir::Function, reginfo: isa::RegInfo, encinfo: isa::EncInfo, cfg: &'a ControlFlowGraph, liveness: Option<&'a Liveness>, } impl<'a> LocationVerifier<'a> { /// Check that the assigned value locations match the operand constraints of their uses. fn check_constraints(&self, errors: &mut VerifierErrors) -> VerifierStepResult<()> { let dfg = &self.func.dfg; let mut divert = RegDiversions::new(); for block in self.func.layout.blocks() { divert.at_block(&self.func.entry_diversions, block); let mut is_after_branch = false; for inst in self.func.layout.block_insts(block) { let enc = self.func.encodings[inst]; if enc.is_legal() { self.check_enc_constraints(inst, enc, &divert, errors)? } else { self.check_ghost_results(inst, errors)?; } if let Some(sig) = dfg.call_signature(inst) { self.check_call_abi(inst, sig, &divert, errors)?; } let opcode = dfg[inst].opcode(); if opcode.is_return() { self.check_return_abi(inst, &divert, errors)?; } else if opcode.is_branch() && !divert.is_empty() { self.check_cfg_edges(inst, &mut divert, is_after_branch, errors)?; } self.update_diversions(inst, &mut divert, errors)?; is_after_branch = opcode.is_branch(); } } Ok(()) } /// Check encoding constraints against the current value locations. fn check_enc_constraints( &self, inst: ir::Inst, enc: isa::Encoding, divert: &RegDiversions, errors: &mut VerifierErrors, ) -> VerifierStepResult<()> { let constraints = self .encinfo .operand_constraints(enc) .expect("check_enc_constraints requires a legal encoding"); if constraints.satisfied(inst, divert, self.func) { return Ok(()); } // TODO: We could give a better error message here. errors.fatal(( inst, format!( "{} constraints not satisfied in: {}\n{}", self.encinfo.display(enc), self.func.dfg.display_inst(inst, self.isa), self.func.display(self.isa), ), )) } /// Check that the result values produced by a ghost instruction are not assigned a value /// location. fn check_ghost_results( &self, inst: ir::Inst, errors: &mut VerifierErrors, ) -> VerifierStepResult<()> { let results = self.func.dfg.inst_results(inst); for &res in results { let loc = self.func.locations[res]; if loc.is_assigned() { return errors.fatal(( inst, format!( "ghost result {} value must not have a location ({}).", res, loc.display(&self.reginfo) ), )); } } Ok(()) } /// Check the ABI argument and result locations for a call. fn check_call_abi( &self, inst: ir::Inst, sig: ir::SigRef, divert: &RegDiversions, errors: &mut VerifierErrors, ) -> VerifierStepResult<()> { let sig = &self.func.dfg.signatures[sig]; let varargs = self.func.dfg.inst_variable_args(inst); let results = self.func.dfg.inst_results(inst); for (abi, &value) in sig.params.iter().zip(varargs) { self.check_abi_location( inst, value, abi, divert.get(value, &self.func.locations), ir::StackSlotKind::OutgoingArg, errors, )?; } for (abi, &value) in sig.returns.iter().zip(results) { self.check_abi_location( inst, value, abi, self.func.locations[value], ir::StackSlotKind::OutgoingArg, errors, )?; } Ok(()) } /// Check the ABI argument locations for a return. fn check_return_abi( &self, inst: ir::Inst, divert: &RegDiversions, errors: &mut VerifierErrors, ) -> VerifierStepResult<()> { let sig = &self.func.signature; let varargs = self.func.dfg.inst_variable_args(inst); for (abi, &value) in sig.returns.iter().zip(varargs) { self.check_abi_location( inst, value, abi, divert.get(value, &self.func.locations), ir::StackSlotKind::IncomingArg, errors, )?; } Ok(()) } /// Check a single ABI location. fn check_abi_location( &self, inst: ir::Inst, value: ir::Value, abi: &ir::AbiParam, loc: ir::ValueLoc, want_kind: ir::StackSlotKind, errors: &mut VerifierErrors, ) -> VerifierStepResult<()> { match abi.location { ir::ArgumentLoc::Unassigned => {} ir::ArgumentLoc::Reg(reg) => { if loc != ir::ValueLoc::Reg(reg) { return errors.fatal(( inst, format!( "ABI expects {} in {}, got {}", value, abi.location.display(&self.reginfo), loc.display(&self.reginfo), ), )); } } ir::ArgumentLoc::Stack(offset) => { if let ir::ValueLoc::Stack(ss) = loc { let slot = &self.func.stack_slots[ss]; if slot.kind != want_kind { return errors.fatal(( inst, format!( "call argument {} should be in a {} slot, but {} is {}", value, want_kind, ss, slot.kind ), )); } if slot.offset.unwrap() != offset { return errors.fatal(( inst, format!( "ABI expects {} at stack offset {}, but {} is at {}", value, offset, ss, slot.offset.unwrap() ), )); } } else { return errors.fatal(( inst, format!( "ABI expects {} at stack offset {}, got {}", value, offset, loc.display(&self.reginfo) ), )); } } } Ok(()) } /// Update diversions to reflect the current instruction and check their consistency. fn update_diversions( &self, inst: ir::Inst, divert: &mut RegDiversions, errors: &mut VerifierErrors, ) -> VerifierStepResult<()> { let (arg, src) = match self.func.dfg[inst] { ir::InstructionData::RegMove { arg, src, .. } | ir::InstructionData::RegSpill { arg, src, .. } => (arg, ir::ValueLoc::Reg(src)), ir::InstructionData::RegFill { arg, src, .. } => (arg, ir::ValueLoc::Stack(src)), _ => return Ok(()), }; if let Some(d) = divert.diversion(arg) { if d.to != src { return errors.fatal(( inst, format!( "inconsistent with current diversion to {}", d.to.display(&self.reginfo) ), )); } } else if self.func.locations[arg] != src { return errors.fatal(( inst, format!( "inconsistent with global location {} ({})", self.func.locations[arg].display(&self.reginfo), self.func.dfg.display_inst(inst, None) ), )); } divert.apply(&self.func.dfg[inst]); Ok(()) } /// We have active diversions before a branch. Make sure none of the diverted values are live /// on the outgoing CFG edges. fn check_cfg_edges( &self, inst: ir::Inst, divert: &mut RegDiversions, is_after_branch: bool, errors: &mut VerifierErrors, ) -> VerifierStepResult<()> { use crate::ir::instructions::BranchInfo::*; let dfg = &self.func.dfg; let branch_kind = dfg.analyze_branch(inst); // We can only check CFG edges if we have a liveness analysis. let liveness = match self.liveness { Some(l) => l, None => return Ok(()), }; match branch_kind { NotABranch => panic!( "No branch information for {}", dfg.display_inst(inst, self.isa) ), SingleDest(block, _) => { let unique_predecessor = self.cfg.pred_iter(block).count() == 1; let mut val_to_remove = vec![]; for (&value, d) in divert.iter() { let lr = &liveness[value]; if is_after_branch && unique_predecessor { // Forward diversions based on the targeted branch. if !lr.is_livein(block, &self.func.layout) { val_to_remove.push(value) } } else if lr.is_livein(block, &self.func.layout) { return errors.fatal(( inst, format!( "SingleDest: {} is diverted to {} and live in to {}", value, d.to.display(&self.reginfo), block, ), )); } } if is_after_branch && unique_predecessor { for val in val_to_remove.into_iter() { divert.remove(val); } debug_assert!(divert.check_block_entry(&self.func.entry_diversions, block)); } } Table(jt, block) => { for (&value, d) in divert.iter() { let lr = &liveness[value]; if let Some(block) = block { if lr.is_livein(block, &self.func.layout) { return errors.fatal(( inst, format!( "Table.default: {} is diverted to {} and live in to {}", value, d.to.display(&self.reginfo), block, ), )); } } for block in self.func.jump_tables[jt].iter() { if lr.is_livein(*block, &self.func.layout) { return errors.fatal(( inst, format!( "Table.case: {} is diverted to {} and live in to {}", value, d.to.display(&self.reginfo), block, ), )); } } } } } Ok(()) } }