diff --git a/Cargo.lock b/Cargo.lock index 6d445da..a2c2368 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,6 +189,7 @@ dependencies = [ name = "brainfuck_prover" version = "0.1.0" dependencies = [ + "brainfuck_vm", "num-traits", "stwo-prover", ] diff --git a/Cargo.toml b/Cargo.toml index 0d671bf..2a37209 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,8 @@ default_trait_access = "allow" module_name_repetitions = "allow" [workspace.dependencies] +brainfuck_vm = { path = "crates/brainfuck_vm" } +brainfuck_prover = { path = "crates/brainfuck_prover" } clap = { version = "4.3.10", features = ["derive"] } stwo-prover = { git = "https://github.com/starkware-libs/stwo", rev = "f6214d1" } tracing = "0.1" diff --git a/crates/brainfuck_prover/Cargo.toml b/crates/brainfuck_prover/Cargo.toml index f28a0e6..2d538e4 100644 --- a/crates/brainfuck_prover/Cargo.toml +++ b/crates/brainfuck_prover/Cargo.toml @@ -11,3 +11,4 @@ workspace = true [dependencies] stwo-prover.workspace = true num-traits.workspace = true +brainfuck_vm.workspace = true diff --git a/crates/brainfuck_prover/src/components/instruction/table.rs b/crates/brainfuck_prover/src/components/instruction/table.rs index adee241..dcecb2c 100644 --- a/crates/brainfuck_prover/src/components/instruction/table.rs +++ b/crates/brainfuck_prover/src/components/instruction/table.rs @@ -1,3 +1,5 @@ +use brainfuck_vm::registers::Registers; +use num_traits::Zero; use stwo_prover::core::fields::m31::BaseField; /// Represents a single row in the Instruction Table. @@ -83,11 +85,76 @@ impl InstructionTable { pub fn get_row(&self, row: &InstructionTableRow) -> Option<&InstructionTableRow> { self.table.iter().find(|r| *r == row) } + + /// Retrieves the last row in the Instruction Table. + /// + /// # Returns + /// An `Option` containing a reference to the last [`InstructionTableRow`] in the table, + /// or `None` if the table is empty. + pub fn last_row(&self) -> Option<&InstructionTableRow> { + self.table.last() + } +} + +impl From> for InstructionTable { + fn from(registers: Vec) -> Self { + // Create a mutable copy of the input `registers` to sort it. + let mut sorted_registers = registers; + // Sort `sorted_registers` by + // 1. `ip` (instruction pointer) + // 2. `clk` (clock cycle) + sorted_registers.sort_by(|a, b| a.ip.cmp(&b.ip).then_with(|| a.clk.cmp(&b.clk))); + + // Initialize a new instruction table to store the sorted and processed rows. + let mut instruction_table = Self::new(); + + // Use `current_ip` to track the current instruction pointer and group elements. + let mut current_ip = None; + + for register in &sorted_registers { + // Check if the current register marks the end of the program with: + // - `ci` = 0 and `ni` = 0 + // If so, stop adding rows and break out of the loop. + if register.ci == BaseField::zero() && register.ni == BaseField::zero() { + break; + } + + // If the instruction pointer (`ip`) changes: + // - Add an extra row to ensure each instruction appears `m + 1` times, where `m` is the + // number of occurrences. + if Some(register.ip) != current_ip { + current_ip = Some(register.ip); + if let Some(last_row) = instruction_table.last_row() { + instruction_table.add_row(InstructionTableRow { + ip: last_row.ip, + ci: last_row.ci, + ni: last_row.ni, + }); + } + } + + // Add the current register to the instruction table. + instruction_table.add_row(InstructionTableRow { + ip: register.ip, + ci: register.ci, + ni: register.ni, + }); + } + + // Ensure the last row is duplicated one final time to maintain the `m + 1` requirement. + if let Some(last_row) = instruction_table.last_row() { + instruction_table.add_row(last_row.clone()); + } + + // Return the fully constructed and populated instruction table. + instruction_table + } } #[cfg(test)] mod tests { use super::*; + use brainfuck_vm::{compiler::Compiler, test_helper::create_test_machine}; use num_traits::Zero; #[test] @@ -191,4 +258,303 @@ mod tests { // Check that the retrieved row is None assert!(retrieved.is_none(), "Should return None for a non-existing row."); } + + #[test] + fn test_instruction_table_last_row() { + let mut instruction_table = InstructionTable::new(); + + // Initially, the table should be empty, so last_row should return None + assert!(instruction_table.last_row().is_none(), "The table should be empty initially."); + + // Add a row to the table + let row = InstructionTableRow { + ip: BaseField::from(1), + ci: BaseField::from(2), + ni: BaseField::from(3), + }; + instruction_table.add_row(row.clone()); + + // Now, last_row should return a reference to the added row + assert_eq!( + instruction_table.last_row(), + Some(&row), + "The last row should match the added row." + ); + + // Add another row + let second_row = InstructionTableRow { + ip: BaseField::from(4), + ci: BaseField::from(5), + ni: BaseField::from(6), + }; + instruction_table.add_row(second_row.clone()); + + // Now, last_row should return a reference to the second row + assert_eq!( + instruction_table.last_row(), + Some(&second_row), + "The last row should match the second added row." + ); + } + + #[test] + fn test_instruction_table_from_registers_empty() { + // Create an empty vector of registers + let registers = vec![]; + + // Convert to InstructionTable and ensure sorting + let instruction_table = InstructionTable::from(registers); + + // Check that the table is empty + assert!(instruction_table.table.is_empty()); + } + + #[test] + fn test_instruction_table_from_registers() { + // Create Registers in unsorted order + let registers = vec![ + Registers { + ip: BaseField::from(1), + clk: BaseField::from(3), + ci: BaseField::from(30), + ni: BaseField::from(40), + mp: BaseField::zero(), + mv: BaseField::zero(), + mvi: BaseField::zero(), + }, + Registers { + ip: BaseField::from(2), + clk: BaseField::from(1), + ci: BaseField::from(10), + ni: BaseField::from(20), + mp: BaseField::zero(), + mv: BaseField::zero(), + mvi: BaseField::zero(), + }, + Registers { + ip: BaseField::from(1), + clk: BaseField::from(2), + ci: BaseField::from(30), + ni: BaseField::from(40), + mp: BaseField::zero(), + mv: BaseField::zero(), + mvi: BaseField::zero(), + }, + ]; + + // Convert to InstructionTable and ensure sorting + let instruction_table = InstructionTable::from(registers); + + // Check the order of the rows + assert_eq!( + instruction_table, + InstructionTable { + table: vec![ + InstructionTableRow { + ip: BaseField::from(1), + ci: BaseField::from(30), + ni: BaseField::from(40) + }, + InstructionTableRow { + ip: BaseField::from(1), + ci: BaseField::from(30), + ni: BaseField::from(40) + }, + InstructionTableRow { + ip: BaseField::from(1), + ci: BaseField::from(30), + ni: BaseField::from(40) + }, + InstructionTableRow { + ip: BaseField::from(2), + ci: BaseField::from(10), + ni: BaseField::from(20) + }, + InstructionTableRow { + ip: BaseField::from(2), + ci: BaseField::from(10), + ni: BaseField::from(20) + }, + ] + } + ); + } + + #[test] + #[allow(clippy::cast_lossless, clippy::too_many_lines)] + fn test_instruction_table_from_registers_example_program() { + // Create a small program and compile it + // Reference: https://neptune.cash/learn/brainfuck-tutorial/ + let code = "++>,<[>+.<-]"; + let mut compiler = Compiler::new(code); + let instructions = compiler.compile(); + // Create a machine and execute the program + let (mut machine, _) = create_test_machine(&instructions, &[1]); + let () = machine.execute().expect("Failed to execute machine"); + + // Get the trace of the executed program + let trace = machine.get_trace(); + + // Convert the trace to an `InstructionTable` + let instruction_table: InstructionTable = trace.into(); + + // Create the expected `InstructionTable` + let expected_instruction_table = InstructionTable { + table: vec![ + InstructionTableRow { + ip: BaseField::from(0), + ci: BaseField::from(b'+' as u32), + ni: BaseField::from(b'+' as u32), + }, + InstructionTableRow { + ip: BaseField::from(0), + ci: BaseField::from(b'+' as u32), + ni: BaseField::from(b'+' as u32), + }, + InstructionTableRow { + ip: BaseField::from(1), + ci: BaseField::from(b'+' as u32), + ni: BaseField::from(b'>' as u32), + }, + InstructionTableRow { + ip: BaseField::from(1), + ci: BaseField::from(b'+' as u32), + ni: BaseField::from(b'>' as u32), + }, + InstructionTableRow { + ip: BaseField::from(2), + ci: BaseField::from(b'>' as u32), + ni: BaseField::from(b',' as u32), + }, + InstructionTableRow { + ip: BaseField::from(2), + ci: BaseField::from(b'>' as u32), + ni: BaseField::from(b',' as u32), + }, + InstructionTableRow { + ip: BaseField::from(3), + ci: BaseField::from(b',' as u32), + ni: BaseField::from(b'<' as u32), + }, + InstructionTableRow { + ip: BaseField::from(3), + ci: BaseField::from(b',' as u32), + ni: BaseField::from(b'<' as u32), + }, + InstructionTableRow { + ip: BaseField::from(4), + ci: BaseField::from(b'<' as u32), + ni: BaseField::from(b'[' as u32), + }, + InstructionTableRow { + ip: BaseField::from(4), + ci: BaseField::from(b'<' as u32), + ni: BaseField::from(b'[' as u32), + }, + InstructionTableRow { + ip: BaseField::from(5), + ci: BaseField::from(b'[' as u32), + ni: BaseField::from(13), + }, + InstructionTableRow { + ip: BaseField::from(5), + ci: BaseField::from(b'[' as u32), + ni: BaseField::from(13), + }, + InstructionTableRow { + ip: BaseField::from(7), + ci: BaseField::from(b'>' as u32), + ni: BaseField::from(b'+' as u32), + }, + InstructionTableRow { + ip: BaseField::from(7), + ci: BaseField::from(b'>' as u32), + ni: BaseField::from(b'+' as u32), + }, + InstructionTableRow { + ip: BaseField::from(7), + ci: BaseField::from(b'>' as u32), + ni: BaseField::from(b'+' as u32), + }, + InstructionTableRow { + ip: BaseField::from(8), + ci: BaseField::from(b'+' as u32), + ni: BaseField::from(b'.' as u32), + }, + InstructionTableRow { + ip: BaseField::from(8), + ci: BaseField::from(b'+' as u32), + ni: BaseField::from(b'.' as u32), + }, + InstructionTableRow { + ip: BaseField::from(8), + ci: BaseField::from(b'+' as u32), + ni: BaseField::from(b'.' as u32), + }, + InstructionTableRow { + ip: BaseField::from(9), + ci: BaseField::from(b'.' as u32), + ni: BaseField::from(b'<' as u32), + }, + InstructionTableRow { + ip: BaseField::from(9), + ci: BaseField::from(b'.' as u32), + ni: BaseField::from(b'<' as u32), + }, + InstructionTableRow { + ip: BaseField::from(9), + ci: BaseField::from(b'.' as u32), + ni: BaseField::from(b'<' as u32), + }, + InstructionTableRow { + ip: BaseField::from(10), + ci: BaseField::from(b'<' as u32), + ni: BaseField::from(b'-' as u32), + }, + InstructionTableRow { + ip: BaseField::from(10), + ci: BaseField::from(b'<' as u32), + ni: BaseField::from(b'-' as u32), + }, + InstructionTableRow { + ip: BaseField::from(10), + ci: BaseField::from(b'<' as u32), + ni: BaseField::from(b'-' as u32), + }, + InstructionTableRow { + ip: BaseField::from(11), + ci: BaseField::from(b'-' as u32), + ni: BaseField::from(b']' as u32), + }, + InstructionTableRow { + ip: BaseField::from(11), + ci: BaseField::from(b'-' as u32), + ni: BaseField::from(b']' as u32), + }, + InstructionTableRow { + ip: BaseField::from(11), + ci: BaseField::from(b'-' as u32), + ni: BaseField::from(b']' as u32), + }, + InstructionTableRow { + ip: BaseField::from(12), + ci: BaseField::from(b']' as u32), + ni: BaseField::from(7), + }, + InstructionTableRow { + ip: BaseField::from(12), + ci: BaseField::from(b']' as u32), + ni: BaseField::from(7), + }, + InstructionTableRow { + ip: BaseField::from(12), + ci: BaseField::from(b']' as u32), + ni: BaseField::from(7), + }, + ], + }; + + // Verify that the instruction table is correct + assert_eq!(instruction_table, expected_instruction_table); + } } diff --git a/crates/brainfuck_vm/src/machine.rs b/crates/brainfuck_vm/src/machine.rs index 3839402..ab6d8f6 100644 --- a/crates/brainfuck_vm/src/machine.rs +++ b/crates/brainfuck_vm/src/machine.rs @@ -129,6 +129,7 @@ impl Machine { } pub fn execute(&mut self) -> Result<(), MachineError> { + println!("ciiiiiiiiiiiic"); while self.state.registers.ip < BaseField::from(self.program.code.len()) { self.state.registers.ci = self.program.code[self.state.registers.ip.0 as usize]; self.state.registers.ni =