Skip to content

Commit

Permalink
upgrade dependency twenty-first to v0.24.0
Browse files Browse the repository at this point in the history
Add `encode` and `decode` for `Program`.
  • Loading branch information
jan-ferdinand committed May 22, 2023
1 parent 193ffa3 commit 07f0980
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 28 deletions.
2 changes: 1 addition & 1 deletion constraint-evaluation-generator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ version = "1"
default-features = false

[dependencies]
twenty-first = "0.23.0"
twenty-first = "0.24.0"
triton-vm = { path = "../triton-vm" }
itertools = "0.10"
2 changes: 1 addition & 1 deletion triton-opcodes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ version = "1"
default-features = false

[dependencies]
twenty-first = "0.23.0"
twenty-first = "0.24.0"
anyhow = "1.0"
get-size = "0.1.3"
itertools = "0.10.5"
Expand Down
44 changes: 31 additions & 13 deletions triton-opcodes/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,28 +348,51 @@ impl<Dest: Display + PartialEq + Default> Display for AnInstruction<Dest> {
}

impl Instruction {
/// Get the argument of the instruction, if it has one.
pub fn arg(&self) -> Option<BFieldElement> {
match self {
// Double-word instructions (instructions that take arguments)
Push(arg) => Some(*arg),
Dup(arg) => Some(ord16_to_bfe(arg)),
Swap(arg) => Some(ord16_to_bfe(arg)),
Dup(arg) => Some(arg.into()),
Swap(arg) => Some(arg.into()),
Call(arg) => Some(*arg),
_ => None,
}
}

/// `true` iff the instruction has an argument.
pub fn has_arg(&self) -> bool {
self.arg().is_some()
}

/// Change the argument of the instruction, if it has one.
/// Returns `None` if the instruction does not have an argument or
/// if the argument is out of range.
pub fn change_arg(&self, new_arg: BFieldElement) -> Option<Self> {
let maybe_ord_16 = new_arg.value().try_into();
match self {
Push(_) => Some(Push(new_arg)),
Dup(_) => match maybe_ord_16 {
Ok(ord_16) => Some(Dup(ord_16)),
Err(_) => None,
},
Swap(_) => match maybe_ord_16 {
Ok(ord_16) => Some(Swap(ord_16)),
Err(_) => None,
},
Call(_) => Some(Call(new_arg)),
_ => None,
}
}
}

impl TryFrom<u32> for Instruction {
type Error = anyhow::Error;

fn try_from(opcode: u32) -> Result<Self> {
if let Some(instruction) =
Instruction::iter().find(|instruction| instruction.opcode() == opcode)
{
Ok(instruction)
} else {
bail!("No instruction with opcode {opcode} exists.")
match Instruction::iter().find(|instruction| instruction.opcode() == opcode) {
Some(instruction) => Ok(instruction),
None => bail!("No instruction with opcode {opcode} exists."),
}
}
}
Expand All @@ -390,11 +413,6 @@ impl TryFrom<usize> for Instruction {
}
}

fn ord16_to_bfe(n: &Ord16) -> BFieldElement {
let n: u32 = n.into();
n.into()
}

/// Convert a program with labels to a program with absolute positions
pub fn convert_labels(program: &[LabelledInstruction]) -> Vec<Instruction> {
let mut label_map = HashMap::<String, usize>::new();
Expand Down
26 changes: 25 additions & 1 deletion triton-opcodes/src/ord_n.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use get_size::GetSize;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use strum_macros::EnumCount as EnumCountMacro;
use twenty_first::shared_math::b_field_element::BFieldElement;

use Ord16::*;
use Ord8::*;
Expand Down Expand Up @@ -61,6 +62,13 @@ impl TryFrom<usize> for Ord8 {
}
}

impl From<Ord8> for BFieldElement {
fn from(n: Ord8) -> Self {
let n: usize = n.into();
BFieldElement::new(n as u64)
}
}

