Skip to content

Commit

Permalink
feat!: Introduce assertion error
Browse files Browse the repository at this point in the history
The instructions `assert` and `assert_vector` now accept some additional
context to simplify debugging Triton assembly programs. For now, only
an error ID is supported. For example, the error returned when running
the program

```tasm
push 2
assert error_id 42
halt
```

will now contain a field with the supplied error ID, in this case, 42.

BREAKING CHANGE: The type `InstructionError` is no longer `Copy`.
  • Loading branch information
jan-ferdinand committed Nov 7, 2024
1 parent 8bf20da commit e67a070
Show file tree
Hide file tree
Showing 7 changed files with 623 additions and 176 deletions.
1 change: 1 addition & 0 deletions triton-isa/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub use crate::instruction::AssertionError;
pub use crate::instruction::InstructionError;
pub use crate::op_stack::NumberOfWordsError;
pub use crate::op_stack::OpStackElementError;
Expand Down
129 changes: 97 additions & 32 deletions triton-isa/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ pub enum LabelledInstruction {
Breakpoint,

TypeHint(TypeHint),

AssertionContext(AssertionContext),
}

/// A hint about a range of stack elements. Helps debugging programs written for Triton VM.
Expand All @@ -133,6 +135,36 @@ pub struct TypeHint {
pub variable_name: String,
}

/// Context to help debugging failing instructions [`assert`](Instruction::Assert) or
/// [`assert_vector`](Instruction::AssertVector).
#[non_exhaustive]
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, GetSize, Arbitrary)]
pub enum AssertionContext {
ID(i128),
// Message(String),
}

impl LabelledInstruction {
pub const fn op_stack_size_influence(&self) -> i32 {
match self {
LabelledInstruction::Instruction(instruction) => instruction.op_stack_size_influence(),
_ => 0,
}
}
}

impl Display for LabelledInstruction {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
LabelledInstruction::Instruction(instruction) => write!(f, "{instruction}"),
LabelledInstruction::Label(label) => write!(f, "{label}:"),
LabelledInstruction::Breakpoint => write!(f, "break"),
LabelledInstruction::TypeHint(type_hint) => write!(f, "{type_hint}"),
LabelledInstruction::AssertionContext(ctx) => write!(f, "{ctx}"),
}
}
}

impl Display for TypeHint {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let variable = &self.variable_name;
Expand All @@ -151,13 +183,21 @@ impl Display for TypeHint {
}
}

impl Display for AssertionContext {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let Self::ID(id) = self;
write!(f, "error_id {id}")
}
}

impl<'a> Arbitrary<'a> for TypeHint {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let starting_index = u.arbitrary()?;
let length = u.int_in_range(1..=500)?;
let type_name = match u.arbitrary()? {
true => Some(u.arbitrary::<TypeHintTypeName>()?.into()),
false => None,
let type_name = if u.arbitrary()? {
Some(u.arbitrary::<TypeHintTypeName>()?.into())
} else {
None
};
let variable_name = u.arbitrary::<TypeHintVariableName>()?.into();

Expand All @@ -171,26 +211,6 @@ impl<'a> Arbitrary<'a> for TypeHint {
}
}

impl LabelledInstruction {
pub const fn op_stack_size_influence(&self) -> i32 {
match self {
LabelledInstruction::Instruction(instruction) => instruction.op_stack_size_influence(),
_ => 0,
}
}
}

impl Display for LabelledInstruction {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
LabelledInstruction::Instruction(instruction) => write!(f, "{instruction}"),
LabelledInstruction::Label(label) => write!(f, "{label}:"),
LabelledInstruction::Breakpoint => write!(f, "break"),
LabelledInstruction::TypeHint(type_hint) => write!(f, "{type_hint}"),
}
}
}

