use alloc::string::String;
use core::fmt;
use crate::entity::{AggId, Block, FuncId, GlobalId, Inst, SigId, StackSlot, Value, Variable};
use crate::ops::PolicyFields;
use crate::types::Type;
#[derive(Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum ModuleError {
DuplicateName {
name: String,
},
EmptyName,
UnknownFunction {
func: FuncId,
},
UnknownGlobal {
global: GlobalId,
},
UnknownAggregate {
agg: AggId,
},
AggregateTooLarge,
BadAlignment {
align: u64,
},
InitSizeMismatch {
size: u64,
found: u64,
},
RelocOutOfRange {
offset: u64,
},
ImportWithData,
CannotDefineImport {
func: FuncId,
},
AlreadyDefined {
func: FuncId,
},
NotDefined {
func: FuncId,
},
CapacityExceeded,
}
impl fmt::Display for ModuleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ModuleError::DuplicateName { name } => write!(f, "symbol `{name}` is already declared"),
ModuleError::EmptyName => f.write_str("symbol names must not be empty"),
ModuleError::UnknownFunction { func } => write!(f, "unknown function {func}"),
ModuleError::UnknownGlobal { global } => write!(f, "unknown global {global}"),
ModuleError::UnknownAggregate { agg } => {
write!(f, "aggregate {agg} is not declared before its use")
}
ModuleError::AggregateTooLarge => f.write_str("aggregate size exceeds 2^48 bytes"),
ModuleError::BadAlignment { align } => {
write!(f, "alignment {align} is not a supported power of two")
}
ModuleError::InitSizeMismatch { size, found } => {
write!(
f,
"initializer has {found} bytes but the global is {size} bytes"
)
}
ModuleError::RelocOutOfRange { offset } => {
write!(
f,
"relocation at offset {offset} does not fit in its global"
)
}
ModuleError::ImportWithData => {
f.write_str("an imported global cannot have an initializer or relocations")
}
ModuleError::CannotDefineImport { func } => {
write!(f, "function {func} is imported and cannot have a body")
}
ModuleError::AlreadyDefined { func } => write!(f, "function {func} already has a body"),
ModuleError::NotDefined { func } => write!(f, "function {func} has no body"),
ModuleError::CapacityExceeded => f.write_str("module table capacity exceeded"),
}
}
}
impl core::error::Error for ModuleError {}
#[derive(Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum ValidationErrorKind {
DuplicateName {
name: String,
},
EmptyName,
UnknownSignature {
sig: SigId,
},
UnknownFunction {
func: FuncId,
},
UnknownGlobal {
global: GlobalId,
},
UnknownAggregate {
agg: AggId,
},
BadAggregate {
agg: AggId,
},
BadAlignment {
align: u64,
},
BadGlobal {
global: GlobalId,
},
MissingBody,
ImportWithBody,
BadEntry,
EntryParams {
expected: usize,
found: usize,
},
MissingTerminator,
BadBlockTarget {
target: Block,
},
EntryBranchTarget,
CorruptLayout,
ValueOutOfRange {
value: Value,
},
InconsistentDefinition {
value: Value,
},
DefinedTwice {
value: Value,
},
UseOfDetached {
value: Value,
},
BadAlias {
value: Value,
},
UseBeforeDef {
value: Value,
},
BadSlot {
slot: StackSlot,
},
TypeMismatch {
value: Option<Value>,
expected: Type,
found: Type,
},
BadOperandType {
op: &'static str,
found: Type,
},
BadConversion {
op: &'static str,
from: Type,
to: Type,
},
PolicyMismatch {
op: &'static str,
required: PolicyFields,
},
ErrorOutsideCheck {
op: &'static str,
},
NotFallible {
op: &'static str,
},
BadConst {
ty: Type,
bits: u64,
},
BadField {
agg: AggId,
field: u32,
},
BadOrdering,
ResultMismatch,
ArgCount {
expected: usize,
found: usize,
},
EdgeArgCount {
target: Block,
expected: usize,
found: usize,
},
BadSwitchCase {
value: u64,
},
BadTailCall,
BadGcList,
BadEdgeArg,
LandingPadPredecessor {
block: Block,
},
UnwindInNounwind,
ReturnInNoreturn,
SafepointInNogc,
LiveAcrossSafepoint {
value: Value,
},
DerivedPointerEscapes {
value: Value,
},
}
impl fmt::Display for ValidationErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use ValidationErrorKind as K;
match self {
K::DuplicateName { name } => write!(f, "symbol `{name}` is declared twice"),
K::EmptyName => f.write_str("a symbol name is empty"),
K::UnknownSignature { sig } => write!(f, "unknown signature {sig}"),
K::UnknownFunction { func } => write!(f, "unknown function {func}"),
K::UnknownGlobal { global } => write!(f, "unknown global {global}"),
K::UnknownAggregate { agg } => write!(f, "unknown or forward aggregate {agg}"),
K::BadAggregate { agg } => write!(f, "aggregate {agg} has an invalid layout"),
K::BadAlignment { align } => write!(f, "alignment {align} is not supported"),
K::BadGlobal { global } => write!(f, "global {global} has an invalid initializer"),
K::MissingBody => f.write_str("a defined function has no body"),
K::ImportWithBody => f.write_str("an imported function has a body"),
K::BadEntry => f.write_str("the entry block is missing"),
K::EntryParams { expected, found } => write!(
f,
"the entry block has {found} parameters but the signature has {expected}"
),
K::MissingTerminator => f.write_str("block has no terminator"),
K::BadBlockTarget { target } => write!(f, "edge targets missing block {target}"),
K::EntryBranchTarget => f.write_str("an edge targets the entry block"),
K::CorruptLayout => f.write_str("the block's instruction list is corrupt"),
K::ValueOutOfRange { value } => write!(f, "reference to nonexistent value {value}"),
K::InconsistentDefinition { value } => {
write!(f, "value {value} disagrees with its definition site")
}
K::DefinedTwice { value } => write!(f, "value {value} is defined twice"),
K::UseOfDetached { value } => write!(f, "value {value} is used but no longer defined"),
K::BadAlias { value } => write!(f, "value {value} has an invalid forwarding"),
K::UseBeforeDef { value } => {
write!(
f,
"value {value} is used where its definition does not dominate"
)
}
K::BadSlot { slot } => write!(f, "invalid stack slot {slot}"),
K::TypeMismatch {
value: Some(v),
expected,
found,
} => write!(f, "value {v} has type {found}, expected {expected}"),
K::TypeMismatch {
value: None,
expected,
found,
} => write!(f, "edge argument has type {found}, expected {expected}"),
K::BadOperandType { op, found } => write!(f, "`{op}` does not accept {found}"),
K::BadConversion { op, from, to } => write!(f, "`{op}` cannot convert {from} to {to}"),
K::PolicyMismatch { op, required } => {
write!(f, "`{op}` must carry exactly the policy fields {required}")
}
K::ErrorOutsideCheck { op } => write!(
f,
"`{op}` can raise an error and must be the operation of a `check` terminator"
),
K::NotFallible { op } => write!(f, "`check` holds `{op}`, which cannot raise an error"),
K::BadConst { ty, bits } => write!(f, "constant {bits:#x} does not fit {ty}"),
K::BadField { agg, field } => write!(f, "{agg} has no field {field}"),
K::BadOrdering => f.write_str("invalid memory ordering for this atomic operation"),
K::ResultMismatch => {
f.write_str("the instruction's results disagree with what it produces")
}
K::ArgCount { expected, found } => {
write!(f, "expected {expected} arguments, found {found}")
}
K::EdgeArgCount {
target,
expected,
found,
} => write!(
f,
"edge to {target} passes {found} arguments for {expected} parameters"
),
K::BadSwitchCase { value } => {
write!(f, "switch case {value} is repeated or out of range")
}
K::BadTailCall => {
f.write_str("tail call does not match this function's returns and convention")
}
K::BadGcList => f.write_str("invalid GC list"),
K::BadEdgeArg => f.write_str("edge argument not allowed on this edge"),
K::LandingPadPredecessor { block } => {
write!(f, "landing pad {block} is reached by a non-unwind edge")
}
K::UnwindInNounwind => f.write_str("a `nounwind` function may unwind"),
K::ReturnInNoreturn => f.write_str("a `noreturn` function returns"),
K::SafepointInNogc => f.write_str("a `nogc` function contains a safepoint"),
K::LiveAcrossSafepoint { value } => write!(
f,
"value {value} is a GC reference (or derived from one) live across a safepoint"
),
K::DerivedPointerEscapes { value } => {
write!(f, "pointer {value} derived from a GC reference escapes")
}
}
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct ValidationError {
pub(crate) func: Option<FuncId>,
pub(crate) block: Option<Block>,
pub(crate) inst: Option<Inst>,
pub(crate) kind: ValidationErrorKind,
}
impl ValidationError {
pub(crate) fn module(kind: ValidationErrorKind) -> ValidationError {
ValidationError {
func: None,
block: None,
inst: None,
kind,
}
}
#[must_use]
pub fn func(&self) -> Option<FuncId> {
self.func
}
#[must_use]
pub fn block(&self) -> Option<Block> {
self.block
}
#[must_use]
pub fn inst(&self) -> Option<Inst> {
self.inst
}
#[must_use]
pub fn kind(&self) -> &ValidationErrorKind {
&self.kind
}
}
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut sep = "";
if let Some(func) = self.func {
write!(f, "{sep}{func}")?;
sep = " ";
}
if let Some(block) = self.block {
write!(f, "{sep}{block}")?;
sep = " ";
}
if let Some(inst) = self.inst {
write!(f, "{sep}{inst}")?;
sep = " ";
}
if sep.is_empty() {
write!(f, "{}", self.kind)
} else {
write!(f, ": {}", self.kind)
}
}
}
impl core::error::Error for ValidationError {}
#[derive(Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum BuildError {
AlreadyTerminated {
block: Block,
},
UnknownBlock {
block: Block,
},
UnknownInst {
inst: Inst,
},
UnknownValue {
value: Value,
},
UnknownParam {
index: usize,
},
EntryBlock,
UnknownBlockParam {
block: Block,
index: usize,
},
Invalid(ValidationErrorKind),
CapacityExceeded,
UnknownVariable {
var: Variable,
},
SealedBlock {
block: Block,
},
NotBlockParam {
value: Value,
},
VariablesActive,
BudgetExceeded,
}
impl fmt::Display for BuildError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BuildError::AlreadyTerminated { block } => {
write!(f, "block {block} already has a terminator")
}
BuildError::UnknownBlock { block } => write!(f, "unknown or removed block {block}"),
BuildError::UnknownInst { inst } => write!(f, "unknown or removed instruction {inst}"),
BuildError::UnknownValue { value } => write!(f, "unknown value {value}"),
BuildError::UnknownParam { index } => write!(f, "no function parameter {index}"),
BuildError::EntryBlock => f.write_str("the entry block cannot be removed or split off"),
BuildError::UnknownBlockParam { block, index } => {
write!(f, "block {block} has no parameter {index}")
}
BuildError::Invalid(kind) => write!(f, "{kind}"),
BuildError::CapacityExceeded => f.write_str("function table capacity exceeded"),
BuildError::UnknownVariable { var } => write!(f, "unknown variable {var}"),
BuildError::SealedBlock { block } => {
write!(f, "block {block} is sealed and cannot get a new edge")
}
BuildError::NotBlockParam { value } => {
write!(f, "value {value} is not a block parameter")
}
BuildError::VariablesActive => {
f.write_str("not allowed while variable construction is in progress")
}
BuildError::BudgetExceeded => f.write_str("the operation's work budget was exceeded"),
}
}
}
impl core::error::Error for BuildError {}
impl From<ValidationErrorKind> for BuildError {
fn from(kind: ValidationErrorKind) -> Self {
BuildError::Invalid(kind)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
#[test]
fn test_validation_error_display_includes_location() {
let e = ValidationError {
func: Some(FuncId::from_u32(1)),
block: Some(Block::from_u32(2)),
inst: None,
kind: ValidationErrorKind::MissingTerminator,
};
assert_eq!(e.to_string(), "f1 b2: block has no terminator");
let m = ValidationError::module(ValidationErrorKind::EmptyName);
assert_eq!(m.to_string(), "a symbol name is empty");
}
#[test]
fn test_every_build_error_displays() {
let errs = [
BuildError::EntryBlock,
BuildError::CapacityExceeded,
BuildError::UnknownParam { index: 3 },
BuildError::Invalid(ValidationErrorKind::BadOrdering),
BuildError::UnknownVariable {
var: Variable::from_u32(1),
},
BuildError::SealedBlock {
block: Block::from_u32(1),
},
BuildError::NotBlockParam {
value: Value::from_u32(1),
},
BuildError::VariablesActive,
BuildError::BudgetExceeded,
];
for e in errs {
assert!(!e.to_string().is_empty());
}
}
}