/// `Ord16` represents numbers that are exactly 0--15.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, GetSize, Serialize, Deserialize)]
pub enum Ord16 {
Expand Down Expand Up @@ -150,7 +158,10 @@ impl TryFrom<u64> for Ord16 {
type Error = String;

fn try_from(n: u64) -> Result<Self, Self::Error> {
let n: u32 = n.try_into().unwrap();
let n: u32 = match n.try_into() {
Ok(n) => n,
Err(_) => return Err(format!("{n} is out of range for Ord16")),
};
n.try_into()
}
}
Expand All @@ -174,6 +185,19 @@ impl From<&Ord16> for usize {
}
}

impl From<Ord16> for BFieldElement {
fn from(n: Ord16) -> Self {
let n: u32 = n.into();
n.into()
}
}

impl From<&Ord16> for BFieldElement {
fn from(n: &Ord16) -> Self {
(*n).into()
}
}

impl TryFrom<usize> for Ord16 {
type Error = &'static str;

Expand Down
4 changes: 2 additions & 2 deletions triton-opcodes/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ fn token1<'a>(token: &'a str) -> impl Fn(&'a str) -> ParseResult<()> {
}

#[cfg(test)]
mod parser_tests {
pub mod parser_tests {
use itertools::Itertools;
use rand::distributions::WeightedIndex;
use rand::prelude::*;
Expand Down Expand Up @@ -652,7 +652,7 @@ mod parser_tests {
// FIXME: Apply shrinking.
#[allow(unstable_name_collisions)]
// reason = "Switch to standard library intersperse_with() when it's ported"
fn program_gen(size: usize) -> String {
pub fn program_gen(size: usize) -> String {
// Generate random program
let mut labels = vec![];
let mut program: Vec<Vec<String>> =
Expand Down
127 changes: 119 additions & 8 deletions triton-opcodes/src/program.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
use std::fmt::Display;
use std::io::Cursor;

use anyhow::bail;
use anyhow::Result;
use get_size::GetSize;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use twenty_first::shared_math::b_field_element::BFieldElement;
use twenty_first::util_types::algebraic_hasher::Hashable;
use twenty_first::shared_math::bfield_codec::BFieldCodec;

use crate::instruction::convert_labels;
use crate::instruction::Instruction;
use crate::instruction::LabelledInstruction;
use crate::parser::parse;
use crate::parser::to_labelled;

/// A `Program` is a `Vec<Instruction>` that contains duplicate elements for instructions with a
/// size of 2. This means that the index in the vector corresponds to the VM's
/// `instruction_pointer`. These duplicate values should most often be skipped/ignored,
/// e.g. when pretty-printing.
#[derive(Debug, Clone, Default, PartialEq, Eq, GetSize, Serialize, Deserialize)]
pub struct Program {
pub instructions: Vec<Instruction>,
Expand All @@ -34,9 +39,57 @@ impl Display for Program {
}
}

impl Hashable for Program {
fn to_sequence(&self) -> Vec<BFieldElement> {
self.to_bwords()
impl BFieldCodec for Program {
fn decode(sequence: &[BFieldElement]) -> Result<Box<Self>> {
if sequence.is_empty() {
bail!("Sequence to decode must not be empty.");
}
let program_length = sequence[0].value() as usize;
let sequence = &sequence[1..];
if sequence.len() != program_length {
bail!(
"Sequence to decode must have length {program_length}, but has length {}.",
sequence.len()
);
}

let mut idx = 0;
let mut instructions = Vec::with_capacity(program_length);
while idx < program_length {
let opcode: u32 = match sequence[idx].value().try_into() {
Ok(opcode) => opcode,
Err(_) => bail!("Invalid opcode {} at index {idx}.", sequence[idx].value()),
};
let instruction: Instruction = opcode.try_into()?;
if !instruction.has_arg() {
instructions.push(instruction);
} else if instructions.len() + 1 >= program_length {
bail!("Missing argument for instruction {instruction} at index {idx}.");
} else {
let instruction = match instruction.change_arg(sequence[idx + 1]) {
Some(instruction) => instruction,
None => {
bail!("Invalid argument for instruction {instruction} at index {idx}.")
}
};
// Instructions with argument are recorded twice to align the `instruction_pointer`.
instructions.push(instruction);
instructions.push(instruction);
}
idx += instruction.size();
}

if idx != program_length {
bail!("Decoded program must have length {program_length}, but has length {idx}.",);
}
Ok(Box::new(Program { instructions }))
}

fn encode(&self) -> Vec<BFieldElement> {
let mut sequence = Vec::with_capacity(self.len_bwords() + 1);
sequence.push(BFieldElement::new(self.len_bwords() as u64));
sequence.extend(self.to_bwords());
sequence
}
}

Expand Down Expand Up @@ -69,10 +122,6 @@ impl IntoIterator for Program {
}
}

/// A `Program` is a `Vec<Instruction>` that contains duplicate elements for
/// instructions with a size of 2. This means that the index in the vector
/// corresponds to the VM's `instruction_pointer`. These duplicate values
/// should most often be skipped/ignored, e.g. when pretty-printing.
impl Program {
/// Create a `Program` from a slice of `Instruction`.
pub fn new(input: &[LabelledInstruction]) -> Self {
Expand Down Expand Up @@ -120,3 +169,65 @@ impl Program {
self.instructions.is_empty()
}
}

#[cfg(test)]
mod test {
use crate::parser::parser_tests::program_gen;
use rand::thread_rng;
use rand::Rng;

use super::*;

#[test]
fn random_program_encode_decode_equivalence() {
let mut rng = thread_rng();
for _ in 0..50 {
let program_len = rng.gen_range(20..420);
let source_code = program_gen(program_len);
let program = Program::from_code(&source_code).unwrap();

let encoded = program.encode();
let decoded = *Program::decode(&encoded).unwrap();

assert_eq!(program, decoded);
}
}

#[test]
fn decode_program_with_missing_argument_as_last_instruction() {
let program = Program::from_code("push 3 push 3 eq assert push 3").unwrap();
let program_length = program.len_bwords() as u64;
let encoded = program.encode();

let mut encoded = encoded[0..encoded.len() - 1].to_vec();
encoded[0] = BFieldElement::new(program_length - 1);

let err = Program::decode(&encoded).err().unwrap();
assert_eq!(
"Missing argument for instruction push 0 at index 6.",
err.to_string(),
);
}

#[test]
fn decode_program_with_length_mismatch() {
let program = Program::from_code("nop nop hash push 0 skiz end: halt call end").unwrap();
let program_length = program.len_bwords() as u64;
let mut encoded = program.encode();

encoded[0] = BFieldElement::new(program_length + 1);

let err = Program::decode(&encoded).err().unwrap();
assert_eq!(
"Sequence to decode must have length 10, but has length 9.",
err.to_string(),
);
}

#[test]
fn decode_program_from_empty_sequence() {
let encoded = vec![];
let err = Program::decode(&encoded).err().unwrap();
assert_eq!("Sequence to decode must not be empty.", err.to_string(),);
}
}
2 changes: 1 addition & 1 deletion triton-profiler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ keywords = ["triton-vm", "stark", "profiler"]
categories = ["cryptography", "mathematics"]

[dependencies]
twenty-first = "0.23.0"
twenty-first = "0.24.0"
colored = "2.0"
rand = "0.8"
unicode-width = "0.1.5"
Expand Down
2 changes: 1 addition & 1 deletion triton-vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ version = "1"
default-features = false

[dependencies]
twenty-first = "0.23.0"
twenty-first = "0.24.0"
triton-opcodes = { path = "../triton-opcodes" }
triton-profiler = { path = "../triton-profiler" }
anyhow = "1.0"
Expand Down

0 comments on commit 07f0980

Please sign in to comment.