/// A Triton VM instruction. See the
/// [Instruction Set Architecture](https://triton-vm.org/spec/isa.html)
/// for more details.
Expand Down Expand Up @@ -574,9 +594,9 @@ impl Instruction {
/// Change the argument of the instruction, if it has one. Returns an `Err` if the instruction
/// does not have an argument or if the argument is out of range.
pub fn change_arg(self, new_arg: BFieldElement) -> Result<Self> {
let illegal_argument_error = InstructionError::IllegalArgument(self, new_arg);
let num_words = new_arg.try_into().map_err(|_| illegal_argument_error);
let op_stack_element = new_arg.try_into().map_err(|_| illegal_argument_error);
let illegal_argument_error = || InstructionError::IllegalArgument(self, new_arg);
let num_words = new_arg.try_into().map_err(|_| illegal_argument_error());
let op_stack_element = new_arg.try_into().map_err(|_| illegal_argument_error());

let new_instruction = match self {
AnInstruction::Pop(_) => AnInstruction::Pop(num_words?),
Expand All @@ -592,7 +612,7 @@ impl Instruction {
AnInstruction::AddI(_) => AnInstruction::AddI(new_arg),
AnInstruction::ReadIo(_) => AnInstruction::ReadIo(num_words?),
AnInstruction::WriteIo(_) => AnInstruction::WriteIo(num_words?),
_ => return Err(illegal_argument_error),
_ => return Err(illegal_argument_error()),
};

Ok(new_instruction)
Expand Down Expand Up @@ -697,6 +717,7 @@ impl<'a> Arbitrary<'a> for LabelledInstruction {
1 => return Ok(Self::Label(u.arbitrary::<InstructionLabel>()?.into())),
2 => return Ok(Self::Breakpoint),
3 => return Ok(Self::TypeHint(u.arbitrary()?)),
4 => return Ok(Self::AssertionContext(u.arbitrary()?)),
_ => unreachable!(),
};
let legal_label = String::from(u.arbitrary::<InstructionLabel>()?);
Expand Down Expand Up @@ -787,7 +808,7 @@ impl<'a> Arbitrary<'a> for TypeHintTypeName {
}

#[non_exhaustive]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Error)]
#[derive(Debug, Clone, Eq, PartialEq, Error)]
pub enum InstructionError {
#[error("opcode {0} is invalid")]
InvalidOpcode(u32),
Expand All @@ -804,11 +825,11 @@ pub enum InstructionError {
#[error("jump stack is empty")]
JumpStackIsEmpty,

#[error("assertion failed: st0 must be 1")]
AssertionFailed,
#[error("assertion failed: {0}")]
AssertionFailed(AssertionError),

#[error("vector assertion failed: stack[{0}] != stack[{}]", .0 + Digest::LEN)]
VectorAssertionFailed(usize),
#[error("vector assertion failed because stack[{0}] != stack[{}]: {1}", .0 + Digest::LEN)]
VectorAssertionFailed(usize, AssertionError),

#[error("0 does not have a multiplicative inverse")]
InverseOfZero,
Expand Down Expand Up @@ -838,6 +859,50 @@ pub enum InstructionError {
OpStackError(#[from] OpStackError),
}

/// An error giving additional context to any failed assertion.
#[non_exhaustive]
#[derive(Debug, Clone, Eq, PartialEq, Error)]
pub struct AssertionError {
/// The [element](BFieldElement) expected by the assertion.
pub expected: BFieldElement,

/// The actual [element](BFieldElement) encountered when executing the assertion.
pub actual: BFieldElement,

/// A user-defined error ID. Only has user-defined, no inherent, semantics.
pub id: Option<i128>,
//
// /// A user-defined error message supplying context to the failed assertion.
// pub message: Option<String>,
}

impl Display for AssertionError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
if let Some(id) = self.id {
write!(f, "[{id}] ")?;
}
write!(f, "expected {}, got {}", self.expected, self.actual)
}
}

impl AssertionError {
pub fn new(expected: impl Into<BFieldElement>, actual: impl Into<BFieldElement>) -> Self {
Self {
expected: expected.into(),
actual: actual.into(),
id: None,
}
}

#[must_use]
pub fn with_context(mut self, context: AssertionContext) -> Self {
match context {
AssertionContext::ID(id) => self.id = Some(id),
};
self
}
}

#[cfg(test)]
pub mod tests {
use std::collections::HashMap;
Expand Down
1 change: 1 addition & 0 deletions triton-isa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ mod tests {
fn public_types_implement_usual_auto_traits() {
fn implements_auto_traits<T: Sized + Send + Sync + Unpin>() {}

implements_auto_traits::<error::AssertionError>();
implements_auto_traits::<error::InstructionError>();
implements_auto_traits::<error::NumberOfWordsError>();
implements_auto_traits::<error::OpStackElementError>();
Expand Down
Loading

0 comments on commit e67a070

Please sign in to comment.