Path: blob/main/cranelift/codegen/src/verifier/mod.rs
1693 views
//! A verifier for ensuring that functions are well formed.1//! It verifies:2//!3//! block integrity4//!5//! - All instructions reached from the `block_insts` iterator must belong to6//! the block as reported by `inst_block()`.7//! - Every block must end in a terminator instruction, and no other instruction8//! can be a terminator.9//! - Every value in the `block_params` iterator belongs to the block as reported by `value_block`.10//!11//! Instruction integrity12//!13//! - The instruction format must match the opcode.14//! - All result values must be created for multi-valued instructions.15//! - All referenced entities must exist. (Values, blocks, stack slots, ...)16//! - Instructions must not reference (eg. branch to) the entry block.17//!18//! SSA form19//!20//! - Values must be defined by an instruction that exists and that is inserted in21//! a block, or be an argument of an existing block.22//! - Values used by an instruction must dominate the instruction.23//!24//! Control flow graph and dominator tree integrity:25//!26//! - All predecessors in the CFG must be branches to the block.27//! - All branches to a block must be present in the CFG.28//! - A recomputed dominator tree is identical to the existing one.29//! - The entry block must not be a cold block.30//!31//! Type checking32//!33//! - Compare input and output values against the opcode's type constraints.34//! For polymorphic opcodes, determine the controlling type variable first.35//! - Branches and jumps must pass arguments to destination blocks that match the36//! expected types exactly. The number of arguments must match.37//! - All blocks in a jump table must take no arguments.38//! - Function calls are type checked against their signature.39//! - The entry block must take arguments that match the signature of the current40//! function.41//! - All return instructions must have return value operands matching the current42//! function signature.43//!44//! Global values45//!46//! - Detect cycles in global values.47//! - Detect use of 'vmctx' global value when no corresponding parameter is defined.48//!49//! Memory types50//!51//! - Ensure that struct fields are in offset order.52//! - Ensure that struct fields are completely within the overall53//! struct size, and do not overlap.54//!55//! TODO:56//! Ad hoc checking57//!58//! - Stack slot loads and stores must be in-bounds.59//! - Immediate constraints for certain opcodes, like `udiv_imm v3, 0`.60//! - `Insertlane` and `extractlane` instructions have immediate lane numbers that must be in61//! range for their polymorphic type.62//! - Swizzle and shuffle instructions take a variable number of lane arguments. The number63//! of arguments must match the destination type, and the lane indexes must be in range.6465use crate::dbg::DisplayList;66use crate::dominator_tree::DominatorTree;67use crate::entity::SparseSet;68use crate::flowgraph::{BlockPredecessor, ControlFlowGraph};69use crate::ir::ExceptionTableItem;70use crate::ir::entities::AnyEntity;71use crate::ir::instructions::{CallInfo, InstructionFormat, ResolvedConstraint};72use crate::ir::{self, ArgumentExtension, BlockArg, ExceptionTable};73use crate::ir::{74ArgumentPurpose, Block, Constant, DynamicStackSlot, FuncRef, Function, GlobalValue, Inst,75JumpTable, MemFlags, MemoryTypeData, Opcode, SigRef, StackSlot, Type, Value, ValueDef,76ValueList, types,77};78use crate::isa::TargetIsa;79use crate::print_errors::pretty_verifier_error;80use crate::settings::FlagsOrIsa;81use crate::timing;82use alloc::collections::BTreeSet;83use alloc::string::{String, ToString};84use alloc::vec::Vec;85use core::fmt::{self, Display, Formatter};8687/// A verifier error.88#[derive(Debug, PartialEq, Eq, Clone)]89pub struct VerifierError {90/// The entity causing the verifier error.91pub location: AnyEntity,92/// Optionally provide some context for the given location; e.g., for `inst42` provide93/// `Some("v3 = iconst.i32 0")` for more comprehensible errors.94pub context: Option<String>,95/// The error message.96pub message: String,97}9899// This is manually implementing Error and Display instead of using thiserror to reduce the amount100// of dependencies used by Cranelift.101impl std::error::Error for VerifierError {}102103impl Display for VerifierError {104fn fmt(&self, f: &mut Formatter) -> fmt::Result {105match &self.context {106None => write!(f, "{}: {}", self.location, self.message),107Some(context) => write!(f, "{} ({}): {}", self.location, context, self.message),108}109}110}111112/// Convenience converter for making error-reporting less verbose.113///114/// Converts a tuple of `(location, context, message)` to a `VerifierError`.115/// ```116/// use cranelift_codegen::verifier::VerifierErrors;117/// use cranelift_codegen::ir::Inst;118/// let mut errors = VerifierErrors::new();119/// errors.report((Inst::from_u32(42), "v3 = iadd v1, v2", "iadd cannot be used with values of this type"));120/// // note the double parenthenses to use this syntax121/// ```122impl<L, C, M> From<(L, C, M)> for VerifierError123where124L: Into<AnyEntity>,125C: Into<String>,126M: Into<String>,127{128fn from(items: (L, C, M)) -> Self {129let (location, context, message) = items;130Self {131location: location.into(),132context: Some(context.into()),133message: message.into(),134}135}136}137138/// Convenience converter for making error-reporting less verbose.139///140/// Same as above but without `context`.141impl<L, M> From<(L, M)> for VerifierError142where143L: Into<AnyEntity>,144M: Into<String>,145{146fn from(items: (L, M)) -> Self {147let (location, message) = items;148Self {149location: location.into(),150context: None,151message: message.into(),152}153}154}155156/// Result of a step in the verification process.157///158/// Functions that return `VerifierStepResult` should also take a159/// mutable reference to `VerifierErrors` as argument in order to report160/// errors.161///162/// Here, `Ok` represents a step that **did not lead to a fatal error**,163/// meaning that the verification process may continue. However, other (non-fatal)164/// errors might have been reported through the previously mentioned `VerifierErrors`165/// argument.166pub type VerifierStepResult = Result<(), ()>;167168/// Result of a verification operation.169///170/// Unlike `VerifierStepResult` which may be `Ok` while still having reported171/// errors, this type always returns `Err` if an error (fatal or not) was reported.172pub type VerifierResult<T> = Result<T, VerifierErrors>;173174/// List of verifier errors.175#[derive(Debug, Default, PartialEq, Eq, Clone)]176pub struct VerifierErrors(pub Vec<VerifierError>);177178// This is manually implementing Error and Display instead of using thiserror to reduce the amount179// of dependencies used by Cranelift.180impl std::error::Error for VerifierErrors {}181182impl VerifierErrors {183/// Return a new `VerifierErrors` struct.184#[inline]185pub fn new() -> Self {186Self(Vec::new())187}188189/// Return whether no errors were reported.190#[inline]191pub fn is_empty(&self) -> bool {192self.0.is_empty()193}194195/// Return whether one or more errors were reported.196#[inline]197pub fn has_error(&self) -> bool {198!self.0.is_empty()199}200201/// Return a `VerifierStepResult` that is fatal if at least one error was reported,202/// and non-fatal otherwise.203#[inline]204pub fn as_result(&self) -> VerifierStepResult {205if self.is_empty() { Ok(()) } else { Err(()) }206}207208/// Report an error, adding it to the list of errors.209pub fn report(&mut self, error: impl Into<VerifierError>) {210self.0.push(error.into());211}212213/// Report a fatal error and return `Err`.214pub fn fatal(&mut self, error: impl Into<VerifierError>) -> VerifierStepResult {215self.report(error);216Err(())217}218219/// Report a non-fatal error and return `Ok`.220pub fn nonfatal(&mut self, error: impl Into<VerifierError>) -> VerifierStepResult {221self.report(error);222Ok(())223}224}225226impl From<Vec<VerifierError>> for VerifierErrors {227fn from(v: Vec<VerifierError>) -> Self {228Self(v)229}230}231232impl From<VerifierErrors> for Vec<VerifierError> {233fn from(errors: VerifierErrors) -> Vec<VerifierError> {234errors.0235}236}237238impl From<VerifierErrors> for VerifierResult<()> {239fn from(errors: VerifierErrors) -> VerifierResult<()> {240if errors.is_empty() {241Ok(())242} else {243Err(errors)244}245}246}247248impl Display for VerifierErrors {249fn fmt(&self, f: &mut Formatter) -> fmt::Result {250for err in &self.0 {251writeln!(f, "- {err}")?;252}253Ok(())254}255}256257/// Verify `func`.258pub fn verify_function<'a, FOI: Into<FlagsOrIsa<'a>>>(259func: &Function,260fisa: FOI,261) -> VerifierResult<()> {262let _tt = timing::verifier();263let mut errors = VerifierErrors::default();264let verifier = Verifier::new(func, fisa.into());265let result = verifier.run(&mut errors);266if errors.is_empty() {267result.unwrap();268Ok(())269} else {270Err(errors)271}272}273274/// Verify `func` after checking the integrity of associated context data structures `cfg` and275/// `domtree`.276pub fn verify_context<'a, FOI: Into<FlagsOrIsa<'a>>>(277func: &Function,278cfg: &ControlFlowGraph,279domtree: &DominatorTree,280fisa: FOI,281errors: &mut VerifierErrors,282) -> VerifierStepResult {283let _tt = timing::verifier();284let verifier = Verifier::new(func, fisa.into());285if cfg.is_valid() {286verifier.cfg_integrity(cfg, errors)?;287}288if domtree.is_valid() {289verifier.domtree_integrity(domtree, errors)?;290}291verifier.run(errors)292}293294#[derive(Clone, Copy, Debug)]295enum BlockCallTargetType {296Normal,297ExNormalRet,298Exception,299}300301struct Verifier<'a> {302func: &'a Function,303expected_cfg: ControlFlowGraph,304expected_domtree: DominatorTree,305isa: Option<&'a dyn TargetIsa>,306}307308impl<'a> Verifier<'a> {309pub fn new(func: &'a Function, fisa: FlagsOrIsa<'a>) -> Self {310let expected_cfg = ControlFlowGraph::with_function(func);311let expected_domtree = DominatorTree::with_function(func, &expected_cfg);312Self {313func,314expected_cfg,315expected_domtree,316isa: fisa.isa,317}318}319320/// Determine a contextual error string for an instruction.321#[inline]322fn context(&self, inst: Inst) -> String {323self.func.dfg.display_inst(inst).to_string()324}325326// Check for:327// - cycles in the global value declarations.328// - use of 'vmctx' when no special parameter declares it.329fn verify_global_values(&self, errors: &mut VerifierErrors) -> VerifierStepResult {330let mut cycle_seen = false;331let mut seen = SparseSet::new();332333'gvs: for gv in self.func.global_values.keys() {334seen.clear();335seen.insert(gv);336337let mut cur = gv;338loop {339match self.func.global_values[cur] {340ir::GlobalValueData::Load { base, .. }341| ir::GlobalValueData::IAddImm { base, .. } => {342if seen.insert(base).is_some() {343if !cycle_seen {344errors.report((345gv,346format!("global value cycle: {}", DisplayList(seen.as_slice())),347));348// ensures we don't report the cycle multiple times349cycle_seen = true;350}351continue 'gvs;352}353354cur = base;355}356_ => break,357}358}359360match self.func.global_values[gv] {361ir::GlobalValueData::VMContext { .. } => {362if self363.func364.special_param(ir::ArgumentPurpose::VMContext)365.is_none()366{367errors.report((gv, format!("undeclared vmctx reference {gv}")));368}369}370ir::GlobalValueData::IAddImm {371base, global_type, ..372} => {373if !global_type.is_int() {374errors.report((375gv,376format!("iadd_imm global value with non-int type {global_type}"),377));378} else if let Some(isa) = self.isa {379let base_type = self.func.global_values[base].global_type(isa);380if global_type != base_type {381errors.report((382gv,383format!(384"iadd_imm type {global_type} differs from operand type {base_type}"385),386));387}388}389}390ir::GlobalValueData::Load { base, .. } => {391if let Some(isa) = self.isa {392let base_type = self.func.global_values[base].global_type(isa);393let pointer_type = isa.pointer_type();394if base_type != pointer_type {395errors.report((396gv,397format!(398"base {base} has type {base_type}, which is not the pointer type {pointer_type}"399),400));401}402}403}404_ => {}405}406}407408// Invalid global values shouldn't stop us from verifying the rest of the function409Ok(())410}411412fn verify_memory_types(&self, errors: &mut VerifierErrors) -> VerifierStepResult {413// Verify that all fields are statically-sized and lie within414// the struct, do not overlap, and are in offset order415for (mt, mt_data) in &self.func.memory_types {416match mt_data {417MemoryTypeData::Struct { size, fields } => {418let mut last_offset = 0;419for field in fields {420if field.offset < last_offset {421errors.report((422mt,423format!(424"memory type {} has a field at offset {}, which is out-of-order",425mt, field.offset426),427));428}429last_offset = match field.offset.checked_add(u64::from(field.ty.bytes())) {430Some(o) => o,431None => {432errors.report((433mt,434format!(435"memory type {} has a field at offset {} of size {}; offset plus size overflows a u64",436mt, field.offset, field.ty.bytes()),437));438break;439}440};441442if last_offset > *size {443errors.report((444mt,445format!(446"memory type {} has a field at offset {} of size {} that overflows the struct size {}",447mt, field.offset, field.ty.bytes(), *size),448));449}450}451}452_ => {}453}454}455456Ok(())457}458459/// Check that the given block can be encoded as a BB, by checking that only460/// branching instructions are ending the block.461fn encodable_as_bb(&self, block: Block, errors: &mut VerifierErrors) -> VerifierStepResult {462match self.func.is_block_basic(block) {463Ok(()) => Ok(()),464Err((inst, message)) => errors.fatal((inst, self.context(inst), message)),465}466}467468fn block_integrity(469&self,470block: Block,471inst: Inst,472errors: &mut VerifierErrors,473) -> VerifierStepResult {474let is_terminator = self.func.dfg.insts[inst].opcode().is_terminator();475let is_last_inst = self.func.layout.last_inst(block) == Some(inst);476477if is_terminator && !is_last_inst {478// Terminating instructions only occur at the end of blocks.479return errors.fatal((480inst,481self.context(inst),482format!("a terminator instruction was encountered before the end of {block}"),483));484}485if is_last_inst && !is_terminator {486return errors.fatal((block, "block does not end in a terminator instruction"));487}488489// Instructions belong to the correct block.490let inst_block = self.func.layout.inst_block(inst);491if inst_block != Some(block) {492return errors.fatal((493inst,494self.context(inst),495format!("should belong to {block} not {inst_block:?}"),496));497}498499// Parameters belong to the correct block.500for &arg in self.func.dfg.block_params(block) {501match self.func.dfg.value_def(arg) {502ValueDef::Param(arg_block, _) => {503if block != arg_block {504return errors.fatal((arg, format!("does not belong to {block}")));505}506}507_ => {508return errors.fatal((arg, "expected an argument, found a result"));509}510}511}512513Ok(())514}515516fn instruction_integrity(&self, inst: Inst, errors: &mut VerifierErrors) -> VerifierStepResult {517let inst_data = &self.func.dfg.insts[inst];518let dfg = &self.func.dfg;519520// The instruction format matches the opcode521if inst_data.opcode().format() != InstructionFormat::from(inst_data) {522return errors.fatal((523inst,524self.context(inst),525"instruction opcode doesn't match instruction format",526));527}528529let expected_num_results = dfg.num_expected_results_for_verifier(inst);530531// All result values for multi-valued instructions are created532let got_results = dfg.inst_results(inst).len();533if got_results != expected_num_results {534return errors.fatal((535inst,536self.context(inst),537format!("expected {expected_num_results} result values, found {got_results}"),538));539}540541self.verify_entity_references(inst, errors)542}543544fn verify_entity_references(545&self,546inst: Inst,547errors: &mut VerifierErrors,548) -> VerifierStepResult {549use crate::ir::instructions::InstructionData::*;550551for arg in self.func.dfg.inst_values(inst) {552self.verify_inst_arg(inst, arg, errors)?;553554// All used values must be attached to something.555let original = self.func.dfg.resolve_aliases(arg);556if !self.func.dfg.value_is_attached(original) {557errors.report((558inst,559self.context(inst),560format!("argument {arg} -> {original} is not attached"),561));562}563}564565for &res in self.func.dfg.inst_results(inst) {566self.verify_inst_result(inst, res, errors)?;567}568569match self.func.dfg.insts[inst] {570MultiAry { ref args, .. } => {571self.verify_value_list(inst, args, errors)?;572}573Jump { destination, .. } => {574self.verify_block(inst, destination.block(&self.func.dfg.value_lists), errors)?;575}576Brif {577arg,578blocks: [block_then, block_else],579..580} => {581self.verify_value(inst, arg, errors)?;582self.verify_block(inst, block_then.block(&self.func.dfg.value_lists), errors)?;583self.verify_block(inst, block_else.block(&self.func.dfg.value_lists), errors)?;584}585BranchTable { table, .. } => {586self.verify_jump_table(inst, table, errors)?;587}588Call {589func_ref, ref args, ..590} => {591self.verify_func_ref(inst, func_ref, errors)?;592self.verify_value_list(inst, args, errors)?;593}594CallIndirect {595sig_ref, ref args, ..596} => {597self.verify_sig_ref(inst, sig_ref, errors)?;598self.verify_value_list(inst, args, errors)?;599}600TryCall {601func_ref,602ref args,603exception,604..605} => {606self.verify_func_ref(inst, func_ref, errors)?;607self.verify_value_list(inst, args, errors)?;608self.verify_exception_table(inst, exception, errors)?;609self.verify_exception_compatible_abi(inst, exception, errors)?;610}611TryCallIndirect {612ref args,613exception,614..615} => {616self.verify_value_list(inst, args, errors)?;617self.verify_exception_table(inst, exception, errors)?;618self.verify_exception_compatible_abi(inst, exception, errors)?;619}620FuncAddr { func_ref, .. } => {621self.verify_func_ref(inst, func_ref, errors)?;622}623StackLoad { stack_slot, .. } | StackStore { stack_slot, .. } => {624self.verify_stack_slot(inst, stack_slot, errors)?;625}626DynamicStackLoad {627dynamic_stack_slot, ..628}629| DynamicStackStore {630dynamic_stack_slot, ..631} => {632self.verify_dynamic_stack_slot(inst, dynamic_stack_slot, errors)?;633}634UnaryGlobalValue { global_value, .. } => {635self.verify_global_value(inst, global_value, errors)?;636}637NullAry {638opcode: Opcode::GetPinnedReg,639}640| Unary {641opcode: Opcode::SetPinnedReg,642..643} => {644if let Some(isa) = &self.isa {645if !isa.flags().enable_pinned_reg() {646return errors.fatal((647inst,648self.context(inst),649"GetPinnedReg/SetPinnedReg cannot be used without enable_pinned_reg",650));651}652} else {653return errors.fatal((654inst,655self.context(inst),656"GetPinnedReg/SetPinnedReg need an ISA!",657));658}659}660NullAry {661opcode: Opcode::GetFramePointer | Opcode::GetReturnAddress,662} => {663if let Some(isa) = &self.isa {664// Backends may already rely on this check implicitly, so do665// not relax it without verifying that it is safe to do so.666if !isa.flags().preserve_frame_pointers() {667return errors.fatal((668inst,669self.context(inst),670"`get_frame_pointer`/`get_return_address` cannot be used without \671enabling `preserve_frame_pointers`",672));673}674} else {675return errors.fatal((676inst,677self.context(inst),678"`get_frame_pointer`/`get_return_address` require an ISA!",679));680}681}682LoadNoOffset {683opcode: Opcode::Bitcast,684flags,685arg,686} => {687self.verify_bitcast(inst, flags, arg, errors)?;688}689LoadNoOffset { opcode, arg, .. } if opcode.can_load() => {690self.verify_is_address(inst, arg, errors)?;691}692Load { opcode, arg, .. } if opcode.can_load() => {693self.verify_is_address(inst, arg, errors)?;694}695AtomicCas {696opcode,697args: [p, _, _],698..699} if opcode.can_load() || opcode.can_store() => {700self.verify_is_address(inst, p, errors)?;701}702AtomicRmw {703opcode,704args: [p, _],705..706} if opcode.can_load() || opcode.can_store() => {707self.verify_is_address(inst, p, errors)?;708}709Store {710opcode,711args: [_, p],712..713} if opcode.can_store() => {714self.verify_is_address(inst, p, errors)?;715}716StoreNoOffset {717opcode,718args: [_, p],719..720} if opcode.can_store() => {721self.verify_is_address(inst, p, errors)?;722}723UnaryConst {724opcode: opcode @ (Opcode::Vconst | Opcode::F128const),725constant_handle,726..727} => {728self.verify_constant_size(inst, opcode, constant_handle, errors)?;729}730731ExceptionHandlerAddress { block, imm, .. } => {732self.verify_block(inst, block, errors)?;733self.verify_try_call_handler_index(inst, block, imm.into(), errors)?;734}735736// Exhaustive list so we can't forget to add new formats737AtomicCas { .. }738| AtomicRmw { .. }739| LoadNoOffset { .. }740| StoreNoOffset { .. }741| Unary { .. }742| UnaryConst { .. }743| UnaryImm { .. }744| UnaryIeee16 { .. }745| UnaryIeee32 { .. }746| UnaryIeee64 { .. }747| Binary { .. }748| BinaryImm8 { .. }749| BinaryImm64 { .. }750| Ternary { .. }751| TernaryImm8 { .. }752| Shuffle { .. }753| IntAddTrap { .. }754| IntCompare { .. }755| IntCompareImm { .. }756| FloatCompare { .. }757| Load { .. }758| Store { .. }759| Trap { .. }760| CondTrap { .. }761| NullAry { .. } => {}762}763764Ok(())765}766767fn verify_block(768&self,769loc: impl Into<AnyEntity>,770e: Block,771errors: &mut VerifierErrors,772) -> VerifierStepResult {773if !self.func.dfg.block_is_valid(e) || !self.func.layout.is_block_inserted(e) {774return errors.fatal((loc, format!("invalid block reference {e}")));775}776if let Some(entry_block) = self.func.layout.entry_block() {777if e == entry_block {778return errors.fatal((loc, format!("invalid reference to entry block {e}")));779}780}781Ok(())782}783784fn verify_sig_ref(785&self,786inst: Inst,787s: SigRef,788errors: &mut VerifierErrors,789) -> VerifierStepResult {790if !self.func.dfg.signatures.is_valid(s) {791errors.fatal((792inst,793self.context(inst),794format!("invalid signature reference {s}"),795))796} else {797Ok(())798}799}800801fn verify_func_ref(802&self,803inst: Inst,804f: FuncRef,805errors: &mut VerifierErrors,806) -> VerifierStepResult {807if !self.func.dfg.ext_funcs.is_valid(f) {808errors.nonfatal((809inst,810self.context(inst),811format!("invalid function reference {f}"),812))813} else {814Ok(())815}816}817818fn verify_stack_slot(819&self,820inst: Inst,821ss: StackSlot,822errors: &mut VerifierErrors,823) -> VerifierStepResult {824if !self.func.sized_stack_slots.is_valid(ss) {825errors.nonfatal((inst, self.context(inst), format!("invalid stack slot {ss}")))826} else {827Ok(())828}829}830831fn verify_dynamic_stack_slot(832&self,833inst: Inst,834ss: DynamicStackSlot,835errors: &mut VerifierErrors,836) -> VerifierStepResult {837if !self.func.dynamic_stack_slots.is_valid(ss) {838errors.nonfatal((839inst,840self.context(inst),841format!("invalid dynamic stack slot {ss}"),842))843} else {844Ok(())845}846}847848fn verify_global_value(849&self,850inst: Inst,851gv: GlobalValue,852errors: &mut VerifierErrors,853) -> VerifierStepResult {854if !self.func.global_values.is_valid(gv) {855errors.nonfatal((856inst,857self.context(inst),858format!("invalid global value {gv}"),859))860} else {861Ok(())862}863}864865fn verify_value_list(866&self,867inst: Inst,868l: &ValueList,869errors: &mut VerifierErrors,870) -> VerifierStepResult {871if !l.is_valid(&self.func.dfg.value_lists) {872errors.nonfatal((873inst,874self.context(inst),875format!("invalid value list reference {l:?}"),876))877} else {878Ok(())879}880}881882fn verify_jump_table(883&self,884inst: Inst,885j: JumpTable,886errors: &mut VerifierErrors,887) -> VerifierStepResult {888if !self.func.stencil.dfg.jump_tables.is_valid(j) {889errors.nonfatal((890inst,891self.context(inst),892format!("invalid jump table reference {j}"),893))894} else {895let pool = &self.func.stencil.dfg.value_lists;896for block in self.func.stencil.dfg.jump_tables[j].all_branches() {897self.verify_block(inst, block.block(pool), errors)?;898}899Ok(())900}901}902903fn verify_exception_table(904&self,905inst: Inst,906et: ExceptionTable,907errors: &mut VerifierErrors,908) -> VerifierStepResult {909// Verify that the exception table reference itself is valid.910if !self.func.stencil.dfg.exception_tables.is_valid(et) {911errors.nonfatal((912inst,913self.context(inst),914format!("invalid exception table reference {et}"),915))?;916}917918let pool = &self.func.stencil.dfg.value_lists;919let exdata = &self.func.stencil.dfg.exception_tables[et];920921// Verify that the exception table's signature reference922// is valid.923self.verify_sig_ref(inst, exdata.signature(), errors)?;924925// Verify that the exception table's block references are valid.926for block in exdata.all_branches() {927self.verify_block(inst, block.block(pool), errors)?;928}929Ok(())930}931932fn verify_exception_compatible_abi(933&self,934inst: Inst,935et: ExceptionTable,936errors: &mut VerifierErrors,937) -> VerifierStepResult {938let callee_sig_ref = self.func.dfg.exception_tables[et].signature();939let callee_sig = &self.func.dfg.signatures[callee_sig_ref];940let callee_call_conv = callee_sig.call_conv;941if !callee_call_conv.supports_exceptions() {942errors.nonfatal((943inst,944self.context(inst),945format!(946"calling convention `{callee_call_conv}` of callee does not support exceptions"947),948))?;949}950Ok(())951}952953fn verify_value(954&self,955loc_inst: Inst,956v: Value,957errors: &mut VerifierErrors,958) -> VerifierStepResult {959let dfg = &self.func.dfg;960if !dfg.value_is_valid(v) {961errors.nonfatal((962loc_inst,963self.context(loc_inst),964format!("invalid value reference {v}"),965))966} else {967Ok(())968}969}970971fn verify_inst_arg(972&self,973loc_inst: Inst,974v: Value,975errors: &mut VerifierErrors,976) -> VerifierStepResult {977self.verify_value(loc_inst, v, errors)?;978979let dfg = &self.func.dfg;980let loc_block = self981.func982.layout983.inst_block(loc_inst)984.expect("Instruction not in layout.");985let is_reachable = self.expected_domtree.is_reachable(loc_block);986987// SSA form988match dfg.value_def(v) {989ValueDef::Result(def_inst, _) => {990// Value is defined by an instruction that exists.991if !dfg.inst_is_valid(def_inst) {992return errors.fatal((993loc_inst,994self.context(loc_inst),995format!("{v} is defined by invalid instruction {def_inst}"),996));997}998// Defining instruction is inserted in a block.999if self.func.layout.inst_block(def_inst) == None {1000return errors.fatal((1001loc_inst,1002self.context(loc_inst),1003format!("{v} is defined by {def_inst} which has no block"),1004));1005}1006// Defining instruction dominates the instruction that uses the value.1007if is_reachable {1008if !self1009.expected_domtree1010.dominates(def_inst, loc_inst, &self.func.layout)1011{1012return errors.fatal((1013loc_inst,1014self.context(loc_inst),1015format!("uses value {v} from non-dominating {def_inst}"),1016));1017}1018if def_inst == loc_inst {1019return errors.fatal((1020loc_inst,1021self.context(loc_inst),1022format!("uses value {v} from itself"),1023));1024}1025}1026}1027ValueDef::Param(block, _) => {1028// Value is defined by an existing block.1029if !dfg.block_is_valid(block) {1030return errors.fatal((1031loc_inst,1032self.context(loc_inst),1033format!("{v} is defined by invalid block {block}"),1034));1035}1036// Defining block is inserted in the layout1037if !self.func.layout.is_block_inserted(block) {1038return errors.fatal((1039loc_inst,1040self.context(loc_inst),1041format!("{v} is defined by {block} which is not in the layout"),1042));1043}1044let user_block = self.func.layout.inst_block(loc_inst).expect("Expected instruction to be in a block as we're traversing code already in layout");1045// The defining block dominates the instruction using this value.1046if is_reachable && !self.expected_domtree.block_dominates(block, user_block) {1047return errors.fatal((1048loc_inst,1049self.context(loc_inst),1050format!("uses value arg from non-dominating {block}"),1051));1052}1053}1054ValueDef::Union(_, _) => {1055// Nothing: union nodes themselves have no location,1056// so we cannot check any dominance properties.1057}1058}1059Ok(())1060}10611062fn verify_inst_result(1063&self,1064loc_inst: Inst,1065v: Value,1066errors: &mut VerifierErrors,1067) -> VerifierStepResult {1068self.verify_value(loc_inst, v, errors)?;10691070match self.func.dfg.value_def(v) {1071ValueDef::Result(def_inst, _) => {1072if def_inst != loc_inst {1073errors.fatal((1074loc_inst,1075self.context(loc_inst),1076format!("instruction result {v} is not defined by the instruction"),1077))1078} else {1079Ok(())1080}1081}1082ValueDef::Param(_, _) => errors.fatal((1083loc_inst,1084self.context(loc_inst),1085format!("instruction result {v} is not defined by the instruction"),1086)),1087ValueDef::Union(_, _) => errors.fatal((1088loc_inst,1089self.context(loc_inst),1090format!("instruction result {v} is a union node"),1091)),1092}1093}10941095fn verify_bitcast(1096&self,1097inst: Inst,1098flags: MemFlags,1099arg: Value,1100errors: &mut VerifierErrors,1101) -> VerifierStepResult {1102let typ = self.func.dfg.ctrl_typevar(inst);1103let value_type = self.func.dfg.value_type(arg);11041105if typ.bits() != value_type.bits() {1106errors.fatal((1107inst,1108format!(1109"The bitcast argument {} has a type of {} bits, which doesn't match an expected type of {} bits",1110arg,1111value_type.bits(),1112typ.bits()1113),1114))1115} else if flags != MemFlags::new()1116&& flags != MemFlags::new().with_endianness(ir::Endianness::Little)1117&& flags != MemFlags::new().with_endianness(ir::Endianness::Big)1118{1119errors.fatal((1120inst,1121"The bitcast instruction only accepts the `big` or `little` memory flags",1122))1123} else if flags == MemFlags::new() && typ.lane_count() != value_type.lane_count() {1124errors.fatal((1125inst,1126"Byte order specifier required for bitcast instruction changing lane count",1127))1128} else {1129Ok(())1130}1131}11321133fn verify_constant_size(1134&self,1135inst: Inst,1136opcode: Opcode,1137constant: Constant,1138errors: &mut VerifierErrors,1139) -> VerifierStepResult {1140let type_size = match opcode {1141Opcode::F128const => types::F128.bytes(),1142Opcode::Vconst => self.func.dfg.ctrl_typevar(inst).bytes(),1143_ => unreachable!("unexpected opcode {opcode:?}"),1144} as usize;1145let constant_size = self.func.dfg.constants.get(constant).len();1146if type_size != constant_size {1147errors.fatal((1148inst,1149format!(1150"The instruction expects {constant} to have a size of {type_size} bytes but it has {constant_size}"1151),1152))1153} else {1154Ok(())1155}1156}11571158fn verify_is_address(1159&self,1160loc_inst: Inst,1161v: Value,1162errors: &mut VerifierErrors,1163) -> VerifierStepResult {1164if let Some(isa) = self.isa {1165let pointer_width = isa.triple().pointer_width()?;1166let value_type = self.func.dfg.value_type(v);1167let expected_width = pointer_width.bits() as u32;1168let value_width = value_type.bits();1169if expected_width != value_width {1170errors.nonfatal((1171loc_inst,1172self.context(loc_inst),1173format!("invalid pointer width (got {value_width}, expected {expected_width}) encountered {v}"),1174))1175} else {1176Ok(())1177}1178} else {1179Ok(())1180}1181}11821183fn domtree_integrity(1184&self,1185domtree: &DominatorTree,1186errors: &mut VerifierErrors,1187) -> VerifierStepResult {1188// We consider two `DominatorTree`s to be equal if they return the same immediate1189// dominator for each block. Therefore the current domtree is valid if it matches the freshly1190// computed one.1191for block in self.func.layout.blocks() {1192let expected = self.expected_domtree.idom(block);1193let got = domtree.idom(block);1194if got != expected {1195return errors.fatal((1196block,1197format!("invalid domtree, expected idom({block}) = {expected:?}, got {got:?}"),1198));1199}1200}1201// We also verify if the postorder defined by `DominatorTree` is sane1202if domtree.cfg_postorder().len() != self.expected_domtree.cfg_postorder().len() {1203return errors.fatal((1204AnyEntity::Function,1205"incorrect number of Blocks in postorder traversal",1206));1207}1208for (index, (&test_block, &true_block)) in domtree1209.cfg_postorder()1210.iter()1211.zip(self.expected_domtree.cfg_postorder().iter())1212.enumerate()1213{1214if test_block != true_block {1215return errors.fatal((1216test_block,1217format!(1218"invalid domtree, postorder block number {index} should be {true_block}, got {test_block}"1219),1220));1221}1222}1223Ok(())1224}12251226fn typecheck_entry_block_params(&self, errors: &mut VerifierErrors) -> VerifierStepResult {1227if let Some(block) = self.func.layout.entry_block() {1228let expected_types = &self.func.signature.params;1229let block_param_count = self.func.dfg.num_block_params(block);12301231if block_param_count != expected_types.len() {1232return errors.fatal((1233block,1234format!(1235"entry block parameters ({}) must match function signature ({})",1236block_param_count,1237expected_types.len()1238),1239));1240}12411242for (i, &arg) in self.func.dfg.block_params(block).iter().enumerate() {1243let arg_type = self.func.dfg.value_type(arg);1244if arg_type != expected_types[i].value_type {1245errors.report((1246block,1247format!(1248"entry block parameter {} expected to have type {}, got {}",1249i, expected_types[i], arg_type1250),1251));1252}1253}1254}12551256errors.as_result()1257}12581259fn check_entry_not_cold(&self, errors: &mut VerifierErrors) -> VerifierStepResult {1260if let Some(entry_block) = self.func.layout.entry_block() {1261if self.func.layout.is_cold(entry_block) {1262return errors1263.fatal((entry_block, format!("entry block cannot be marked as cold")));1264}1265}1266errors.as_result()1267}12681269fn typecheck(&self, inst: Inst, errors: &mut VerifierErrors) -> VerifierStepResult {1270let inst_data = &self.func.dfg.insts[inst];1271let constraints = inst_data.opcode().constraints();12721273let ctrl_type = if let Some(value_typeset) = constraints.ctrl_typeset() {1274// For polymorphic opcodes, determine the controlling type variable first.1275let ctrl_type = self.func.dfg.ctrl_typevar(inst);12761277if !value_typeset.contains(ctrl_type) {1278errors.report((1279inst,1280self.context(inst),1281format!(1282"has an invalid controlling type {ctrl_type} (allowed set is {value_typeset:?})"1283),1284));1285}12861287ctrl_type1288} else {1289// Non-polymorphic instructions don't check the controlling type variable, so `Option`1290// is unnecessary and we can just make it `INVALID`.1291types::INVALID1292};12931294// Typechecking instructions is never fatal1295let _ = self.typecheck_results(inst, ctrl_type, errors);1296let _ = self.typecheck_fixed_args(inst, ctrl_type, errors);1297let _ = self.typecheck_variable_args(inst, errors);1298let _ = self.typecheck_return(inst, errors);1299let _ = self.typecheck_special(inst, errors);13001301Ok(())1302}13031304fn typecheck_results(1305&self,1306inst: Inst,1307ctrl_type: Type,1308errors: &mut VerifierErrors,1309) -> VerifierStepResult {1310let mut i = 0;1311for &result in self.func.dfg.inst_results(inst) {1312let result_type = self.func.dfg.value_type(result);1313let expected_type = self.func.dfg.compute_result_type(inst, i, ctrl_type);1314if let Some(expected_type) = expected_type {1315if result_type != expected_type {1316errors.report((1317inst,1318self.context(inst),1319format!(1320"expected result {i} ({result}) to have type {expected_type}, found {result_type}"1321),1322));1323}1324} else {1325return errors.nonfatal((1326inst,1327self.context(inst),1328"has more result values than expected",1329));1330}1331i += 1;1332}13331334// There aren't any more result types left.1335if self.func.dfg.compute_result_type(inst, i, ctrl_type) != None {1336return errors.nonfatal((1337inst,1338self.context(inst),1339"has fewer result values than expected",1340));1341}1342Ok(())1343}13441345fn typecheck_fixed_args(1346&self,1347inst: Inst,1348ctrl_type: Type,1349errors: &mut VerifierErrors,1350) -> VerifierStepResult {1351let constraints = self.func.dfg.insts[inst].opcode().constraints();13521353for (i, &arg) in self.func.dfg.inst_fixed_args(inst).iter().enumerate() {1354let arg_type = self.func.dfg.value_type(arg);1355match constraints.value_argument_constraint(i, ctrl_type) {1356ResolvedConstraint::Bound(expected_type) => {1357if arg_type != expected_type {1358errors.report((1359inst,1360self.context(inst),1361format!(1362"arg {i} ({arg}) has type {arg_type}, expected {expected_type}"1363),1364));1365}1366}1367ResolvedConstraint::Free(type_set) => {1368if !type_set.contains(arg_type) {1369errors.report((1370inst,1371self.context(inst),1372format!(1373"arg {i} ({arg}) with type {arg_type} failed to satisfy type set {type_set:?}"1374),1375));1376}1377}1378}1379}1380Ok(())1381}13821383/// Typecheck both instructions that contain variable arguments like calls, and those that1384/// include references to basic blocks with their arguments.1385fn typecheck_variable_args(1386&self,1387inst: Inst,1388errors: &mut VerifierErrors,1389) -> VerifierStepResult {1390match &self.func.dfg.insts[inst] {1391ir::InstructionData::Jump { destination, .. } => {1392self.typecheck_block_call(inst, destination, BlockCallTargetType::Normal, errors)?;1393}1394ir::InstructionData::Brif {1395blocks: [block_then, block_else],1396..1397} => {1398self.typecheck_block_call(inst, block_then, BlockCallTargetType::Normal, errors)?;1399self.typecheck_block_call(inst, block_else, BlockCallTargetType::Normal, errors)?;1400}1401ir::InstructionData::BranchTable { table, .. } => {1402for block in self.func.stencil.dfg.jump_tables[*table].all_branches() {1403self.typecheck_block_call(inst, block, BlockCallTargetType::Normal, errors)?;1404}1405}1406ir::InstructionData::TryCall { exception, .. }1407| ir::InstructionData::TryCallIndirect { exception, .. } => {1408let exdata = &self.func.dfg.exception_tables[*exception];1409self.typecheck_block_call(1410inst,1411exdata.normal_return(),1412BlockCallTargetType::ExNormalRet,1413errors,1414)?;1415for item in exdata.items() {1416match item {1417ExceptionTableItem::Tag(_, block_call)1418| ExceptionTableItem::Default(block_call) => {1419self.typecheck_block_call(1420inst,1421&block_call,1422BlockCallTargetType::Exception,1423errors,1424)?;1425}1426ExceptionTableItem::Context(_) => {}1427}1428}1429}1430inst => debug_assert!(!inst.opcode().is_branch()),1431}14321433match self.func.dfg.insts[inst]1434.analyze_call(&self.func.dfg.value_lists, &self.func.dfg.exception_tables)1435{1436CallInfo::Direct(func_ref, args) => {1437let sig_ref = self.func.dfg.ext_funcs[func_ref].signature;1438let arg_types = self.func.dfg.signatures[sig_ref]1439.params1440.iter()1441.map(|a| a.value_type);1442self.typecheck_variable_args_iterator(inst, arg_types, args, errors)?;1443}1444CallInfo::DirectWithSig(func_ref, sig_ref, args) => {1445let expected_sig_ref = self.func.dfg.ext_funcs[func_ref].signature;1446let sigdata = &self.func.dfg.signatures;1447// Compare signatures by value, not by ID -- any1448// equivalent signature ID is acceptable.1449if sigdata[sig_ref] != sigdata[expected_sig_ref] {1450errors.nonfatal((1451inst,1452self.context(inst),1453format!(1454"exception table signature {sig_ref} did not match function {func_ref}'s signature {expected_sig_ref}"1455),1456))?;1457}1458let arg_types = self.func.dfg.signatures[sig_ref]1459.params1460.iter()1461.map(|a| a.value_type);1462self.typecheck_variable_args_iterator(inst, arg_types, args, errors)?;1463}1464CallInfo::Indirect(sig_ref, args) => {1465let arg_types = self.func.dfg.signatures[sig_ref]1466.params1467.iter()1468.map(|a| a.value_type);1469self.typecheck_variable_args_iterator(inst, arg_types, args, errors)?;1470}1471CallInfo::NotACall => {}1472}1473Ok(())1474}14751476fn typecheck_block_call(1477&self,1478inst: Inst,1479block: &ir::BlockCall,1480target_type: BlockCallTargetType,1481errors: &mut VerifierErrors,1482) -> VerifierStepResult {1483let pool = &self.func.dfg.value_lists;1484let block_params = self.func.dfg.block_params(block.block(pool));1485let args = block.args(pool);1486if args.len() != block_params.len() {1487return errors.nonfatal((1488inst,1489self.context(inst),1490format!(1491"mismatched argument count for `{}`: got {}, expected {}",1492self.func.dfg.display_inst(inst),1493args.len(),1494block_params.len(),1495),1496));1497}1498for (arg, param) in args.zip(block_params.iter()) {1499let Some(arg_ty) = self.block_call_arg_ty(arg, inst, target_type, errors)? else {1500continue;1501};1502let param_ty = self.func.dfg.value_type(*param);1503if arg_ty != param_ty {1504errors.nonfatal((1505inst,1506self.context(inst),1507format!("arg {arg} has type {arg_ty}, expected {param_ty}"),1508))?;1509}1510}1511Ok(())1512}15131514fn block_call_arg_ty(1515&self,1516arg: BlockArg,1517inst: Inst,1518target_type: BlockCallTargetType,1519errors: &mut VerifierErrors,1520) -> Result<Option<Type>, ()> {1521match arg {1522BlockArg::Value(v) => Ok(Some(self.func.dfg.value_type(v))),1523BlockArg::TryCallRet(_) | BlockArg::TryCallExn(_) => {1524// Get the invoked signature.1525let et = match self.func.dfg.insts[inst].exception_table() {1526Some(et) => et,1527None => {1528errors.fatal((1529inst,1530self.context(inst),1531format!(1532"`retN` block argument in block-call not on `try_call` instruction"1533),1534))?;1535unreachable!()1536}1537};1538let exdata = &self.func.dfg.exception_tables[et];1539let sig = &self.func.dfg.signatures[exdata.signature()];15401541match (arg, target_type) {1542(BlockArg::TryCallRet(i), BlockCallTargetType::ExNormalRet)1543if (i as usize) < sig.returns.len() =>1544{1545Ok(Some(sig.returns[i as usize].value_type))1546}1547(BlockArg::TryCallRet(_), BlockCallTargetType::ExNormalRet) => {1548errors.fatal((1549inst,1550self.context(inst),1551format!("out-of-bounds `retN` block argument"),1552))?;1553unreachable!()1554}1555(BlockArg::TryCallRet(_), _) => {1556errors.fatal((1557inst,1558self.context(inst),1559format!("`retN` block argument used outside normal-return target of `try_call`"),1560))?;1561unreachable!()1562}1563(BlockArg::TryCallExn(i), BlockCallTargetType::Exception) => {1564if let Some(isa) = self.isa {1565match sig1566.call_conv1567.exception_payload_types(isa.pointer_type())1568.get(i as usize)1569{1570Some(ty) => Ok(Some(*ty)),1571None => {1572errors.fatal((1573inst,1574self.context(inst),1575format!("out-of-bounds `exnN` block argument"),1576))?;1577unreachable!()1578}1579}1580} else {1581Ok(None)1582}1583}1584(BlockArg::TryCallExn(_), _) => {1585errors.fatal((1586inst,1587self.context(inst),1588format!("`exnN` block argument used outside normal-return target of `try_call`"),1589))?;1590unreachable!()1591}1592_ => unreachable!(),1593}1594}1595}1596}15971598fn typecheck_variable_args_iterator(1599&self,1600inst: Inst,1601iter: impl ExactSizeIterator<Item = Type>,1602variable_args: &[Value],1603errors: &mut VerifierErrors,1604) -> VerifierStepResult {1605let mut i = 0;16061607for expected_type in iter {1608if i >= variable_args.len() {1609// Result count mismatch handled below, we want the full argument count first though1610i += 1;1611continue;1612}1613let arg = variable_args[i];1614let arg_type = self.func.dfg.value_type(arg);1615if expected_type != arg_type {1616errors.report((1617inst,1618self.context(inst),1619format!(1620"arg {} ({}) has type {}, expected {}",1621i, variable_args[i], arg_type, expected_type1622),1623));1624}1625i += 1;1626}1627if i != variable_args.len() {1628return errors.nonfatal((1629inst,1630self.context(inst),1631format!(1632"mismatched argument count for `{}`: got {}, expected {}",1633self.func.dfg.display_inst(inst),1634variable_args.len(),1635i,1636),1637));1638}1639Ok(())1640}16411642fn typecheck_return(&self, inst: Inst, errors: &mut VerifierErrors) -> VerifierStepResult {1643match self.func.dfg.insts[inst] {1644ir::InstructionData::MultiAry {1645opcode: Opcode::Return,1646args,1647} => {1648let types = args1649.as_slice(&self.func.dfg.value_lists)1650.iter()1651.map(|v| self.func.dfg.value_type(*v));1652self.typecheck_return_types(1653inst,1654types,1655errors,1656"arguments of return must match function signature",1657)?;1658}1659ir::InstructionData::Call {1660opcode: Opcode::ReturnCall,1661func_ref,1662..1663} => {1664let sig_ref = self.func.dfg.ext_funcs[func_ref].signature;1665self.typecheck_tail_call(inst, sig_ref, errors)?;1666}1667ir::InstructionData::CallIndirect {1668opcode: Opcode::ReturnCallIndirect,1669sig_ref,1670..1671} => {1672self.typecheck_tail_call(inst, sig_ref, errors)?;1673}1674inst => debug_assert!(!inst.opcode().is_return()),1675}1676Ok(())1677}16781679fn typecheck_tail_call(1680&self,1681inst: Inst,1682sig_ref: SigRef,1683errors: &mut VerifierErrors,1684) -> VerifierStepResult {1685let signature = &self.func.dfg.signatures[sig_ref];1686let cc = signature.call_conv;1687if !cc.supports_tail_calls() {1688errors.report((1689inst,1690self.context(inst),1691format!("calling convention `{cc}` does not support tail calls"),1692));1693}1694if cc != self.func.signature.call_conv {1695errors.report((1696inst,1697self.context(inst),1698"callee's calling convention must match caller",1699));1700}1701let types = signature.returns.iter().map(|param| param.value_type);1702self.typecheck_return_types(inst, types, errors, "results of callee must match caller")?;1703Ok(())1704}17051706fn typecheck_return_types(1707&self,1708inst: Inst,1709actual_types: impl ExactSizeIterator<Item = Type>,1710errors: &mut VerifierErrors,1711message: &str,1712) -> VerifierStepResult {1713let expected_types = &self.func.signature.returns;1714if actual_types.len() != expected_types.len() {1715return errors.nonfatal((inst, self.context(inst), message));1716}1717for (i, (actual_type, &expected_type)) in actual_types.zip(expected_types).enumerate() {1718if actual_type != expected_type.value_type {1719errors.report((1720inst,1721self.context(inst),1722format!(1723"result {i} has type {actual_type}, must match function signature of \1724{expected_type}"1725),1726));1727}1728}1729Ok(())1730}17311732// Check special-purpose type constraints that can't be expressed in the normal opcode1733// constraints.1734fn typecheck_special(&self, inst: Inst, errors: &mut VerifierErrors) -> VerifierStepResult {1735match self.func.dfg.insts[inst] {1736ir::InstructionData::UnaryGlobalValue { global_value, .. } => {1737if let Some(isa) = self.isa {1738let inst_type = self.func.dfg.value_type(self.func.dfg.first_result(inst));1739let global_type = self.func.global_values[global_value].global_type(isa);1740if inst_type != global_type {1741return errors.nonfatal((1742inst, self.context(inst),1743format!(1744"global_value instruction with type {inst_type} references global value with type {global_type}"1745)),1746);1747}1748}1749}1750_ => {}1751}1752Ok(())1753}17541755fn cfg_integrity(1756&self,1757cfg: &ControlFlowGraph,1758errors: &mut VerifierErrors,1759) -> VerifierStepResult {1760let mut expected_succs = BTreeSet::<Block>::new();1761let mut got_succs = BTreeSet::<Block>::new();1762let mut expected_preds = BTreeSet::<Inst>::new();1763let mut got_preds = BTreeSet::<Inst>::new();17641765for block in self.func.layout.blocks() {1766expected_succs.extend(self.expected_cfg.succ_iter(block));1767got_succs.extend(cfg.succ_iter(block));17681769let missing_succs: Vec<Block> =1770expected_succs.difference(&got_succs).cloned().collect();1771if !missing_succs.is_empty() {1772errors.report((1773block,1774format!("cfg lacked the following successor(s) {missing_succs:?}"),1775));1776continue;1777}17781779let excess_succs: Vec<Block> = got_succs.difference(&expected_succs).cloned().collect();1780if !excess_succs.is_empty() {1781errors.report((1782block,1783format!("cfg had unexpected successor(s) {excess_succs:?}"),1784));1785continue;1786}17871788expected_preds.extend(1789self.expected_cfg1790.pred_iter(block)1791.map(|BlockPredecessor { inst, .. }| inst),1792);1793got_preds.extend(1794cfg.pred_iter(block)1795.map(|BlockPredecessor { inst, .. }| inst),1796);17971798let missing_preds: Vec<Inst> = expected_preds.difference(&got_preds).cloned().collect();1799if !missing_preds.is_empty() {1800errors.report((1801block,1802format!("cfg lacked the following predecessor(s) {missing_preds:?}"),1803));1804continue;1805}18061807let excess_preds: Vec<Inst> = got_preds.difference(&expected_preds).cloned().collect();1808if !excess_preds.is_empty() {1809errors.report((1810block,1811format!("cfg had unexpected predecessor(s) {excess_preds:?}"),1812));1813continue;1814}18151816expected_succs.clear();1817got_succs.clear();1818expected_preds.clear();1819got_preds.clear();1820}1821errors.as_result()1822}18231824fn immediate_constraints(&self, inst: Inst, errors: &mut VerifierErrors) -> VerifierStepResult {1825let inst_data = &self.func.dfg.insts[inst];18261827match *inst_data {1828ir::InstructionData::Store { flags, .. } => {1829if flags.readonly() {1830errors.fatal((1831inst,1832self.context(inst),1833"A store instruction cannot have the `readonly` MemFlag",1834))1835} else {1836Ok(())1837}1838}1839ir::InstructionData::BinaryImm8 {1840opcode: ir::instructions::Opcode::Extractlane,1841imm: lane,1842arg,1843..1844}1845| ir::InstructionData::TernaryImm8 {1846opcode: ir::instructions::Opcode::Insertlane,1847imm: lane,1848args: [arg, _],1849..1850} => {1851// We must be specific about the opcodes above because other instructions are using1852// the same formats.1853let ty = self.func.dfg.value_type(arg);1854if lane as u32 >= ty.lane_count() {1855errors.fatal((1856inst,1857self.context(inst),1858format!("The lane {lane} does not index into the type {ty}",),1859))1860} else {1861Ok(())1862}1863}1864ir::InstructionData::Shuffle {1865opcode: ir::instructions::Opcode::Shuffle,1866imm,1867..1868} => {1869let imm = self.func.dfg.immediates.get(imm).unwrap().as_slice();1870if imm.len() != 16 {1871errors.fatal((1872inst,1873self.context(inst),1874format!("the shuffle immediate wasn't 16-bytes long"),1875))1876} else if let Some(i) = imm.iter().find(|i| **i >= 32) {1877errors.fatal((1878inst,1879self.context(inst),1880format!("shuffle immediate index {i} is larger than the maximum 31"),1881))1882} else {1883Ok(())1884}1885}1886_ => Ok(()),1887}1888}18891890fn iconst_bounds(&self, inst: Inst, errors: &mut VerifierErrors) -> VerifierStepResult {1891use crate::ir::instructions::InstructionData::UnaryImm;18921893let inst_data = &self.func.dfg.insts[inst];1894if let UnaryImm {1895opcode: Opcode::Iconst,1896imm,1897} = inst_data1898{1899let ctrl_typevar = self.func.dfg.ctrl_typevar(inst);1900let bounds_mask = match ctrl_typevar {1901types::I8 => u8::MAX.into(),1902types::I16 => u16::MAX.into(),1903types::I32 => u32::MAX.into(),1904types::I64 => u64::MAX,1905_ => unreachable!(),1906};19071908let value = imm.bits() as u64;1909if value & bounds_mask != value {1910errors.fatal((1911inst,1912self.context(inst),1913"constant immediate is out of bounds",1914))1915} else {1916Ok(())1917}1918} else {1919Ok(())1920}1921}19221923fn typecheck_function_signature(&self, errors: &mut VerifierErrors) -> VerifierStepResult {1924let params = self1925.func1926.signature1927.params1928.iter()1929.enumerate()1930.map(|p| (true, p));1931let returns = self1932.func1933.signature1934.returns1935.iter()1936.enumerate()1937.map(|p| (false, p));19381939for (is_argument, (i, param)) in params.chain(returns) {1940let is_return = !is_argument;1941let item = if is_argument {1942"Parameter"1943} else {1944"Return value"1945};19461947if param.value_type == types::INVALID {1948errors.report((1949AnyEntity::Function,1950format!("{item} at position {i} has an invalid type"),1951));1952}19531954if let ArgumentPurpose::StructArgument(_) = param.purpose {1955if is_return {1956errors.report((1957AnyEntity::Function,1958format!("{item} at position {i} can't be an struct argument"),1959))1960}1961}19621963let ty_allows_extension = param.value_type.is_int();1964let has_extension = param.extension != ArgumentExtension::None;1965if !ty_allows_extension && has_extension {1966errors.report((1967AnyEntity::Function,1968format!(1969"{} at position {} has invalid extension {:?}",1970item, i, param.extension1971),1972));1973}1974}19751976if errors.has_error() { Err(()) } else { Ok(()) }1977}19781979fn verify_try_call_handler_index(1980&self,1981inst: Inst,1982block: Block,1983index_imm: i64,1984errors: &mut VerifierErrors,1985) -> VerifierStepResult {1986if index_imm < 0 {1987return errors.fatal((1988inst,1989format!("exception handler index {index_imm} cannot be negative"),1990));1991}1992let Ok(index) = usize::try_from(index_imm) else {1993return errors.fatal((1994inst,1995format!("exception handler index {index_imm} is out-of-range"),1996));1997};1998let Some(terminator) = self.func.layout.last_inst(block) else {1999return errors.fatal((2000inst,2001format!("referenced block {block} does not have a terminator"),2002));2003};2004let Some(et) = self.func.dfg.insts[terminator].exception_table() else {2005return errors.fatal((2006inst,2007format!("referenced block {block} does not end in a try_call"),2008));2009};20102011let etd = &self.func.dfg.exception_tables[et];2012// The exception table's out-edges consist of all exceptional2013// edges first, followed by the normal return last. For N2014// out-edges, there are N-1 exception handlers that can be2015// selected.2016let num_exceptional_edges = etd.all_branches().len() - 1;2017if index >= num_exceptional_edges {2018return errors.fatal((2019inst,2020format!("exception handler index {index_imm} is out-of-range"),2021));2022}20232024Ok(())2025}20262027pub fn run(&self, errors: &mut VerifierErrors) -> VerifierStepResult {2028self.verify_global_values(errors)?;2029self.verify_memory_types(errors)?;2030self.typecheck_entry_block_params(errors)?;2031self.check_entry_not_cold(errors)?;2032self.typecheck_function_signature(errors)?;20332034for block in self.func.layout.blocks() {2035if self.func.layout.first_inst(block).is_none() {2036return errors.fatal((block, format!("{block} cannot be empty")));2037}2038for inst in self.func.layout.block_insts(block) {2039crate::trace!("verifying {inst:?}: {}", self.func.dfg.display_inst(inst));2040self.block_integrity(block, inst, errors)?;2041self.instruction_integrity(inst, errors)?;2042self.typecheck(inst, errors)?;2043self.immediate_constraints(inst, errors)?;2044self.iconst_bounds(inst, errors)?;2045}20462047self.encodable_as_bb(block, errors)?;2048}20492050if !errors.is_empty() {2051log::warn!(2052"Found verifier errors in function:\n{}",2053pretty_verifier_error(self.func, None, errors.clone())2054);2055}20562057Ok(())2058}2059}20602061#[cfg(test)]2062mod tests {2063use super::{Verifier, VerifierError, VerifierErrors};2064use crate::ir::instructions::{InstructionData, Opcode};2065use crate::ir::{AbiParam, Function, Type, types};2066use crate::settings;20672068macro_rules! assert_err_with_msg {2069($e:expr, $msg:expr) => {2070match $e.0.get(0) {2071None => panic!("Expected an error"),2072Some(&VerifierError { ref message, .. }) => {2073if !message.contains($msg) {2074#[cfg(feature = "std")]2075panic!("'{}' did not contain the substring '{}'", message, $msg);2076#[cfg(not(feature = "std"))]2077panic!("error message did not contain the expected substring");2078}2079}2080}2081};2082}20832084#[test]2085fn empty() {2086let func = Function::new();2087let flags = &settings::Flags::new(settings::builder());2088let verifier = Verifier::new(&func, flags.into());2089let mut errors = VerifierErrors::default();20902091assert_eq!(verifier.run(&mut errors), Ok(()));2092assert!(errors.0.is_empty());2093}20942095#[test]2096fn bad_instruction_format() {2097let mut func = Function::new();2098let block0 = func.dfg.make_block();2099func.layout.append_block(block0);2100let nullary_with_bad_opcode = func.dfg.make_inst(InstructionData::UnaryImm {2101opcode: Opcode::F32const,2102imm: 0.into(),2103});2104func.layout.append_inst(nullary_with_bad_opcode, block0);2105let destination = func.dfg.block_call(block0, &[]);2106func.stencil.layout.append_inst(2107func.stencil.dfg.make_inst(InstructionData::Jump {2108opcode: Opcode::Jump,2109destination,2110}),2111block0,2112);2113let flags = &settings::Flags::new(settings::builder());2114let verifier = Verifier::new(&func, flags.into());2115let mut errors = VerifierErrors::default();21162117let _ = verifier.run(&mut errors);21182119assert_err_with_msg!(errors, "instruction format");2120}21212122fn test_iconst_bounds(immediate: i64, ctrl_typevar: Type) -> VerifierErrors {2123let mut func = Function::new();2124let block0 = func.dfg.make_block();2125func.layout.append_block(block0);21262127let test_inst = func.dfg.make_inst(InstructionData::UnaryImm {2128opcode: Opcode::Iconst,2129imm: immediate.into(),2130});21312132let end_inst = func.dfg.make_inst(InstructionData::MultiAry {2133opcode: Opcode::Return,2134args: Default::default(),2135});21362137func.dfg.make_inst_results(test_inst, ctrl_typevar);2138func.layout.append_inst(test_inst, block0);2139func.layout.append_inst(end_inst, block0);21402141let flags = &settings::Flags::new(settings::builder());2142let verifier = Verifier::new(&func, flags.into());2143let mut errors = VerifierErrors::default();21442145let _ = verifier.run(&mut errors);2146errors2147}21482149fn test_iconst_bounds_err(immediate: i64, ctrl_typevar: Type) {2150assert_err_with_msg!(2151test_iconst_bounds(immediate, ctrl_typevar),2152"constant immediate is out of bounds"2153);2154}21552156fn test_iconst_bounds_ok(immediate: i64, ctrl_typevar: Type) {2157assert!(test_iconst_bounds(immediate, ctrl_typevar).is_empty());2158}21592160#[test]2161fn negative_iconst_8() {2162test_iconst_bounds_err(-10, types::I8);2163}21642165#[test]2166fn negative_iconst_32() {2167test_iconst_bounds_err(-1, types::I32);2168}21692170#[test]2171fn large_iconst_8() {2172test_iconst_bounds_err(1 + u8::MAX as i64, types::I8);2173}21742175#[test]2176fn large_iconst_16() {2177test_iconst_bounds_err(10 + u16::MAX as i64, types::I16);2178}21792180#[test]2181fn valid_iconst_8() {2182test_iconst_bounds_ok(10, types::I8);2183}21842185#[test]2186fn valid_iconst_32() {2187test_iconst_bounds_ok(u32::MAX as i64, types::I32);2188}21892190#[test]2191fn test_function_invalid_param() {2192let mut func = Function::new();2193func.signature.params.push(AbiParam::new(types::INVALID));21942195let mut errors = VerifierErrors::default();2196let flags = &settings::Flags::new(settings::builder());2197let verifier = Verifier::new(&func, flags.into());21982199let _ = verifier.typecheck_function_signature(&mut errors);2200assert_err_with_msg!(errors, "Parameter at position 0 has an invalid type");2201}22022203#[test]2204fn test_function_invalid_return_value() {2205let mut func = Function::new();2206func.signature.returns.push(AbiParam::new(types::INVALID));22072208let mut errors = VerifierErrors::default();2209let flags = &settings::Flags::new(settings::builder());2210let verifier = Verifier::new(&func, flags.into());22112212let _ = verifier.typecheck_function_signature(&mut errors);2213assert_err_with_msg!(errors, "Return value at position 0 has an invalid type");2214}22152216#[test]2217fn test_printing_contextual_errors() {2218// Build function.2219let mut func = Function::new();2220let block0 = func.dfg.make_block();2221func.layout.append_block(block0);22222223// Build instruction "f64const 0.0" (missing one required result)2224let inst = func.dfg.make_inst(InstructionData::UnaryIeee64 {2225opcode: Opcode::F64const,2226imm: 0.0.into(),2227});2228func.layout.append_inst(inst, block0);22292230// Setup verifier.2231let mut errors = VerifierErrors::default();2232let flags = &settings::Flags::new(settings::builder());2233let verifier = Verifier::new(&func, flags.into());22342235// Now the error message, when printed, should contain the instruction sequence causing the2236// error (i.e. f64const 0.0) and not only its entity value (i.e. inst0)2237let _ = verifier.typecheck_results(inst, types::I32, &mut errors);2238assert_eq!(2239format!("{}", errors.0[0]),2240"inst0 (f64const 0.0): has fewer result values than expected"2241)2242}22432244#[test]2245fn test_empty_block() {2246let mut func = Function::new();2247let block0 = func.dfg.make_block();2248func.layout.append_block(block0);22492250let flags = &settings::Flags::new(settings::builder());2251let verifier = Verifier::new(&func, flags.into());2252let mut errors = VerifierErrors::default();2253let _ = verifier.run(&mut errors);22542255assert_err_with_msg!(errors, "block0 cannot be empty");2256}2257}225822592260