From 7bcc09eafcbf026d702de79215d3b03f61665230 Mon Sep 17 00:00:00 2001 From: Simon Shine Date: Thu, 22 Dec 2022 11:58:04 +0100 Subject: [PATCH] Split 'Instruction' types into separate sub-crate 'triton-opcodes' - Move 'Program' to 'triton-opcodes' and decouple run() and simulate() - Inline most 'sample_programs' into their single test callers --- Cargo.toml | 7 +- triton-opcodes/Cargo.toml | 32 ++ .../src/instruction.rs | 356 +----------------- triton-opcodes/src/lib.rs | 3 + {triton-vm => triton-opcodes}/src/ord_n.rs | 0 triton-opcodes/src/program.rs | 104 +++++ triton-vm/Cargo.toml | 3 +- triton-vm/benches/prove_fib_100.rs | 9 +- triton-vm/benches/prove_halt.rs | 5 +- triton-vm/benches/verify_halt.rs | 5 +- triton-vm/src/lib.rs | 2 - triton-vm/src/op_stack.rs | 6 +- triton-vm/src/shared_tests.rs | 104 ++++- triton-vm/src/stark.rs | 17 +- triton-vm/src/state.rs | 253 ++++++++++--- triton-vm/src/table/jump_stack_table.rs | 2 +- triton-vm/src/table/processor_table.rs | 17 +- triton-vm/src/table/ram_table.rs | 2 +- triton-vm/src/vm.rs | 334 +++++++--------- 19 files changed, 620 insertions(+), 641 deletions(-) create mode 100644 triton-opcodes/Cargo.toml rename {triton-vm => triton-opcodes}/src/instruction.rs (72%) create mode 100644 triton-opcodes/src/lib.rs rename {triton-vm => triton-opcodes}/src/ord_n.rs (100%) create mode 100644 triton-opcodes/src/program.rs diff --git a/Cargo.toml b/Cargo.toml index 1e1fee546..0f1ba2714 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,10 @@ [workspace] -members = ["triton-vm", "triton-profiler", "constraint-evaluation-generator"] +members = [ + "triton-vm", + "triton-opcodes", + "triton-profiler", + "constraint-evaluation-generator", +] [profile.dev] opt-level = 0 diff --git a/triton-opcodes/Cargo.toml b/triton-opcodes/Cargo.toml new file mode 100644 index 000000000..aec338bd2 --- /dev/null +++ b/triton-opcodes/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "triton-opcodes" +version = "0.1.0" +edition = "2021" +authors = ["Triton Software AG"] + +license = "Apache-2.0" +description = "Triton VM instructions and their translations" +homepage = "https://triton-vm.org/" +documentation = "https://github.com/TritonVM/triton-vm/blob/master/triton-opcodes" +repository = "https://github.com/TritonVM/triton-vm" +readme = "README.md" + +[dev-dependencies] +proptest = "1.0" + +[dev-dependencies.criterion] +version = "0.4.0" +features = ["html_reports"] + +[dev-dependencies.cargo-husky] +version = "1" +default-features = false + +[dependencies] +twenty-first = "0.9" +anyhow = "1.0" +itertools = "0.10.5" +num-traits = "0.2" +regex = "1.7" +strum = "0.24" +strum_macros = "0.24" diff --git a/triton-vm/src/instruction.rs b/triton-opcodes/src/instruction.rs similarity index 72% rename from triton-vm/src/instruction.rs rename to triton-opcodes/src/instruction.rs index bc7114ddd..aaa2837e2 100644 --- a/triton-vm/src/instruction.rs +++ b/triton-opcodes/src/instruction.rs @@ -16,6 +16,7 @@ use strum::IntoEnumIterator; use strum_macros::Display as DisplayMacro; use strum_macros::EnumCount as EnumCountMacro; use strum_macros::EnumIter; + use twenty_first::shared_math::b_field_element::BFieldElement; use AnInstruction::*; @@ -834,12 +835,6 @@ pub fn all_labelled_instructions_with_args() -> Vec { } pub mod sample_programs { - use twenty_first::shared_math::b_field_element::BFieldElement; - - use super::super::vm::Program; - use super::AnInstruction::*; - use super::LabelledInstruction; - pub const PUSH_PUSH_ADD_POP_S: &str = " push 1 push 1 @@ -847,167 +842,7 @@ pub mod sample_programs { pop "; - pub fn push_push_add_pop_p() -> Program { - let instructions: Vec = vec![ - Push(BFieldElement::new(1)), - Push(BFieldElement::new(1)), - Add, - Pop, - ] - .into_iter() - .map(LabelledInstruction::Instruction) - .collect(); - - Program::new(&instructions) - } - - /// TVM assembly to sample weights for the recursive verifier - /// - /// input: seed, num_weights - /// - /// output: num_weights-many random weights - pub const SAMPLE_WEIGHTS: &str = concat!( - "push 17 push 13 push 11 ", // get seed – should be an argument - "read_io ", // number of weights – should be argument - "sample_weights: ", // proper program starts here - "call sample_weights_loop ", // setup done, start sampling loop - "pop pop ", // clean up stack: RAM value & pointer - "pop pop pop pop ", // clean up stack: seed & countdown - "halt ", // done – should be return - "", // - "sample_weights_loop: ", // subroutine: loop until all weights are sampled - "dup0 push 0 eq skiz return ", // no weights left - "push -1 add ", // decrease number of weights to still sample - "push 0 push 0 push 0 push 0 ", // prepare for hashing - "push 0 push 0 push 0 push 0 ", // prepare for hashing - "dup11 dup11 dup11 dup11 ", // prepare for hashing - "hash ", // hash seed & countdown - "swap13 swap10 pop ", // re-organize stack - "swap13 swap10 pop ", // re-organize stack - "swap13 swap10 swap7 ", // re-organize stack - "pop pop pop pop pop pop pop ", // remove unnecessary remnants of digest - "recurse ", // repeat - ); - - /// TVM assembly to verify Merkle authentication paths - /// - /// input: merkle root, number of leafs, leaf values, APs - /// - /// output: Result<(), VMFail> - pub const MT_AP_VERIFY: &str = concat!( - "read_io ", // number of authentication paths to test - "", // stack: [num] - "mt_ap_verify: ", // proper program starts here - "push 0 swap1 write_mem pop pop ", // store number of APs at RAM address 0 - "", // stack: [] - "read_io read_io read_io read_io read_io ", // read Merkle root - "", // stack: [r4 r3 r2 r1 r0] - "call check_aps ", // - "pop pop pop pop pop ", // leave clean stack: Merkle root - "", // stack: [] - "halt ", // done – should be “return” - "", - "", // subroutine: check AP one at a time - "", // stack before: [* r4 r3 r2 r1 r0] - "", // stack after: [* r4 r3 r2 r1 r0] - "check_aps: ", // start function description: - "push 0 push 0 read_mem dup0 ", // get number of APs left to check - "", // stack: [* r4 r3 r2 r1 r0 0 num_left num_left] - "push 0 eq ", // see if there are authentication paths left - "", // stack: [* r4 r3 r2 r1 r0 0 num_left num_left==0] - "skiz return ", // return if no authentication paths left - "push -1 add write_mem pop pop ", // decrease number of authentication paths left to check - "", // stack: [* r4 r3 r2 r1 r0] - "call get_idx_and_hash_leaf ", // - "", // stack: [* r4 r3 r2 r1 r0 idx d4 d3 d2 d1 d0 0 0 0 0 0] - "call traverse_tree ", // - "", // stack: [* r4 r3 r2 r1 r0 idx>>2 - - - - - - - - - -] - "call assert_tree_top ", // - // stack: [* r4 r3 r2 r1 r0] - "recurse ", // check next AP - "", - "", // subroutine: read index & hash leaf - "", // stack before: [*] - "", // stack afterwards: [* idx d4 d3 d2 d1 d0 0 0 0 0 0] - "get_idx_and_hash_leaf: ", // start function description: - "read_io ", // read node index - "read_io read_io read_io read_io read_io ", // read leaf's value - "push 0 push 0 push 0 push 0 push 0 ", // pad before fixed-length hash - "hash return ", // compute leaf's digest - "", - "", // subroutine: go up tree - "", // stack before: [* idx - - - - - - - - - -] - "", // stack after: [* idx>>2 - - - - - - - - - -] - "traverse_tree: ", // start function description: - "dup10 push 1 eq skiz return ", // break loop if node index is 1 - "divine_sibling hash recurse ", // move up one level in the Merkle tree - "", - "", // subroutine: compare digests - "", // stack before: [* r4 r3 r2 r1 r0 idx a b c d e - - - - -] - "", // stack after: [* r4 r3 r2 r1 r0] - "assert_tree_top: ", // start function description: - "pop pop pop pop pop ", // remove unnecessary “0”s from hashing - "", // stack: [* r4 r3 r2 r1 r0 idx a b c d e] - "swap1 swap2 swap3 swap4 swap5 ", - "", // stack: [* r4 r3 r2 r1 r0 a b c d e idx] - "assert ", // - "", // stack: [* r4 r3 r2 r1 r0 a b c d e] - "assert_vector ", // actually compare to root of tree - "pop pop pop pop pop ", // clean up stack, leave only one root - "return ", // - ); - - // see also: get_colinear_y in src/shared_math/polynomial.rs - pub const GET_COLINEAR_Y: &str = concat!( - "read_io ", // p2_x - "read_io read_io ", // p1_y p1_x - "read_io read_io ", // p0_y p0_x - "swap3 push -1 mul dup1 add ", // dy = p0_y - p1_y - "dup3 push -1 mul dup5 add mul ", // dy·(p2_x - p0_x) - "dup3 dup3 push -1 mul add ", // dx = p0_x - p1_x - "invert mul add ", // compute result - "swap3 pop pop pop ", // leave a clean stack - "write_io halt ", - ); - - pub const HELLO_WORLD_1: &str = " - push 10 - push 33 - push 100 - push 108 - push 114 - push 111 - push 87 - push 32 - push 44 - push 111 - push 108 - push 108 - push 101 - push 72 - - write_io write_io write_io write_io write_io write_io write_io - write_io write_io write_io write_io write_io write_io write_io - "; - - pub const BASIC_RAM_READ_WRITE: &str = concat!( - "push 5 push 6 write_mem pop pop ", - "push 15 push 16 write_mem pop pop ", - "push 5 push 0 read_mem pop pop ", - "push 15 push 0 read_mem pop pop ", - "push 5 push 7 write_mem pop pop ", - "push 15 push 0 read_mem ", - "push 5 push 0 read_mem ", - "halt ", - ); - - pub const EDGY_RAM_WRITES: &str = concat!( - "write_mem ", // this should write 0 to address 0 - "push 5 swap2 push 3 swap2 pop pop ", // stack is now of length 16 again - "write_mem ", // this should write 3 to address 5 - "swap2 read_mem ", // stack's top should now be 3, 5, 3, 0, 0, … - "halt ", - ); + pub const EDGY_RAM_WRITES: &str = concat!(); pub const READ_WRITE_X3: &str = " read_io @@ -1030,161 +865,6 @@ pub mod sample_programs { write_io write_io "; - pub const COUNTDOWN_FROM_10: &str = " - push 10 - call loop - loop: dup0 - write_io - push -1 - add - dup0 - skiz - recurse - write_io - halt - "; - - pub const FIBONACCI_VIT: &str = " - push 0 - push 1 - read_io - dup0 - dup0 - dup0 - mul - eq - skiz - call bar - call foo - foo: call bob - swap1 - push -1 - add - dup0 - skiz - recurse - call baz - bar: dup0 - push 0 - eq - skiz - pop - baz: pop - write_io - halt - bob: dup2 - dup2 - add - return - "; - - pub const FIB_SHOOTOUT: &str = " - // Initialize stack: _ 0 1 i - push 0 - push 1 - divine - - call fib-loop - write_io // After loop, this is 0 - write_io // After loop, this is fib(i) - halt - - fib-loop: - dup0 skiz call fib-step - dup0 skiz recurse - return - - // Before: _ a b i - // After: _ b (a+b) (i-1) - fib-step: - push -1 - add - swap2 - dup1 - add - swap1 - swap2 - return - "; - - pub const FIB_FIXED_7_LT: &str = " - push 0 - push 1 - push 7 - push 2 - dup1 - lt - skiz - call 29 - call 16 - 16: call 38 - swap1 - push -1 - add - dup0 - skiz - recurse - call 36 - 29: dup0 - push 0 - eq - skiz - pop - 36: pop - halt - 38: dup2 - dup2 - add - return - "; - - pub const GCD_X_Y: &str = concat!( - // ∅ - "read_io ", - // a - "read_io ", - // a b - "dup1 ", - // a b a - "dup1 ", - // a b a b - "lt ", - // a b b d - // --- - "loop_cond: ", - "dup1 ", - "push 0 ", - "eq ", - "skiz ", - "call terminate ", - // _ d n where d != 0 - "dup1 ", - // _ d n d - "dup1 ", - // _ d n d n - "div ", - // _ d n q r - "swap2 ", - // _ d r q n - "pop ", - // _ d r q - "pop ", - // _ d r - "swap1 ", - // _ r d - "call loop_cond ", - // --- - "terminate: ", - // _ d n where d == 0 - "write_io ", - // _ d - "halt ", - ); - pub const HASH_HASH_HASH_HALT: &str = " hash hash @@ -1289,12 +969,12 @@ mod instruction_tests { use crate::instruction::all_labelled_instructions_with_args; use crate::ord_n::Ord7; - use crate::vm::Program; + use crate::program::Program; use super::all_instructions_without_args; use super::parse; use super::sample_programs; - use super::AnInstruction; + use super::AnInstruction::{self, *}; #[test] fn opcode_test() { @@ -1354,19 +1034,23 @@ mod instruction_tests { } #[test] - fn parse_display_push_pop_test() { - let pgm_expected = sample_programs::push_push_add_pop_p(); - let pgm_pretty = format!("{}", pgm_expected); - let instructions = parse(&pgm_pretty).unwrap(); - let pgm_actual = Program::new(&instructions); - - assert_eq!(pgm_expected, pgm_actual); - - let pgm_text = sample_programs::PUSH_PUSH_ADD_POP_S; - let instructions_2 = parse(pgm_text).unwrap(); - let pgm_actual_2 = Program::new(&instructions_2); + fn parse_push_pop_test() { + let code = " + push 1 + push 1 + add + pop + "; + let program = Program::from_code(code).unwrap(); + let instructions = program.into_iter().collect_vec(); + let expected = vec![ + Push(BFieldElement::one()), + Push(BFieldElement::one()), + Add, + Pop, + ]; - assert_eq!(pgm_expected, pgm_actual_2); + assert_eq!(expected, instructions); } #[test] diff --git a/triton-opcodes/src/lib.rs b/triton-opcodes/src/lib.rs new file mode 100644 index 000000000..d20ece21a --- /dev/null +++ b/triton-opcodes/src/lib.rs @@ -0,0 +1,3 @@ +pub mod instruction; +pub mod ord_n; +pub mod program; diff --git a/triton-vm/src/ord_n.rs b/triton-opcodes/src/ord_n.rs similarity index 100% rename from triton-vm/src/ord_n.rs rename to triton-opcodes/src/ord_n.rs diff --git a/triton-opcodes/src/program.rs b/triton-opcodes/src/program.rs new file mode 100644 index 000000000..34b687431 --- /dev/null +++ b/triton-opcodes/src/program.rs @@ -0,0 +1,104 @@ +use anyhow::Result; +use std::fmt::Display; +use std::io::Cursor; + +use twenty_first::shared_math::b_field_element::BFieldElement; + +use crate::instruction::{convert_labels, parse, Instruction, LabelledInstruction}; + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct Program { + pub instructions: Vec, +} + +impl Display for Program { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut stream = self.instructions.iter(); + while let Some(instruction) = stream.next() { + writeln!(f, "{}", instruction)?; + + // Skip duplicate placeholder used for aligning instructions and instruction_pointer in VM. + for _ in 1..instruction.size() { + stream.next(); + } + } + Ok(()) + } +} + +pub struct SkippyIter { + cursor: Cursor>, +} + +impl Iterator for SkippyIter { + type Item = Instruction; + + fn next(&mut self) -> Option { + let pos = self.cursor.position() as usize; + let instructions = self.cursor.get_ref(); + let instruction = *instructions.get(pos)?; + self.cursor.set_position((pos + instruction.size()) as u64); + + Some(instruction) + } +} + +impl IntoIterator for Program { + type Item = Instruction; + + type IntoIter = SkippyIter; + + fn into_iter(self) -> Self::IntoIter { + let cursor = Cursor::new(self.instructions); + SkippyIter { cursor } + } +} + +/// A `Program` is a `Vec` 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 { + let instructions = convert_labels(input) + .iter() + .flat_map(|instr| vec![*instr; instr.size()]) + .collect::>(); + + Program { instructions } + } + + /// Create a `Program` by parsing source code. + pub fn from_code(code: &str) -> Result { + let instructions = parse(code)?; + Ok(Program::new(&instructions)) + } + + /// Convert a `Program` to a `Vec`. + /// + /// Every single-word instruction is converted to a single word. + /// + /// Every double-word instruction is converted to two words. + pub fn to_bwords(&self) -> Vec { + self.clone() + .into_iter() + .flat_map(|instruction| { + let opcode = instruction.opcode_b(); + if let Some(arg) = instruction.arg() { + vec![opcode, arg] + } else { + vec![opcode] + } + }) + .collect() + } + + pub fn len(&self) -> usize { + self.instructions.len() + } + + pub fn is_empty(&self) -> bool { + self.instructions.is_empty() + } +} diff --git a/triton-vm/Cargo.toml b/triton-vm/Cargo.toml index 874a69451..d3b68e0a2 100644 --- a/triton-vm/Cargo.toml +++ b/triton-vm/Cargo.toml @@ -31,6 +31,7 @@ default-features = false [dependencies] twenty-first = "0.9" +triton-opcodes = { version = "0.1", path = "../triton-opcodes" } triton-profiler = "0.9" anyhow = "1.0" bincode = "1.3" @@ -64,7 +65,7 @@ serde_with = "2.1" structopt = { version = "0.3", features = ["paw"] } strum = "0.24" strum_macros = "0.24" -ndarray = { version = "0.15", features = ["rayon"]} +ndarray = { version = "0.15", features = ["rayon"] } [[bench]] name = "prove_halt" diff --git a/triton-vm/benches/prove_fib_100.rs b/triton-vm/benches/prove_fib_100.rs index 979af7833..062550ab3 100644 --- a/triton-vm/benches/prove_fib_100.rs +++ b/triton-vm/benches/prove_fib_100.rs @@ -3,15 +3,16 @@ use criterion::criterion_main; use criterion::BenchmarkId; use criterion::Criterion; +use triton_opcodes::program::Program; use triton_profiler::prof_start; use triton_profiler::prof_stop; use triton_profiler::triton_profiler::Report; use triton_profiler::triton_profiler::TritonProfiler; -use triton_vm::instruction::sample_programs; use triton_vm::proof::Claim; +use triton_vm::shared_tests::FIBONACCI_VIT; use triton_vm::stark::Stark; use triton_vm::table::master_table::MasterBaseTable; -use triton_vm::vm::Program; +use triton_vm::vm::simulate; /// cargo criterion --bench prove_fib_100 fn prove_fib_100(criterion: &mut Criterion) { @@ -24,12 +25,12 @@ fn prove_fib_100(criterion: &mut Criterion) { let mut report: Report = Report::placeholder(); // stark object - let program = match Program::from_code(sample_programs::FIBONACCI_VIT) { + let program = match Program::from_code(FIBONACCI_VIT) { Err(e) => panic!("Cannot compile source code into program: {}", e), Ok(p) => p, }; let input = vec![100_u64.into()]; - let (aet, output, err) = program.simulate(input.clone(), vec![]); + let (aet, output, err) = simulate(&program, input.clone(), vec![]); if let Some(error) = err { panic!("The VM encountered the following problem: {}", error); } diff --git a/triton-vm/benches/prove_halt.rs b/triton-vm/benches/prove_halt.rs index 852585646..5508f8a6e 100644 --- a/triton-vm/benches/prove_halt.rs +++ b/triton-vm/benches/prove_halt.rs @@ -2,6 +2,7 @@ use criterion::criterion_group; use criterion::criterion_main; use criterion::Criterion; +use triton_opcodes::program::Program; use triton_profiler::triton_profiler::Report; use triton_profiler::triton_profiler::TritonProfiler; use triton_vm::proof::Claim; @@ -9,7 +10,7 @@ use triton_vm::shared_tests::save_proof; use triton_vm::stark::Stark; use triton_vm::stark::StarkParameters; use triton_vm::table::master_table::MasterBaseTable; -use triton_vm::vm::Program; +use triton_vm::vm::simulate_no_input; /// cargo criterion --bench prove_halt fn prove_halt(_criterion: &mut Criterion) { @@ -23,7 +24,7 @@ fn prove_halt(_criterion: &mut Criterion) { }; // witness - let (aet, output, err) = program.simulate_no_input(); + let (aet, output, err) = simulate_no_input(&program); if let Some(error) = err { panic!("The VM encountered the following problem: {}", error); } diff --git a/triton-vm/benches/verify_halt.rs b/triton-vm/benches/verify_halt.rs index 4010a4bdf..fd11e8a24 100644 --- a/triton-vm/benches/verify_halt.rs +++ b/triton-vm/benches/verify_halt.rs @@ -7,6 +7,7 @@ use triton_profiler::prof_stop; use triton_profiler::triton_profiler::Report; use triton_profiler::triton_profiler::TritonProfiler; +use triton_opcodes::program::Program; use triton_vm::proof::Claim; use triton_vm::shared_tests::load_proof; use triton_vm::shared_tests::proof_file_exists; @@ -14,7 +15,7 @@ use triton_vm::shared_tests::save_proof; use triton_vm::stark::Stark; use triton_vm::stark::StarkParameters; use triton_vm::table::master_table::MasterBaseTable; -use triton_vm::vm::Program; +use triton_vm::vm::simulate_no_input; /// cargo criterion --bench verify_halt fn verify_halt(criterion: &mut Criterion) { @@ -48,7 +49,7 @@ fn verify_halt(criterion: &mut Criterion) { let stark = Stark::new(claim, stark_parameters); (proof, stark) } else { - let (aet, output, err) = program.simulate_no_input(); + let (aet, output, err) = simulate_no_input(&program); if let Some(error) = err { panic!("The VM encountered the following problem: {}", error); } diff --git a/triton-vm/src/lib.rs b/triton-vm/src/lib.rs index 0a1059b70..d3fccdd78 100644 --- a/triton-vm/src/lib.rs +++ b/triton-vm/src/lib.rs @@ -2,9 +2,7 @@ pub mod arithmetic_domain; pub mod bfield_codec; pub mod error; pub mod fri; -pub mod instruction; pub mod op_stack; -pub mod ord_n; pub mod proof; pub mod proof_item; pub mod proof_stream; diff --git a/triton-vm/src/op_stack.rs b/triton-vm/src/op_stack.rs index 220f7ea5c..d540bcf2f 100644 --- a/triton-vm/src/op_stack.rs +++ b/triton-vm/src/op_stack.rs @@ -1,12 +1,12 @@ use anyhow::Result; use num_traits::Zero; +use triton_opcodes::ord_n::Ord16; +use triton_opcodes::ord_n::Ord16::*; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::x_field_element::XFieldElement; use super::error::vm_fail; use super::error::InstructionError::*; -use super::ord_n::Ord16; -use super::ord_n::Ord16::*; #[derive(Debug, Clone)] pub struct OpStack { @@ -131,7 +131,7 @@ mod op_stack_test { use twenty_first::shared_math::b_field_element::BFieldElement; use crate::op_stack::OpStack; - use crate::ord_n::Ord16; + use triton_opcodes::ord_n::Ord16; #[test] fn test_sanity() { diff --git a/triton-vm/src/shared_tests.rs b/triton-vm/src/shared_tests.rs index 1057657d2..12d82d323 100644 --- a/triton-vm/src/shared_tests.rs +++ b/triton-vm/src/shared_tests.rs @@ -6,6 +6,7 @@ use std::path::Path; use anyhow::Error; use anyhow::Result; +use triton_opcodes::program::Program; use triton_profiler::prof_start; use triton_profiler::prof_stop; use triton_profiler::triton_profiler::TritonProfiler; @@ -16,8 +17,9 @@ use crate::proof::Proof; use crate::stark::Stark; use crate::stark::StarkParameters; use crate::table::master_table::MasterBaseTable; +use crate::vm::run; +use crate::vm::simulate; use crate::vm::AlgebraicExecutionTrace; -use crate::vm::Program; pub fn parse_setup_simulate( code: &str, @@ -31,7 +33,7 @@ pub fn parse_setup_simulate( let program = program.unwrap(); prof_start!(maybe_profiler, "simulate"); - let (aet, stdout, err) = program.simulate(input_symbols, secret_input_symbols); + let (aet, stdout, err) = simulate(&program, input_symbols, secret_input_symbols); if let Some(error) = err { panic!("The VM encountered the following problem: {}", error); } @@ -90,7 +92,7 @@ impl SourceCodeAndInput { pub fn run(&self) -> Vec { let program = Program::from_code(&self.source_code).expect("Could not load source code"); - let (_, output, err) = program.run(self.input.clone(), self.secret_input.clone()); + let (_, output, err) = run(&program, self.input.clone(), self.secret_input.clone()); if let Some(e) = err { panic!("Running the program failed: {}", e) } @@ -99,7 +101,7 @@ impl SourceCodeAndInput { pub fn simulate(&self) -> (AlgebraicExecutionTrace, Vec, Option) { let program = Program::from_code(&self.source_code).expect("Could not load source code."); - program.simulate(self.input.clone(), self.secret_input.clone()) + simulate(&program, self.input.clone(), self.secret_input.clone()) } } @@ -165,3 +167,97 @@ pub fn save_proof(filename: &str, proof: Proof) -> Result<()> { println!("Wrote {} bytes of proof data to disk.", amount); Ok(()) } + +pub const FIBONACCI_VIT: &str = " + push 0 + push 1 + read_io + dup0 + dup0 + dup0 + mul + eq + skiz + call bar + call foo + foo: call bob + swap1 + push -1 + add + dup0 + skiz + recurse + call baz + bar: dup0 + push 0 + eq + skiz + pop + baz: pop + write_io + halt + bob: dup2 + dup2 + add + return + "; + +pub const FIB_SHOOTOUT: &str = " + // Initialize stack: _ 0 1 i + push 0 + push 1 + divine + + call fib-loop + write_io // After loop, this is 0 + write_io // After loop, this is fib(i) + halt + + fib-loop: + dup0 skiz call fib-step + dup0 skiz recurse + return + + // Before: _ a b i + // After: _ b (a+b) (i-1) + fib-step: + push -1 + add + swap2 + dup1 + add + swap1 + swap2 + return + "; + +pub const FIB_FIXED_7_LT: &str = " + push 0 + push 1 + push 7 + push 2 + dup1 + lt + skiz + call 29 + call 16 +16: call 38 + swap1 + push -1 + add + dup0 + skiz + recurse + call 36 +29: dup0 + push 0 + eq + skiz + pop +36: pop + halt +38: dup2 + dup2 + add + return +"; diff --git a/triton-vm/src/stark.rs b/triton-vm/src/stark.rs index 218a82fdb..034039590 100644 --- a/triton-vm/src/stark.rs +++ b/triton-vm/src/stark.rs @@ -878,8 +878,9 @@ pub(crate) mod triton_stark_tests { use ndarray::Array1; use num_traits::Zero; - use crate::instruction::sample_programs; - use crate::instruction::AnInstruction; + use triton_opcodes::instruction::AnInstruction; + use triton_opcodes::program::Program; + use crate::shared_tests::*; use crate::table::cross_table_argument::CrossTableArg; use crate::table::cross_table_argument::EvalArg; @@ -903,12 +904,12 @@ pub(crate) mod triton_stark_tests { use crate::table::table_column::ProcessorExtTableColumn::InputTableEvalArg; use crate::table::table_column::ProcessorExtTableColumn::OutputTableEvalArg; use crate::table::table_column::RamBaseTableColumn; + use crate::vm::simulate; use crate::vm::triton_vm_tests::bigger_tasm_test_programs; use crate::vm::triton_vm_tests::property_based_test_programs; use crate::vm::triton_vm_tests::small_tasm_test_programs; use crate::vm::triton_vm_tests::test_hash_nop_nop_lt; use crate::vm::AlgebraicExecutionTrace; - use crate::vm::Program; use super::*; @@ -922,7 +923,7 @@ pub(crate) mod triton_stark_tests { assert!(program.is_ok(), "program parses correctly"); let program = program.unwrap(); - let (aet, stdout, err) = program.simulate(input_symbols, secret_input_symbols); + let (aet, stdout, err) = simulate(&program, input_symbols, secret_input_symbols); if let Some(error) = err { panic!("The VM encountered the following problem: {}", error); } @@ -1541,7 +1542,7 @@ pub(crate) mod triton_stark_tests { #[test] fn triton_table_constraints_evaluate_to_zero_on_fibonacci_test() { let source_code_and_input = SourceCodeAndInput { - source_code: sample_programs::FIBONACCI_VIT.to_string(), + source_code: FIBONACCI_VIT.to_string(), input: vec![BFieldElement::new(100)], secret_input: vec![], }; @@ -1787,7 +1788,7 @@ pub(crate) mod triton_stark_tests { #[test] fn prove_verify_fibonacci_100_test() { let mut profiler = Some(TritonProfiler::new("Prove Fib 100")); - let source_code = sample_programs::FIBONACCI_VIT; + let source_code = FIBONACCI_VIT; let stdin = vec![100_u64.into()]; let secret_in = vec![]; @@ -1818,7 +1819,7 @@ pub(crate) mod triton_stark_tests { fn prove_verify_fib_shootout_test() { let cases = [(7, 21)]; - let code = sample_programs::FIB_SHOOTOUT; + let code = FIB_SHOOTOUT; for (n, expected) in cases { let stdin = vec![]; @@ -1839,7 +1840,7 @@ pub(crate) mod triton_stark_tests { #[test] #[ignore = "stress test"] fn prove_fib_successively_larger() { - let source_code = sample_programs::FIBONACCI_VIT; + let source_code = FIBONACCI_VIT; for fibonacci_number in [100, 200, 400, 800, 1600, 3200, 6400, 12800, 25600, 51200] { let mut profiler = Some(TritonProfiler::new(&format!( diff --git a/triton-vm/src/state.rs b/triton-vm/src/state.rs index 9c767efa5..45d53ece4 100644 --- a/triton-vm/src/state.rs +++ b/triton-vm/src/state.rs @@ -6,6 +6,11 @@ use anyhow::Result; use ndarray::Array1; use num_traits::One; use num_traits::Zero; + +use triton_opcodes::instruction::DivinationHint; +use triton_opcodes::instruction::{AnInstruction::*, Instruction}; +use triton_opcodes::ord_n::{Ord16, Ord16::*, Ord7}; +use triton_opcodes::program::Program; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::rescue_prime_regular::RescuePrimeRegular; use twenty_first::shared_math::rescue_prime_regular::DIGEST_LENGTH; @@ -17,18 +22,11 @@ use twenty_first::shared_math::x_field_element::XFieldElement; use crate::error::vm_err; use crate::error::vm_fail; use crate::error::InstructionError::*; -use crate::instruction::AnInstruction::*; -use crate::instruction::DivinationHint; -use crate::instruction::Instruction; use crate::op_stack::OpStack; -use crate::ord_n::Ord16; -use crate::ord_n::Ord16::*; -use crate::ord_n::Ord7; use crate::table::processor_table; use crate::table::processor_table::ProcessorMatrixRow; use crate::table::table_column::BaseTableColumn; use crate::table::table_column::ProcessorBaseTableColumn; -use crate::vm::Program; /// The number of state registers for hashing-specific instructions. pub const STATE_REGISTER_COUNT: usize = 16; @@ -699,9 +697,11 @@ mod vm_state_tests { use twenty_first::util_types::merkle_tree::MerkleTree; use twenty_first::util_types::merkle_tree_maker::MerkleTreeMaker; - use crate::instruction::sample_programs; use crate::op_stack::OP_STACK_REG_COUNT; + use crate::shared_tests::{FIBONACCI_VIT, FIB_FIXED_7_LT}; use crate::stark::Maker; + use crate::vm::run; + use crate::vm::triton_vm_tests::GCD_X_Y; use super::*; @@ -719,8 +719,8 @@ mod vm_state_tests { #[test] fn run_tvm_parse_pop_p_test() { - let program = sample_programs::push_push_add_pop_p(); - let (trace, _out, _err) = program.run(vec![], vec![]); + let program = Program::from_code("push 1 push 1 add pop").unwrap(); + let (trace, _out, _err) = run(&program, vec![], vec![]); for state in trace.iter() { println!("{}", state); @@ -729,9 +729,27 @@ mod vm_state_tests { #[test] fn run_tvm_hello_world_1_test() { - let code = sample_programs::HELLO_WORLD_1; + let code = " + push 10 + push 33 + push 100 + push 108 + push 114 + push 111 + push 87 + push 32 + push 44 + push 111 + push 108 + push 108 + push 101 + push 72 + + write_io write_io write_io write_io write_io write_io write_io + write_io write_io write_io write_io write_io write_io write_io + "; let program = Program::from_code(code).unwrap(); - let (trace, _out, _err) = program.run(vec![], vec![]); + let (trace, _out, _err) = run(&program, vec![], vec![]); let last_state = trace.last().unwrap(); assert_eq!(BFieldElement::zero(), last_state.op_stack.safe_peek(ST0)); @@ -743,7 +761,7 @@ mod vm_state_tests { fn run_tvm_halt_then_do_stuff_test() { let halt_then_do_stuff = "halt push 1 push 2 add invert write_io"; let program = Program::from_code(halt_then_do_stuff).unwrap(); - let (trace, _out, err) = program.run(vec![], vec![]); + let (trace, _out, err) = run(&program, vec![], vec![]); for state in trace.iter() { println!("{}", state); @@ -759,16 +777,18 @@ mod vm_state_tests { #[test] fn run_tvm_basic_ram_read_write_test() { - let program = Program::from_code(sample_programs::BASIC_RAM_READ_WRITE).unwrap(); - - for instruction in program.instructions.iter() { - println!("Instruction opcode: {}", instruction.opcode()); - } - let (trace, _out, err) = program.run(vec![], vec![]); - - for state in trace.iter() { - println!("{}", state); - } + let basic_ram_read_write_code = " + push 5 push 6 write_mem pop pop + push 15 push 16 write_mem pop pop + push 5 push 0 read_mem pop pop + push 15 push 0 read_mem pop pop + push 5 push 7 write_mem pop pop + push 15 push 0 read_mem + push 5 push 0 read_mem + halt + "; + let program = Program::from_code(basic_ram_read_write_code).unwrap(); + let (trace, _out, err) = run(&program, vec![], vec![]); if let Some(e) = err { println!("Error: {}", e); } @@ -788,12 +808,15 @@ mod vm_state_tests { #[test] fn run_tvm_edgy_ram_writes_test() { - let program = Program::from_code(sample_programs::EDGY_RAM_WRITES).unwrap(); - let (trace, _out, err) = program.run(vec![], vec![]); - - for state in trace.iter() { - println!("{}", state); - } + let edgy_ram_writes_code = " + write_mem // this should write 0 to address 0 + push 5 swap2 push 3 swap2 pop pop // stack is now of length 16 again + write_mem // this should write 3 to address 5 + swap2 read_mem // stack's top should now be 3, 5, 3, 0, 0, … + halt + "; + let program = Program::from_code(edgy_ram_writes_code).unwrap(); + let (trace, _out, err) = run(&program, vec![], vec![]); if let Some(e) = err { println!("Error: {}", e); } @@ -811,10 +834,37 @@ mod vm_state_tests { #[test] fn run_tvm_sample_weights_test() { - let program = Program::from_code(sample_programs::SAMPLE_WEIGHTS).unwrap(); + // TVM assembly to sample weights for the recursive verifier + // + // input: seed, num_weights + // + // output: num_weights-many random weights + let sample_weights_code = " + push 17 push 13 push 11 // get seed - should be an argument + read_io // number of weights - should be argument + sample_weights: // proper program starts here + call sample_weights_loop // setup done, start sampling loop + pop pop // clean up stack: RAM value & pointer + pop pop pop pop // clean up stack: seed & countdown + halt // done - should be return + + sample_weights_loop: // subroutine: loop until all weights are sampled + dup0 push 0 eq skiz return // no weights left + push -1 add // decrease number of weights to still sample + push 0 push 0 push 0 push 0 // prepare for hashing + push 0 push 0 push 0 push 0 // prepare for hashing + dup11 dup11 dup11 dup11 // prepare for hashing + hash // hash seed & countdown + swap13 swap10 pop // re-organize stack + swap13 swap10 pop // re-organize stack + swap13 swap10 swap7 // re-organize stack + pop pop pop pop pop pop pop // remove unnecessary remnants of digest + recurse // repeat + "; + let program = Program::from_code(sample_weights_code).unwrap(); println!("Successfully parsed the program."); let input_symbols = vec![BFieldElement::new(11)]; - let (trace, _out, err) = program.run(input_symbols, vec![]); + let (trace, _out, err) = run(&program, input_symbols, vec![]); for state in trace.iter() { println!("{}", state); @@ -828,6 +878,74 @@ mod vm_state_tests { assert_eq!(last_state.current_instruction().unwrap(), Halt); } + /// TVM assembly to verify Merkle authentication paths + /// + /// input: merkle root, number of leafs, leaf values, APs + /// + /// output: Result<(), VMFail> + const MT_AP_VERIFY: &str = concat!( + "read_io ", // number of authentication paths to test + "", // stack: [num] + "mt_ap_verify: ", // proper program starts here + "push 0 swap1 write_mem pop pop ", // store number of APs at RAM address 0 + "", // stack: [] + "read_io read_io read_io read_io read_io ", // read Merkle root + "", // stack: [r4 r3 r2 r1 r0] + "call check_aps ", // + "pop pop pop pop pop ", // leave clean stack: Merkle root + "", // stack: [] + "halt ", // done – should be “return” + "", + "", // subroutine: check AP one at a time + "", // stack before: [* r4 r3 r2 r1 r0] + "", // stack after: [* r4 r3 r2 r1 r0] + "check_aps: ", // start function description: + "push 0 push 0 read_mem dup0 ", // get number of APs left to check + "", // stack: [* r4 r3 r2 r1 r0 0 num_left num_left] + "push 0 eq ", // see if there are authentication paths left + "", // stack: [* r4 r3 r2 r1 r0 0 num_left num_left==0] + "skiz return ", // return if no authentication paths left + "push -1 add write_mem pop pop ", // decrease number of authentication paths left to check + "", // stack: [* r4 r3 r2 r1 r0] + "call get_idx_and_hash_leaf ", // + "", // stack: [* r4 r3 r2 r1 r0 idx d4 d3 d2 d1 d0 0 0 0 0 0] + "call traverse_tree ", // + "", // stack: [* r4 r3 r2 r1 r0 idx>>2 - - - - - - - - - -] + "call assert_tree_top ", // + // stack: [* r4 r3 r2 r1 r0] + "recurse ", // check next AP + "", + "", // subroutine: read index & hash leaf + "", // stack before: [*] + "", // stack afterwards: [* idx d4 d3 d2 d1 d0 0 0 0 0 0] + "get_idx_and_hash_leaf: ", // start function description: + "read_io ", // read node index + "read_io read_io read_io read_io read_io ", // read leaf's value + "push 0 push 0 push 0 push 0 push 0 ", // pad before fixed-length hash + "hash return ", // compute leaf's digest + "", + "", // subroutine: go up tree + "", // stack before: [* idx - - - - - - - - - -] + "", // stack after: [* idx>>2 - - - - - - - - - -] + "traverse_tree: ", // start function description: + "dup10 push 1 eq skiz return ", // break loop if node index is 1 + "divine_sibling hash recurse ", // move up one level in the Merkle tree + "", + "", // subroutine: compare digests + "", // stack before: [* r4 r3 r2 r1 r0 idx a b c d e - - - - -] + "", // stack after: [* r4 r3 r2 r1 r0] + "assert_tree_top: ", // start function description: + "pop pop pop pop pop ", // remove unnecessary “0”s from hashing + "", // stack: [* r4 r3 r2 r1 r0 idx a b c d e] + "swap1 swap2 swap3 swap4 swap5 ", + "", // stack: [* r4 r3 r2 r1 r0 a b c d e idx] + "assert ", // + "", // stack: [* r4 r3 r2 r1 r0 a b c d e] + "assert_vector ", // actually compare to root of tree + "pop pop pop pop pop ", // clean up stack, leave only one root + "return ", // + ); + #[test] fn run_tvm_mt_ap_verify_test() { // generate merkle tree @@ -844,7 +962,7 @@ mod vm_state_tests { let root: Digest = merkle_tree.get_root(); // generate program - let program = Program::from_code(sample_programs::MT_AP_VERIFY).unwrap(); + let program = Program::from_code(MT_AP_VERIFY).unwrap(); let order: Vec = (0..5).rev().collect(); let selected_leaf_indices = [0, 28, 55]; @@ -904,7 +1022,7 @@ mod vm_state_tests { leafs[55].values()[order[4]], ]; - let (trace, _out, err) = program.run(input, secret_input); + let (trace, _out, err) = run(&program, input, secret_input); for state in trace.iter() { println!("{}", state); @@ -920,10 +1038,23 @@ mod vm_state_tests { #[test] fn run_tvm_get_colinear_y_test() { - let program = Program::from_code(sample_programs::GET_COLINEAR_Y).unwrap(); + // see also: get_colinear_y in src/shared_math/polynomial.rs + let get_colinear_y_code = " + read_io // p2_x + read_io read_io // p1_y p1_x + read_io read_io // p0_y p0_x + swap3 push -1 mul dup1 add // dy = p0_y - p1_y + dup3 push -1 mul dup5 add mul // dy·(p2_x - p0_x) + dup3 dup3 push -1 mul add // dx = p0_x - p1_x + invert mul add // compute result + swap3 pop pop pop // leave a clean stack + write_io halt + "; + + let program = Program::from_code(get_colinear_y_code).unwrap(); println!("Successfully parsed the program."); let input_symbols = [7, 2, 1, 3, 4].map(BFieldElement::new).to_vec(); - let (trace, out, err) = program.run(input_symbols, vec![]); + let (trace, out, err) = run(&program, input_symbols, vec![]); assert_eq!(out[0], BFieldElement::new(4)); for state in trace.iter() { println!("{}", state); @@ -939,9 +1070,24 @@ mod vm_state_tests { #[test] fn run_tvm_countdown_from_10_test() { - let code = sample_programs::COUNTDOWN_FROM_10; - let program = Program::from_code(code).unwrap(); - let (trace, out, err) = program.run(vec![], vec![]); + let countdown_code = " + push 10 + call loop + + loop: + dup0 + write_io + push -1 + add + dup0 + skiz + recurse + write_io + halt + "; + + let program = Program::from_code(countdown_code).unwrap(); + let (trace, out, err) = run(&program, vec![], vec![]); println!("{}", program); for state in trace.iter() { @@ -958,14 +1104,9 @@ mod vm_state_tests { #[test] fn run_tvm_fibonacci_vit_tvm() { - let code = sample_programs::FIBONACCI_VIT; + let code = FIBONACCI_VIT; let program = Program::from_code(code).unwrap(); - - let (trace, out, err) = program.run(vec![7_u64.into()], vec![]); - - for state in trace.iter() { - println!("{}", state); - } + let (_trace, out, err) = run(&program, vec![7_u64.into()], vec![]); if let Some(e) = err { panic!("The VM encountered an error: {e}"); } @@ -975,26 +1116,20 @@ mod vm_state_tests { #[test] fn run_tvm_fibonacci_lt_test() { - let code = sample_programs::FIB_FIXED_7_LT; + let code = FIB_FIXED_7_LT; let program = Program::from_code(code).unwrap(); - let (trace, _out, _err) = program.run(vec![], vec![]); - - println!("{}", program); - for state in trace.iter() { - println!("{}", state); - } - + let (trace, _out, _err) = run(&program, vec![], vec![]); let last_state = trace.last().unwrap(); assert_eq!(BFieldElement::new(21), last_state.op_stack.st(ST0)); } #[test] fn run_tvm_gcd_test() { - let code = sample_programs::GCD_X_Y; + let code = GCD_X_Y; let program = Program::from_code(code).unwrap(); println!("{}", program); - let (trace, out, _err) = program.run(vec![42_u64.into(), 56_u64.into()], vec![]); + let (trace, out, _err) = run(&program, vec![42_u64.into(), 56_u64.into()], vec![]); println!("{}", program); for state in trace.iter() { @@ -1010,17 +1145,13 @@ mod vm_state_tests { fn run_tvm_swap_test() { let code = "push 1 push 2 swap1 halt"; let program = Program::from_code(code).unwrap(); - let (trace, _out, _err) = program.run(vec![], vec![]); - - for state in trace.iter() { - println!("{}", state); - } + let (_trace, _out, _err) = run(&program, vec![], vec![]); } #[test] fn read_mem_unitialized() { let program = Program::from_code("read_mem halt").unwrap(); - let (trace, _out, err) = program.run(vec![], vec![]); + let (trace, _out, err) = run(&program, vec![], vec![]); assert!(err.is_none(), "Reading from uninitialized memory address"); assert_eq!(2, trace.len()); } diff --git a/triton-vm/src/table/jump_stack_table.rs b/triton-vm/src/table/jump_stack_table.rs index 44d948888..86a04ac48 100644 --- a/triton-vm/src/table/jump_stack_table.rs +++ b/triton-vm/src/table/jump_stack_table.rs @@ -13,6 +13,7 @@ use strum::EnumCount; use strum_macros::Display; use strum_macros::EnumCount as EnumCountMacro; use strum_macros::EnumIter; +use triton_opcodes::instruction::Instruction; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::traits::Inverse; use twenty_first::shared_math::x_field_element::XFieldElement; @@ -21,7 +22,6 @@ use std::fmt::Display; use std::fmt::Formatter; use JumpStackTableChallengeId::*; -use crate::instruction::Instruction; use crate::table::challenges::TableChallenges; use crate::table::constraint_circuit::ConstraintCircuit; use crate::table::constraint_circuit::ConstraintCircuitBuilder; diff --git a/triton-vm/src/table/processor_table.rs b/triton-vm/src/table/processor_table.rs index 8c2ae7271..ea0d686f5 100644 --- a/triton-vm/src/table/processor_table.rs +++ b/triton-vm/src/table/processor_table.rs @@ -18,16 +18,15 @@ use strum::EnumCount; use strum_macros::Display; use strum_macros::EnumCount as EnumCountMacro; use strum_macros::EnumIter; +use triton_opcodes::instruction::all_instructions_without_args; +use triton_opcodes::instruction::{AnInstruction::*, Instruction}; +use triton_opcodes::ord_n::Ord7; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::traits::Inverse; use twenty_first::shared_math::x_field_element::XFieldElement; use ProcessorTableChallengeId::*; -use crate::instruction::all_instructions_without_args; -use crate::instruction::AnInstruction::*; -use crate::instruction::Instruction; -use crate::ord_n::Ord7; use crate::table::challenges::TableChallenges; use crate::table::constraint_circuit::ConstraintCircuit; use crate::table::constraint_circuit::ConstraintCircuitBuilder; @@ -4403,20 +4402,22 @@ impl<'a> Display for ExtProcessorMatrixRow<'a> { mod constraint_polynomial_tests { use ndarray::Array2; - use crate::ord_n::Ord16; use crate::stark::triton_stark_tests::parse_simulate_pad; use crate::table::challenges::AllChallenges; use crate::table::master_table::MasterTable; use crate::table::processor_table::ProcessorMatrixRow; - use crate::vm::Program; + use crate::vm::simulate_no_input; + use triton_opcodes::ord_n::Ord16; + use triton_opcodes::program::Program; use super::*; #[test] /// helps identifying whether the printing causes an infinite loop fn print_simple_processor_table_row_test() { - let program = Program::from_code("push 2 push -1 add assert halt").unwrap(); - let (aet, _, _) = program.simulate_no_input(); + let code = "push 2 push -1 add assert halt"; + let program = Program::from_code(code).unwrap(); + let (aet, _, _) = simulate_no_input(&program); for row in aet.processor_matrix.rows() { println!("{}", ProcessorMatrixRow { row }); } diff --git a/triton-vm/src/table/ram_table.rs b/triton-vm/src/table/ram_table.rs index 39afb8a13..d14caabd9 100644 --- a/triton-vm/src/table/ram_table.rs +++ b/triton-vm/src/table/ram_table.rs @@ -13,6 +13,7 @@ use strum::EnumCount; use strum_macros::Display; use strum_macros::EnumCount as EnumCountMacro; use strum_macros::EnumIter; +use triton_opcodes::instruction::Instruction; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::polynomial::Polynomial; use twenty_first::shared_math::traits::Inverse; @@ -20,7 +21,6 @@ use twenty_first::shared_math::x_field_element::XFieldElement; use RamTableChallengeId::*; -use crate::instruction::Instruction; use crate::table::challenges::TableChallenges; use crate::table::constraint_circuit::ConstraintCircuit; use crate::table::constraint_circuit::ConstraintCircuitBuilder; diff --git a/triton-vm/src/vm.rs b/triton-vm/src/vm.rs index 573cc6a05..2d61831b9 100644 --- a/triton-vm/src/vm.rs +++ b/triton-vm/src/vm.rs @@ -1,19 +1,12 @@ -use std::fmt::Display; -use std::io::Cursor; - -use anyhow::Result; -use itertools::Itertools; use ndarray::Array2; use ndarray::Axis; + +use triton_opcodes::program::Program; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::rescue_prime_regular::NUM_ROUNDS; use twenty_first::shared_math::rescue_prime_regular::ROUND_CONSTANTS; use twenty_first::shared_math::rescue_prime_regular::STATE_SIZE; -use crate::instruction; -use crate::instruction::parse; -use crate::instruction::Instruction; -use crate::instruction::LabelledInstruction; use crate::state::VMOutput; use crate::state::VMState; use crate::table::hash_table; @@ -24,6 +17,93 @@ use crate::table::table_column::HashBaseTableColumn::CONSTANT0A; use crate::table::table_column::HashBaseTableColumn::ROUNDNUMBER; use crate::table::table_column::HashBaseTableColumn::STATE0; +/// Simulate (execute) a `Program` and record every state transition. Returns an +/// `AlgebraicExecutionTrace` recording every intermediate state of the processor and all co- +/// processors. +/// +/// On premature termination of the VM, returns the `AlgebraicExecutionTrace` for the execution +/// up to the point of failure. +pub fn simulate( + program: &Program, + mut stdin: Vec, + mut secret_in: Vec, +) -> ( + AlgebraicExecutionTrace, + Vec, + Option, +) { + let mut aet = AlgebraicExecutionTrace::default(); + let mut state = VMState::new(program); + // record initial state + aet.processor_matrix + .push_row(state.to_processor_row().view()) + .expect("shapes must be identical"); + + let mut stdout = vec![]; + while !state.is_complete() { + let vm_output = match state.step_mut(&mut stdin, &mut secret_in) { + Err(err) => return (aet, stdout, Some(err)), + Ok(vm_output) => vm_output, + }; + + match vm_output { + Some(VMOutput::XlixTrace(hash_trace)) => aet.append_hash_trace(*hash_trace), + Some(VMOutput::WriteOutputSymbol(written_word)) => stdout.push(written_word), + None => (), + } + // Record next, to be executed state. + aet.processor_matrix + .push_row(state.to_processor_row().view()) + .expect("shapes must be identical"); + } + + (aet, stdout, None) +} + +/// Wrapper around `.simulate_with_input()` and thus also around +/// `.simulate()` for convenience when neither explicit nor non- +/// deterministic input is provided. Behavior is the same as that +/// of `.simulate_with_input()` +pub fn simulate_no_input( + program: &Program, +) -> ( + AlgebraicExecutionTrace, + Vec, + Option, +) { + simulate(program, vec![], vec![]) +} + +pub fn run( + program: &Program, + mut stdin: Vec, + mut secret_in: Vec, +) -> (Vec, Vec, Option) { + let mut states = vec![VMState::new(program)]; + let mut current_state = states.last().unwrap(); + + let mut stdout = vec![]; + while !current_state.is_complete() { + let step = current_state.step(&mut stdin, &mut secret_in); + let (next_state, vm_output) = match step { + Err(err) => { + println!("Encountered an error when running VM."); + return (states, stdout, Some(err)); + } + Ok((next_state, vm_output)) => (next_state, vm_output), + }; + + if let Some(VMOutput::WriteOutputSymbol(written_word)) = vm_output { + stdout.push(written_word); + } + + states.push(next_state); + current_state = states.last().unwrap(); + } + + (states, stdout, None) +} + #[derive(Debug, Clone)] pub struct AlgebraicExecutionTrace { pub processor_matrix: Array2, @@ -78,203 +158,12 @@ impl AlgebraicExecutionTrace { } } -#[derive(Debug, Clone, Default, PartialEq, Eq)] -pub struct Program { - pub instructions: Vec, -} - -impl Display for Program { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut stream = self.instructions.iter(); - while let Some(instruction) = stream.next() { - writeln!(f, "{}", instruction)?; - - // Skip duplicate placeholder used for aligning instructions and instruction_pointer in VM. - for _ in 1..instruction.size() { - stream.next(); - } - } - Ok(()) - } -} - -pub struct SkippyIter { - cursor: Cursor>, -} - -impl Iterator for SkippyIter { - type Item = Instruction; - - fn next(&mut self) -> Option { - let pos = self.cursor.position() as usize; - let instructions = self.cursor.get_ref(); - let instruction = *instructions.get(pos)?; - self.cursor.set_position((pos + instruction.size()) as u64); - - Some(instruction) - } -} - -impl IntoIterator for Program { - type Item = Instruction; - - type IntoIter = SkippyIter; - - fn into_iter(self) -> Self::IntoIter { - let cursor = Cursor::new(self.instructions); - SkippyIter { cursor } - } -} - -/// A `Program` is a `Vec` 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`. - /// - /// All valid programs terminate with `Halt`. - /// - /// `new()` will append `Halt` if not present. - pub fn new(input: &[LabelledInstruction]) -> Self { - let instructions = instruction::convert_labels(input) - .iter() - .flat_map(|instr| vec![*instr; instr.size()]) - .collect::>(); - - Program { instructions } - } - - /// Create a `Program` by parsing source code. - /// - /// All valid programs terminate with `Halt`. - /// - /// `from_code()` will append `Halt` if not present. - pub fn from_code(code: &str) -> Result { - let instructions = parse(code)?; - Ok(Program::new(&instructions)) - } - - /// Convert a `Program` to a `Vec`. - /// - /// Every single-word instruction is converted to a single word. - /// - /// Every double-word instruction is converted to two words. - pub fn to_bwords(&self) -> Vec { - self.clone() - .into_iter() - .map(|instruction| { - let opcode = instruction.opcode_b(); - if let Some(arg) = instruction.arg() { - vec![opcode, arg] - } else { - vec![opcode] - } - }) - .concat() - } - - /// Simulate (execute) a `Program` and record every state transition. Returns an - /// `AlgebraicExecutionTrace` recording every intermediate state of the processor and all co- - /// processors. - /// - /// On premature termination of the VM, returns the `AlgebraicExecutionTrace` for the execution - /// up to the point of failure. - pub fn simulate( - &self, - mut stdin: Vec, - mut secret_in: Vec, - ) -> ( - AlgebraicExecutionTrace, - Vec, - Option, - ) { - let mut aet = AlgebraicExecutionTrace::default(); - let mut state = VMState::new(self); - // record initial state - aet.processor_matrix - .push_row(state.to_processor_row().view()) - .expect("shapes must be identical"); - - let mut stdout = vec![]; - while !state.is_complete() { - let vm_output = match state.step_mut(&mut stdin, &mut secret_in) { - Err(err) => return (aet, stdout, Some(err)), - Ok(vm_output) => vm_output, - }; - - match vm_output { - Some(VMOutput::XlixTrace(hash_trace)) => aet.append_hash_trace(*hash_trace), - Some(VMOutput::WriteOutputSymbol(written_word)) => stdout.push(written_word), - None => (), - } - // Record next, to be executed state. - aet.processor_matrix - .push_row(state.to_processor_row().view()) - .expect("shapes must be identical"); - } - - (aet, stdout, None) - } - - /// Wrapper around `.simulate_with_input()` and thus also around - /// `.simulate()` for convenience when neither explicit nor non- - /// deterministic input is provided. Behavior is the same as that - /// of `.simulate_with_input()` - pub fn simulate_no_input( - &self, - ) -> ( - AlgebraicExecutionTrace, - Vec, - Option, - ) { - self.simulate(vec![], vec![]) - } - - pub fn run( - &self, - mut stdin: Vec, - mut secret_in: Vec, - ) -> (Vec, Vec, Option) { - let mut states = vec![VMState::new(self)]; - let mut current_state = states.last().unwrap(); - - let mut stdout = vec![]; - while !current_state.is_complete() { - let step = current_state.step(&mut stdin, &mut secret_in); - let (next_state, vm_output) = match step { - Err(err) => { - println!("Encountered an error when running VM."); - return (states, stdout, Some(err)); - } - Ok((next_state, vm_output)) => (next_state, vm_output), - }; - - if let Some(VMOutput::WriteOutputSymbol(written_word)) = vm_output { - stdout.push(written_word); - } - - states.push(next_state); - current_state = states.last().unwrap(); - } - - (states, stdout, None) - } - - pub fn len(&self) -> usize { - self.instructions.len() - } - - pub fn is_empty(&self) -> bool { - self.instructions.is_empty() - } -} - #[cfg(test)] pub mod triton_vm_tests { use std::ops::BitAnd; use std::ops::BitXor; + use itertools::Itertools; use ndarray::Array1; use ndarray::ArrayView1; use num_traits::One; @@ -286,7 +175,6 @@ pub mod triton_vm_tests { use twenty_first::shared_math::rescue_prime_regular::RescuePrimeRegular; use twenty_first::shared_math::traits::FiniteField; - use crate::instruction::sample_programs; use crate::shared_tests::SourceCodeAndInput; use crate::table::processor_table::ProcessorMatrixRow; @@ -300,14 +188,46 @@ pub mod triton_vm_tests { .join(", ") } + pub const GCD_X_Y: &str = " + read_io // _ a + read_io // _ a b + dup1 // _ a b a + dup1 // _ a b a b + lt // _ a b b d + + // --- + loop_cond: + dup1 + push 0 + eq + skiz + call terminate // _ d n where d != 0 + dup1 // _ d n d + dup1 // _ d n d n + div // _ d n q r + swap2 // _ d r q n + pop // _ d r q + pop // _ d r + swap1 // _ r d + call loop_cond + // --- + + terminate: + // _ d n where d == 0 + write_io // _ d + halt + "; + #[test] fn initialise_table_test() { - let code = sample_programs::GCD_X_Y; + let code = GCD_X_Y; let program = Program::from_code(code).unwrap(); let stdin = vec![BFieldElement::new(42), BFieldElement::new(56)]; - let (aet, stdout, err) = program.simulate(stdin, vec![]); + let (aet, stdout, err) = simulate(&program, stdin, vec![]); println!( "VM output: [{}]", @@ -335,7 +255,7 @@ pub mod triton_vm_tests { println!("{}", program); - let (aet, _, err) = program.simulate_no_input(); + let (aet, _, err) = simulate_no_input(&program); println!("{:?}", err); for row in aet.processor_matrix.rows() { @@ -345,11 +265,11 @@ pub mod triton_vm_tests { #[test] fn simulate_tvm_gcd_test() { - let code = sample_programs::GCD_X_Y; + let code = GCD_X_Y; let program = Program::from_code(code).unwrap(); let stdin = vec![42_u64.into(), 56_u64.into()]; - let (_, stdout, err) = program.simulate(stdin, vec![]); + let (_, stdout, err) = simulate(&program, stdin, vec![]); let stdout = Array1::from(stdout); println!("VM output: [{}]", pretty_print_array_view(stdout.view()));