From 8e9ecf98820d66141e0d63db9531df3d9325fbc6 Mon Sep 17 00:00:00 2001 From: Thomas Coratger Date: Tue, 23 Sep 2025 12:52:16 +0200 Subject: [PATCH 01/10] utils: faster add_multilinears --- Cargo.lock | 1 + Cargo.toml | 5 ++ benches/multilinear_add.rs | 87 +++++++++++++++++++++++++++++++++ crates/lean_compiler/src/lib.rs | 2 +- crates/utils/src/multilinear.rs | 31 ++++++++++-- 5 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 benches/multilinear_add.rs diff --git a/Cargo.lock b/Cargo.lock index 3d51f2620..efa64de39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1458,6 +1458,7 @@ dependencies = [ "p3-util", "packed_pcs", "rand", + "rayon", "rec_aggregation", "utils", "whir-p3", diff --git a/Cargo.toml b/Cargo.toml index 3024623e3..ad99181cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,6 +102,7 @@ p3-air.workspace = true [dev-dependencies] criterion = { version = "0.7", default-features = false, features = ["cargo_bench_support"] } rec_aggregation.workspace = true +rayon.workspace = true [[bench]] name = "poseidon2" @@ -114,3 +115,7 @@ harness = false [[bench]] name = "xmss" harness = false + +[[bench]] +name = "multilinear_add" +harness = false diff --git a/benches/multilinear_add.rs b/benches/multilinear_add.rs new file mode 100644 index 000000000..73a02cca6 --- /dev/null +++ b/benches/multilinear_add.rs @@ -0,0 +1,87 @@ +use std::hint::black_box; + +use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; +use p3_field::PrimeCharacteristicRing; +use p3_koala_bear::KoalaBear; +use rand::rngs::StdRng; +use rand::{Rng, SeedableRng}; +use utils::add_multilinears; + +type F = KoalaBear; + +fn generate_random_multilinear(log_size: usize, seed: u64) -> Vec { + let mut rng = StdRng::seed_from_u64(seed); + (0..1 << log_size).map(|_| rng.random()).collect() +} + +fn bench_add_multilinears(c: &mut Criterion) { + let mut group = c.benchmark_group("add_multilinears_fn"); + + // Test different polynomial sizes: 2^10, 2^14, 2^18, 2^20 + let log_sizes = [10, 14, 18, 20]; + + for &log_size in &log_sizes { + let size = 1 << log_size; + let pol1 = generate_random_multilinear(log_size, 42); + let pol2 = generate_random_multilinear(log_size, 43); + + group.throughput(Throughput::Elements(size as u64)); + + // Benchmark optimized version + group.bench_with_input( + BenchmarkId::new("optimized", log_size), + &(pol1.clone(), pol2.clone()), + |b, (p1, p2)| { + b.iter(|| { + let result = add_multilinears(black_box(p1), black_box(p2)); + black_box(result); + }); + }, + ); + } + + group.finish(); +} + +fn bench_add_multilinears_sparse(c: &mut Criterion) { + let mut group = c.benchmark_group("add_multilinears_sparse"); + + let log_size = 16; + let sparsity_levels = [0.1, 0.5, 0.9, 1.0]; // Fraction of non-zero elements + + for &sparsity in &sparsity_levels { + let mut rng = StdRng::seed_from_u64(42); + let size = 1 << log_size; + + let mut pol1 = vec![F::ZERO; size]; + let mut pol2 = vec![F::ZERO; size]; + + // Fill with random non-zero values according to sparsity + let non_zero_count = (size as f64 * sparsity) as usize; + for i in 0..non_zero_count { + pol1[i] = rng.random(); + pol2[i] = rng.random(); + } + + group.throughput(Throughput::Elements(size as u64)); + + group.bench_function( + BenchmarkId::new("optimized", (sparsity * 100.0) as u32), + |b| { + b.iter(|| { + let result = add_multilinears(black_box(&pol1), black_box(&pol2)); + black_box(result); + }); + }, + ); + } + + group.finish(); +} + +criterion_group!( + benches, + bench_add_multilinears, + bench_add_multilinears_sparse +); +criterion_main!(benches); diff --git a/crates/lean_compiler/src/lib.rs b/crates/lean_compiler/src/lib.rs index 135c5815c..2ae8628d5 100644 --- a/crates/lean_compiler/src/lib.rs +++ b/crates/lean_compiler/src/lib.rs @@ -24,7 +24,7 @@ pub fn compile_program(program: &str) -> (Bytecode, BTreeMap) { let intermediate_bytecode = compile_to_intermediate_bytecode(simple_program).unwrap(); // println!("Intermediate Bytecode:\n\n{}", intermediate_bytecode.to_string()); let compiled = compile_to_low_level_bytecode(intermediate_bytecode).unwrap(); - println!("Compiled Program:\n\n{}", compiled.to_string()); + println!("Compiled Program:\n\n{compiled}"); (compiled, function_locations) } diff --git a/crates/utils/src/multilinear.rs b/crates/utils/src/multilinear.rs index 1d151c4f9..c89dcf285 100644 --- a/crates/utils/src/multilinear.rs +++ b/crates/utils/src/multilinear.rs @@ -214,11 +214,32 @@ pub fn multilinear_eval_constants_at_right(limit: usize, point: &[F]) pub fn add_multilinears(pol1: &[F], pol2: &[F]) -> Vec { assert_eq!(pol1.len(), pol2.len()); - let mut dst = pol1.to_vec(); - dst.par_iter_mut() - .zip(pol2.par_iter()) - .for_each(|(a, b)| *a += *b); - dst + + if pol1.is_empty() { + return Vec::new(); + } + + let len = pol1.len(); + let mut result = Vec::with_capacity(len); + #[allow(clippy::uninit_vec)] + unsafe { + result.set_len(len); + } + + result + .par_iter_mut() + .zip(pol1.par_iter().zip(pol2.par_iter())) + .for_each(|(dst, (a, b))| { + *dst = if a.is_zero() { + *b + } else if b.is_zero() { + *a + } else { + *a + *b + } + }); + + result } pub fn padd_with_zero_to_next_power_of_two(pol: &[F]) -> Vec { From c9da9785780b054e30cbc4b2ba5417026888b8a4 Mon Sep 17 00:00:00 2001 From: Thomas Coratger Date: Tue, 23 Sep 2025 12:59:22 +0200 Subject: [PATCH 02/10] cleanup --- Cargo.lock | 1 - Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index efa64de39..3d51f2620 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1458,7 +1458,6 @@ dependencies = [ "p3-util", "packed_pcs", "rand", - "rayon", "rec_aggregation", "utils", "whir-p3", diff --git a/Cargo.toml b/Cargo.toml index ad99181cf..ef98f8196 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,7 +102,6 @@ p3-air.workspace = true [dev-dependencies] criterion = { version = "0.7", default-features = false, features = ["cargo_bench_support"] } rec_aggregation.workspace = true -rayon.workspace = true [[bench]] name = "poseidon2" From 257bca4ac657df4b875d69294fbdcab093786b1d Mon Sep 17 00:00:00 2001 From: Thomas Coratger Date: Tue, 23 Sep 2025 16:47:04 +0200 Subject: [PATCH 03/10] utils: add add_multilinears_inplace --- Cargo.toml | 4 -- benches/multilinear_add.rs | 87 --------------------------------- crates/air/src/prove.rs | 19 +++---- crates/utils/src/multilinear.rs | 32 ++---------- 4 files changed, 13 insertions(+), 129 deletions(-) delete mode 100644 benches/multilinear_add.rs diff --git a/Cargo.toml b/Cargo.toml index ef98f8196..3024623e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,7 +114,3 @@ harness = false [[bench]] name = "xmss" harness = false - -[[bench]] -name = "multilinear_add" -harness = false diff --git a/benches/multilinear_add.rs b/benches/multilinear_add.rs deleted file mode 100644 index 73a02cca6..000000000 --- a/benches/multilinear_add.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::hint::black_box; - -use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; -use p3_field::PrimeCharacteristicRing; -use p3_koala_bear::KoalaBear; -use rand::rngs::StdRng; -use rand::{Rng, SeedableRng}; -use utils::add_multilinears; - -type F = KoalaBear; - -fn generate_random_multilinear(log_size: usize, seed: u64) -> Vec { - let mut rng = StdRng::seed_from_u64(seed); - (0..1 << log_size).map(|_| rng.random()).collect() -} - -fn bench_add_multilinears(c: &mut Criterion) { - let mut group = c.benchmark_group("add_multilinears_fn"); - - // Test different polynomial sizes: 2^10, 2^14, 2^18, 2^20 - let log_sizes = [10, 14, 18, 20]; - - for &log_size in &log_sizes { - let size = 1 << log_size; - let pol1 = generate_random_multilinear(log_size, 42); - let pol2 = generate_random_multilinear(log_size, 43); - - group.throughput(Throughput::Elements(size as u64)); - - // Benchmark optimized version - group.bench_with_input( - BenchmarkId::new("optimized", log_size), - &(pol1.clone(), pol2.clone()), - |b, (p1, p2)| { - b.iter(|| { - let result = add_multilinears(black_box(p1), black_box(p2)); - black_box(result); - }); - }, - ); - } - - group.finish(); -} - -fn bench_add_multilinears_sparse(c: &mut Criterion) { - let mut group = c.benchmark_group("add_multilinears_sparse"); - - let log_size = 16; - let sparsity_levels = [0.1, 0.5, 0.9, 1.0]; // Fraction of non-zero elements - - for &sparsity in &sparsity_levels { - let mut rng = StdRng::seed_from_u64(42); - let size = 1 << log_size; - - let mut pol1 = vec![F::ZERO; size]; - let mut pol2 = vec![F::ZERO; size]; - - // Fill with random non-zero values according to sparsity - let non_zero_count = (size as f64 * sparsity) as usize; - for i in 0..non_zero_count { - pol1[i] = rng.random(); - pol2[i] = rng.random(); - } - - group.throughput(Throughput::Elements(size as u64)); - - group.bench_function( - BenchmarkId::new("optimized", (sparsity * 100.0) as u32), - |b| { - b.iter(|| { - let result = add_multilinears(black_box(&pol1), black_box(&pol2)); - black_box(result); - }); - }, - ); - } - - group.finish(); -} - -criterion_group!( - benches, - bench_add_multilinears, - bench_add_multilinears_sparse -); -criterion_main!(benches); diff --git a/crates/air/src/prove.rs b/crates/air/src/prove.rs index 8604fd253..def006779 100644 --- a/crates/air/src/prove.rs +++ b/crates/air/src/prove.rs @@ -5,8 +5,8 @@ use p3_field::{ExtensionField, Field, cyclic_subgroup_known_order}; use p3_util::{log2_ceil_usize, log2_strict_usize}; use sumcheck::{MleGroup, MleGroupOwned, MleGroupRef, ProductComputation}; use tracing::{info_span, instrument}; -use utils::PF; -use utils::{FSProver, add_multilinears, multilinears_linear_combination}; +use utils::{FSProver, multilinears_linear_combination}; +use utils::{PF, add_multilinears_inplace}; use whir_p3::fiat_shamir::FSChallenger; use whir_p3::poly::evals::{eval_eq, fold_multilinear, scale_poly}; use whir_p3::poly::{evals::EvaluationsList, multilinear::MultilinearPoint}; @@ -195,8 +195,9 @@ fn open_structured_columns> + ExtensionField, IF: let batched_column = multilinears_linear_combination(witness, &poly_eq_batching_scalars[..n_columns]); - let batched_column_mixed = add_multilinears( - &column_up(&batched_column), + let mut batched_column_mixed = column_up(&batched_column); + add_multilinears_inplace( + &mut batched_column_mixed, &scale_poly(&column_down(&batched_column), alpha), ); // TODO do not recompute this (we can deduce it from already computed values) @@ -217,13 +218,9 @@ fn open_structured_columns> + ExtensionField, IF: // TODO do not recompute this (we can deduce it from already computed values) let inner_sum = batched_column_mixed.evaluate(&MultilinearPoint(point.clone())); - let inner_mle = MleGroupOwned::Extension(vec![ - add_multilinears( - &matrix_up_folded(&point), - &scale_poly(&matrix_down_folded(&point), alpha), - ), - batched_column, - ]); + let mut mat_up = matrix_up_folded(&point); + add_multilinears_inplace(&mut mat_up, &scale_poly(&matrix_down_folded(&point), alpha)); + let inner_mle = MleGroupOwned::Extension(vec![mat_up, batched_column]); let (inner_challenges, _, _) = sumcheck::prove::( 1, diff --git a/crates/utils/src/multilinear.rs b/crates/utils/src/multilinear.rs index c89dcf285..d4602739a 100644 --- a/crates/utils/src/multilinear.rs +++ b/crates/utils/src/multilinear.rs @@ -212,34 +212,12 @@ pub fn multilinear_eval_constants_at_right(limit: usize, point: &[F]) // dst // } -pub fn add_multilinears(pol1: &[F], pol2: &[F]) -> Vec { - assert_eq!(pol1.len(), pol2.len()); +pub fn add_multilinears_inplace(dst: &mut [F], src: &[F]) { + assert_eq!(dst.len(), src.len()); - if pol1.is_empty() { - return Vec::new(); - } - - let len = pol1.len(); - let mut result = Vec::with_capacity(len); - #[allow(clippy::uninit_vec)] - unsafe { - result.set_len(len); - } - - result - .par_iter_mut() - .zip(pol1.par_iter().zip(pol2.par_iter())) - .for_each(|(dst, (a, b))| { - *dst = if a.is_zero() { - *b - } else if b.is_zero() { - *a - } else { - *a + *b - } - }); - - result + dst.par_iter_mut() + .zip(src.par_iter()) + .for_each(|(a, b)| *a += *b); } pub fn padd_with_zero_to_next_power_of_two(pol: &[F]) -> Vec { From 822874e1f33a7b9dd678b5982046c4faddf89180 Mon Sep 17 00:00:00 2001 From: Thomas Coratger Date: Tue, 23 Sep 2025 18:17:11 +0200 Subject: [PATCH 04/10] chore: revamp lean vm crate --- crates/lean_vm/src/core/constants.rs | 28 + crates/lean_vm/src/core/mod.rs | 7 + crates/lean_vm/src/core/types.rs | 12 + crates/lean_vm/src/diagnostics/error.rs | 59 ++ crates/lean_vm/src/diagnostics/mod.rs | 10 + .../lean_vm/src/{ => diagnostics}/profiler.rs | 0 .../src/{ => diagnostics}/stack_trace.rs | 0 crates/lean_vm/src/error.rs | 23 - crates/lean_vm/src/execution/context.rs | 69 ++ crates/lean_vm/src/{ => execution}/memory.rs | 85 ++- crates/lean_vm/src/execution/mod.rs | 9 + crates/lean_vm/src/execution/runner.rs | 351 +++++++++ crates/lean_vm/src/isa/bytecode.rs | 59 ++ crates/lean_vm/src/isa/instruction.rs | 435 +++++++++++ crates/lean_vm/src/isa/mod.rs | 14 + crates/lean_vm/src/isa/operands/hint.rs | 227 ++++++ .../src/isa/operands/mem_or_constant.rs | 61 ++ crates/lean_vm/src/isa/operands/mem_or_fp.rs | 51 ++ .../src/isa/operands/mem_or_fp_or_constant.rs | 56 ++ crates/lean_vm/src/isa/operands/mod.rs | 11 + crates/lean_vm/src/isa/operation.rs | 45 ++ crates/lean_vm/src/lean_isa.rs | 298 -------- crates/lean_vm/src/lib.rs | 212 ++---- crates/lean_vm/src/runner.rs | 719 ------------------ crates/lean_vm/src/witness/dot_product.rs | 66 ++ crates/lean_vm/src/witness/mod.rs | 11 + .../lean_vm/src/witness/multilinear_eval.rs | 79 ++ crates/lean_vm/src/witness/poseidon16.rs | 73 ++ crates/lean_vm/src/witness/poseidon24.rs | 75 ++ crates/lean_vm/tests/test_lean_vm.rs | 1 + 30 files changed, 1949 insertions(+), 1197 deletions(-) create mode 100644 crates/lean_vm/src/core/constants.rs create mode 100644 crates/lean_vm/src/core/mod.rs create mode 100644 crates/lean_vm/src/core/types.rs create mode 100644 crates/lean_vm/src/diagnostics/error.rs create mode 100644 crates/lean_vm/src/diagnostics/mod.rs rename crates/lean_vm/src/{ => diagnostics}/profiler.rs (100%) rename crates/lean_vm/src/{ => diagnostics}/stack_trace.rs (100%) delete mode 100644 crates/lean_vm/src/error.rs create mode 100644 crates/lean_vm/src/execution/context.rs rename crates/lean_vm/src/{ => execution}/memory.rs (57%) create mode 100644 crates/lean_vm/src/execution/mod.rs create mode 100644 crates/lean_vm/src/execution/runner.rs create mode 100644 crates/lean_vm/src/isa/bytecode.rs create mode 100644 crates/lean_vm/src/isa/instruction.rs create mode 100644 crates/lean_vm/src/isa/mod.rs create mode 100644 crates/lean_vm/src/isa/operands/hint.rs create mode 100644 crates/lean_vm/src/isa/operands/mem_or_constant.rs create mode 100644 crates/lean_vm/src/isa/operands/mem_or_fp.rs create mode 100644 crates/lean_vm/src/isa/operands/mem_or_fp_or_constant.rs create mode 100644 crates/lean_vm/src/isa/operands/mod.rs create mode 100644 crates/lean_vm/src/isa/operation.rs delete mode 100644 crates/lean_vm/src/lean_isa.rs delete mode 100644 crates/lean_vm/src/runner.rs create mode 100644 crates/lean_vm/src/witness/dot_product.rs create mode 100644 crates/lean_vm/src/witness/mod.rs create mode 100644 crates/lean_vm/src/witness/multilinear_eval.rs create mode 100644 crates/lean_vm/src/witness/poseidon16.rs create mode 100644 crates/lean_vm/src/witness/poseidon24.rs diff --git a/crates/lean_vm/src/core/constants.rs b/crates/lean_vm/src/core/constants.rs new file mode 100644 index 000000000..d0521a3b8 --- /dev/null +++ b/crates/lean_vm/src/core/constants.rs @@ -0,0 +1,28 @@ +//! VM constants and configuration + +/// Vector dimension for field operations +pub const DIMENSION: usize = 5; + +/// Logarithm of vector length +pub const LOG_VECTOR_LEN: usize = 3; + +/// Vector length (2^LOG_VECTOR_LEN) +pub const VECTOR_LEN: usize = 1 << LOG_VECTOR_LEN; + +/// Convention: vectorized pointer of size 2, pointing to 16 zeros +pub const ZERO_VEC_PTR: usize = 0; + +/// Convention: vectorized pointer of size 1, pointing to 10000000 +pub const ONE_VEC_PTR: usize = 2; + +/// Convention: vectorized pointer of size 2, = the 16 elements of poseidon_16(0) +pub const POSEIDON_16_NULL_HASH_PTR: usize = 3; + +/// Convention: vectorized pointer of size 1, = the last 8 elements of poseidon_24(0) +pub const POSEIDON_24_NULL_HASH_PTR: usize = 5; + +/// Normal pointer to start of public input +pub const PUBLIC_INPUT_START: usize = 6 * 8; + +/// Maximum memory size for VM runner +pub const MAX_RUNNER_MEMORY_SIZE: usize = 1 << 20; \ No newline at end of file diff --git a/crates/lean_vm/src/core/mod.rs b/crates/lean_vm/src/core/mod.rs new file mode 100644 index 000000000..63ecf62f1 --- /dev/null +++ b/crates/lean_vm/src/core/mod.rs @@ -0,0 +1,7 @@ +//! Core VM abstractions and type definitions + +pub mod constants; +pub mod types; + +pub use constants::*; +pub use types::*; \ No newline at end of file diff --git a/crates/lean_vm/src/core/types.rs b/crates/lean_vm/src/core/types.rs new file mode 100644 index 000000000..5e8f8a873 --- /dev/null +++ b/crates/lean_vm/src/core/types.rs @@ -0,0 +1,12 @@ +//! Core type definitions for the VM + +use p3_koala_bear::{KoalaBear, QuinticExtensionFieldKB}; + +/// Base field type for VM operations +pub type F = KoalaBear; + +/// Extension field type for VM operations +pub type EF = QuinticExtensionFieldKB; + +/// Location in source code for debugging +pub type LocationInSourceCode = usize; \ No newline at end of file diff --git a/crates/lean_vm/src/diagnostics/error.rs b/crates/lean_vm/src/diagnostics/error.rs new file mode 100644 index 000000000..b36ed9cb3 --- /dev/null +++ b/crates/lean_vm/src/diagnostics/error.rs @@ -0,0 +1,59 @@ +//! VM error types and execution results + +use crate::core::F; +use crate::execution::Memory; +use crate::witness::{WitnessDotProduct, WitnessMultilinearEval, WitnessPoseidon16, WitnessPoseidon24}; +use thiserror::Error; + +/// Errors that can occur during VM execution +#[derive(Debug, Clone, Error)] +pub enum RunnerError { + #[error("Out of memory")] + OutOfMemory, + + #[error("Memory already set")] + MemoryAlreadySet, + + #[error("Not a pointer")] + NotAPointer, + + #[error("Division by zero")] + DivByZero, + + #[error("Computation invalid: {0} != {1}")] + NotEqual(F, F), + + #[error("Undefined memory access")] + UndefinedMemory, + + #[error("Program counter out of bounds")] + PCOutOfBounds, + + #[error("Point for multilinear eval not padded with zeros")] + MultilinearEvalPointNotPadded, +} + +/// Result type for VM operations +pub type VMResult = Result; + +/// Execution result containing outputs and execution data +#[derive(Debug)] +pub struct ExecutionResult { + pub no_vec_runtime_memory: usize, + pub public_memory_size: usize, + pub memory: Memory, + pub pcs: Vec, + pub fps: Vec, + pub poseidons_16: Vec, + pub poseidons_24: Vec, + pub dot_products: Vec, + pub multilinear_evals: Vec, +} + +impl ExecutionResult { + /// Check if execution was successful (always true for this version) + pub fn is_success(&self) -> bool { + true + } +} + diff --git a/crates/lean_vm/src/diagnostics/mod.rs b/crates/lean_vm/src/diagnostics/mod.rs new file mode 100644 index 000000000..ab2fd78c9 --- /dev/null +++ b/crates/lean_vm/src/diagnostics/mod.rs @@ -0,0 +1,10 @@ +//! Diagnostics, error handling, and debugging utilities +#![allow(unused_imports)] + +pub mod error; +pub mod profiler; +pub mod stack_trace; + +pub use error::*; +pub use profiler::*; +pub use stack_trace::*; diff --git a/crates/lean_vm/src/profiler.rs b/crates/lean_vm/src/diagnostics/profiler.rs similarity index 100% rename from crates/lean_vm/src/profiler.rs rename to crates/lean_vm/src/diagnostics/profiler.rs diff --git a/crates/lean_vm/src/stack_trace.rs b/crates/lean_vm/src/diagnostics/stack_trace.rs similarity index 100% rename from crates/lean_vm/src/stack_trace.rs rename to crates/lean_vm/src/diagnostics/stack_trace.rs diff --git a/crates/lean_vm/src/error.rs b/crates/lean_vm/src/error.rs deleted file mode 100644 index 8d4ad0a6e..000000000 --- a/crates/lean_vm/src/error.rs +++ /dev/null @@ -1,23 +0,0 @@ -use thiserror::Error; - -use crate::F; - -#[derive(Debug, Clone, Error)] -pub enum RunnerError { - #[error("Out of memory")] - OutOfMemory, - #[error("Memory already set")] - MemoryAlreadySet, - #[error("Not a pointer")] - NotAPointer, - #[error("Division by zero")] - DivByZero, - #[error("Computation invalid: {0} != {1}")] - NotEqual(F, F), - #[error("Undefined memory access")] - UndefinedMemory, - #[error("Program counter out of bounds")] - PCOutOfBounds, - #[error("Point for multilinear eval not padded with zeros")] - MultilinearEvalPointNotPadded, -} diff --git a/crates/lean_vm/src/execution/context.rs b/crates/lean_vm/src/execution/context.rs new file mode 100644 index 000000000..dc7dc43a9 --- /dev/null +++ b/crates/lean_vm/src/execution/context.rs @@ -0,0 +1,69 @@ +//! Execution context and state management + +use crate::core::LocationInSourceCode; +use std::collections::BTreeMap; + +/// Execution history for profiling and debugging +#[derive(Debug, Clone, Default)] +pub struct ExecutionHistory { + pub lines: Vec, + pub cycles: Vec, // for each line, how many cycles it took +} + +impl ExecutionHistory { + pub fn new() -> Self { + Self::default() + } + + pub fn add_line(&mut self, location: LocationInSourceCode, cycles: usize) { + self.lines.push(location); + self.cycles.push(cycles); + } + + pub fn total_cycles(&self) -> usize { + self.cycles.iter().sum() + } + + pub fn len(&self) -> usize { + self.lines.len() + } + + pub fn is_empty(&self) -> bool { + self.lines.is_empty() + } +} + +/// VM execution context +#[derive(Debug)] +pub struct ExecutionContext<'a> { + pub source_code: &'a str, + pub function_locations: &'a BTreeMap, + pub profiler_enabled: bool, + pub std_out: String, + pub instruction_history: ExecutionHistory, +} + +impl<'a> ExecutionContext<'a> { + pub fn new( + source_code: &'a str, + function_locations: &'a BTreeMap, + profiler_enabled: bool, + ) -> Self { + Self { + source_code, + function_locations, + profiler_enabled, + std_out: String::new(), + instruction_history: ExecutionHistory::new(), + } + } + + pub fn print(&mut self, message: &str) { + self.std_out.push_str(message); + } + + pub fn println(&mut self, message: &str) { + self.std_out.push_str(message); + self.std_out.push('\n'); + } +} diff --git a/crates/lean_vm/src/memory.rs b/crates/lean_vm/src/execution/memory.rs similarity index 57% rename from crates/lean_vm/src/memory.rs rename to crates/lean_vm/src/execution/memory.rs index a086e996e..f6e7098f1 100644 --- a/crates/lean_vm/src/memory.rs +++ b/crates/lean_vm/src/execution/memory.rs @@ -1,29 +1,31 @@ -use crate::F; -use p3_field::BasedVectorSpace; -use p3_field::PrimeCharacteristicRing; -use rayon::prelude::*; +//! Memory management for the VM -use crate::*; +use crate::core::{EF, F, DIMENSION, VECTOR_LEN, MAX_RUNNER_MEMORY_SIZE}; +use crate::diagnostics::RunnerError; +use p3_field::{BasedVectorSpace, PrimeCharacteristicRing}; +use rayon::prelude::*; pub const MIN_LOG_MEMORY_SIZE: usize = 16; pub const MAX_LOG_MEMORY_SIZE: usize = 29; -// For now, we restrict ourselves to executions where memory usage < 2^24 words. -// But the VM supports theorically a memory of size 2^29. -pub(crate) const MAX_RUNNER_MEMORY_SIZE: usize = 1 << 24; - +/// VM memory implementation with sparse allocation #[derive(Debug, Clone, Default)] pub struct Memory(pub Vec>); impl Memory { - /// Creates a new memory instance, initializing it with public data. + /// Creates a new memory instance, initializing it with public data pub fn new(public_memory: Vec) -> Self { Self(public_memory.into_par_iter().map(Some).collect()) } - /// Reads a single value from a memory address. + /// Creates an empty memory instance + pub fn empty() -> Self { + Self::default() + } + + /// Reads a single value from a memory address /// - /// Returns an error if the address is uninitialized. + /// Returns an error if the address is uninitialized pub fn get(&self, index: usize) -> Result { self.0 .get(index) @@ -32,6 +34,10 @@ impl Memory { .ok_or(RunnerError::UndefinedMemory) } + /// Sets a value at a memory address + /// + /// Returns an error if the address is already set to a different value + /// or if we exceed memory limits pub fn set(&mut self, index: usize, value: F) -> Result<(), RunnerError> { if index >= self.0.len() { if index >= MAX_RUNNER_MEMORY_SIZE { @@ -49,10 +55,49 @@ impl Memory { Ok(()) } + /// Reads a slice of memory values + pub fn get_slice(&self, start: usize, len: usize) -> Result, RunnerError> { + (start..start + len).map(|i| self.get(i)).collect() + } + + /// Sets a slice of memory values + pub fn set_slice(&mut self, start: usize, values: &[F]) -> Result<(), RunnerError> { + for (i, &value) in values.iter().enumerate() { + self.set(start + i, value)?; + } + Ok(()) + } + + /// Gets the current size of allocated memory + pub fn size(&self) -> usize { + self.0.len() + } + + /// Checks if a memory address is initialized + pub fn is_initialized(&self, index: usize) -> bool { + self.0.get(index).and_then(|x| x.as_ref()).is_some() + } + + /// Gets all initialized memory addresses and their values + pub fn initialized_entries(&self) -> Vec<(usize, F)> { + self.0 + .iter() + .enumerate() + .filter_map(|(i, opt)| opt.map(|v| (i, v))) + .collect() + } + + /// Clears all memory + pub fn clear(&mut self) { + self.0.clear(); + } + + /// Get a vector from vectorized memory pub fn get_vector(&self, index: usize) -> Result<[F; VECTOR_LEN], RunnerError> { Ok(self.get_vectorized_slice(index, 1)?.try_into().unwrap()) } + /// Get an extension field element from memory pub fn get_ef_element(&self, index: usize) -> Result { // index: non vectorized pointer let mut coeffs = [F::ZERO; DIMENSION]; @@ -62,12 +107,14 @@ impl Memory { Ok(EF::from_basis_coefficients_slice(&coeffs).unwrap()) } + /// Get a vectorized slice from memory pub fn get_vectorized_slice(&self, index: usize, len: usize) -> Result, RunnerError> { let start = index * VECTOR_LEN; let total_len = len * VECTOR_LEN; (0..total_len).map(|i| self.get(start + i)).collect() } + /// Get a continuous slice of extension field elements pub fn get_continuous_slice_of_ef_elements( &self, index: usize, // normal pointer @@ -78,10 +125,7 @@ impl Memory { .collect() } - pub fn slice(&self, index: usize, len: usize) -> Result, RunnerError> { - (0..len).map(|i| self.get(index + i)).collect() - } - + /// Set an extension field element in memory pub fn set_ef_element(&mut self, index: usize, value: EF) -> Result<(), RunnerError> { for (i, v) in value.as_basis_coefficients_slice().iter().enumerate() { self.set(index + i, *v)?; @@ -89,6 +133,7 @@ impl Memory { Ok(()) } + /// Set a vector in vectorized memory pub fn set_vector(&mut self, index: usize, value: [F; VECTOR_LEN]) -> Result<(), RunnerError> { for (i, v) in value.iter().enumerate() { let idx = VECTOR_LEN * index + i; @@ -97,6 +142,7 @@ impl Memory { Ok(()) } + /// Set a vectorized slice in memory pub fn set_vectorized_slice(&mut self, index: usize, value: &[F]) -> Result<(), RunnerError> { assert!(value.len().is_multiple_of(VECTOR_LEN)); let start = index * VECTOR_LEN; @@ -105,4 +151,9 @@ impl Memory { .enumerate() .try_for_each(|(i, &v)| self.set(start + i, v)) } -} + + /// Get a slice from memory for multilinear evaluation + pub fn slice(&self, start: usize, len: usize) -> Result, RunnerError> { + (0..len).map(|i| self.get(start + i)).collect() + } +} \ No newline at end of file diff --git a/crates/lean_vm/src/execution/mod.rs b/crates/lean_vm/src/execution/mod.rs new file mode 100644 index 000000000..60b772f63 --- /dev/null +++ b/crates/lean_vm/src/execution/mod.rs @@ -0,0 +1,9 @@ +//! VM execution engine and memory management + +pub mod context; +pub mod memory; +pub mod runner; + +pub use context::*; +pub use memory::*; +pub use runner::*; \ No newline at end of file diff --git a/crates/lean_vm/src/execution/runner.rs b/crates/lean_vm/src/execution/runner.rs new file mode 100644 index 000000000..68199bd7c --- /dev/null +++ b/crates/lean_vm/src/execution/runner.rs @@ -0,0 +1,351 @@ +//! VM execution runner + +use crate::core::{ + DIMENSION, F, MAX_RUNNER_MEMORY_SIZE, ONE_VEC_PTR, POSEIDON_16_NULL_HASH_PTR, + POSEIDON_24_NULL_HASH_PTR, PUBLIC_INPUT_START, VECTOR_LEN, ZERO_VEC_PTR, +}; +use crate::diagnostics::{ExecutionResult, RunnerError}; +use crate::execution::{ExecutionHistory, Memory}; +use crate::isa::instruction::InstructionContext; +use crate::isa::operands::hint::HintExecutionContext; +use crate::isa::{Bytecode, Instruction}; +use crate::witness::{ + WitnessDotProduct, WitnessMultilinearEval, WitnessPoseidon16, WitnessPoseidon24, +}; +use p3_field::PrimeCharacteristicRing; +use p3_symmetric::Permutation; +use std::collections::BTreeMap; +use utils::{get_poseidon16, get_poseidon24, pretty_integer}; + +/// Number of instructions to show in stack trace +const STACK_TRACE_INSTRUCTIONS: usize = 5000; + +/// Build public memory with standard initialization +pub fn build_public_memory(public_input: &[F]) -> Vec { + // padded to a power of two + let public_memory_len = (PUBLIC_INPUT_START + public_input.len()).next_power_of_two(); + let mut public_memory = F::zero_vec(public_memory_len); + public_memory[PUBLIC_INPUT_START..][..public_input.len()].copy_from_slice(public_input); + + // "zero" vector + let zero_start = ZERO_VEC_PTR * VECTOR_LEN; + for slot in public_memory + .iter_mut() + .skip(zero_start) + .take(2 * VECTOR_LEN) + { + *slot = F::ZERO; + } + + // "one" vector + public_memory[ONE_VEC_PTR * VECTOR_LEN] = F::ONE; + let one_start = ONE_VEC_PTR * VECTOR_LEN + 1; + for slot in public_memory + .iter_mut() + .skip(one_start) + .take(VECTOR_LEN - 1) + { + *slot = F::ZERO; + } + + public_memory + [POSEIDON_16_NULL_HASH_PTR * VECTOR_LEN..(POSEIDON_16_NULL_HASH_PTR + 2) * VECTOR_LEN] + .copy_from_slice(&get_poseidon16().permute([F::ZERO; 16])); + public_memory + [POSEIDON_24_NULL_HASH_PTR * VECTOR_LEN..(POSEIDON_24_NULL_HASH_PTR + 1) * VECTOR_LEN] + .copy_from_slice(&get_poseidon24().permute([F::ZERO; 24])[16..]); + public_memory +} + +/// Execute bytecode with the given inputs and execution context +/// +/// This is the main VM execution entry point that processes bytecode instructions +/// and generates execution traces with witness data. +pub fn execute_bytecode_impl( + bytecode: &Bytecode, + public_input: &[F], + private_input: &[F], + source_code: &str, + function_locations: &BTreeMap, + profiler: bool, +) -> ExecutionResult { + let mut std_out = String::new(); + let mut instruction_history = ExecutionHistory::new(); + let first_exec = match execute_bytecode_helper( + bytecode, + public_input, + private_input, + MAX_RUNNER_MEMORY_SIZE / 2, + false, + &mut std_out, + &mut instruction_history, + false, + function_locations, + ) { + Ok(first_exec) => first_exec, + Err(err) => { + let lines_history = &instruction_history.lines; + let latest_instructions = + &lines_history[lines_history.len().saturating_sub(STACK_TRACE_INSTRUCTIONS)..]; + println!( + "\n{}", + crate::diagnostics::pretty_stack_trace( + source_code, + latest_instructions, + function_locations + ) + ); + if !std_out.is_empty() { + println!("╔══════════════════════════════════════════════════════════════╗"); + println!("║ STD-OUT ║"); + println!("╚══════════════════════════════════════════════════════════════╝\n"); + print!("{std_out}"); + } + panic!("Error during bytecode execution: {err}"); + } + }; + instruction_history = ExecutionHistory::new(); + execute_bytecode_helper( + bytecode, + public_input, + private_input, + first_exec.no_vec_runtime_memory, + true, + &mut String::new(), + &mut instruction_history, + profiler, + function_locations, + ) + .unwrap() +} + +/// Helper function that performs the actual bytecode execution +#[allow(clippy::too_many_arguments)] // TODO +fn execute_bytecode_helper( + bytecode: &Bytecode, + public_input: &[F], + private_input: &[F], + no_vec_runtime_memory: usize, + final_execution: bool, + std_out: &mut String, + instruction_history: &mut ExecutionHistory, + profiler: bool, + function_locations: &BTreeMap, +) -> Result { + // set public memory + let mut memory = Memory::new(build_public_memory(public_input)); + + let public_memory_size = (PUBLIC_INPUT_START + public_input.len()).next_power_of_two(); + let mut fp = public_memory_size; + + for (i, value) in private_input.iter().enumerate() { + memory.set(fp + i, *value)?; + } + fp += private_input.len(); + fp = fp.next_multiple_of(DIMENSION); + + let initial_ap = fp + bytecode.starting_frame_memory; + let initial_ap_vec = + (initial_ap + no_vec_runtime_memory).next_multiple_of(VECTOR_LEN) / VECTOR_LEN; + + let mut pc = 0; + let mut ap = initial_ap; + let mut ap_vec = initial_ap_vec; + + let mut poseidon16_calls = 0; + let mut poseidon24_calls = 0; + let mut dot_product_ext_ext_calls = 0; + let mut multilinear_eval_calls = 0; + let mut cpu_cycles = 0; + + let mut last_checkpoint_cpu_cycles = 0; + let mut checkpoint_ap = initial_ap; + let mut checkpoint_ap_vec = ap_vec; + + let mut pcs = Vec::new(); + let mut fps = Vec::new(); + + // Events collected only in final execution + let mut poseidons_16: Vec = Vec::new(); + let mut poseidons_24: Vec = Vec::new(); + let mut dot_products: Vec = Vec::new(); + let mut multilinear_evals: Vec = Vec::new(); + + let mut add_counts = 0; + let mut mul_counts = 0; + let mut deref_counts = 0; + let mut jump_counts = 0; + + let mut counter_hint = 0; + let mut cpu_cycles_before_new_line = 0; + + while pc != bytecode.ending_pc { + if pc >= bytecode.instructions.len() { + return Err(RunnerError::PCOutOfBounds); + } + + pcs.push(pc); + fps.push(fp); + + cpu_cycles += 1; + cpu_cycles_before_new_line += 1; + + for hint in bytecode.hints.get(&pc).unwrap_or(&vec![]) { + let mut hint_ctx = HintExecutionContext { + memory: &mut memory, + fp, + ap: &mut ap, + ap_vec: &mut ap_vec, + counter_hint: &mut counter_hint, + std_out, + instruction_history, + cpu_cycles_before_new_line: &mut cpu_cycles_before_new_line, + cpu_cycles, + last_checkpoint_cpu_cycles: &mut last_checkpoint_cpu_cycles, + checkpoint_ap: &mut checkpoint_ap, + checkpoint_ap_vec: &mut checkpoint_ap_vec, + }; + let should_continue = hint.execute_hint(&mut hint_ctx)?; + if should_continue { + continue; + } + } + + let instruction = &bytecode.instructions[pc]; + let mut instruction_ctx = InstructionContext { + memory: &mut memory, + fp: &mut fp, + pc: &mut pc, + pcs: &pcs, + final_execution, + poseidons_16: &mut poseidons_16, + poseidons_24: &mut poseidons_24, + dot_products: &mut dot_products, + multilinear_evals: &mut multilinear_evals, + add_counts: &mut add_counts, + mul_counts: &mut mul_counts, + deref_counts: &mut deref_counts, + jump_counts: &mut jump_counts, + }; + instruction.execute_instruction(&mut instruction_ctx)?; + + // Update call counters based on instruction type + match instruction { + Instruction::Poseidon2_16 { .. } => poseidon16_calls += 1, + Instruction::Poseidon2_24 { .. } => poseidon24_calls += 1, + Instruction::DotProductExtensionExtension { .. } => dot_product_ext_ext_calls += 1, + Instruction::MultilinearEval { .. } => multilinear_eval_calls += 1, + _ => {} + } + } + + debug_assert_eq!(pc, bytecode.ending_pc); + pcs.push(pc); + fps.push(fp); + + if final_execution { + // Ensure event counts match call counts in final execution + debug_assert_eq!(poseidon16_calls, poseidons_16.len()); + debug_assert_eq!(poseidon24_calls, poseidons_24.len()); + debug_assert_eq!(dot_product_ext_ext_calls, dot_products.len()); + debug_assert_eq!(multilinear_eval_calls, multilinear_evals.len()); + if profiler { + let report = + crate::diagnostics::profiling_report(instruction_history, function_locations); + println!("\n{report}"); + } + if !std_out.is_empty() { + println!("╔═════════════════════════════════════════════════════════════════════════╗"); + println!("║ STD-OUT ║"); + println!("╚═════════════════════════════════════════════════════════════════════════╝"); + print!("\n{std_out}"); + println!( + "──────────────────────────────────────────────────────────────────────────\n" + ); + } + + println!("╔═════════════════════════════════════════════════════════════════════════╗"); + println!("║ STATS ║"); + println!("╚═════════════════════════════════════════════════════════════════════════╝\n"); + + println!("CYCLES: {}", pretty_integer(cpu_cycles)); + println!("MEMORY: {}", pretty_integer(memory.0.len())); + println!(); + + let runtime_memory_size = memory.0.len() - (PUBLIC_INPUT_START + public_input.len()); + println!( + "Bytecode size: {}", + pretty_integer(bytecode.instructions.len()) + ); + println!("Public input size: {}", pretty_integer(public_input.len())); + println!( + "Private input size: {}", + pretty_integer(private_input.len()) + ); + println!( + "Runtime memory: {} ({:.2}% vec)", + pretty_integer(runtime_memory_size), + (DIMENSION * (ap_vec - initial_ap_vec)) as f64 / runtime_memory_size as f64 * 100.0 + ); + let used_memory_cells = memory + .0 + .iter() + .skip(PUBLIC_INPUT_START + public_input.len()) + .filter(|&&x| x.is_some()) + .count(); + println!( + "Memory usage: {:.1}%", + used_memory_cells as f64 / runtime_memory_size as f64 * 100.0 + ); + + println!(); + + if poseidon16_calls + poseidon24_calls > 0 { + println!( + "Poseidon2_16 calls: {}, Poseidon2_24 calls: {} (1 poseidon per {} instructions)", + pretty_integer(poseidon16_calls), + pretty_integer(poseidon24_calls), + cpu_cycles / (poseidon16_calls + poseidon24_calls) + ); + } + if dot_product_ext_ext_calls > 0 { + println!( + "DotProduct calls: {}", + pretty_integer(dot_product_ext_ext_calls) + ); + } + if multilinear_eval_calls > 0 { + println!( + "MultilinearEval calls: {}", + pretty_integer(multilinear_eval_calls) + ); + } + + if false { + println!("Low level instruction counts:"); + println!( + "COMPUTE: {} ({} ADD, {} MUL)", + add_counts + mul_counts, + add_counts, + mul_counts + ); + println!("DEREF: {deref_counts}"); + println!("JUMP: {jump_counts}"); + } + + println!("──────────────────────────────────────────────────────────────────────────\n"); + } + + let no_vec_runtime_memory = ap - initial_ap; + Ok(ExecutionResult { + no_vec_runtime_memory, + public_memory_size, + memory, + pcs, + fps, + poseidons_16, + poseidons_24, + dot_products, + multilinear_evals, + }) +} diff --git a/crates/lean_vm/src/isa/bytecode.rs b/crates/lean_vm/src/isa/bytecode.rs new file mode 100644 index 000000000..60d69c78b --- /dev/null +++ b/crates/lean_vm/src/isa/bytecode.rs @@ -0,0 +1,59 @@ +//! Bytecode representation and management + +use super::operands::Hint; +use super::Instruction; +use std::collections::BTreeMap; +use std::fmt::{Display, Formatter}; + +/// Complete bytecode representation with instructions and hints +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Bytecode { + pub instructions: Vec, + pub hints: BTreeMap>, // pc -> hints + pub starting_frame_memory: usize, + pub ending_pc: usize, +} + +impl Bytecode { + /// Create new bytecode with the given instructions + pub fn new(instructions: Vec) -> Self { + Self { + ending_pc: instructions.len().saturating_sub(1), + instructions, + hints: BTreeMap::new(), + starting_frame_memory: 0, + } + } + + /// Add a hint at the specified program counter + pub fn add_hint(&mut self, pc: usize, hint: Hint) { + self.hints.entry(pc).or_default().push(hint); + } + + /// Get hints for a specific program counter + pub fn get_hints(&self, pc: usize) -> &[Hint] { + self.hints.get(&pc).map_or(&[], |v| v.as_slice()) + } + + /// Get the number of instructions + pub fn len(&self) -> usize { + self.instructions.len() + } + + /// Check if bytecode is empty + pub fn is_empty(&self) -> bool { + self.instructions.is_empty() + } +} + +impl Display for Bytecode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + for (pc, instruction) in self.instructions.iter().enumerate() { + for hint in self.hints.get(&pc).unwrap_or(&Vec::new()) { + writeln!(f, "hint: {hint}")?; + } + writeln!(f, "{pc:>4}: {instruction}")?; + } + Ok(()) + } +} \ No newline at end of file diff --git a/crates/lean_vm/src/isa/instruction.rs b/crates/lean_vm/src/isa/instruction.rs new file mode 100644 index 000000000..8a8453a2a --- /dev/null +++ b/crates/lean_vm/src/isa/instruction.rs @@ -0,0 +1,435 @@ +//! VM instruction definitions + +use super::Operation; +use super::operands::{MemOrConstant, MemOrFp, MemOrFpOrConstant}; +use crate::core::{DIMENSION, EF, F, VECTOR_LEN}; +use crate::diagnostics::RunnerError; +use crate::execution::Memory; +use crate::witness::{ + RowMultilinearEval, WitnessDotProduct, WitnessMultilinearEval, WitnessPoseidon16, + WitnessPoseidon24, +}; +use p3_field::{BasedVectorSpace, PrimeCharacteristicRing, dot_product}; +use p3_symmetric::Permutation; +use p3_util::log2_ceil_usize; +use std::fmt::{Display, Formatter}; +use utils::{ToUsize, get_poseidon16, get_poseidon24}; +use whir_p3::poly::evals::EvaluationsList; +use whir_p3::poly::multilinear::MultilinearPoint; + +/// Complete set of VM instruction types with comprehensive operation support +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Instruction { + /// Basic arithmetic computation instruction (ADD, MUL) + Computation { + /// The arithmetic operation to perform + operation: Operation, + /// First operand (can be constant or memory location) + arg_a: MemOrConstant, + /// Second operand (can be memory location or frame pointer) + arg_c: MemOrFp, + /// Result destination (can be constant or memory location) + res: MemOrConstant, + }, + + /// Memory dereference instruction: res = m[m[fp + shift_0] + shift_1] + Deref { + /// First offset from frame pointer for base address + shift_0: usize, + /// Second offset added to dereferenced base address + shift_1: usize, + /// Result destination (can be memory, frame pointer, or constant) + res: MemOrFpOrConstant, + }, + + /// Conditional jump instruction for control flow + Jump { + /// Jump condition (jump if non-zero) + condition: MemOrConstant, + /// Jump destination address + dest: MemOrConstant, + /// New frame pointer value after jump + updated_fp: MemOrFp, + }, + + /// Poseidon2 cryptographic hash with 16-element input + Poseidon2_16 { + /// First input vector (vectorized pointer, size 1) + arg_a: MemOrConstant, + /// Second input vector (vectorized pointer, size 1) + arg_b: MemOrConstant, + /// Output hash result (vectorized pointer, size 2) + res: MemOrFp, + }, + + /// Poseidon2 cryptographic hash with 24-element input + Poseidon2_24 { + /// First input vector (vectorized pointer, size 2) + arg_a: MemOrConstant, + /// Second input vector (vectorized pointer, size 1) + arg_b: MemOrConstant, + /// Output hash result (vectorized pointer, size 1) + res: MemOrFp, + }, + + /// Dot product computation between extension field element vectors + DotProductExtensionExtension { + /// First vector pointer (normal pointer, size `size`) + arg0: MemOrConstant, + /// Second vector pointer (normal pointer, size `size`) + arg1: MemOrConstant, + /// Result destination (normal pointer, size 1) + res: MemOrFp, + /// Vector length for the dot product + size: usize, + }, + + /// Multilinear polynomial evaluation instruction + MultilinearEval { + /// Polynomial coefficients (vectorized pointer, chunk size = 2^n_vars) + coeffs: MemOrConstant, + /// Evaluation point (normal pointer to `n_vars` continuous EF elements) + point: MemOrConstant, + /// Evaluation result (vectorized pointer to 1 EF element) + res: MemOrFp, + /// Number of variables in the multilinear polynomial + n_vars: usize, + }, +} + +/// Execution context for instruction processing +#[derive(Debug)] +pub struct InstructionContext<'a> { + pub memory: &'a mut Memory, + pub fp: &'a mut usize, + pub pc: &'a mut usize, + pub pcs: &'a Vec, + pub final_execution: bool, + pub poseidons_16: &'a mut Vec, + pub poseidons_24: &'a mut Vec, + pub dot_products: &'a mut Vec, + pub multilinear_evals: &'a mut Vec, + pub add_counts: &'a mut usize, + pub mul_counts: &'a mut usize, + pub deref_counts: &'a mut usize, + pub jump_counts: &'a mut usize, +} + +impl Instruction { + /// Execute this instruction within the given execution context + pub fn execute_instruction(&self, ctx: &mut InstructionContext<'_>) -> Result<(), RunnerError> { + match self { + Self::Computation { + operation, + arg_a, + arg_c, + res, + } => { + if res.is_value_unknown(ctx.memory, *ctx.fp) { + let memory_address_res = res.memory_address(*ctx.fp)?; + let a_value = arg_a.read_value(ctx.memory, *ctx.fp)?; + let b_value = arg_c.read_value(ctx.memory, *ctx.fp)?; + let res_value = operation.compute(a_value, b_value); + ctx.memory.set(memory_address_res, res_value)?; + } else if arg_a.is_value_unknown(ctx.memory, *ctx.fp) { + let memory_address_a = arg_a.memory_address(*ctx.fp)?; + let res_value = res.read_value(ctx.memory, *ctx.fp)?; + let b_value = arg_c.read_value(ctx.memory, *ctx.fp)?; + let a_value = operation + .inverse_compute(res_value, b_value) + .ok_or(RunnerError::DivByZero)?; + ctx.memory.set(memory_address_a, a_value)?; + } else if arg_c.is_value_unknown(ctx.memory, *ctx.fp) { + let memory_address_b = arg_c.memory_address(*ctx.fp)?; + let res_value = res.read_value(ctx.memory, *ctx.fp)?; + let a_value = arg_a.read_value(ctx.memory, *ctx.fp)?; + let b_value = operation + .inverse_compute(res_value, a_value) + .ok_or(RunnerError::DivByZero)?; + ctx.memory.set(memory_address_b, b_value)?; + } else { + let a_value = arg_a.read_value(ctx.memory, *ctx.fp)?; + let b_value = arg_c.read_value(ctx.memory, *ctx.fp)?; + let res_value = res.read_value(ctx.memory, *ctx.fp)?; + let computed_value = operation.compute(a_value, b_value); + if res_value != computed_value { + return Err(RunnerError::NotEqual(computed_value, res_value)); + } + } + + match operation { + Operation::Add => *ctx.add_counts += 1, + Operation::Mul => *ctx.mul_counts += 1, + } + + *ctx.pc += 1; + Ok(()) + } + Self::Deref { + shift_0, + shift_1, + res, + } => { + if res.is_value_unknown(ctx.memory, *ctx.fp) { + let memory_address_res = res.memory_address(*ctx.fp)?; + let ptr = ctx.memory.get(*ctx.fp + shift_0)?; + let value = ctx.memory.get(ptr.to_usize() + shift_1)?; + ctx.memory.set(memory_address_res, value)?; + } else { + let value = res.read_value(ctx.memory, *ctx.fp)?; + let ptr = ctx.memory.get(*ctx.fp + shift_0)?; + ctx.memory.set(ptr.to_usize() + shift_1, value)?; + } + + *ctx.deref_counts += 1; + *ctx.pc += 1; + Ok(()) + } + Self::Jump { + condition, + dest, + updated_fp, + } => { + let condition_value = condition.read_value(ctx.memory, *ctx.fp)?; + assert!([F::ZERO, F::ONE].contains(&condition_value),); + if condition_value == F::ZERO { + *ctx.pc += 1; + } else { + *ctx.pc = dest.read_value(ctx.memory, *ctx.fp)?.to_usize(); + *ctx.fp = updated_fp.read_value(ctx.memory, *ctx.fp)?.to_usize(); + } + + *ctx.jump_counts += 1; + Ok(()) + } + Self::Poseidon2_16 { arg_a, arg_b, res } => { + let poseidon_16 = get_poseidon16(); + + let a_value = arg_a.read_value(ctx.memory, *ctx.fp)?; + let b_value = arg_b.read_value(ctx.memory, *ctx.fp)?; + let res_value = res.read_value(ctx.memory, *ctx.fp)?; + + let arg0 = ctx.memory.get_vector(a_value.to_usize())?; + let arg1 = ctx.memory.get_vector(b_value.to_usize())?; + + let mut input = [F::ZERO; VECTOR_LEN * 2]; + input[..VECTOR_LEN].copy_from_slice(&arg0); + input[VECTOR_LEN..].copy_from_slice(&arg1); + + // Keep a copy of the input before permutation for event recording + let input_before = input; + poseidon_16.permute_mut(&mut input); + + let res0: [F; VECTOR_LEN] = input[..VECTOR_LEN].try_into().unwrap(); + let res1: [F; VECTOR_LEN] = input[VECTOR_LEN..].try_into().unwrap(); + + ctx.memory.set_vector(res_value.to_usize(), res0)?; + ctx.memory.set_vector(1 + res_value.to_usize(), res1)?; + + if ctx.final_execution { + let cycle = ctx.pcs.len() - 1; + let addr_input_a = a_value.to_usize(); + let addr_input_b = b_value.to_usize(); + let addr_output = res_value.to_usize(); + // Build output by concatenating the two result vectors we wrote to memory + let output: [F; 16] = [res0.as_slice(), res1.as_slice()] + .concat() + .try_into() + .unwrap(); + ctx.poseidons_16.push(WitnessPoseidon16 { + cycle: Some(cycle), + addr_input_a, + addr_input_b, + addr_output, + input: input_before, + output, + }); + } + + *ctx.pc += 1; + Ok(()) + } + Self::Poseidon2_24 { arg_a, arg_b, res } => { + let poseidon_24 = get_poseidon24(); + + let a_value = arg_a.read_value(ctx.memory, *ctx.fp)?; + let b_value = arg_b.read_value(ctx.memory, *ctx.fp)?; + let res_value = res.read_value(ctx.memory, *ctx.fp)?; + + let arg0 = ctx.memory.get_vector(a_value.to_usize())?; + let arg1 = ctx.memory.get_vector(1 + a_value.to_usize())?; + let arg2 = ctx.memory.get_vector(b_value.to_usize())?; + + let mut input = [F::ZERO; VECTOR_LEN * 3]; + input[..VECTOR_LEN].copy_from_slice(&arg0); + input[VECTOR_LEN..2 * VECTOR_LEN].copy_from_slice(&arg1); + input[2 * VECTOR_LEN..].copy_from_slice(&arg2); + + // Keep a copy of the input before permutation for event recording + let input_before = input; + poseidon_24.permute_mut(&mut input); + + let res: [F; VECTOR_LEN] = input[2 * VECTOR_LEN..].try_into().unwrap(); + + ctx.memory.set_vector(res_value.to_usize(), res)?; + + if ctx.final_execution { + let cycle = ctx.pcs.len() - 1; + let addr_input_a = a_value.to_usize(); + let addr_input_b = b_value.to_usize(); + let addr_output = res_value.to_usize(); + ctx.poseidons_24.push(WitnessPoseidon24 { + cycle: Some(cycle), + addr_input_a, + addr_input_b, + addr_output, + input: input_before, + output: res, + }); + } + + *ctx.pc += 1; + Ok(()) + } + Self::DotProductExtensionExtension { + arg0, + arg1, + res, + size, + } => { + let ptr_arg_0 = arg0.read_value(ctx.memory, *ctx.fp)?.to_usize(); + let ptr_arg_1 = arg1.read_value(ctx.memory, *ctx.fp)?.to_usize(); + let ptr_res = res.read_value(ctx.memory, *ctx.fp)?.to_usize(); + + let slice_0 = ctx + .memory + .get_continuous_slice_of_ef_elements(ptr_arg_0, *size)?; + let slice_1 = ctx + .memory + .get_continuous_slice_of_ef_elements(ptr_arg_1, *size)?; + + let dot_product_result = + dot_product::(slice_0.iter().copied(), slice_1.iter().copied()); + ctx.memory.set_ef_element(ptr_res, dot_product_result)?; + + if ctx.final_execution { + let cycle = ctx.pcs.len() - 1; + ctx.dot_products.push(WitnessDotProduct { + cycle, + addr_0: ptr_arg_0, + addr_1: ptr_arg_1, + addr_res: ptr_res, + len: *size, + slice_0: slice_0.clone(), + slice_1: slice_1.clone(), + res: dot_product_result, + }); + } + + *ctx.pc += 1; + Ok(()) + } + Self::MultilinearEval { + coeffs, + point, + res, + n_vars, + } => { + let ptr_coeffs = coeffs.read_value(ctx.memory, *ctx.fp)?.to_usize(); + let ptr_point = point.read_value(ctx.memory, *ctx.fp)?.to_usize(); + let ptr_res = res.read_value(ctx.memory, *ctx.fp)?.to_usize(); + let n_coeffs = 1 << *n_vars; + let slice_coeffs = ctx.memory.slice(ptr_coeffs << *n_vars, n_coeffs)?; + + let log_point_size = log2_ceil_usize(*n_vars * DIMENSION); + let point_slice = ctx + .memory + .slice(ptr_point << log_point_size, *n_vars * DIMENSION)?; + for i in *n_vars * DIMENSION..(*n_vars * DIMENSION).next_power_of_two() { + ctx.memory.set((ptr_point << log_point_size) + i, F::ZERO)?; // padding + } + let point = point_slice[..*n_vars * DIMENSION] + .chunks_exact(DIMENSION) + .map(|chunk| EF::from_basis_coefficients_slice(chunk).unwrap()) + .collect::>(); + + let eval = slice_coeffs.evaluate(&MultilinearPoint(point.clone())); + let mut res_vec = eval.as_basis_coefficients_slice().to_vec(); + res_vec.resize(VECTOR_LEN, F::ZERO); + ctx.memory + .set_vector(ptr_res, res_vec.try_into().unwrap())?; + + if ctx.final_execution { + let cycle = ctx.pcs.len() - 1; + ctx.multilinear_evals.push(WitnessMultilinearEval { + cycle, + inner: RowMultilinearEval { + addr_coeffs: ptr_coeffs, + addr_point: ptr_point, + addr_res: ptr_res, + point, + res: eval, + }, + }); + } + + *ctx.pc += 1; + Ok(()) + } + } + } +} + +impl Display for Instruction { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Computation { + operation, + arg_a, + arg_c, + res, + } => { + write!(f, "{res} = {arg_a} {operation} {arg_c}") + } + Self::Deref { + shift_0, + shift_1, + res, + } => { + write!(f, "{res} = m[m[fp + {shift_0}] + {shift_1}]") + } + Self::DotProductExtensionExtension { + arg0, + arg1, + res, + size, + } => { + write!(f, "dot_product({arg0}, {arg1}, {res}, {size})") + } + Self::MultilinearEval { + coeffs, + point, + res, + n_vars, + } => { + write!(f, "multilinear_eval({coeffs}, {point}, {res}, {n_vars})") + } + Self::Jump { + condition, + dest, + updated_fp, + } => { + write!( + f, + "if {condition} != 0 jump to {dest} with next(fp) = {updated_fp}" + ) + } + Self::Poseidon2_16 { arg_a, arg_b, res } => { + write!(f, "{res} = poseidon2_16({arg_a}, {arg_b})") + } + Self::Poseidon2_24 { arg_a, arg_b, res } => { + write!(f, "{res} = poseidon2_24({arg_a}, {arg_b})") + } + } + } +} diff --git a/crates/lean_vm/src/isa/mod.rs b/crates/lean_vm/src/isa/mod.rs new file mode 100644 index 000000000..e372c9db9 --- /dev/null +++ b/crates/lean_vm/src/isa/mod.rs @@ -0,0 +1,14 @@ +//! Instruction Set Architecture (ISA) definitions + +pub mod bytecode; +pub mod instruction; +pub mod operation; +pub mod operands; + +pub use bytecode::*; +pub use instruction::*; +pub use operation::*; +pub use operands::*; + +/// String label for bytecode locations +pub type Label = String; \ No newline at end of file diff --git a/crates/lean_vm/src/isa/operands/hint.rs b/crates/lean_vm/src/isa/operands/hint.rs new file mode 100644 index 000000000..1fe9da01f --- /dev/null +++ b/crates/lean_vm/src/isa/operands/hint.rs @@ -0,0 +1,227 @@ +//! VM execution hints + +use crate::core::{DIMENSION, F, LOG_VECTOR_LEN, LocationInSourceCode, VECTOR_LEN}; +use crate::diagnostics::RunnerError; +use crate::execution::{ExecutionHistory, Memory}; +use crate::isa::operands::MemOrConstant; +use p3_field::{Field, PrimeCharacteristicRing}; +use std::fmt::{Display, Formatter}; +use utils::{ToUsize, pretty_integer}; + +/// VM hints provide execution guidance and debugging information +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Hint { + /// Compute the inverse of a field element + Inverse { + /// The value to invert (return 0 if arg is zero) + arg: MemOrConstant, + /// Memory offset where result will be stored: m[fp + res_offset] + res_offset: usize, + }, + /// Request memory allocation + RequestMemory { + /// Memory offset where hint will be stored: m[fp + offset] + offset: usize, + /// The requested memory size + size: MemOrConstant, + /// Whether memory should be vectorized (aligned) + vectorized: bool, + /// Length for vectorized memory allocation + vectorized_len: usize, + }, + /// Decompose values into their bit representations + DecomposeBits { + /// Memory offset for results: m[fp + res_offset..fp + res_offset + 31 * len(to_decompose)] + res_offset: usize, + /// Values to decompose into bits + to_decompose: Vec, + }, + /// Provide a counter value + CounterHint { + /// Memory offset where counter result will be stored: m[fp + res_offset] + res_offset: usize, + }, + /// Print debug information during execution + Print { + /// Source code location information + line_info: String, + /// Values to print + content: Vec, + }, + /// Report source code location for debugging + LocationReport { + /// Source code location + location: LocationInSourceCode, + }, +} + +/// Execution state for hint processing +#[derive(Debug)] +pub struct HintExecutionContext<'a> { + pub memory: &'a mut Memory, + pub fp: usize, + pub ap: &'a mut usize, + pub ap_vec: &'a mut usize, + pub counter_hint: &'a mut usize, + pub std_out: &'a mut String, + pub instruction_history: &'a mut ExecutionHistory, + pub cpu_cycles_before_new_line: &'a mut usize, + pub cpu_cycles: usize, + pub last_checkpoint_cpu_cycles: &'a mut usize, + pub checkpoint_ap: &'a mut usize, + pub checkpoint_ap_vec: &'a mut usize, +} + +impl Hint { + /// Execute this hint within the given execution context + pub fn execute_hint(&self, ctx: &mut HintExecutionContext<'_>) -> Result { + match self { + Self::RequestMemory { + offset, + size, + vectorized, + vectorized_len, + } => { + let size = size.read_value(ctx.memory, ctx.fp)?.to_usize(); + + if *vectorized { + assert!(*vectorized_len >= LOG_VECTOR_LEN, "TODO"); + + // padding: + while !(*ctx.ap_vec * VECTOR_LEN).is_multiple_of(1 << *vectorized_len) { + *ctx.ap_vec += 1; + } + ctx.memory.set( + ctx.fp + *offset, + F::from_usize(*ctx.ap_vec >> (*vectorized_len - LOG_VECTOR_LEN)), + )?; + *ctx.ap_vec += size << (*vectorized_len - LOG_VECTOR_LEN); + } else { + ctx.memory.set(ctx.fp + *offset, F::from_usize(*ctx.ap))?; + *ctx.ap += size; + } + Ok(false) // does not increase PC + } + Self::DecomposeBits { + res_offset, + to_decompose, + } => { + let mut memory_index = ctx.fp + *res_offset; + for value_source in to_decompose { + let value = value_source.read_value(ctx.memory, ctx.fp)?.to_usize(); + for i in 0..F::bits() { + let bit = F::from_bool(value & (1 << i) != 0); + ctx.memory.set(memory_index, bit)?; + memory_index += 1; + } + } + Ok(false) + } + Self::CounterHint { res_offset } => { + ctx.memory + .set(ctx.fp + *res_offset, F::from_usize(*ctx.counter_hint))?; + *ctx.counter_hint += 1; + Ok(false) + } + Self::Inverse { arg, res_offset } => { + let value = arg.read_value(ctx.memory, ctx.fp)?; + let result = value.try_inverse().unwrap_or(F::ZERO); + ctx.memory.set(ctx.fp + *res_offset, result)?; + Ok(false) + } + Self::Print { line_info, content } => { + let values = content + .iter() + .map(|value| Ok(value.read_value(ctx.memory, ctx.fp)?.to_string())) + .collect::, _>>()?; + // Logs for performance analysis: + if values[0] == "123456789" { + if values.len() == 1 { + *ctx.std_out += "[CHECKPOINT]\n"; + } else { + assert_eq!(values.len(), 2); + let new_no_vec_memory = *ctx.ap - *ctx.checkpoint_ap; + let new_vec_memory = (*ctx.ap_vec - *ctx.checkpoint_ap_vec) * DIMENSION; + *ctx.std_out += &format!( + "[CHECKPOINT {}] new CPU cycles: {}, new runtime memory: {} ({:.1}% vec)\n", + values[1], + pretty_integer(ctx.cpu_cycles - *ctx.last_checkpoint_cpu_cycles), + pretty_integer(new_no_vec_memory + new_vec_memory), + new_vec_memory as f64 / (new_no_vec_memory + new_vec_memory) as f64 + * 100.0 + ); + } + + *ctx.last_checkpoint_cpu_cycles = ctx.cpu_cycles; + *ctx.checkpoint_ap = *ctx.ap; + *ctx.checkpoint_ap_vec = *ctx.ap_vec; + return Ok(true); // continue (skip the rest of this iteration) + } + + let line_info = line_info.replace(';', ""); + *ctx.std_out += &format!("\"{}\" -> {}\n", line_info, values.join(", ")); + Ok(false) + } + Self::LocationReport { location } => { + ctx.instruction_history.lines.push(*location); + ctx.instruction_history + .cycles + .push(*ctx.cpu_cycles_before_new_line); + *ctx.cpu_cycles_before_new_line = 0; + Ok(false) + } + } + } +} + +impl Display for Hint { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::RequestMemory { + offset, + size, + vectorized, + vectorized_len, + } => { + if *vectorized { + write!( + f, + "m[fp + {offset}] = request_memory_vec({size}, {vectorized_len})" + ) + } else { + write!(f, "m[fp + {offset}] = request_memory({size})") + } + } + Self::DecomposeBits { + res_offset, + to_decompose, + } => { + write!(f, "m[fp + {res_offset}] = decompose_bits(")?; + for (i, v) in to_decompose.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{v}")?; + } + write!(f, ")") + } + Self::CounterHint { res_offset } => { + write!(f, "m[fp + {res_offset}] = counter_hint()") + } + Self::Print { line_info, content } => { + write!(f, "print(")?; + for (i, v) in content.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{v}")?; + } + write!(f, ") for \"{line_info}\"") + } + Self::Inverse { arg, res_offset } => { + write!(f, "m[fp + {res_offset}] = inverse({arg})") + } + Self::LocationReport { .. } => Ok(()), + } + } +} diff --git a/crates/lean_vm/src/isa/operands/mem_or_constant.rs b/crates/lean_vm/src/isa/operands/mem_or_constant.rs new file mode 100644 index 000000000..ba23a0b22 --- /dev/null +++ b/crates/lean_vm/src/isa/operands/mem_or_constant.rs @@ -0,0 +1,61 @@ +//! Memory or constant operand type + +use crate::core::F; +use crate::diagnostics::RunnerError; +use crate::execution::Memory; +use p3_field::PrimeCharacteristicRing; +use std::fmt::{Display, Formatter}; + +/// Memory or constant operand - represents a value that can be either a constant or memory location +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum MemOrConstant { + /// Direct constant value + Constant(F), + /// Memory address relative to frame pointer: m[fp + offset] + MemoryAfterFp { + /// Offset from frame pointer + offset: usize, + }, +} + +impl MemOrConstant { + /// Create a constant operand with value zero + pub const fn zero() -> Self { + Self::Constant(F::ZERO) + } + + /// Create a constant operand with value one + pub const fn one() -> Self { + Self::Constant(F::ONE) + } + + /// Read the value from memory or return the constant + pub fn read_value(&self, memory: &Memory, fp: usize) -> Result { + match self { + Self::Constant(c) => Ok(*c), + Self::MemoryAfterFp { offset } => memory.get(fp + *offset), + } + } + + /// Check if the value is unknown (would error when read) + pub fn is_value_unknown(&self, memory: &Memory, fp: usize) -> bool { + self.read_value(memory, fp).is_err() + } + + /// Get the memory address (returns error for constants) + pub const fn memory_address(&self, fp: usize) -> Result { + match self { + Self::Constant(_) => Err(RunnerError::NotAPointer), + Self::MemoryAfterFp { offset } => Ok(fp + *offset), + } + } +} + +impl Display for MemOrConstant { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Constant(c) => write!(f, "{c}"), + Self::MemoryAfterFp { offset } => write!(f, "m[fp + {offset}]"), + } + } +} diff --git a/crates/lean_vm/src/isa/operands/mem_or_fp.rs b/crates/lean_vm/src/isa/operands/mem_or_fp.rs new file mode 100644 index 000000000..db742561c --- /dev/null +++ b/crates/lean_vm/src/isa/operands/mem_or_fp.rs @@ -0,0 +1,51 @@ +//! Memory or frame pointer operand type + +use crate::core::F; +use crate::diagnostics::RunnerError; +use crate::execution::Memory; +use p3_field::PrimeCharacteristicRing; +use std::fmt::{Display, Formatter}; + +/// Memory or frame pointer operand - represents a value from memory or the frame pointer itself +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum MemOrFp { + /// Memory address relative to frame pointer: m[fp + offset] + MemoryAfterFp { + /// Offset from frame pointer + offset: usize, + }, + /// The frame pointer value itself + Fp, +} + +impl MemOrFp { + /// Read the value from memory or return the frame pointer + pub fn read_value(&self, memory: &Memory, fp: usize) -> Result { + match self { + Self::MemoryAfterFp { offset } => memory.get(fp + *offset), + Self::Fp => Ok(F::from_usize(fp)), + } + } + + /// Check if the value is unknown (would error when read) + pub fn is_value_unknown(&self, memory: &Memory, fp: usize) -> bool { + self.read_value(memory, fp).is_err() + } + + /// Get the memory address (returns error for Fp) + pub const fn memory_address(&self, fp: usize) -> Result { + match self { + Self::MemoryAfterFp { offset } => Ok(fp + *offset), + Self::Fp => Err(RunnerError::NotAPointer), + } + } +} + +impl Display for MemOrFp { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::MemoryAfterFp { offset } => write!(f, "m[fp + {offset}]"), + Self::Fp => write!(f, "fp"), + } + } +} diff --git a/crates/lean_vm/src/isa/operands/mem_or_fp_or_constant.rs b/crates/lean_vm/src/isa/operands/mem_or_fp_or_constant.rs new file mode 100644 index 000000000..537f00f41 --- /dev/null +++ b/crates/lean_vm/src/isa/operands/mem_or_fp_or_constant.rs @@ -0,0 +1,56 @@ +//! Memory, frame pointer, or constant operand type + +use crate::core::F; +use crate::diagnostics::RunnerError; +use crate::execution::Memory; +use p3_field::PrimeCharacteristicRing; +use std::fmt::{Display, Formatter}; + +/// Memory, frame pointer, or constant operand - the most flexible operand type +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum MemOrFpOrConstant { + /// Memory address relative to frame pointer: m[fp + offset] + MemoryAfterFp { + /// Offset from frame pointer + offset: usize, + }, + /// The frame pointer value itself + Fp, + /// Direct constant value + Constant(F), +} + +impl MemOrFpOrConstant { + /// Read the value from memory, return fp, or return the constant + pub fn read_value(&self, memory: &Memory, fp: usize) -> Result { + match self { + Self::MemoryAfterFp { offset } => memory.get(fp + *offset), + Self::Fp => Ok(F::from_usize(fp)), + Self::Constant(c) => Ok(*c), + } + } + + /// Check if the value is unknown (would error when read) + pub fn is_value_unknown(&self, memory: &Memory, fp: usize) -> bool { + self.read_value(memory, fp).is_err() + } + + /// Get the memory address (returns error for Fp and constants) + pub const fn memory_address(&self, fp: usize) -> Result { + match self { + Self::MemoryAfterFp { offset } => Ok(fp + *offset), + Self::Fp => Err(RunnerError::NotAPointer), + Self::Constant(_) => Err(RunnerError::NotAPointer), + } + } +} + +impl Display for MemOrFpOrConstant { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::MemoryAfterFp { offset } => write!(f, "m[fp + {offset}]"), + Self::Fp => write!(f, "fp"), + Self::Constant(c) => write!(f, "{c}"), + } + } +} diff --git a/crates/lean_vm/src/isa/operands/mod.rs b/crates/lean_vm/src/isa/operands/mod.rs new file mode 100644 index 000000000..58a204e48 --- /dev/null +++ b/crates/lean_vm/src/isa/operands/mod.rs @@ -0,0 +1,11 @@ +//! VM operand types and addressing modes + +pub mod mem_or_constant; +pub mod mem_or_fp; +pub mod mem_or_fp_or_constant; +pub mod hint; + +pub use mem_or_constant::*; +pub use mem_or_fp::*; +pub use mem_or_fp_or_constant::*; +pub use hint::*; \ No newline at end of file diff --git a/crates/lean_vm/src/isa/operation.rs b/crates/lean_vm/src/isa/operation.rs new file mode 100644 index 000000000..9afd345ff --- /dev/null +++ b/crates/lean_vm/src/isa/operation.rs @@ -0,0 +1,45 @@ +//! VM operation definitions + +use crate::core::F; +use p3_field::PrimeCharacteristicRing; +use std::fmt::{Display, Formatter}; + +/// Basic arithmetic operations supported by the VM +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Operation { + Add, + Mul, +} + +impl Operation { + /// Compute the operation on two field elements + pub fn compute(&self, a: F, b: F) -> F { + match self { + Self::Add => a + b, + Self::Mul => a * b, + } + } + + /// Compute the inverse operation for solving equations + pub fn inverse_compute(&self, a: F, b: F) -> Option { + match self { + Self::Add => Some(a - b), + Self::Mul => { + if b == F::ZERO { + None + } else { + Some(a / b) + } + } + } + } +} + +impl Display for Operation { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Add => write!(f, "+"), + Self::Mul => write!(f, "x"), + } + } +} \ No newline at end of file diff --git a/crates/lean_vm/src/lean_isa.rs b/crates/lean_vm/src/lean_isa.rs deleted file mode 100644 index 9edd5ee7b..000000000 --- a/crates/lean_vm/src/lean_isa.rs +++ /dev/null @@ -1,298 +0,0 @@ -use crate::{F, LocationInSourceCode}; -use p3_field::PrimeCharacteristicRing; -use std::{ - collections::BTreeMap, - fmt::{Display, Formatter}, -}; - -pub type Label = String; - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Bytecode { - pub instructions: Vec, - pub hints: BTreeMap>, // pc -> hints - pub starting_frame_memory: usize, - pub ending_pc: usize, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum MemOrConstant { - Constant(F), - MemoryAfterFp { offset: usize }, // m[fp + offset] -} -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum MemOrFpOrConstant { - MemoryAfterFp { offset: usize }, // m[fp + offset] - Fp, - Constant(F), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum MemOrFp { - MemoryAfterFp { offset: usize }, // m[fp + offset] - Fp, -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Operation { - Add, - Mul, -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Instruction { - // 4 basic instructions: ADD, MUL, DEREF, JUMP - Computation { - operation: Operation, - arg_a: MemOrConstant, - arg_c: MemOrFp, - res: MemOrConstant, - }, - Deref { - shift_0: usize, - shift_1: usize, - res: MemOrFpOrConstant, - }, // res = m[m[fp + shift_0] + shift_1] - Jump { - condition: MemOrConstant, - dest: MemOrConstant, - updated_fp: MemOrFp, - }, - // 4 precompiles: - Poseidon2_16 { - arg_a: MemOrConstant, // vectorized pointer, of size 1 - arg_b: MemOrConstant, // vectorized pointer, of size 1 - res: MemOrFp, // vectorized pointer, of size 2 (The Fp would never be used in practice) - }, - Poseidon2_24 { - arg_a: MemOrConstant, // vectorized pointer, of size 2 (2 first inputs) - arg_b: MemOrConstant, // vectorized pointer, of size 1 (3rd = last input) - res: MemOrFp, // vectorized pointer, of size 1 (3rd = last output) (The Fp would never be used in practice) - }, - DotProductExtensionExtension { - arg0: MemOrConstant, // normal pointer, of size `size` - arg1: MemOrConstant, // normal pointer, of size `size` - res: MemOrFp, // normal pointer, of size 1 (never Fp in practice) - size: usize, - }, - MultilinearEval { - coeffs: MemOrConstant, // vectorized pointer, chunk size = 2^n_vars - point: MemOrConstant, // normal pointer, pointing to `n_vars` continuous extension field elements - res: MemOrFp, // vectorized pointer, pointing to 1 EF element (ending with 8 - DIM zeros) - n_vars: usize, - }, -} - -impl Operation { - pub fn compute(&self, a: F, b: F) -> F { - match self { - Self::Add => a + b, - Self::Mul => a * b, - } - } - - pub fn inverse_compute(&self, a: F, b: F) -> Option { - match self { - Self::Add => Some(a - b), - Self::Mul => { - if b == F::ZERO { - None - } else { - Some(a / b) - } - } - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Hint { - Inverse { - arg: MemOrConstant, // the value to invert (return 0 if arg is zero) - res_offset: usize, // m[fp + res_offset] will contain the result - }, - RequestMemory { - offset: usize, // m[fp + offset] where the hint will be stored - size: MemOrConstant, // the hint - vectorized: bool, // if true, will be (2^vectorized_len)-alligned, and the returned pointer will be "divied" by 2^vectorized_len - vectorized_len: usize, - }, - DecomposeBits { - res_offset: usize, // m[fp + res_offset..fp + res_offset + 31 * len(to_decompose)] will contain the decomposed bits - to_decompose: Vec, - }, - CounterHint { - res_offset: usize, // m[fp + res_offset] will contain the result - }, - Print { - line_info: String, - content: Vec, - }, - LocationReport { - location: LocationInSourceCode, // debug purpose - }, -} - -impl MemOrConstant { - pub const fn zero() -> Self { - Self::Constant(F::ZERO) - } - - pub const fn one() -> Self { - Self::Constant(F::ONE) - } -} -impl Display for Bytecode { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - for (pc, instruction) in self.instructions.iter().enumerate() { - for hint in self.hints.get(&pc).unwrap_or(&Vec::new()) { - writeln!(f, "hint: {hint}")?; - } - writeln!(f, "{pc:>4}: {instruction}")?; - } - Ok(()) - } -} - -impl Display for MemOrConstant { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Self::Constant(c) => write!(f, "{c}"), - Self::MemoryAfterFp { offset } => write!(f, "m[fp + {offset}]"), - } - } -} - -impl Display for MemOrFp { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Self::MemoryAfterFp { offset } => write!(f, "m[fp + {offset}]"), - Self::Fp => write!(f, "fp"), - } - } -} - -impl Display for MemOrFpOrConstant { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Self::MemoryAfterFp { offset } => write!(f, "m[fp + {offset}]"), - Self::Fp => write!(f, "fp"), - Self::Constant(c) => write!(f, "{c}"), - } - } -} - -impl Display for Operation { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Self::Add => write!(f, "+"), - Self::Mul => write!(f, "x"), - } - } -} - -impl Display for Instruction { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Self::Computation { - operation, - arg_a, - arg_c, - res, - } => { - write!(f, "{res} = {arg_a} {operation} {arg_c}") - } - Self::Deref { - shift_0, - shift_1, - res, - } => { - write!(f, "{res} = m[m[fp + {shift_0}] + {shift_1}]") - } - Self::DotProductExtensionExtension { - arg0, - arg1, - res, - size, - } => { - write!(f, "dot_product({arg0}, {arg1}, {res}, {size})") - } - Self::MultilinearEval { - coeffs, - point, - res, - n_vars, - } => { - write!(f, "multilinear_eval({coeffs}, {point}, {res}, {n_vars})") - } - Self::Jump { - condition, - dest, - updated_fp, - } => { - write!( - f, - "if {condition} != 0 jump to {dest} with next(fp) = {updated_fp}" - ) - } - Self::Poseidon2_16 { arg_a, arg_b, res } => { - write!(f, "{res} = poseidon2_16({arg_a}, {arg_b})") - } - Self::Poseidon2_24 { arg_a, arg_b, res } => { - write!(f, "{res} = poseidon2_24({arg_a}, {arg_b})") - } - } - } -} - -impl Display for Hint { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Self::RequestMemory { - offset, - size, - vectorized, - vectorized_len, - } => { - if *vectorized { - write!( - f, - "m[fp + {offset}] = request_memory_vec({size}, {vectorized_len})" - ) - } else { - write!(f, "m[fp + {offset}] = request_memory({size})") - } - } - Self::DecomposeBits { - res_offset, - to_decompose, - } => { - write!(f, "m[fp + {res_offset}] = decompose_bits(")?; - for (i, v) in to_decompose.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - write!(f, "{v}")?; - } - write!(f, ")") - } - Self::CounterHint { res_offset } => { - write!(f, "m[fp + {res_offset}] = counter_hint()") - } - Self::Print { line_info, content } => { - write!(f, "print(")?; - for (i, v) in content.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - write!(f, "{v}")?; - } - write!(f, ") for \"{line_info}\"") - } - Self::Inverse { arg, res_offset } => { - write!(f, "m[fp + {res_offset}] = inverse({arg})") - } - Self::LocationReport { .. } => Ok(()), - } - } -} diff --git a/crates/lean_vm/src/lib.rs b/crates/lean_vm/src/lib.rs index f379bff07..bf228033b 100644 --- a/crates/lean_vm/src/lib.rs +++ b/crates/lean_vm/src/lib.rs @@ -1,150 +1,82 @@ -use p3_field::PrimeCharacteristicRing; -use p3_koala_bear::{KoalaBear, QuinticExtensionFieldKB}; -use p3_symmetric::Permutation; - -mod error; -mod lean_isa; -mod memory; -mod profiler; -mod runner; -mod stack_trace; -pub use error::*; -pub use lean_isa::*; -pub use memory::*; -pub use runner::*; -use utils::{get_poseidon16, get_poseidon24}; - -pub type LocationInSourceCode = usize; - -pub const DIMENSION: usize = 5; -pub const LOG_VECTOR_LEN: usize = 3; -pub const VECTOR_LEN: usize = 1 << LOG_VECTOR_LEN; -pub type F = KoalaBear; -pub type EF = QuinticExtensionFieldKB; - -pub const ZERO_VEC_PTR: usize = 0; // convention (vectorized pointer of size 2, pointing to 16 zeros) -pub const ONE_VEC_PTR: usize = 2; // convention (vectorized pointer of size 1, pointing to 10000000) -pub const POSEIDON_16_NULL_HASH_PTR: usize = 3; // convention (vectorized pointer of size 2, = the 16 elements of poseidon_16(0)) -pub const POSEIDON_24_NULL_HASH_PTR: usize = 5; // convention (vectorized pointer of size 1, = the last 8 elements of poseidon_24(0)) -pub const PUBLIC_INPUT_START: usize = 6 * 8; // normal pointer - -// VM-side events collected during final execution only. -// These events are designed to be self-contained (store values), -// allowing zk_vm_trace to construct Witness* without rescanning memory. - -#[derive(Debug, Clone)] -pub struct WitnessDotProduct { - pub cycle: usize, - pub addr_0: usize, // normal pointer - pub addr_1: usize, // normal pointer - pub addr_res: usize, // normal pointer - pub len: usize, - pub slice_0: Vec, - pub slice_1: Vec, - pub res: EF, -} -impl WitnessDotProduct { - pub fn addresses_and_len_field_repr(&self) -> [F; 4] { - [ - F::from_usize(self.addr_0), - F::from_usize(self.addr_1), - F::from_usize(self.addr_res), - F::from_usize(self.len), - ] - } -} - -#[derive(Debug)] -pub struct RowMultilinearEval { - pub addr_coeffs: usize, - pub addr_point: usize, - pub addr_res: usize, - pub point: Vec, - pub res: EF, -} - -impl RowMultilinearEval { - pub fn n_vars(&self) -> usize { - self.point.len() - } - - pub fn addresses_and_n_vars_field_repr(&self) -> [F; 4] { - [ - F::from_usize(self.addr_coeffs), - F::from_usize(self.addr_point), - F::from_usize(self.addr_res), - F::from_usize(self.n_vars()), - ] - } -} - -#[derive(Debug, derive_more::Deref)] -pub struct WitnessMultilinearEval { - pub cycle: usize, - #[deref] - pub inner: RowMultilinearEval, +//! Lean VM - A minimal virtual machine implementation + +pub mod core; +pub mod diagnostics; +pub mod execution; +pub mod isa; +pub mod witness; + +pub use core::*; +pub use diagnostics::*; +pub use execution::*; +pub use isa::*; +pub use witness::*; + +/// Main execution entry point for the VM +pub fn execute_bytecode( + bytecode: &Bytecode, + public_input: &[F], + private_input: &[F], + source_code: &str, + function_locations: &std::collections::BTreeMap, + profiler: bool, +) -> ExecutionResult { + execution::execute_bytecode_impl( + bytecode, + public_input, + private_input, + source_code, + function_locations, + profiler, + ) } -#[derive(Debug, Clone)] -pub struct WitnessPoseidon16 { - pub cycle: Option, - pub addr_input_a: usize, // vectorized pointer (size 1) - pub addr_input_b: usize, // vectorized pointer (size 1) - pub addr_output: usize, // vectorized pointer (size 2) - pub input: [F; 16], - pub output: [F; 16], -} - -impl WitnessPoseidon16 { - pub fn poseidon_of_zero() -> Self { - Self { - cycle: None, - addr_input_a: ZERO_VEC_PTR, - addr_input_b: ZERO_VEC_PTR, - addr_output: POSEIDON_16_NULL_HASH_PTR, - input: [F::ZERO; 16], - output: get_poseidon16().permute([F::ZERO; 16]), - } +#[cfg(test)] +mod tests { + use super::*; + use p3_field::PrimeCharacteristicRing; + + #[test] + fn test_vm_basic_functionality() { + let bytecode = Bytecode::new(vec![]); + let result = execute_bytecode( + &bytecode, + &[], + &[], + "", + &std::collections::BTreeMap::new(), + false, + ); + assert!(result.is_success()); } - pub fn addresses_field_repr(&self) -> [F; 3] { - [ - F::from_usize(self.addr_input_a), - F::from_usize(self.addr_input_b), - F::from_usize(self.addr_output), - ] + #[test] + fn test_memory_operations() { + let mut memory = Memory::empty(); + assert!(memory.set(0, F::from_usize(42)).is_ok()); + assert_eq!(memory.get(0).unwrap(), F::from_usize(42)); } -} - -#[derive(Debug, Clone)] -pub struct WitnessPoseidon24 { - pub cycle: Option, - pub addr_input_a: usize, // vectorized pointer (size 2) - pub addr_input_b: usize, // vectorized pointer (size 1) - pub addr_output: usize, // vectorized pointer (size 1) - pub input: [F; 24], - pub output: [F; 8], // last 8 elements of the output -} -impl WitnessPoseidon24 { - pub fn poseidon_of_zero() -> Self { - Self { - cycle: None, - addr_input_a: ZERO_VEC_PTR, - addr_input_b: ZERO_VEC_PTR, - addr_output: POSEIDON_24_NULL_HASH_PTR, - input: [F::ZERO; 24], - output: get_poseidon24().permute([F::ZERO; 24])[16..24] - .try_into() - .unwrap(), - } + #[test] + fn test_operation_compute() { + use crate::isa::Operation; + + let add = Operation::Add; + let mul = Operation::Mul; + + assert_eq!( + add.compute(F::from_usize(2), F::from_usize(3)), + F::from_usize(5) + ); + assert_eq!( + mul.compute(F::from_usize(2), F::from_usize(3)), + F::from_usize(6) + ); } - pub fn addresses_field_repr(&self) -> [F; 3] { - [ - F::from_usize(self.addr_input_a), - F::from_usize(self.addr_input_b), - F::from_usize(self.addr_output), - ] + #[test] + fn test_witness_creation() { + let witness = WitnessPoseidon16::poseidon_of_zero(); + assert_eq!(witness.input, [F::ZERO; 16]); } } diff --git a/crates/lean_vm/src/runner.rs b/crates/lean_vm/src/runner.rs deleted file mode 100644 index f8c32f3b0..000000000 --- a/crates/lean_vm/src/runner.rs +++ /dev/null @@ -1,719 +0,0 @@ -use p3_field::BasedVectorSpace; -use p3_field::PrimeCharacteristicRing; -use p3_field::dot_product; -use p3_util::log2_ceil_usize; -use std::collections::BTreeMap; -use utils::ToUsize; -use utils::get_poseidon16; -use utils::get_poseidon24; -use utils::pretty_integer; -use whir_p3::poly::evals::EvaluationsList; -use whir_p3::poly::multilinear::MultilinearPoint; - -use crate::lean_isa::*; -use crate::profiler::profiling_report; -use crate::stack_trace::pretty_stack_trace; -use crate::*; -use p3_field::Field; -use p3_symmetric::Permutation; - -const STACK_TRACE_INSTRUCTIONS: usize = 5000; - -impl MemOrConstant { - pub fn read_value(&self, memory: &Memory, fp: usize) -> Result { - match self { - Self::Constant(c) => Ok(*c), - Self::MemoryAfterFp { offset } => memory.get(fp + *offset), - } - } - - pub fn is_value_unknown(&self, memory: &Memory, fp: usize) -> bool { - self.read_value(memory, fp).is_err() - } - - pub const fn memory_address(&self, fp: usize) -> Result { - match self { - Self::Constant(_) => Err(RunnerError::NotAPointer), - Self::MemoryAfterFp { offset } => Ok(fp + *offset), - } - } -} - -impl MemOrFp { - pub fn read_value(&self, memory: &Memory, fp: usize) -> Result { - match self { - Self::MemoryAfterFp { offset } => memory.get(fp + *offset), - Self::Fp => Ok(F::from_usize(fp)), - } - } - - pub fn is_value_unknown(&self, memory: &Memory, fp: usize) -> bool { - self.read_value(memory, fp).is_err() - } - - pub const fn memory_address(&self, fp: usize) -> Result { - match self { - Self::MemoryAfterFp { offset } => Ok(fp + *offset), - Self::Fp => Err(RunnerError::NotAPointer), - } - } -} - -impl MemOrFpOrConstant { - pub fn read_value(&self, memory: &Memory, fp: usize) -> Result { - match self { - Self::MemoryAfterFp { offset } => memory.get(fp + *offset), - Self::Fp => Ok(F::from_usize(fp)), - Self::Constant(c) => Ok(*c), - } - } - - pub fn is_value_unknown(&self, memory: &Memory, fp: usize) -> bool { - self.read_value(memory, fp).is_err() - } - - pub const fn memory_address(&self, fp: usize) -> Result { - match self { - Self::MemoryAfterFp { offset } => Ok(fp + *offset), - Self::Fp => Err(RunnerError::NotAPointer), - Self::Constant(_) => Err(RunnerError::NotAPointer), - } - } -} - -#[derive(Debug, Clone, Default)] -pub(crate) struct ExecutionHistory { - pub(crate) lines: Vec, - pub(crate) cycles: Vec, // for each line, how many cycles it took -} - -pub fn execute_bytecode( - bytecode: &Bytecode, - public_input: &[F], - private_input: &[F], - source_code: &str, // debug purpose - function_locations: &BTreeMap, // debug purpose - profiler: bool, -) -> ExecutionResult { - let mut std_out = String::new(); - let mut instruction_history = ExecutionHistory::default(); - let first_exec = match execute_bytecode_helper( - bytecode, - public_input, - private_input, - MAX_RUNNER_MEMORY_SIZE / 2, - false, - &mut std_out, - &mut instruction_history, - false, - function_locations, - ) { - Ok(first_exec) => first_exec, - Err(err) => { - let lines_history = &instruction_history.lines; - let latest_instructions = - &lines_history[lines_history.len().saturating_sub(STACK_TRACE_INSTRUCTIONS)..]; - println!( - "\n{}", - pretty_stack_trace(source_code, latest_instructions, function_locations) - ); - if !std_out.is_empty() { - println!("╔══════════════════════════════════════════════════════════════╗"); - println!("║ STD-OUT ║"); - println!("╚══════════════════════════════════════════════════════════════╝\n"); - print!("{std_out}"); - } - panic!("Error during bytecode execution: {err}"); - } - }; - instruction_history = ExecutionHistory::default(); - execute_bytecode_helper( - bytecode, - public_input, - private_input, - first_exec.no_vec_runtime_memory, - true, - &mut String::new(), - &mut instruction_history, - profiler, - function_locations, - ) - .unwrap() -} - -#[derive(Debug)] -pub struct ExecutionResult { - pub no_vec_runtime_memory: usize, - pub public_memory_size: usize, - pub memory: Memory, - pub pcs: Vec, - pub fps: Vec, - // precompiles history: - pub poseidons_16: Vec, - pub poseidons_24: Vec, - pub dot_products: Vec, - pub multilinear_evals: Vec, -} - -pub fn build_public_memory(public_input: &[F]) -> Vec { - // padded to a power of two - let public_memory_len = (PUBLIC_INPUT_START + public_input.len()).next_power_of_two(); - let mut public_memory = F::zero_vec(public_memory_len); - public_memory[PUBLIC_INPUT_START..][..public_input.len()].copy_from_slice(public_input); - - // "zero" vector - let zero_start = ZERO_VEC_PTR * VECTOR_LEN; - for slot in public_memory - .iter_mut() - .skip(zero_start) - .take(2 * VECTOR_LEN) - { - *slot = F::ZERO; - } - - // "one" vector - public_memory[ONE_VEC_PTR * VECTOR_LEN] = F::ONE; - let one_start = ONE_VEC_PTR * VECTOR_LEN + 1; - for slot in public_memory - .iter_mut() - .skip(one_start) - .take(VECTOR_LEN - 1) - { - *slot = F::ZERO; - } - - public_memory - [POSEIDON_16_NULL_HASH_PTR * VECTOR_LEN..(POSEIDON_16_NULL_HASH_PTR + 2) * VECTOR_LEN] - .copy_from_slice(&get_poseidon16().permute([F::ZERO; 16])); - public_memory - [POSEIDON_24_NULL_HASH_PTR * VECTOR_LEN..(POSEIDON_24_NULL_HASH_PTR + 1) * VECTOR_LEN] - .copy_from_slice(&get_poseidon24().permute([F::ZERO; 24])[16..]); - public_memory -} - -#[allow(clippy::too_many_arguments)] // TODO -fn execute_bytecode_helper( - bytecode: &Bytecode, - public_input: &[F], - private_input: &[F], - no_vec_runtime_memory: usize, - final_execution: bool, - std_out: &mut String, - instruction_history: &mut ExecutionHistory, - profiler: bool, - function_locations: &BTreeMap, -) -> Result { - let poseidon_16 = get_poseidon16(); - let poseidon_24 = get_poseidon24(); - - // set public memory - let mut memory = Memory::new(build_public_memory(public_input)); - - let public_memory_size = (PUBLIC_INPUT_START + public_input.len()).next_power_of_two(); - let mut fp = public_memory_size; - - for (i, value) in private_input.iter().enumerate() { - memory.set(fp + i, *value)?; - } - fp += private_input.len(); - fp = fp.next_multiple_of(DIMENSION); - - let initial_ap = fp + bytecode.starting_frame_memory; - let initial_ap_vec = - (initial_ap + no_vec_runtime_memory).next_multiple_of(VECTOR_LEN) / VECTOR_LEN; - - let mut pc = 0; - let mut ap = initial_ap; - let mut ap_vec = initial_ap_vec; - - let mut poseidon16_calls = 0; - let mut poseidon24_calls = 0; - let mut dot_product_ext_ext_calls = 0; - let mut multilinear_eval_calls = 0; - let mut cpu_cycles = 0; - - let mut last_checkpoint_cpu_cycles = 0; - let mut checkpoint_ap = initial_ap; - let mut checkpoint_ap_vec = ap_vec; - - let mut pcs = Vec::new(); - let mut fps = Vec::new(); - - // Events collected only in final execution - let mut poseidons_16: Vec = Vec::new(); - let mut poseidons_24: Vec = Vec::new(); - let mut dot_products: Vec = Vec::new(); - let mut multilinear_evals: Vec = Vec::new(); - - let mut add_counts = 0; - let mut mul_counts = 0; - let mut deref_counts = 0; - let mut jump_counts = 0; - - let mut counter_hint = 0; - let mut cpu_cycles_before_new_line = 0; - - while pc != bytecode.ending_pc { - if pc >= bytecode.instructions.len() { - return Err(RunnerError::PCOutOfBounds); - } - - pcs.push(pc); - fps.push(fp); - - cpu_cycles += 1; - cpu_cycles_before_new_line += 1; - - for hint in bytecode.hints.get(&pc).unwrap_or(&vec![]) { - match hint { - Hint::RequestMemory { - offset, - size, - vectorized, - vectorized_len, - } => { - let size = size.read_value(&memory, fp)?.to_usize(); - - if *vectorized { - assert!(*vectorized_len >= LOG_VECTOR_LEN, "TODO"); - - // padding: - while !(ap_vec * VECTOR_LEN).is_multiple_of(1 << *vectorized_len) { - ap_vec += 1; - } - memory.set( - fp + *offset, - F::from_usize(ap_vec >> (*vectorized_len - LOG_VECTOR_LEN)), - )?; - ap_vec += size << (*vectorized_len - LOG_VECTOR_LEN); - } else { - memory.set(fp + *offset, F::from_usize(ap))?; - ap += size; - } - // does not increase PC - } - Hint::DecomposeBits { - res_offset, - to_decompose, - } => { - let mut memory_index = fp + *res_offset; - for value_source in to_decompose { - let value = value_source.read_value(&memory, fp)?.to_usize(); - for i in 0..F::bits() { - let bit = F::from_bool(value & (1 << i) != 0); - memory.set(memory_index, bit)?; - memory_index += 1; - } - } - } - Hint::CounterHint { res_offset } => { - memory.set(fp + *res_offset, F::from_usize(counter_hint))?; - counter_hint += 1; - } - Hint::Inverse { arg, res_offset } => { - let value = arg.read_value(&memory, fp)?; - let result = value.try_inverse().unwrap_or(F::ZERO); - memory.set(fp + *res_offset, result)?; - } - Hint::Print { line_info, content } => { - let values = content - .iter() - .map(|value| Ok(value.read_value(&memory, fp)?.to_string())) - .collect::, _>>()?; - // Logs for performance analysis: - if values[0] == "123456789" { - if values.len() == 1 { - *std_out += "[CHECKPOINT]\n"; - } else { - assert_eq!(values.len(), 2); - let new_no_vec_memory = ap - checkpoint_ap; - let new_vec_memory = (ap_vec - checkpoint_ap_vec) * DIMENSION; - *std_out += &format!( - "[CHECKPOINT {}] new CPU cycles: {}, new runtime memory: {} ({:.1}% vec)\n", - values[1], - pretty_integer(cpu_cycles - last_checkpoint_cpu_cycles), - pretty_integer(new_no_vec_memory + new_vec_memory), - new_vec_memory as f64 / (new_no_vec_memory + new_vec_memory) as f64 - * 100.0 - ); - } - - last_checkpoint_cpu_cycles = cpu_cycles; - checkpoint_ap = ap; - checkpoint_ap_vec = ap_vec; - continue; - } - - let line_info = line_info.replace(';', ""); - *std_out += &format!("\"{}\" -> {}\n", line_info, values.join(", ")); - // does not increase PC - } - Hint::LocationReport { location } => { - instruction_history.lines.push(*location); - instruction_history.cycles.push(cpu_cycles_before_new_line); - cpu_cycles_before_new_line = 0; - } - } - } - - let instruction = &bytecode.instructions[pc]; - match instruction { - Instruction::Computation { - operation, - arg_a, - arg_c, - res, - } => { - if res.is_value_unknown(&memory, fp) { - let memory_address_res = res.memory_address(fp)?; - let a_value = arg_a.read_value(&memory, fp)?; - let b_value = arg_c.read_value(&memory, fp)?; - let res_value = operation.compute(a_value, b_value); - memory.set(memory_address_res, res_value)?; - } else if arg_a.is_value_unknown(&memory, fp) { - let memory_address_a = arg_a.memory_address(fp)?; - let res_value = res.read_value(&memory, fp)?; - let b_value = arg_c.read_value(&memory, fp)?; - let a_value = operation - .inverse_compute(res_value, b_value) - .ok_or(RunnerError::DivByZero)?; - memory.set(memory_address_a, a_value)?; - } else if arg_c.is_value_unknown(&memory, fp) { - let memory_address_b = arg_c.memory_address(fp)?; - let res_value = res.read_value(&memory, fp)?; - let a_value = arg_a.read_value(&memory, fp)?; - let b_value = operation - .inverse_compute(res_value, a_value) - .ok_or(RunnerError::DivByZero)?; - memory.set(memory_address_b, b_value)?; - } else { - let a_value = arg_a.read_value(&memory, fp)?; - let b_value = arg_c.read_value(&memory, fp)?; - let res_value = res.read_value(&memory, fp)?; - let computed_value = operation.compute(a_value, b_value); - if res_value != computed_value { - return Err(RunnerError::NotEqual(computed_value, res_value)); - } - } - - match operation { - Operation::Add => add_counts += 1, - Operation::Mul => mul_counts += 1, - } - - pc += 1; - } - Instruction::Deref { - shift_0, - shift_1, - res, - } => { - if res.is_value_unknown(&memory, fp) { - let memory_address_res = res.memory_address(fp)?; - let ptr = memory.get(fp + shift_0)?; - let value = memory.get(ptr.to_usize() + shift_1)?; - memory.set(memory_address_res, value)?; - } else { - let value = res.read_value(&memory, fp)?; - let ptr = memory.get(fp + shift_0)?; - memory.set(ptr.to_usize() + shift_1, value)?; - } - - deref_counts += 1; - pc += 1; - } - Instruction::Jump { - condition, - dest, - updated_fp, - } => { - let condition_value = condition.read_value(&memory, fp)?; - assert!([F::ZERO, F::ONE].contains(&condition_value),); - if condition_value == F::ZERO { - pc += 1; - } else { - pc = dest.read_value(&memory, fp)?.to_usize(); - fp = updated_fp.read_value(&memory, fp)?.to_usize(); - } - - jump_counts += 1; - } - Instruction::Poseidon2_16 { arg_a, arg_b, res } => { - poseidon16_calls += 1; - - let a_value = arg_a.read_value(&memory, fp)?; - let b_value = arg_b.read_value(&memory, fp)?; - let res_value = res.read_value(&memory, fp)?; - - let arg0 = memory.get_vector(a_value.to_usize())?; - let arg1 = memory.get_vector(b_value.to_usize())?; - - let mut input = [F::ZERO; VECTOR_LEN * 2]; - input[..VECTOR_LEN].copy_from_slice(&arg0); - input[VECTOR_LEN..].copy_from_slice(&arg1); - - // Keep a copy of the input before permutation for event recording - let input_before = input; - poseidon_16.permute_mut(&mut input); - - let res0: [F; VECTOR_LEN] = input[..VECTOR_LEN].try_into().unwrap(); - let res1: [F; VECTOR_LEN] = input[VECTOR_LEN..].try_into().unwrap(); - - memory.set_vector(res_value.to_usize(), res0)?; - memory.set_vector(1 + res_value.to_usize(), res1)?; - - if final_execution { - let cycle = pcs.len() - 1; - let addr_input_a = a_value.to_usize(); - let addr_input_b = b_value.to_usize(); - let addr_output = res_value.to_usize(); - // Build output by concatenating the two result vectors we wrote to memory - let output: [F; 16] = [res0.as_slice(), res1.as_slice()] - .concat() - .try_into() - .unwrap(); - poseidons_16.push(WitnessPoseidon16 { - cycle: Some(cycle), - addr_input_a, - addr_input_b, - addr_output, - input: input_before, - output, - }); - } - - pc += 1; - } - Instruction::Poseidon2_24 { arg_a, arg_b, res } => { - poseidon24_calls += 1; - - let a_value = arg_a.read_value(&memory, fp)?; - let b_value = arg_b.read_value(&memory, fp)?; - let res_value = res.read_value(&memory, fp)?; - - let arg0 = memory.get_vector(a_value.to_usize())?; - let arg1 = memory.get_vector(1 + a_value.to_usize())?; - let arg2 = memory.get_vector(b_value.to_usize())?; - - let mut input = [F::ZERO; VECTOR_LEN * 3]; - input[..VECTOR_LEN].copy_from_slice(&arg0); - input[VECTOR_LEN..2 * VECTOR_LEN].copy_from_slice(&arg1); - input[2 * VECTOR_LEN..].copy_from_slice(&arg2); - - // Keep a copy of the input before permutation for event recording - let input_before = input; - poseidon_24.permute_mut(&mut input); - - let res: [F; VECTOR_LEN] = input[2 * VECTOR_LEN..].try_into().unwrap(); - - memory.set_vector(res_value.to_usize(), res)?; - - if final_execution { - let cycle = pcs.len() - 1; - let addr_input_a = a_value.to_usize(); - let addr_input_b = b_value.to_usize(); - let addr_output = res_value.to_usize(); - poseidons_24.push(WitnessPoseidon24 { - cycle: Some(cycle), - addr_input_a, - addr_input_b, - addr_output, - input: input_before, - output: res, - }); - } - - pc += 1; - } - Instruction::DotProductExtensionExtension { - arg0, - arg1, - res, - size, - } => { - dot_product_ext_ext_calls += 1; - - let ptr_arg_0 = arg0.read_value(&memory, fp)?.to_usize(); - let ptr_arg_1 = arg1.read_value(&memory, fp)?.to_usize(); - let ptr_res = res.read_value(&memory, fp)?.to_usize(); - - let slice_0 = memory.get_continuous_slice_of_ef_elements(ptr_arg_0, *size)?; - let slice_1 = memory.get_continuous_slice_of_ef_elements(ptr_arg_1, *size)?; - - let dot_product = - dot_product::(slice_0.iter().copied(), slice_1.iter().copied()); - memory.set_ef_element(ptr_res, dot_product)?; - - if final_execution { - let cycle = pcs.len() - 1; - dot_products.push(WitnessDotProduct { - cycle, - addr_0: ptr_arg_0, - addr_1: ptr_arg_1, - addr_res: ptr_res, - len: *size, - slice_0: slice_0.clone(), - slice_1: slice_1.clone(), - res: dot_product, - }); - } - - pc += 1; - } - Instruction::MultilinearEval { - coeffs, - point, - res, - n_vars, - } => { - multilinear_eval_calls += 1; - - let ptr_coeffs = coeffs.read_value(&memory, fp)?.to_usize(); - let ptr_point = point.read_value(&memory, fp)?.to_usize(); - let ptr_res = res.read_value(&memory, fp)?.to_usize(); - let n_coeffs = 1 << *n_vars; - let slice_coeffs = memory.slice(ptr_coeffs << *n_vars, n_coeffs)?; - - let log_point_size = log2_ceil_usize(*n_vars * DIMENSION); - let point_slice = memory.slice(ptr_point << log_point_size, *n_vars * DIMENSION)?; - for i in *n_vars * DIMENSION..(*n_vars * DIMENSION).next_power_of_two() { - memory.set((ptr_point << log_point_size) + i, F::ZERO)?; // padding - } - let point = point_slice[..*n_vars * DIMENSION] - .chunks_exact(DIMENSION) - .map(|chunk| EF::from_basis_coefficients_slice(chunk).unwrap()) - .collect::>(); - - let eval = slice_coeffs.evaluate(&MultilinearPoint(point.clone())); - let mut res_vec = eval.as_basis_coefficients_slice().to_vec(); - res_vec.resize(VECTOR_LEN, F::ZERO); - memory.set_vector(ptr_res, res_vec.try_into().unwrap())?; - - if final_execution { - let cycle = pcs.len() - 1; - multilinear_evals.push(WitnessMultilinearEval { - cycle, - inner: RowMultilinearEval { - addr_coeffs: ptr_coeffs, - addr_point: ptr_point, - addr_res: ptr_res, - point, - res: eval, - }, - }); - } - - pc += 1; - } - } - } - - debug_assert_eq!(pc, bytecode.ending_pc); - pcs.push(pc); - fps.push(fp); - - if final_execution { - // Ensure event counts match call counts in final execution - debug_assert_eq!(poseidon16_calls, poseidons_16.len()); - debug_assert_eq!(poseidon24_calls, poseidons_24.len()); - debug_assert_eq!(dot_product_ext_ext_calls, dot_products.len()); - debug_assert_eq!(multilinear_eval_calls, multilinear_evals.len()); - if profiler { - let report = profiling_report(instruction_history, function_locations); - println!("\n{report}"); - } - if !std_out.is_empty() { - println!("╔═════════════════════════════════════════════════════════════════════════╗"); - println!("║ STD-OUT ║"); - println!("╚═════════════════════════════════════════════════════════════════════════╝"); - print!("\n{std_out}"); - println!( - "──────────────────────────────────────────────────────────────────────────\n" - ); - } - - println!("╔═════════════════════════════════════════════════════════════════════════╗"); - println!("║ STATS ║"); - println!("╚═════════════════════════════════════════════════════════════════════════╝\n"); - - println!("CYCLES: {}", pretty_integer(cpu_cycles)); - println!("MEMORY: {}", pretty_integer(memory.0.len())); - println!(); - - let runtime_memory_size = memory.0.len() - (PUBLIC_INPUT_START + public_input.len()); - println!( - "Bytecode size: {}", - pretty_integer(bytecode.instructions.len()) - ); - println!("Public input size: {}", pretty_integer(public_input.len())); - println!( - "Private input size: {}", - pretty_integer(private_input.len()) - ); - println!( - "Runtime memory: {} ({:.2}% vec)", - pretty_integer(runtime_memory_size), - (DIMENSION * (ap_vec - initial_ap_vec)) as f64 / runtime_memory_size as f64 * 100.0 - ); - let used_memory_cells = memory - .0 - .iter() - .skip(PUBLIC_INPUT_START + public_input.len()) - .filter(|&&x| x.is_some()) - .count(); - println!( - "Memory usage: {:.1}%", - used_memory_cells as f64 / runtime_memory_size as f64 * 100.0 - ); - - println!(); - - if poseidon16_calls + poseidon24_calls > 0 { - println!( - "Poseidon2_16 calls: {}, Poseidon2_24 calls: {} (1 poseidon per {} instructions)", - pretty_integer(poseidon16_calls), - pretty_integer(poseidon24_calls), - cpu_cycles / (poseidon16_calls + poseidon24_calls) - ); - } - if dot_product_ext_ext_calls > 0 { - println!( - "DotProduct calls: {}", - pretty_integer(dot_product_ext_ext_calls) - ); - } - if multilinear_eval_calls > 0 { - println!( - "MultilinearEval calls: {}", - pretty_integer(multilinear_eval_calls) - ); - } - - if false { - println!("Low level instruction counts:"); - println!( - "COMPUTE: {} ({} ADD, {} MUL)", - add_counts + mul_counts, - add_counts, - mul_counts - ); - println!("DEREF: {deref_counts}"); - println!("JUMP: {jump_counts}"); - } - - println!("──────────────────────────────────────────────────────────────────────────\n"); - } - - let no_vec_runtime_memory = ap - initial_ap; - Ok(ExecutionResult { - no_vec_runtime_memory, - public_memory_size, - memory, - pcs, - fps, - poseidons_16, - poseidons_24, - dot_products, - multilinear_evals, - }) -} diff --git a/crates/lean_vm/src/witness/dot_product.rs b/crates/lean_vm/src/witness/dot_product.rs new file mode 100644 index 000000000..f1eff87c2 --- /dev/null +++ b/crates/lean_vm/src/witness/dot_product.rs @@ -0,0 +1,66 @@ +//! Dot product witness for arithmetic operations between extension field elements + +use crate::core::{EF, F}; +use p3_field::PrimeCharacteristicRing; + +/// Witness data for dot product operations between extension field element vectors +/// +/// This witness captures all the necessary information to verify a dot product computation +/// including the input vectors, result, and memory addresses involved. +#[derive(Debug, Clone)] +pub struct WitnessDotProduct { + /// Execution cycle when this operation occurred + pub cycle: usize, + /// Memory address of first input vector (normal pointer) + pub addr_0: usize, + /// Memory address of second input vector (normal pointer) + pub addr_1: usize, + /// Memory address where result is stored (normal pointer) + pub addr_res: usize, + /// Length of the input vectors + pub len: usize, + /// First input vector data + pub slice_0: Vec, + /// Second input vector data + pub slice_1: Vec, + /// Computed dot product result + pub res: EF, +} + +impl WitnessDotProduct { + /// Create a new dot product witness with all required data + #[allow(clippy::too_many_arguments)] + pub fn new( + cycle: usize, + addr_0: usize, + addr_1: usize, + addr_res: usize, + len: usize, + slice_0: Vec, + slice_1: Vec, + res: EF, + ) -> Self { + Self { + cycle, + addr_0, + addr_1, + addr_res, + len, + slice_0, + slice_1, + res, + } + } + + /// Get memory addresses and vector length as field element representation + /// + /// Returns [addr_0, addr_1, addr_res, len] as base field elements + pub fn addresses_and_len_field_repr(&self) -> [F; 4] { + [ + F::from_usize(self.addr_0), + F::from_usize(self.addr_1), + F::from_usize(self.addr_res), + F::from_usize(self.len), + ] + } +} diff --git a/crates/lean_vm/src/witness/mod.rs b/crates/lean_vm/src/witness/mod.rs new file mode 100644 index 000000000..9af192ecb --- /dev/null +++ b/crates/lean_vm/src/witness/mod.rs @@ -0,0 +1,11 @@ +//! Witness generation for VM execution traces + +pub mod dot_product; +pub mod multilinear_eval; +pub mod poseidon16; +pub mod poseidon24; + +pub use dot_product::*; +pub use multilinear_eval::*; +pub use poseidon16::*; +pub use poseidon24::*; diff --git a/crates/lean_vm/src/witness/multilinear_eval.rs b/crates/lean_vm/src/witness/multilinear_eval.rs new file mode 100644 index 000000000..88683c4af --- /dev/null +++ b/crates/lean_vm/src/witness/multilinear_eval.rs @@ -0,0 +1,79 @@ +//! Multilinear polynomial evaluation witness + +use crate::core::{EF, F}; +use p3_field::PrimeCharacteristicRing; + +/// Row data for multilinear polynomial evaluation +/// +/// Contains the core data needed for a multilinear evaluation operation +/// including memory addresses, evaluation point, and result. +#[derive(Debug, Clone)] +pub struct RowMultilinearEval { + /// Memory address of polynomial coefficients + pub addr_coeffs: usize, + /// Memory address of evaluation point + pub addr_point: usize, + /// Memory address where result is stored + pub addr_res: usize, + /// Evaluation point coordinates (one per variable) + pub point: Vec, + /// Computed evaluation result + pub res: EF, +} + +impl RowMultilinearEval { + /// Create a new multilinear evaluation row with all required data + pub fn new( + addr_coeffs: usize, + addr_point: usize, + addr_res: usize, + point: Vec, + res: EF, + ) -> Self { + Self { + addr_coeffs, + addr_point, + addr_res, + point, + res, + } + } + + /// Get the number of variables in this multilinear polynomial + /// + /// This is determined by the length of the evaluation point vector + pub fn n_vars(&self) -> usize { + self.point.len() + } + + /// Get memory addresses and variable count as field element representation + /// + /// Returns [addr_coeffs, addr_point, addr_res, n_vars] as base field elements + pub fn addresses_and_n_vars_field_repr(&self) -> [F; 4] { + [ + F::from_usize(self.addr_coeffs), + F::from_usize(self.addr_point), + F::from_usize(self.addr_res), + F::from_usize(self.n_vars()), + ] + } +} + +/// Complete witness for multilinear polynomial evaluation with execution context +/// +/// Combines the row data with cycle information to provide full execution trace details +#[derive(Debug, Clone, derive_more::Deref)] +pub struct WitnessMultilinearEval { + /// Execution cycle when this evaluation occurred + pub cycle: usize, + /// Core multilinear evaluation data + #[deref] + pub inner: RowMultilinearEval, +} + +impl WitnessMultilinearEval { + /// Create a new multilinear evaluation witness + pub fn new(cycle: usize, inner: RowMultilinearEval) -> Self { + Self { cycle, inner } + } +} \ No newline at end of file diff --git a/crates/lean_vm/src/witness/poseidon16.rs b/crates/lean_vm/src/witness/poseidon16.rs new file mode 100644 index 000000000..250f5b61a --- /dev/null +++ b/crates/lean_vm/src/witness/poseidon16.rs @@ -0,0 +1,73 @@ +//! Poseidon2 hash witness for 16-element input + +use crate::core::{F, POSEIDON_16_NULL_HASH_PTR, ZERO_VEC_PTR}; +use p3_field::PrimeCharacteristicRing; +use p3_symmetric::Permutation; +use utils::get_poseidon16; + +/// Witness data for Poseidon2 cryptographic hash operations with 16-element input +/// +/// This witness captures all the information needed to verify a Poseidon2 hash computation +/// including input data, output hash, and memory addresses involved. +#[derive(Debug, Clone)] +pub struct WitnessPoseidon16 { + /// Execution cycle when this hash occurred (None for precomputed values) + pub cycle: Option, + /// Memory address of first input vector (vectorized pointer, size 1) + pub addr_input_a: usize, + /// Memory address of second input vector (vectorized pointer, size 1) + pub addr_input_b: usize, + /// Memory address where hash output is stored (vectorized pointer, size 2) + pub addr_output: usize, + /// Complete 16-element input to the hash function + pub input: [F; 16], + /// Complete 16-element hash output + pub output: [F; 16], +} + +impl WitnessPoseidon16 { + /// Create a new Poseidon16 witness with all hash data + pub fn new( + cycle: Option, + addr_input_a: usize, + addr_input_b: usize, + addr_output: usize, + input: [F; 16], + output: [F; 16], + ) -> Self { + Self { + cycle, + addr_input_a, + addr_input_b, + addr_output, + input, + output, + } + } + + /// Create a precomputed Poseidon16 witness for all-zero input + /// + /// This is used for common zero-input hashes that can be precomputed + /// and stored at known memory locations for efficiency. + pub fn poseidon_of_zero() -> Self { + Self { + cycle: None, + addr_input_a: ZERO_VEC_PTR, + addr_input_b: ZERO_VEC_PTR, + addr_output: POSEIDON_16_NULL_HASH_PTR, + input: [F::ZERO; 16], + output: get_poseidon16().permute([F::ZERO; 16]), + } + } + + /// Get all memory addresses as field element representation + /// + /// Returns [addr_input_a, addr_input_b, addr_output] as base field elements + pub fn addresses_field_repr(&self) -> [F; 3] { + [ + F::from_usize(self.addr_input_a), + F::from_usize(self.addr_input_b), + F::from_usize(self.addr_output), + ] + } +} \ No newline at end of file diff --git a/crates/lean_vm/src/witness/poseidon24.rs b/crates/lean_vm/src/witness/poseidon24.rs new file mode 100644 index 000000000..670ead746 --- /dev/null +++ b/crates/lean_vm/src/witness/poseidon24.rs @@ -0,0 +1,75 @@ +//! Poseidon2 hash witness for 24-element input + +use crate::core::{F, POSEIDON_24_NULL_HASH_PTR, ZERO_VEC_PTR}; +use p3_field::PrimeCharacteristicRing; +use p3_symmetric::Permutation; +use utils::get_poseidon24; + +/// Witness data for Poseidon2 cryptographic hash operations with 24-element input +/// +/// This witness captures all the information needed to verify a Poseidon2 hash computation +/// with larger 24-element input, storing only the last 8 elements of output for efficiency. +#[derive(Debug, Clone)] +pub struct WitnessPoseidon24 { + /// Execution cycle when this hash occurred (None for precomputed values) + pub cycle: Option, + /// Memory address of first input vector (vectorized pointer, size 2) + pub addr_input_a: usize, + /// Memory address of second input vector (vectorized pointer, size 1) + pub addr_input_b: usize, + /// Memory address where hash output is stored (vectorized pointer, size 1) + pub addr_output: usize, + /// Complete 24-element input to the hash function + pub input: [F; 24], + /// Last 8 elements of the hash output (optimization for storage) + pub output: [F; 8], +} + +impl WitnessPoseidon24 { + /// Create a new Poseidon24 witness with all hash data + pub fn new( + cycle: Option, + addr_input_a: usize, + addr_input_b: usize, + addr_output: usize, + input: [F; 24], + output: [F; 8], + ) -> Self { + Self { + cycle, + addr_input_a, + addr_input_b, + addr_output, + input, + output, + } + } + + /// Create a precomputed Poseidon24 witness for all-zero input + /// + /// This is used for common zero-input hashes that can be precomputed + /// and stored at known memory locations for efficiency. + pub fn poseidon_of_zero() -> Self { + Self { + cycle: None, + addr_input_a: ZERO_VEC_PTR, + addr_input_b: ZERO_VEC_PTR, + addr_output: POSEIDON_24_NULL_HASH_PTR, + input: [F::ZERO; 24], + output: get_poseidon24().permute([F::ZERO; 24])[16..24] + .try_into() + .unwrap(), + } + } + + /// Get all memory addresses as field element representation + /// + /// Returns [addr_input_a, addr_input_b, addr_output] as base field elements + pub fn addresses_field_repr(&self) -> [F; 3] { + [ + F::from_usize(self.addr_input_a), + F::from_usize(self.addr_input_b), + F::from_usize(self.addr_output), + ] + } +} \ No newline at end of file diff --git a/crates/lean_vm/tests/test_lean_vm.rs b/crates/lean_vm/tests/test_lean_vm.rs index a6b4d7e35..33bdbf515 100644 --- a/crates/lean_vm/tests/test_lean_vm.rs +++ b/crates/lean_vm/tests/test_lean_vm.rs @@ -1,3 +1,4 @@ +use lean_vm::error::ExecutionResult; use lean_vm::*; use p3_field::BasedVectorSpace; use p3_field::PrimeCharacteristicRing; From b5729fa0031a40e81f28ec2b59db875f55187551 Mon Sep 17 00:00:00 2001 From: Thomas Coratger Date: Tue, 23 Sep 2025 18:18:41 +0200 Subject: [PATCH 05/10] fmt --- crates/lean_vm/src/core/constants.rs | 2 +- crates/lean_vm/src/core/mod.rs | 2 +- crates/lean_vm/src/core/types.rs | 2 +- crates/lean_vm/src/diagnostics/error.rs | 5 +++-- crates/lean_vm/src/execution/memory.rs | 4 ++-- crates/lean_vm/src/execution/mod.rs | 2 +- crates/lean_vm/src/isa/bytecode.rs | 4 ++-- crates/lean_vm/src/isa/mod.rs | 6 +++--- crates/lean_vm/src/isa/operands/mod.rs | 4 ++-- crates/lean_vm/src/isa/operation.rs | 2 +- crates/lean_vm/src/witness/multilinear_eval.rs | 2 +- crates/lean_vm/src/witness/poseidon16.rs | 2 +- crates/lean_vm/src/witness/poseidon24.rs | 2 +- 13 files changed, 20 insertions(+), 19 deletions(-) diff --git a/crates/lean_vm/src/core/constants.rs b/crates/lean_vm/src/core/constants.rs index d0521a3b8..dd32aaea1 100644 --- a/crates/lean_vm/src/core/constants.rs +++ b/crates/lean_vm/src/core/constants.rs @@ -25,4 +25,4 @@ pub const POSEIDON_24_NULL_HASH_PTR: usize = 5; pub const PUBLIC_INPUT_START: usize = 6 * 8; /// Maximum memory size for VM runner -pub const MAX_RUNNER_MEMORY_SIZE: usize = 1 << 20; \ No newline at end of file +pub const MAX_RUNNER_MEMORY_SIZE: usize = 1 << 20; diff --git a/crates/lean_vm/src/core/mod.rs b/crates/lean_vm/src/core/mod.rs index 63ecf62f1..5b48f76cb 100644 --- a/crates/lean_vm/src/core/mod.rs +++ b/crates/lean_vm/src/core/mod.rs @@ -4,4 +4,4 @@ pub mod constants; pub mod types; pub use constants::*; -pub use types::*; \ No newline at end of file +pub use types::*; diff --git a/crates/lean_vm/src/core/types.rs b/crates/lean_vm/src/core/types.rs index 5e8f8a873..6055260a1 100644 --- a/crates/lean_vm/src/core/types.rs +++ b/crates/lean_vm/src/core/types.rs @@ -9,4 +9,4 @@ pub type F = KoalaBear; pub type EF = QuinticExtensionFieldKB; /// Location in source code for debugging -pub type LocationInSourceCode = usize; \ No newline at end of file +pub type LocationInSourceCode = usize; diff --git a/crates/lean_vm/src/diagnostics/error.rs b/crates/lean_vm/src/diagnostics/error.rs index b36ed9cb3..863413d67 100644 --- a/crates/lean_vm/src/diagnostics/error.rs +++ b/crates/lean_vm/src/diagnostics/error.rs @@ -2,7 +2,9 @@ use crate::core::F; use crate::execution::Memory; -use crate::witness::{WitnessDotProduct, WitnessMultilinearEval, WitnessPoseidon16, WitnessPoseidon24}; +use crate::witness::{ + WitnessDotProduct, WitnessMultilinearEval, WitnessPoseidon16, WitnessPoseidon24, +}; use thiserror::Error; /// Errors that can occur during VM execution @@ -56,4 +58,3 @@ impl ExecutionResult { true } } - diff --git a/crates/lean_vm/src/execution/memory.rs b/crates/lean_vm/src/execution/memory.rs index f6e7098f1..7d408799a 100644 --- a/crates/lean_vm/src/execution/memory.rs +++ b/crates/lean_vm/src/execution/memory.rs @@ -1,6 +1,6 @@ //! Memory management for the VM -use crate::core::{EF, F, DIMENSION, VECTOR_LEN, MAX_RUNNER_MEMORY_SIZE}; +use crate::core::{DIMENSION, EF, F, MAX_RUNNER_MEMORY_SIZE, VECTOR_LEN}; use crate::diagnostics::RunnerError; use p3_field::{BasedVectorSpace, PrimeCharacteristicRing}; use rayon::prelude::*; @@ -156,4 +156,4 @@ impl Memory { pub fn slice(&self, start: usize, len: usize) -> Result, RunnerError> { (0..len).map(|i| self.get(start + i)).collect() } -} \ No newline at end of file +} diff --git a/crates/lean_vm/src/execution/mod.rs b/crates/lean_vm/src/execution/mod.rs index 60b772f63..7837c3bcb 100644 --- a/crates/lean_vm/src/execution/mod.rs +++ b/crates/lean_vm/src/execution/mod.rs @@ -6,4 +6,4 @@ pub mod runner; pub use context::*; pub use memory::*; -pub use runner::*; \ No newline at end of file +pub use runner::*; diff --git a/crates/lean_vm/src/isa/bytecode.rs b/crates/lean_vm/src/isa/bytecode.rs index 60d69c78b..d3b283f24 100644 --- a/crates/lean_vm/src/isa/bytecode.rs +++ b/crates/lean_vm/src/isa/bytecode.rs @@ -1,7 +1,7 @@ //! Bytecode representation and management -use super::operands::Hint; use super::Instruction; +use super::operands::Hint; use std::collections::BTreeMap; use std::fmt::{Display, Formatter}; @@ -56,4 +56,4 @@ impl Display for Bytecode { } Ok(()) } -} \ No newline at end of file +} diff --git a/crates/lean_vm/src/isa/mod.rs b/crates/lean_vm/src/isa/mod.rs index e372c9db9..a3b103aff 100644 --- a/crates/lean_vm/src/isa/mod.rs +++ b/crates/lean_vm/src/isa/mod.rs @@ -2,13 +2,13 @@ pub mod bytecode; pub mod instruction; -pub mod operation; pub mod operands; +pub mod operation; pub use bytecode::*; pub use instruction::*; -pub use operation::*; pub use operands::*; +pub use operation::*; /// String label for bytecode locations -pub type Label = String; \ No newline at end of file +pub type Label = String; diff --git a/crates/lean_vm/src/isa/operands/mod.rs b/crates/lean_vm/src/isa/operands/mod.rs index 58a204e48..d093072e9 100644 --- a/crates/lean_vm/src/isa/operands/mod.rs +++ b/crates/lean_vm/src/isa/operands/mod.rs @@ -1,11 +1,11 @@ //! VM operand types and addressing modes +pub mod hint; pub mod mem_or_constant; pub mod mem_or_fp; pub mod mem_or_fp_or_constant; -pub mod hint; +pub use hint::*; pub use mem_or_constant::*; pub use mem_or_fp::*; pub use mem_or_fp_or_constant::*; -pub use hint::*; \ No newline at end of file diff --git a/crates/lean_vm/src/isa/operation.rs b/crates/lean_vm/src/isa/operation.rs index 9afd345ff..70271ad55 100644 --- a/crates/lean_vm/src/isa/operation.rs +++ b/crates/lean_vm/src/isa/operation.rs @@ -42,4 +42,4 @@ impl Display for Operation { Self::Mul => write!(f, "x"), } } -} \ No newline at end of file +} diff --git a/crates/lean_vm/src/witness/multilinear_eval.rs b/crates/lean_vm/src/witness/multilinear_eval.rs index 88683c4af..c8aadada4 100644 --- a/crates/lean_vm/src/witness/multilinear_eval.rs +++ b/crates/lean_vm/src/witness/multilinear_eval.rs @@ -76,4 +76,4 @@ impl WitnessMultilinearEval { pub fn new(cycle: usize, inner: RowMultilinearEval) -> Self { Self { cycle, inner } } -} \ No newline at end of file +} diff --git a/crates/lean_vm/src/witness/poseidon16.rs b/crates/lean_vm/src/witness/poseidon16.rs index 250f5b61a..1b3ca3dea 100644 --- a/crates/lean_vm/src/witness/poseidon16.rs +++ b/crates/lean_vm/src/witness/poseidon16.rs @@ -70,4 +70,4 @@ impl WitnessPoseidon16 { F::from_usize(self.addr_output), ] } -} \ No newline at end of file +} diff --git a/crates/lean_vm/src/witness/poseidon24.rs b/crates/lean_vm/src/witness/poseidon24.rs index 670ead746..0cc2c565f 100644 --- a/crates/lean_vm/src/witness/poseidon24.rs +++ b/crates/lean_vm/src/witness/poseidon24.rs @@ -72,4 +72,4 @@ impl WitnessPoseidon24 { F::from_usize(self.addr_output), ] } -} \ No newline at end of file +} From 7dc974dc332842d3824121587cfa8264aa654de5 Mon Sep 17 00:00:00 2001 From: Thomas Coratger Date: Tue, 23 Sep 2025 18:34:43 +0200 Subject: [PATCH 06/10] add memory tests --- crates/lean_vm/src/execution/memory.rs | 117 ++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 1 deletion(-) diff --git a/crates/lean_vm/src/execution/memory.rs b/crates/lean_vm/src/execution/memory.rs index 7d408799a..2fbc3dd12 100644 --- a/crates/lean_vm/src/execution/memory.rs +++ b/crates/lean_vm/src/execution/memory.rs @@ -69,7 +69,7 @@ impl Memory { } /// Gets the current size of allocated memory - pub fn size(&self) -> usize { + pub const fn size(&self) -> usize { self.0.len() } @@ -157,3 +157,118 @@ impl Memory { (0..len).map(|i| self.get(start + i)).collect() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_basic_memory_operations() { + let mut memory = Memory::empty(); + + // Test setting and getting values + memory.set(0, F::ONE).unwrap(); + memory.set(5, F::from_usize(42)).unwrap(); + + assert_eq!(memory.get(0).unwrap(), F::ONE); + assert_eq!(memory.get(5).unwrap(), F::from_usize(42)); + + // Test undefined memory access + assert!(matches!(memory.get(1), Err(RunnerError::UndefinedMemory))); + } + + #[test] + fn test_memory_already_set_error() { + let mut memory = Memory::empty(); + + memory.set(0, F::ONE).unwrap(); + // Setting same value should work + memory.set(0, F::ONE).unwrap(); + + // Setting different value should fail + assert!(matches!( + memory.set(0, F::ZERO), + Err(RunnerError::MemoryAlreadySet) + )); + } + + #[test] + fn test_memory_slices() { + let mut memory = Memory::empty(); + let values = vec![F::ONE, F::ZERO, F::from_usize(42), F::from_usize(100)]; + + memory.set_slice(10, &values).unwrap(); + let retrieved = memory.get_slice(10, 4).unwrap(); + + assert_eq!(retrieved, values); + } + + #[test] + fn test_memory_initialization_and_utilities() { + let public_data = vec![F::ONE, F::ZERO, F::from_usize(123)]; + let memory = Memory::new(public_data.clone()); + + // All public data should be initialized + for (i, expected) in public_data.iter().enumerate() { + assert!(memory.is_initialized(i)); + assert_eq!(memory.get(i).unwrap(), *expected); + } + + assert_eq!(memory.size(), 3); + + let entries = memory.initialized_entries(); + assert_eq!(entries.len(), 3); + assert_eq!(entries[0], (0, F::ONE)); + assert_eq!(entries[2], (2, F::from_usize(123))); + } + + #[test] + fn test_vectorized_operations() { + let mut memory = Memory::empty(); + let vector = [F::ONE; VECTOR_LEN]; + + memory.set_vector(0, vector).unwrap(); + let retrieved = memory.get_vector(0).unwrap(); + + assert_eq!(retrieved, vector); + + // Test vectorized slice + let slice_data = vec![F::from_usize(1); 2 * VECTOR_LEN]; + memory.set_vectorized_slice(1, &slice_data).unwrap(); + let retrieved_slice = memory.get_vectorized_slice(1, 2).unwrap(); + + assert_eq!(retrieved_slice, slice_data); + } + + #[test] + fn test_extension_field_operations() { + let mut memory = Memory::empty(); + + // Create a simple extension field element with proper dimension + let mut coeffs = [F::ZERO; DIMENSION]; + coeffs[0] = F::ONE; + let ef_value = EF::from_basis_coefficients_slice(&coeffs).unwrap(); + + memory.set_ef_element(0, ef_value).unwrap(); + let retrieved = memory.get_ef_element(0).unwrap(); + + assert_eq!(retrieved, ef_value); + + // Test continuous slice of EF elements + memory.set_ef_element(DIMENSION, ef_value).unwrap(); + let ef_slice = memory.get_continuous_slice_of_ef_elements(0, 2).unwrap(); + + assert_eq!(ef_slice.len(), 2); + assert_eq!(ef_slice[0], ef_value); + assert_eq!(ef_slice[1], ef_value); + } + + #[test] + fn test_memory_clear() { + let mut memory = Memory::new(vec![F::ONE, F::ZERO]); + assert_eq!(memory.size(), 2); + + memory.clear(); + assert_eq!(memory.size(), 0); + } +} From 128076d9f2c40b41bad406179c03adc4c318fe0c Mon Sep 17 00:00:00 2001 From: Thomas Coratger Date: Tue, 23 Sep 2025 18:40:08 +0200 Subject: [PATCH 07/10] add update_call_counters --- crates/lean_vm/src/diagnostics/error.rs | 4 +++- crates/lean_vm/src/execution/context.rs | 4 ++-- crates/lean_vm/src/execution/runner.rs | 15 +++++++-------- crates/lean_vm/src/isa/instruction.rs | 17 +++++++++++++++++ 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/crates/lean_vm/src/diagnostics/error.rs b/crates/lean_vm/src/diagnostics/error.rs index 863413d67..79997dd10 100644 --- a/crates/lean_vm/src/diagnostics/error.rs +++ b/crates/lean_vm/src/diagnostics/error.rs @@ -53,7 +53,9 @@ pub struct ExecutionResult { } impl ExecutionResult { - /// Check if execution was successful (always true for this version) + /// Check if execution was successful + /// + /// TODO: placeholder for now. pub fn is_success(&self) -> bool { true } diff --git a/crates/lean_vm/src/execution/context.rs b/crates/lean_vm/src/execution/context.rs index dc7dc43a9..8c7f410b9 100644 --- a/crates/lean_vm/src/execution/context.rs +++ b/crates/lean_vm/src/execution/context.rs @@ -24,11 +24,11 @@ impl ExecutionHistory { self.cycles.iter().sum() } - pub fn len(&self) -> usize { + pub const fn len(&self) -> usize { self.lines.len() } - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.lines.is_empty() } } diff --git a/crates/lean_vm/src/execution/runner.rs b/crates/lean_vm/src/execution/runner.rs index 68199bd7c..af93e61df 100644 --- a/crates/lean_vm/src/execution/runner.rs +++ b/crates/lean_vm/src/execution/runner.rs @@ -8,7 +8,7 @@ use crate::diagnostics::{ExecutionResult, RunnerError}; use crate::execution::{ExecutionHistory, Memory}; use crate::isa::instruction::InstructionContext; use crate::isa::operands::hint::HintExecutionContext; -use crate::isa::{Bytecode, Instruction}; +use crate::isa::Bytecode; use crate::witness::{ WitnessDotProduct, WitnessMultilinearEval, WitnessPoseidon16, WitnessPoseidon24, }; @@ -230,13 +230,12 @@ fn execute_bytecode_helper( instruction.execute_instruction(&mut instruction_ctx)?; // Update call counters based on instruction type - match instruction { - Instruction::Poseidon2_16 { .. } => poseidon16_calls += 1, - Instruction::Poseidon2_24 { .. } => poseidon24_calls += 1, - Instruction::DotProductExtensionExtension { .. } => dot_product_ext_ext_calls += 1, - Instruction::MultilinearEval { .. } => multilinear_eval_calls += 1, - _ => {} - } + instruction.update_call_counters( + &mut poseidon16_calls, + &mut poseidon24_calls, + &mut dot_product_ext_ext_calls, + &mut multilinear_eval_calls, + ); } debug_assert_eq!(pc, bytecode.ending_pc); diff --git a/crates/lean_vm/src/isa/instruction.rs b/crates/lean_vm/src/isa/instruction.rs index 8a8453a2a..27ce3ce1f 100644 --- a/crates/lean_vm/src/isa/instruction.rs +++ b/crates/lean_vm/src/isa/instruction.rs @@ -378,6 +378,23 @@ impl Instruction { } } } + + /// Update call counters based on instruction type + pub fn update_call_counters( + &self, + poseidon16_calls: &mut usize, + poseidon24_calls: &mut usize, + dot_product_ext_ext_calls: &mut usize, + multilinear_eval_calls: &mut usize, + ) { + match self { + Self::Poseidon2_16 { .. } => *poseidon16_calls += 1, + Self::Poseidon2_24 { .. } => *poseidon24_calls += 1, + Self::DotProductExtensionExtension { .. } => *dot_product_ext_ext_calls += 1, + Self::MultilinearEval { .. } => *multilinear_eval_calls += 1, + _ => {} + } + } } impl Display for Instruction { From 2dc099aa6f51d0957a114e434eb5a4e4eea63c68 Mon Sep 17 00:00:00 2001 From: Thomas Coratger Date: Tue, 23 Sep 2025 19:02:26 +0200 Subject: [PATCH 08/10] make some functions constants --- crates/lean_vm/src/witness/dot_product.rs | 2 +- crates/lean_vm/src/witness/multilinear_eval.rs | 4 ++-- crates/lean_vm/src/witness/poseidon16.rs | 2 +- crates/lean_vm/src/witness/poseidon24.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/lean_vm/src/witness/dot_product.rs b/crates/lean_vm/src/witness/dot_product.rs index f1eff87c2..359409907 100644 --- a/crates/lean_vm/src/witness/dot_product.rs +++ b/crates/lean_vm/src/witness/dot_product.rs @@ -30,7 +30,7 @@ pub struct WitnessDotProduct { impl WitnessDotProduct { /// Create a new dot product witness with all required data #[allow(clippy::too_many_arguments)] - pub fn new( + pub const fn new( cycle: usize, addr_0: usize, addr_1: usize, diff --git a/crates/lean_vm/src/witness/multilinear_eval.rs b/crates/lean_vm/src/witness/multilinear_eval.rs index c8aadada4..911a43081 100644 --- a/crates/lean_vm/src/witness/multilinear_eval.rs +++ b/crates/lean_vm/src/witness/multilinear_eval.rs @@ -42,7 +42,7 @@ impl RowMultilinearEval { /// Get the number of variables in this multilinear polynomial /// /// This is determined by the length of the evaluation point vector - pub fn n_vars(&self) -> usize { + pub const fn n_vars(&self) -> usize { self.point.len() } @@ -73,7 +73,7 @@ pub struct WitnessMultilinearEval { impl WitnessMultilinearEval { /// Create a new multilinear evaluation witness - pub fn new(cycle: usize, inner: RowMultilinearEval) -> Self { + pub const fn new(cycle: usize, inner: RowMultilinearEval) -> Self { Self { cycle, inner } } } diff --git a/crates/lean_vm/src/witness/poseidon16.rs b/crates/lean_vm/src/witness/poseidon16.rs index 1b3ca3dea..4861ad5d8 100644 --- a/crates/lean_vm/src/witness/poseidon16.rs +++ b/crates/lean_vm/src/witness/poseidon16.rs @@ -27,7 +27,7 @@ pub struct WitnessPoseidon16 { impl WitnessPoseidon16 { /// Create a new Poseidon16 witness with all hash data - pub fn new( + pub const fn new( cycle: Option, addr_input_a: usize, addr_input_b: usize, diff --git a/crates/lean_vm/src/witness/poseidon24.rs b/crates/lean_vm/src/witness/poseidon24.rs index 0cc2c565f..ec970e32e 100644 --- a/crates/lean_vm/src/witness/poseidon24.rs +++ b/crates/lean_vm/src/witness/poseidon24.rs @@ -27,7 +27,7 @@ pub struct WitnessPoseidon24 { impl WitnessPoseidon24 { /// Create a new Poseidon24 witness with all hash data - pub fn new( + pub const fn new( cycle: Option, addr_input_a: usize, addr_input_b: usize, From 52e14c363b198f00b748023556507ab3f9a29f40 Mon Sep 17 00:00:00 2001 From: Thomas Coratger Date: Tue, 23 Sep 2025 19:02:46 +0200 Subject: [PATCH 09/10] fmt --- crates/lean_vm/src/execution/runner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lean_vm/src/execution/runner.rs b/crates/lean_vm/src/execution/runner.rs index af93e61df..a0984835d 100644 --- a/crates/lean_vm/src/execution/runner.rs +++ b/crates/lean_vm/src/execution/runner.rs @@ -6,9 +6,9 @@ use crate::core::{ }; use crate::diagnostics::{ExecutionResult, RunnerError}; use crate::execution::{ExecutionHistory, Memory}; +use crate::isa::Bytecode; use crate::isa::instruction::InstructionContext; use crate::isa::operands::hint::HintExecutionContext; -use crate::isa::Bytecode; use crate::witness::{ WitnessDotProduct, WitnessMultilinearEval, WitnessPoseidon16, WitnessPoseidon24, }; From 97bd0010cc7b4a0e98c230d19dd351cd146be99b Mon Sep 17 00:00:00 2001 From: Thomas Coratger Date: Tue, 23 Sep 2025 19:10:41 +0200 Subject: [PATCH 10/10] more constant values --- crates/lean_vm/src/isa/bytecode.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/lean_vm/src/isa/bytecode.rs b/crates/lean_vm/src/isa/bytecode.rs index d3b283f24..bf128a11a 100644 --- a/crates/lean_vm/src/isa/bytecode.rs +++ b/crates/lean_vm/src/isa/bytecode.rs @@ -36,12 +36,12 @@ impl Bytecode { } /// Get the number of instructions - pub fn len(&self) -> usize { + pub const fn len(&self) -> usize { self.instructions.len() } /// Check if bytecode is empty - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.instructions.is_empty() } }