Skip to content

Commit

Permalink
chore(ssa refactor): Implement acir_gen errors (#2071)
Browse files Browse the repository at this point in the history
* implement acir gen errors

* comment

* remove unwrap

* rename to internal error

* comment

* comment

* .

* .

* .

* review

* .

* chore: fix merge conflict

* chore: make multiplication of 2 witnesses more explicit

* Update crates/noirc_evaluator/src/errors.rs

---------

Co-authored-by: Tom French <[email protected]>
Co-authored-by: TomAFrench <[email protected]>
Co-authored-by: jfecher <[email protected]>
  • Loading branch information
4 people authored Jul 31, 2023
1 parent 8981c7d commit 9b417da
Show file tree
Hide file tree
Showing 7 changed files with 459 additions and 423 deletions.
219 changes: 86 additions & 133 deletions crates/noirc_evaluator/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,151 +1,104 @@
//! Noir Evaluator has two types of errors
//!
//! [RuntimeError]s that should be displayed to the user
//!
//! [InternalError]s that are used for checking internal logics of the SSA
//!
//! An Error of the former is a user Error
//!
//! An Error of the latter is an error in the implementation of the compiler
use acvm::FieldElement;
use noirc_errors::{CustomDiagnostic as Diagnostic, FileDiagnostic, Location};
use thiserror::Error;

#[derive(Debug)]
pub struct RuntimeError {
pub location: Option<Location>,
pub kind: RuntimeErrorKind,
}

impl RuntimeError {
// XXX: In some places, we strip the span because we do not want span to
// be introduced into the binary op or low level function code, for simplicity.
//
// It's possible to have it there, but it means we will need to proliferate the code with span
//
// This does make error reporting, less specific!
pub fn remove_span(self) -> RuntimeErrorKind {
self.kind
}

pub fn new(kind: RuntimeErrorKind, location: Option<Location>) -> RuntimeError {
RuntimeError { location, kind }
}

// Keep one of the two location which is Some, if possible
// This is used when we optimize instructions so that we do not lose track of location
pub fn merge_location(a: Option<Location>, b: Option<Location>) -> Option<Location> {
match (a, b) {
(Some(loc), _) | (_, Some(loc)) => Some(loc),
(None, None) => None,
}
}
#[derive(Debug, PartialEq, Eq, Clone, Error)]
pub enum RuntimeError {
// We avoid showing the actual lhs and rhs since most of the time they are just 0
// and 1 respectively. This would confuse users if a constraint such as
// assert(foo < bar) fails with "failed constraint: 0 = 1."
#[error("Failed constraint")]
FailedConstraint { lhs: FieldElement, rhs: FieldElement, location: Option<Location> },
#[error(transparent)]
InternalError(#[from] InternalError),
#[error("Index out of bounds, array has size {index:?}, but index was {array_size:?}")]
IndexOutOfBounds { index: usize, array_size: usize, location: Option<Location> },
#[error("All Witnesses are by default u{num_bits:?} Applying this type does not apply any constraints.\n We also currently do not allow integers of size more than {num_bits:?}, this will be handled by BigIntegers.")]
InvalidRangeConstraint { num_bits: u32, location: Option<Location> },
#[error("Expected array index to fit into a u64")]
TypeConversion { from: String, into: String, location: Option<Location> },
#[error("{name:?} is not initialized")]
UnInitialized { name: String, location: Option<Location> },
#[error("Integer sized {num_bits:?} is over the max supported size of {max_num_bits:?}")]
UnsupportedIntegerSize { num_bits: u32, max_num_bits: u32, location: Option<Location> },
}

impl From<RuntimeErrorKind> for RuntimeError {
fn from(kind: RuntimeErrorKind) -> RuntimeError {
RuntimeError { location: None, kind }
}
#[derive(Debug, PartialEq, Eq, Clone, Error)]
pub enum InternalError {
#[error("ICE: Both expressions should have degree<=1")]
DegreeNotReduced { location: Option<Location> },
#[error("Try to get element from empty array")]
EmptyArray { location: Option<Location> },
#[error("ICE: {message:?}")]
General { message: String, location: Option<Location> },
#[error("ICE: {name:?} missing {arg:?} arg")]
MissingArg { name: String, arg: String, location: Option<Location> },
#[error("ICE: {name:?} should be a constant")]
NotAConstant { name: String, location: Option<Location> },
#[error("{name:?} is not implemented yet")]
NotImplemented { name: String, location: Option<Location> },
#[error("ICE: Undeclared AcirVar")]
UndeclaredAcirVar { location: Option<Location> },
#[error("ICE: Expected {expected:?}, found {found:?}")]
UnExpected { expected: String, found: String, location: Option<Location> },
}

impl From<RuntimeError> for FileDiagnostic {
fn from(err: RuntimeError) -> Self {
let file_id = err.location.map(|loc| loc.file).unwrap();
FileDiagnostic { file_id, diagnostic: err.into() }
fn from(error: RuntimeError) -> Self {
match error {
RuntimeError::InternalError(ref ice_error) => match ice_error {
InternalError::DegreeNotReduced { location }
| InternalError::EmptyArray { location }
| InternalError::General { location, .. }
| InternalError::MissingArg { location, .. }
| InternalError::NotAConstant { location, .. }
| InternalError::NotImplemented { location, .. }
| InternalError::UndeclaredAcirVar { location }
| InternalError::UnExpected { location, .. } => {
let file_id = location.map(|loc| loc.file).unwrap();
FileDiagnostic { file_id, diagnostic: error.into() }
}
},
RuntimeError::FailedConstraint { location, .. }
| RuntimeError::IndexOutOfBounds { location, .. }
| RuntimeError::InvalidRangeConstraint { location, .. }
| RuntimeError::TypeConversion { location, .. }
| RuntimeError::UnInitialized { location, .. }
| RuntimeError::UnsupportedIntegerSize { location, .. } => {
let file_id = location.map(|loc| loc.file).unwrap();
FileDiagnostic { file_id, diagnostic: error.into() }
}
}
}
}

#[derive(Error, Debug)]
pub enum RuntimeErrorKind {
// Array errors
#[error("Out of bounds")]
ArrayOutOfBounds { index: u128, bound: u128 },

#[error("index out of bounds: the len is {index} but the index is {bound}")]
IndexOutOfBounds { index: u32, bound: u128 },

#[error("cannot call {func_name} function in non main function")]
FunctionNonMainContext { func_name: String },

// Environment errors
#[error("Cannot find Array")]
ArrayNotFound { found_type: String, name: String },

#[error("Not an object")]
NotAnObject,

#[error("Invalid id")]
InvalidId,

#[error("Attempt to divide by zero")]
DivisionByZero,

#[error("Failed range constraint when constraining to {0} bits")]
FailedRangeConstraint(u32),

#[error("Unsupported integer size of {num_bits} bits. The maximum supported size is {max_num_bits} bits.")]
UnsupportedIntegerSize { num_bits: u32, max_num_bits: u32 },

#[error("Failed constraint")]
FailedConstraint,

#[error(
"All Witnesses are by default u{0}. Applying this type does not apply any constraints."
)]
DefaultWitnesses(u32),

#[error("Constraint is always false")]
ConstraintIsAlwaysFalse,

#[error("ICE: cannot convert signed {0} bit size into field")]
CannotConvertSignedIntoField(u32),

#[error("we do not allow private ABI inputs to be returned as public outputs")]
PrivateAbiInput,

#[error("unimplemented")]
Unimplemented(String),

#[error("Unsupported operation error")]
UnsupportedOp { op: String, first_type: String, second_type: String },
}

impl From<RuntimeError> for Diagnostic {
fn from(error: RuntimeError) -> Diagnostic {
let span =
if let Some(loc) = error.location { loc.span } else { noirc_errors::Span::new(0..0) };
match &error.kind {
RuntimeErrorKind::ArrayOutOfBounds { index, bound } => Diagnostic::simple_error(
"index out of bounds".to_string(),
format!("out of bounds error, index is {index} but length is {bound}"),
span,
),
RuntimeErrorKind::ArrayNotFound { found_type, name } => Diagnostic::simple_error(
format!("cannot find an array with name {name}"),
format!("{found_type} has type"),
span,
match error {
RuntimeError::InternalError(_) => Diagnostic::simple_error(
"Internal Consistency Evaluators Errors: \n
This is likely a bug. Consider Opening an issue at https://github.com/noir-lang/noir/issues".to_owned(),
"".to_string(),
noirc_errors::Span::new(0..0)
),
RuntimeErrorKind::NotAnObject
| RuntimeErrorKind::InvalidId
| RuntimeErrorKind::DivisionByZero
| RuntimeErrorKind::FailedRangeConstraint(_)
| RuntimeErrorKind::UnsupportedIntegerSize { .. }
| RuntimeErrorKind::FailedConstraint
| RuntimeErrorKind::DefaultWitnesses(_)
| RuntimeErrorKind::CannotConvertSignedIntoField(_)
| RuntimeErrorKind::IndexOutOfBounds { .. }
| RuntimeErrorKind::PrivateAbiInput => {
Diagnostic::simple_error("".to_owned(), error.kind.to_string(), span)
RuntimeError::FailedConstraint { location, .. }
| RuntimeError::IndexOutOfBounds { location, .. }
| RuntimeError::InvalidRangeConstraint { location, .. }
| RuntimeError::TypeConversion { location, .. }
| RuntimeError::UnInitialized { location, .. }
| RuntimeError::UnsupportedIntegerSize { location, .. } => {
let span = if let Some(loc) = location { loc.span } else { noirc_errors::Span::new(0..0) };
Diagnostic::simple_error("".to_owned(), error.to_string(), span)
}
RuntimeErrorKind::UnsupportedOp { op, first_type, second_type } => {
Diagnostic::simple_error(
"unsupported operation".to_owned(),
format!("no support for {op} with types {first_type} and {second_type}"),
span,
)
}
RuntimeErrorKind::ConstraintIsAlwaysFalse if error.location.is_some() => {
Diagnostic::simple_error("".to_owned(), error.kind.to_string(), span)
}
RuntimeErrorKind::ConstraintIsAlwaysFalse => {
Diagnostic::from_message(&error.kind.to_string())
}
RuntimeErrorKind::Unimplemented(message) => Diagnostic::from_message(message),
RuntimeErrorKind::FunctionNonMainContext { func_name } => Diagnostic::simple_error(
"cannot call function outside of main".to_owned(),
format!("function {func_name} can only be called in main"),
span,
),
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
pub(crate) mod acir_variable;
pub(crate) mod errors;
pub(crate) mod generated_acir;
pub(crate) mod sort;
Loading

0 comments on commit 9b417da

Please sign in to comment.