diff --git a/.github/workflows/build-cache.yml b/.github/workflows/build-cache.yml index 7808efdc9c..755d6d7806 100644 --- a/.github/workflows/build-cache.yml +++ b/.github/workflows/build-cache.yml @@ -22,8 +22,8 @@ jobs: run: git rev-parse HEAD > target/cache-commit-hash.txt ##### The block below is shared between cache build and PR build workflows ##### - - name: Install Rust toolchain nightly-2025-05-14 (with clippy and rustfmt) - run: rustup toolchain install nightly-2025-05-14 --component clippy,rustfmt + - name: Install Rust toolchain nightly-2025-10-01 (with clippy and rustfmt) + run: rustup toolchain install nightly-2025-10-01 --component clippy,rustfmt - name: Install Rust toolchain run: rustup toolchain install nightly-2025-02-14 --component rust-src - name: Install Rust toolchain 1.88 (stable) diff --git a/.github/workflows/nightly-tests.yml b/.github/workflows/nightly-tests.yml index 4195612750..05f854ddcc 100644 --- a/.github/workflows/nightly-tests.yml +++ b/.github/workflows/nightly-tests.yml @@ -43,11 +43,11 @@ jobs: - name: Install Rust toolchain 1.88 run: rustup toolchain install 1.88 - name: Install nightly - run: rustup toolchain install nightly-2025-05-14 --component rust-src + run: rustup toolchain install nightly-2025-10-01 --component rust-src - name: Install nightly run: rustup toolchain install nightly-2025-02-14 --component rust-src - name: Install riscv target - run: rustup target add riscv32imac-unknown-none-elf --toolchain nightly-2025-05-14 + run: rustup target add riscv32imac-unknown-none-elf --toolchain nightly-2025-10-01 - name: Install test dependencies run: sudo apt-get update && sudo apt-get install -y binutils-riscv64-unknown-elf lld - name: Build @@ -77,12 +77,12 @@ jobs: target/ Cargo.lock key: ${{ runner.os }}-cargo-pr-tests - - name: Install Rust toolchain nightly-2025-05-14 (with clippy and rustfmt) - run: rustup toolchain install nightly-2025-05-14 --component clippy,rustfmt,rust-src + - name: Install Rust toolchain nightly-2025-10-01 (with clippy and rustfmt) + run: rustup toolchain install nightly-2025-10-01 --component clippy,rustfmt,rust-src - name: Install Rust toolchain 1.88 run: rustup toolchain install 1.88 - name: Install riscv target - run: rustup target add riscv32imac-unknown-none-elf --toolchain nightly-2025-05-14 + run: rustup target add riscv32imac-unknown-none-elf --toolchain nightly-2025-10-01 - name: Install test dependencies run: sudo apt-get update && sudo apt-get install -y binutils-riscv64-unknown-elf lld - name: Run benchmarks @@ -122,10 +122,9 @@ jobs: - name: Install cargo openvm # Rust 1.88 is needed by fresher versions of dependencies of cargo-openvm. - # For now we point to the powdr-labs/openvm commit that switches to Rust 1.88. run: | rustup toolchain install 1.88 - cargo +1.88 install --git 'http://github.com/powdr-labs/openvm.git' --rev fbd69da cargo-openvm + cargo +1.88 install --git 'http://github.com/openvm-org/openvm.git' cargo-openvm - name: Setup python venv run: | @@ -148,7 +147,7 @@ jobs: uses: actions/checkout@v4 with: repository: powdr-labs/openvm-reth-benchmark - ref: main + ref: 9641a47c70e4003e54a96947d4138c63825f1b12 path: openvm-reth-benchmark - name: Patch openvm-reth-benchmark to use local powdr diff --git a/.github/workflows/post-merge-tests.yml b/.github/workflows/post-merge-tests.yml index 3e46900456..18e620855a 100644 --- a/.github/workflows/post-merge-tests.yml +++ b/.github/workflows/post-merge-tests.yml @@ -38,10 +38,9 @@ jobs: - name: Install cargo openvm # Rust 1.88 is needed by fresher versions of dependencies of cargo-openvm. - # For now we point to the powdr-labs/openvm commit that switches to Rust 1.88. run: | rustup toolchain install 1.88 - cargo +1.88 install --git 'http://github.com/powdr-labs/openvm.git' --rev fbd69da cargo-openvm + cargo +1.88 install --git 'http://github.com/openvm-org/openvm.git' cargo-openvm - name: Run keccak with 100 APCs run: /usr/bin/time -v cargo run --bin powdr_openvm -r prove guest-keccak --input 10000 --autoprecompiles 100 --recursion @@ -56,7 +55,7 @@ jobs: uses: actions/checkout@v4 with: repository: powdr-labs/openvm-reth-benchmark - ref: main + ref: 9641a47c70e4003e54a96947d4138c63825f1b12 path: openvm-reth-benchmark - name: Patch openvm-reth-benchmark to use local powdr diff --git a/.github/workflows/pr-tests.yml b/.github/workflows/pr-tests.yml index 7bea1d811c..2e854e8041 100644 --- a/.github/workflows/pr-tests.yml +++ b/.github/workflows/pr-tests.yml @@ -44,8 +44,8 @@ jobs: run: git checkout "$(cat target/cache-commit-hash.txt || echo 'f02fd626e2bb9e46a22ea1cda96b4feb5c6bda43')" && git ls-files -z | xargs -0 -n1 touch -d "Fri, 18 Apr 2025 03:30:58 +0000" && git checkout HEAD@{1} ##### The block below is shared between cache build and PR build workflows ##### - - name: Install Rust toolchain nightly-2025-05-14 (with clippy and rustfmt) - run: rustup toolchain install nightly-2025-05-14 --component clippy,rustfmt + - name: Install Rust toolchain nightly-2025-10-01 (with clippy and rustfmt) + run: rustup toolchain install nightly-2025-10-01 --component clippy,rustfmt - name: Install Rust toolchain 1.88 (stable) run: rustup toolchain install 1.88 - name: Set cargo to perform shallow clones @@ -97,11 +97,11 @@ jobs: - name: Install Rust toolchain nightly-2025-05-14 (with clippy and rustfmt) run: rustup toolchain install nightly-2025-05-14 --component clippy,rustfmt,rust-src - name: Install riscv target - run: rustup target add riscv32imac-unknown-none-elf --toolchain nightly-2025-05-14 + run: rustup target add riscv32imac-unknown-none-elf --toolchain nightly-2025-10-01 - name: Install test dependencies run: sudo apt-get update && sudo apt-get install -y binutils-riscv64-unknown-elf lld - name: Install Rust deps - run: rustup install nightly-2025-05-14 --component rust-src + run: rustup install nightly-2025-10-01 --component rust-src - name: Install Rust deps run: rustup install nightly-2025-02-14 --component rust-src - uses: taiki-e/install-action@nextest @@ -146,16 +146,15 @@ jobs: - name: Install cargo openvm # Rust 1.88 is needed by fresher versions of dependencies of cargo-openvm. - # For now we point to the powdr-labs/openvm commit that switches to Rust 1.88. run: | rustup toolchain install 1.88 - cargo +1.88 install --git 'http://github.com/powdr-labs/openvm.git' --rev fbd69da cargo-openvm + cargo +1.88 install --git 'http://github.com/openvm-org/openvm.git' cargo-openvm - name: Checkout openvm-reth-benchmark uses: actions/checkout@v4 with: repository: powdr-labs/openvm-reth-benchmark - ref: main + ref: 9641a47c70e4003e54a96947d4138c63825f1b12 path: openvm-reth-benchmark - name: Patch openvm-reth-benchmark to use local powdr diff --git a/Cargo.toml b/Cargo.toml index 3dca51b161..cbd83266f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,52 +45,50 @@ powdr-openvm-hints-transpiler = { path = "./openvm/extensions/hints-transpiler", powdr-openvm-hints-circuit = { path = "./openvm/extensions/hints-circuit", version = "0.1.4" } # openvm -openvm = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-build = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-rv32im-circuit = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-rv32im-transpiler = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-rv32im-guest = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26", default-features = false } -openvm-transpiler = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-circuit = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-circuit-derive = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-circuit-primitives = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-circuit-primitives-derive = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-instructions = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-instructions-derive = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-sdk = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26", default-features = false, features = [ +openvm = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-build = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-rv32im-circuit = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-rv32im-transpiler = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-rv32im-guest = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a", default-features = false } +openvm-transpiler = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-circuit = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-circuit-derive = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-circuit-primitives = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-circuit-primitives-derive = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-instructions = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-instructions-derive = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-sdk = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a", default-features = false, features = [ "parallel", "jemalloc", "nightly-features", - "bench-metrics", + "evm-prove", ] } -openvm-ecc-circuit = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-ecc-transpiler = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-keccak256-circuit = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-keccak256-transpiler = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-sha256-circuit = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-sha256-transpiler = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-algebra-circuit = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-algebra-transpiler = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-bigint-circuit = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-bigint-transpiler = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-pairing-circuit = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-pairing-transpiler = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-native-circuit = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26", default-features = false } -openvm-native-recursion = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26", default-features = false } -openvm-platform = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -openvm-custom-insn = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } +openvm-ecc-circuit = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-ecc-transpiler = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-keccak256-circuit = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-keccak256-transpiler = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-sha256-circuit = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-sha256-transpiler = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-algebra-circuit = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-algebra-transpiler = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-bigint-circuit = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-bigint-transpiler = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-pairing-circuit = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-pairing-transpiler = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-native-circuit = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a", default-features = false } +openvm-native-recursion = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a", default-features = false } +openvm-platform = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-custom-insn = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } # stark-backend -openvm-stark-sdk = { git = "https://github.com/powdr-labs/stark-backend.git", rev = "ee4e22b", default-features = false, features = [ +openvm-stark-sdk = { git = "https://github.com/powdr-labs/stark-backend.git", rev = "cd36ceb", default-features = false, features = [ "parallel", "jemalloc", "nightly-features", - "bench-metrics", ] } -openvm-stark-backend = { git = "https://github.com/powdr-labs/stark-backend.git", rev = "ee4e22b", default-features = false, features = [ +openvm-stark-backend = { git = "https://github.com/powdr-labs/stark-backend.git", rev = "cd36ceb", default-features = false, features = [ "parallel", "jemalloc", - "bench-metrics", ] } [profile.pr-tests] @@ -146,3 +144,5 @@ iter_over_hash_type = "deny" # openvm-pairing-transpiler = { path = "../openvm/extensions/pairing/transpiler" } # openvm-native-circuit = { path = "../openvm/extensions/native/circuit" } # openvm-native-recursion = { path = "../openvm/extensions/native/recursion" } +# openvm-platform = { path = "../openvm/crates/toolchain/platform" } +# openvm-custom-insn = { path = "../openvm/crates/toolchain/custom_insn" } \ No newline at end of file diff --git a/cli-openvm/Cargo.toml b/cli-openvm/Cargo.toml index be0c1d72c5..fdb7d628fc 100644 --- a/cli-openvm/Cargo.toml +++ b/cli-openvm/Cargo.toml @@ -6,6 +6,10 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[features] +default = ["metrics"] +metrics = ["powdr-openvm/metrics", "openvm-sdk/metrics", "openvm-stark-backend/metrics", "openvm-stark-sdk/metrics"] + [[bin]] name = "powdr_openvm" path = "src/main.rs" diff --git a/number/src/serialize.rs b/number/src/serialize.rs index 18c386f2aa..49979026aa 100644 --- a/number/src/serialize.rs +++ b/number/src/serialize.rs @@ -11,19 +11,14 @@ use serde_with::{DeserializeAs, SerializeAs}; use crate::FieldElement; -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] pub enum CsvRenderMode { SignedBase10, UnsignedBase10, + #[default] Hex, } -impl Default for CsvRenderMode { - fn default() -> Self { - Self::Hex - } -} - const ROW_NAME: &str = "Row"; pub fn write_polys_csv_file( diff --git a/openvm/Cargo.toml b/openvm/Cargo.toml index 4df0010f46..1b85410f1d 100644 --- a/openvm/Cargo.toml +++ b/openvm/Cargo.toml @@ -6,6 +6,11 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[features] +default = [] +tco = ["openvm-sdk/tco"] +metrics = ["openvm-sdk/metrics", "openvm-stark-backend/metrics", "openvm-stark-sdk/metrics"] + [dependencies] openvm.workspace = true openvm-build.workspace = true @@ -80,6 +85,7 @@ openvm-pairing-transpiler.workspace = true serde_cbor = "0.11.2" expect-test = "1.5.1" criterion = { version = "0.4", features = ["html_reports"] } +tracing-log = "0.2.0" [lib] bench = false # See https://github.com/bheisler/criterion.rs/issues/458 diff --git a/openvm/extensions/hints-circuit/Cargo.toml b/openvm/extensions/hints-circuit/Cargo.toml index e3da7e2c0c..693651ce3e 100644 --- a/openvm/extensions/hints-circuit/Cargo.toml +++ b/openvm/extensions/hints-circuit/Cargo.toml @@ -16,3 +16,6 @@ powdr-openvm-hints-transpiler = { workspace = true } eyre = "0.6.12" crypto-bigint = "0.6.1" elliptic-curve = "0.13.8" +rand = { version = "0.8.5", default-features = false } +serde = "1.0.219" + diff --git a/openvm/extensions/hints-circuit/src/executors.rs b/openvm/extensions/hints-circuit/src/executors.rs index d3832fd862..7f351ba21f 100644 --- a/openvm/extensions/hints-circuit/src/executors.rs +++ b/openvm/extensions/hints-circuit/src/executors.rs @@ -1,9 +1,10 @@ use openvm_circuit::arch::{PhantomSubExecutor, Streams}; -use openvm_circuit::system::memory::MemoryController; +use openvm_circuit::system::memory::online::GuestMemory; use openvm_instructions::riscv::RV32_MEMORY_AS; use openvm_instructions::PhantomDiscriminant; -use openvm_rv32im_circuit::adapters::unsafe_read_rv32_register; +use openvm_rv32im_circuit::adapters::read_rv32_register; use openvm_stark_backend::p3_field::PrimeField32; +use rand::rngs::StdRng; use crate::field10x26_k256; @@ -13,24 +14,26 @@ pub struct ReverseBytesSubEx; impl PhantomSubExecutor for ReverseBytesSubEx { fn phantom_execute( - &mut self, - memory: &MemoryController, + &self, + memory: &GuestMemory, streams: &mut Streams, - _discriminant: PhantomDiscriminant, - a: F, - _b: F, + _: &mut StdRng, + _: PhantomDiscriminant, + a: u32, + _: u32, c_upper: u16, ) -> eyre::Result<()> { assert_eq!(c_upper, 0); // read register - let rs1 = unsafe_read_rv32_register(memory, a); + let rs1 = read_rv32_register(memory, a); // read memory - let bytes = memory.unsafe_read::<4>( - F::from_canonical_u32(RV32_MEMORY_AS), - F::from_canonical_u32(rs1), - ); + let bytes = unsafe { memory.read::(RV32_MEMORY_AS, rs1) }; // write hint as bytes in reverse - let hint_bytes = bytes.into_iter().rev().collect(); + let hint_bytes = bytes + .into_iter() + .rev() + .map(|b| F::from_canonical_u8(b)) + .collect(); streams.hint_stream = hint_bytes; Ok(()) } @@ -55,28 +58,20 @@ impl_modulus!( impl PhantomSubExecutor for K256InverseFieldSubEx { fn phantom_execute( - &mut self, - memory: &MemoryController, + &self, + memory: &GuestMemory, streams: &mut Streams, + _: &mut StdRng, _: PhantomDiscriminant, - a: F, - _b: F, + a: u32, + _: u32, c_upper: u16, ) -> eyre::Result<()> { assert_eq!(c_upper, 0); // read register - let rs1 = unsafe_read_rv32_register(memory, a); + let rs1 = read_rv32_register(memory, a); // read the field element - let bytes: [u8; 32] = memory - .unsafe_read::<32>( - F::from_canonical_u32(RV32_MEMORY_AS), - F::from_canonical_u32(rs1), - ) - .into_iter() - .map(|f| u8::try_from(f.as_canonical_u32()).expect("value not a byte")) - .collect::>() - .try_into() - .unwrap(); + let bytes: [u8; 32] = unsafe { memory.read::(RV32_MEMORY_AS, rs1) }; let n = U256::from_be_bytes(bytes); @@ -108,28 +103,22 @@ pub struct K256InverseField10x26SubEx; impl PhantomSubExecutor for K256InverseField10x26SubEx { fn phantom_execute( - &mut self, - memory: &MemoryController, + &self, + memory: &GuestMemory, streams: &mut Streams, + _: &mut StdRng, _: PhantomDiscriminant, - a: F, - _b: F, + a: u32, + _: u32, c_upper: u16, ) -> eyre::Result<()> { assert_eq!(c_upper, 0); // read register - let rs1 = unsafe_read_rv32_register(memory, a); + let rs1 = read_rv32_register(memory, a); // read the k256 field_10x26 as raw bytes - let bytes: [u8; FIELD10X26_BYTES] = memory - .unsafe_read::<{ FIELD10X26_BYTES }>( - F::from_canonical_u32(RV32_MEMORY_AS), - F::from_canonical_u32(rs1), - ) - .into_iter() - .map(|f| u8::try_from(f.as_canonical_u32()).expect("value not a byte")) - .collect::>() - .try_into() - .unwrap(); + let bytes: [u8; FIELD10X26_BYTES] = + unsafe { memory.read::(RV32_MEMORY_AS, rs1) }; + // we just reinterpret the bytes as a k256 field element. We don't use mem::transmute to avoid alignment issues let mut elem = [0u32; 10]; unsafe { @@ -164,28 +153,22 @@ pub struct K256SqrtField10x26SubEx; impl PhantomSubExecutor for K256SqrtField10x26SubEx { fn phantom_execute( - &mut self, - memory: &MemoryController, + &self, + memory: &GuestMemory, streams: &mut Streams, + _: &mut StdRng, _: PhantomDiscriminant, - a: F, - _b: F, + a: u32, + _: u32, c_upper: u16, ) -> eyre::Result<()> { assert_eq!(c_upper, 0); // read register - let rs1 = unsafe_read_rv32_register(memory, a); + let rs1 = read_rv32_register(memory, a); // read the k256 field_10x26 as raw bytes - let bytes: [u8; FIELD10X26_BYTES] = memory - .unsafe_read::<{ FIELD10X26_BYTES }>( - F::from_canonical_u32(RV32_MEMORY_AS), - F::from_canonical_u32(rs1), - ) - .into_iter() - .map(|f| u8::try_from(f.as_canonical_u32()).expect("value not a byte")) - .collect::>() - .try_into() - .unwrap(); + let bytes: [u8; FIELD10X26_BYTES] = + unsafe { memory.read::(RV32_MEMORY_AS, rs1) }; + // we just reinterpret the bytes as a k256 field element. Can't use mem::transmute due to alighment requirements let mut elem = [0u32; 10]; unsafe { diff --git a/openvm/extensions/hints-circuit/src/lib.rs b/openvm/extensions/hints-circuit/src/lib.rs index 7a30eed059..066d95ea3b 100644 --- a/openvm/extensions/hints-circuit/src/lib.rs +++ b/openvm/extensions/hints-circuit/src/lib.rs @@ -1,56 +1,75 @@ -use openvm_circuit::arch::{VmExtension, VmInventory}; -use openvm_circuit::circuit_derive::{Chip, ChipUsageGetter}; -use openvm_circuit::derive::{AnyEnum, InstructionExecutor}; -use openvm_circuit::system::phantom::PhantomChip; +use openvm_circuit::arch::{ + AirInventory, AirInventoryError, ChipInventory, ChipInventoryError, ExecutorInventoryBuilder, + ExecutorInventoryError, VmCircuitExtension, VmExecutionExtension, VmProverExtension, +}; +use openvm_circuit::derive::{AnyEnum, Executor, MeteredExecutor, PreflightExecutor}; +use openvm_circuit::system::phantom::PhantomExecutor; use openvm_instructions::PhantomDiscriminant; -use openvm_stark_backend::p3_field::PrimeField32; +use openvm_stark_backend::config::{StarkGenericConfig, Val}; +use openvm_stark_backend::p3_field::{Field, PrimeField32}; +use openvm_stark_sdk::engine::StarkEngine; use powdr_openvm_hints_transpiler::HintsPhantom; +use serde::{Deserialize, Serialize}; // this module is mostly copy/pasted code from k256 for the field element representation in 32-bit architectures mod executors; mod field10x26_k256; /// OpenVM extension with miscellaneous hint implementations. +#[derive(Clone, Serialize, Deserialize, Debug)] pub struct HintsExtension; -#[derive(ChipUsageGetter, Chip, InstructionExecutor, AnyEnum)] -pub enum HintsExecutor { - Phantom(PhantomChip), +#[derive(AnyEnum, PreflightExecutor, Executor, MeteredExecutor, Clone)] +pub enum HintsExtensionExecutor { + Phantom(PhantomExecutor), } -#[derive(ChipUsageGetter, Chip, AnyEnum)] -pub enum HintsPeriphery { - Phantom(PhantomChip), -} - -impl VmExtension for HintsExtension { - type Executor = HintsExecutor; - type Periphery = HintsPeriphery; +impl VmExecutionExtension for HintsExtension { + type Executor = HintsExtensionExecutor; - fn build( + fn extend_execution( &self, - builder: &mut openvm_circuit::arch::VmInventoryBuilder, - ) -> Result< - openvm_circuit::arch::VmInventory, - openvm_circuit::arch::VmInventoryError, - > { - let inventory = VmInventory::new(); - builder.add_phantom_sub_executor( + inventory: &mut ExecutorInventoryBuilder, + ) -> Result<(), ExecutorInventoryError> { + inventory.add_phantom_sub_executor( executors::ReverseBytesSubEx, PhantomDiscriminant(HintsPhantom::HintReverseBytes as u16), )?; - builder.add_phantom_sub_executor( + inventory.add_phantom_sub_executor( executors::K256InverseFieldSubEx, PhantomDiscriminant(HintsPhantom::HintK256InverseField as u16), )?; - builder.add_phantom_sub_executor( + inventory.add_phantom_sub_executor( executors::K256InverseField10x26SubEx, PhantomDiscriminant(HintsPhantom::HintK256InverseField10x26 as u16), )?; - builder.add_phantom_sub_executor( + inventory.add_phantom_sub_executor( executors::K256SqrtField10x26SubEx, PhantomDiscriminant(HintsPhantom::HintK256SqrtField10x26 as u16), )?; - Ok(inventory) + Ok(()) + } +} + +impl VmCircuitExtension for HintsExtension { + fn extend_circuit(&self, _: &mut AirInventory) -> Result<(), AirInventoryError> { + Ok(()) + } +} + +pub struct HintsCpuProverExt; + +impl VmProverExtension for HintsCpuProverExt +where + E: StarkEngine, + Val: PrimeField32, +{ + fn extend_prover( + &self, + _: &HintsExtension, + _: &mut ChipInventory, + ) -> Result<(), ChipInventoryError> { + // No chips to add for hints + Ok(()) } } diff --git a/openvm/guest-ecc-manual/Cargo.toml b/openvm/guest-ecc-manual/Cargo.toml index 18da524132..6fa4837fee 100644 --- a/openvm/guest-ecc-manual/Cargo.toml +++ b/openvm/guest-ecc-manual/Cargo.toml @@ -6,10 +6,10 @@ version = "0.0.0" edition = "2021" [dependencies] -openvm = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26" } -openvm-ecc-guest = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26", subdirectory = "extensions/ecc/guest", default-features = false } -openvm-algebra-guest = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26", subdirectory = "extensions/algebra/guest", default-features = false } -openvm-k256 = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26", subdirectory = "guest-libs/k256", package = "k256", features = [ +openvm = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-ecc-guest = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a", subdirectory = "extensions/ecc/guest", default-features = false } +openvm-algebra-guest = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a", subdirectory = "extensions/algebra/guest", default-features = false } +openvm-k256 = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a", subdirectory = "guest-libs/k256", package = "k256", features = [ "ecdsa", ]} diff --git a/openvm/guest-ecc-powdr-affine-hint/Cargo.toml b/openvm/guest-ecc-powdr-affine-hint/Cargo.toml index b7a6de2e89..f2a7085d47 100644 --- a/openvm/guest-ecc-powdr-affine-hint/Cargo.toml +++ b/openvm/guest-ecc-powdr-affine-hint/Cargo.toml @@ -5,8 +5,8 @@ version = "0.0.0" edition = "2021" [dependencies] -openvm = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -k256 = { git = "https://github.com/powdr-labs/elliptic-curves-k256", tag = "k256/powdr/v0.13.4-2025.09.10", default-features = false, features = [ +openvm = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +k256 = { git = "https://github.com/powdr-labs/elliptic-curves-k256", tag = "k256/powdr/v0.13.4-2025.10.20", default-features = false, features = [ "expose-field", "arithmetic", ] } diff --git a/openvm/guest-ecc-projective/Cargo.toml b/openvm/guest-ecc-projective/Cargo.toml index 33ff30c4be..a1d613d9e0 100644 --- a/openvm/guest-ecc-projective/Cargo.toml +++ b/openvm/guest-ecc-projective/Cargo.toml @@ -5,6 +5,6 @@ version = "0.0.0" edition = "2021" [dependencies] -openvm = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26" } +openvm = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } k256 = { version = "0.13", default-features = false, features = ["arithmetic"] } hex-literal = "1.0.0" \ No newline at end of file diff --git a/openvm/guest-ecrecover-manual/Cargo.toml b/openvm/guest-ecrecover-manual/Cargo.toml index 3e1b21fee0..11ccd59166 100644 --- a/openvm/guest-ecrecover-manual/Cargo.toml +++ b/openvm/guest-ecrecover-manual/Cargo.toml @@ -5,12 +5,12 @@ version = "0.0.0" edition = "2021" [dependencies] -openvm = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26" } -openvm-algebra-guest = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26" } -openvm-algebra-moduli-macros = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26" } -openvm-ecc-guest = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26" } -openvm-ecc-sw-macros = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26" } -openvm-k256 = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26", package = "k256" } +openvm = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-algebra-guest = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-algebra-moduli-macros = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-ecc-guest = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-ecc-sw-macros = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-k256 = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a", package = "k256" } elliptic-curve = { version = "0.13.8" } ecdsa = { version = "0.16.9" } diff --git a/openvm/guest-ecrecover/Cargo.toml b/openvm/guest-ecrecover/Cargo.toml index dfeb08dc17..a05b45a72b 100644 --- a/openvm/guest-ecrecover/Cargo.toml +++ b/openvm/guest-ecrecover/Cargo.toml @@ -5,8 +5,8 @@ version = "0.0.0" edition = "2021" [dependencies] -openvm = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } -k256 = { git = "https://github.com/powdr-labs/elliptic-curves-k256", tag = "k256/powdr/v0.13.4-2025.09.10", default-features = false, features = [ +openvm = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +k256 = { git = "https://github.com/powdr-labs/elliptic-curves-k256", tag = "k256/powdr/v0.13.4-2025.10.20", default-features = false, features = [ "expose-field", "arithmetic", "ecdsa", diff --git a/openvm/guest-hints-test/Cargo.toml b/openvm/guest-hints-test/Cargo.toml index d1c0f334d5..65fa8ad2ea 100644 --- a/openvm/guest-hints-test/Cargo.toml +++ b/openvm/guest-hints-test/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] # The `rev` here must point to the same version used in the workspace. # Otherwise, there is conflict with the `powdr-openvm-hints-guest` dependency (which is part of the workspace). -openvm = { git = "https://github.com/powdr-labs/openvm.git", rev = "62c5d26" } +openvm = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } powdr-openvm-hints-guest = { path = "../extensions/hints-guest/" } [profile.release-with-debug] diff --git a/openvm/guest-keccak-manual-precompile/Cargo.toml b/openvm/guest-keccak-manual-precompile/Cargo.toml index c1d15439ad..67e3da34f2 100644 --- a/openvm/guest-keccak-manual-precompile/Cargo.toml +++ b/openvm/guest-keccak-manual-precompile/Cargo.toml @@ -7,9 +7,9 @@ edition = "2021" members = [] [dependencies] -openvm = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26" } -openvm-platform = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26" } -openvm-keccak256 = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26" } +openvm = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-platform = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-keccak256 = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } [features] default = [] diff --git a/openvm/guest-keccak/Cargo.toml b/openvm/guest-keccak/Cargo.toml index cc558b5b33..c65c12c190 100644 --- a/openvm/guest-keccak/Cargo.toml +++ b/openvm/guest-keccak/Cargo.toml @@ -5,7 +5,7 @@ version = "0.0.0" edition = "2021" [dependencies] -openvm = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26" } +openvm = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } tiny-keccak = { version = "2.0.2", features = ["keccak"] } [profile.release-with-debug] diff --git a/openvm/guest-matmul/Cargo.toml b/openvm/guest-matmul/Cargo.toml index f1cb298924..ac52cda0c5 100644 --- a/openvm/guest-matmul/Cargo.toml +++ b/openvm/guest-matmul/Cargo.toml @@ -5,7 +5,7 @@ version = "0.0.0" edition = "2021" [dependencies] -openvm = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26" } +openvm = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } [profile.release-with-debug] inherits = "release" diff --git a/openvm/guest-pairing-manual-precompile/Cargo.toml b/openvm/guest-pairing-manual-precompile/Cargo.toml index d2dfcacb4c..8c088c668f 100644 --- a/openvm/guest-pairing-manual-precompile/Cargo.toml +++ b/openvm/guest-pairing-manual-precompile/Cargo.toml @@ -7,10 +7,10 @@ edition = "2021" members = [] [dependencies] -openvm = { git = "https://github.com/powdr-labs/openvm.git", features = ["std"] } +openvm = { git = "https://github.com/openvm-org/openvm.git", rev = "2d03a6a", features = ["std"] } -openvm-algebra-guest = { git = "https://github.com/powdr-labs/openvm.git", default-features = false } -openvm-ecc-guest = { git = "https://github.com/powdr-labs/openvm.git", default-features = false } -openvm-pairing = { git = "https://github.com/powdr-labs/openvm.git", default-features = false, features = ["bn254"] } +openvm-algebra-guest = { git = "https://github.com/openvm-org/openvm.git", rev = "2d03a6a", default-features = false } +openvm-ecc-guest = { git = "https://github.com/openvm-org/openvm.git", rev = "2d03a6a", default-features = false } +openvm-pairing = { git = "https://github.com/openvm-org/openvm.git", rev = "2d03a6a", default-features = false, features = ["bn254"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } diff --git a/openvm/guest-pairing/Cargo.toml b/openvm/guest-pairing/Cargo.toml index 96702cdf95..d1855473ce 100644 --- a/openvm/guest-pairing/Cargo.toml +++ b/openvm/guest-pairing/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" members = [] [dependencies] -openvm = { git = "https://github.com/powdr-labs/openvm.git", features = ["std"] } +openvm = { git = "https://github.com/openvm-org/openvm.git", rev = "2d03a6a", features = ["std"] } ark-bn254 = "0.5" ark-ec = "0.5" diff --git a/openvm/guest-sha256-manual-precompile/Cargo.toml b/openvm/guest-sha256-manual-precompile/Cargo.toml index 5f5fd8e321..3cd92e0a99 100644 --- a/openvm/guest-sha256-manual-precompile/Cargo.toml +++ b/openvm/guest-sha256-manual-precompile/Cargo.toml @@ -7,9 +7,9 @@ edition = "2021" members = [] [dependencies] -openvm = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26" } -openvm-platform = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26" } -openvm-sha2 = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26" } +openvm = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-platform = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } +openvm-sha2 = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } [features] default = [] diff --git a/openvm/guest-sha256/Cargo.toml b/openvm/guest-sha256/Cargo.toml index 08052f16eb..2b9f09560f 100644 --- a/openvm/guest-sha256/Cargo.toml +++ b/openvm/guest-sha256/Cargo.toml @@ -5,7 +5,7 @@ version = "0.0.0" edition = "2021" [dependencies] -openvm = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26" } +openvm = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } sha2 = { version = "0.10", default-features = false } digest = { version = "0.10", default-features = false } diff --git a/openvm/guest-u256-manual-precompile/Cargo.toml b/openvm/guest-u256-manual-precompile/Cargo.toml index 84ccd20133..4f75ad3c8c 100644 --- a/openvm/guest-u256-manual-precompile/Cargo.toml +++ b/openvm/guest-u256-manual-precompile/Cargo.toml @@ -7,10 +7,10 @@ edition = "2021" members = [] [dependencies] -openvm = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26", features = [ +openvm = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a", features = [ "std", ] } -openvm-ruint = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26", package = "ruint" } +openvm-ruint = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a", package = "ruint" } [features] default = [] diff --git a/openvm/guest-u256/Cargo.toml b/openvm/guest-u256/Cargo.toml index 520f55b37f..4118b49b3f 100644 --- a/openvm/guest-u256/Cargo.toml +++ b/openvm/guest-u256/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" members = [] [dependencies] -openvm = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26", features = [ +openvm = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a", features = [ "std", ] } ruint = "1.16" diff --git a/openvm/guest/Cargo.toml b/openvm/guest/Cargo.toml index 61849c7542..890d076e3f 100644 --- a/openvm/guest/Cargo.toml +++ b/openvm/guest/Cargo.toml @@ -5,7 +5,7 @@ version = "0.0.0" edition = "2021" [dependencies] -openvm = { git = "https://github.com/powdr-labs/openvm.git", rev="62c5d26" } +openvm = { git = "https://github.com/powdr-labs/openvm.git", rev = "2d03a6a" } [profile.release-with-debug] inherits = "release" diff --git a/openvm/scripts/run_guest_benches.sh b/openvm/scripts/run_guest_benches.sh index 7feefe1976..5e1cd8c9af 100755 --- a/openvm/scripts/run_guest_benches.sh +++ b/openvm/scripts/run_guest_benches.sh @@ -28,7 +28,7 @@ run_bench() { --log "${run_name}"/psrecord.csv \ --log-format csv \ --plot "${run_name}"/psrecord.png \ - "cargo run --bin powdr_openvm -r prove \"$guest\" --input \"$input\" --autoprecompiles \"$apcs\" --metrics \"${run_name}/metrics.json\" --recursion --apc-candidates-dir \"${run_name}\"" + "cargo run --bin powdr_openvm -r --features metrics prove \"$guest\" --input \"$input\" --autoprecompiles \"$apcs\" --metrics \"${run_name}/metrics.json\" --recursion --apc-candidates-dir \"${run_name}\"" python3 "$SCRIPTS_DIR"/plot_trace_cells.py -o "${run_name}"/trace_cells.png "${run_name}"/metrics.json > "${run_name}"/trace_cells.txt diff --git a/openvm/src/customize_exe.rs b/openvm/src/customize_exe.rs index 6f9ffb0778..6a86f1ce23 100644 --- a/openvm/src/customize_exe.rs +++ b/openvm/src/customize_exe.rs @@ -18,7 +18,7 @@ use crate::PrecompileImplementation; use crate::{CompiledProgram, SpecializedConfig}; use itertools::Itertools; use openvm_instructions::instruction::Instruction as OpenVmInstruction; -use openvm_instructions::program::Program as OpenVmProgram; +use openvm_instructions::program::{Program as OpenVmProgram, DEFAULT_PC_STEP}; use openvm_instructions::VmOpcode; use openvm_stark_backend::{ interaction::SymbolicInteraction, @@ -47,17 +47,6 @@ use crate::{ pub const POWDR_OPCODE: usize = 0x10ff; -#[derive(Debug)] -pub enum Error { - AutoPrecompileError, -} - -impl From for Error { - fn from(_e: powdr_autoprecompiles::constraint_optimizer::Error) -> Self { - Error::AutoPrecompileError - } -} - /// An adapter for the BabyBear OpenVM precompiles. /// Note: This could be made generic over the field, but the implementation of `Candidate` is BabyBear-specific. /// The lifetime parameter is used because we use a reference to the `OpenVmProgram` in the `Prog` type. @@ -135,7 +124,7 @@ impl<'a, F: PrimeField32> Program> for Prog<'a, F> { } fn pc_step(&self) -> u32 { - self.0.step + DEFAULT_PC_STEP } fn instructions(&self) -> Box> + '_> { @@ -153,7 +142,7 @@ impl<'a, F: PrimeField32> Program> for Prog<'a, F> { } pub fn customize<'a, P: PgoAdapter>>( - OriginalCompiledProgram { mut exe, vm_config }: OriginalCompiledProgram, + OriginalCompiledProgram { exe, vm_config }: OriginalCompiledProgram, labels: &BTreeSet, debug_info: &DebugInfo, config: PowdrConfig, @@ -168,16 +157,12 @@ pub fn customize<'a, P: PgoAdapter>>( &exe.program, labels.clone(), exe.program.pc_base, - exe.program.step, + DEFAULT_PC_STEP, ); let program = Prog(&exe.program); - let range_tuple_checker_sizes = vm_config - .sdk_vm_config - .rv32m - .unwrap() - .range_tuple_checker_sizes; + let range_tuple_checker_sizes = vm_config.sdk.rv32m.unwrap().range_tuple_checker_sizes; let vm_config = VmConfig { instruction_handler: &airs, bus_interaction_handler: OpenVmBusInteractionHandler::new( @@ -236,7 +221,9 @@ pub fn customize<'a, P: PgoAdapter>>( metrics::gauge!("total_apc_gen_time_ms").set(start.elapsed().as_millis() as f64); let pc_base = exe.program.pc_base; - let pc_step = exe.program.step; + let pc_step = DEFAULT_PC_STEP; + // We need to clone the program because we need to modify it to add the apc instructions. + let mut exe = (*exe).clone(); let program = &mut exe.program; tracing::info!("Adjust the program with the autoprecompiles"); @@ -267,7 +254,7 @@ pub fn customize<'a, P: PgoAdapter>>( .collect(); CompiledProgram { - exe, + exe: Arc::new(exe), vm_config: SpecializedConfig::new( original_config, extensions, diff --git a/openvm/src/extraction_utils.rs b/openvm/src/extraction_utils.rs index d3c2def607..3f6cff29e9 100644 --- a/openvm/src/extraction_utils.rs +++ b/openvm/src/extraction_utils.rs @@ -4,20 +4,28 @@ use std::sync::{Arc, Mutex}; use crate::air_builder::AirKeygenBuilder; use crate::bus_map::{BusMap, OpenVmBusType}; use crate::opcode::branch_opcodes_set; +use crate::powdr_extension::executor::RecordArenaDimension; use crate::{opcode::instruction_allowlist, BabyBearSC, SpecializedConfig}; -use crate::{ - AirMetrics, ExtendedVmConfig, ExtendedVmConfigExecutor, ExtendedVmConfigPeriphery, Instr, - SpecializedExecutor, +use crate::{AirMetrics, ExtendedVmConfig, ExtendedVmConfigExecutor, Instr}; +use crate::{BabyBearPoseidon2Engine, ExtendedVmConfigCpuBuilder}; +use openvm_circuit::arch::{ + AirInventory, AirInventoryError, ExecutorInventory, ExecutorInventoryError, MatrixRecordArena, + SystemConfig, VmBuilder, VmChipComplex, VmCircuitConfig, VmExecutionConfig, }; -use openvm_circuit::arch::{VmChipComplex, VmConfig, VmInventoryError}; +use openvm_circuit::system::memory::interface::MemoryInterfaceAirs; +use openvm_circuit::system::SystemChipInventory; use openvm_circuit_primitives::bitwise_op_lookup::SharedBitwiseOperationLookupChip; use openvm_circuit_primitives::range_tuple::SharedRangeTupleCheckerChip; use openvm_instructions::VmOpcode; +use crate::utils::get_pil; use openvm_stark_backend::air_builders::symbolic::SymbolicRapBuilder; +use openvm_stark_backend::config::Val; use openvm_stark_backend::interaction::fri_log_up::find_interaction_chunks; +use openvm_stark_backend::p3_field::PrimeField32; +use openvm_stark_backend::prover::cpu::CpuBackend; use openvm_stark_backend::{ - air_builders::symbolic::SymbolicConstraints, config::StarkGenericConfig, rap::AnyRap, Chip, + air_builders::symbolic::SymbolicConstraints, config::StarkGenericConfig, rap::AnyRap, }; use openvm_stark_sdk::config::{ baby_bear_poseidon2::{config_from_perm, default_perm}, @@ -27,14 +35,14 @@ use openvm_stark_sdk::p3_baby_bear::{self, BabyBear}; use powdr_autoprecompiles::bus_map::BusType; use powdr_autoprecompiles::evaluation::AirStats; use powdr_autoprecompiles::expression::try_convert; -use powdr_autoprecompiles::{InstructionHandler, SymbolicMachine}; +use powdr_autoprecompiles::{Apc, InstructionHandler, SymbolicMachine}; use serde::{Deserialize, Serialize}; use std::iter::Sum; use std::ops::Deref; use std::ops::{Add, Sub}; use std::sync::MutexGuard; -use crate::utils::{get_pil, UnsupportedOpenVmReferenceError}; +use crate::utils::UnsupportedOpenVmReferenceError; use crate::customize_exe::openvm_bus_interaction_to_powdr; use crate::utils::symbolic_to_algebraic; @@ -123,23 +131,50 @@ impl OriginalAirs { } } -fn to_option(mut v: Vec) -> Option { - match v.len() { - 0 => None, - 1 => Some(v.pop().unwrap()), - _ => panic!("Expected at most one element, got multiple"), - } +/// For each air name, the dimension of a record arena needed to store the +/// records for a single APC call. +pub fn record_arena_dimension_by_air_name_per_apc_call( + apc: &Apc>, + air_by_opcode_id: &OriginalAirs, +) -> HashMap { + apc.instructions().iter().map(|instr| &instr.0.opcode).fold( + HashMap::new(), + |mut acc, opcode| { + // Get the air name for this opcode + let air_name = air_by_opcode_id.opcode_to_air.get(opcode).unwrap(); + + // Increment the height for this air name, initializing if necessary + acc.entry(air_name.clone()) + .or_insert_with(|| { + let (_, air_metrics) = + air_by_opcode_id.air_name_to_machine.get(air_name).unwrap(); + + // TODO: main_columns might not be correct, as the RA::with_capacity() uses the following `main_width()` + // pub fn main_width(&self) -> usize { + // self.cached_mains.iter().sum::() + self.common_main + // } + + RecordArenaDimension { + height: 0, + width: air_metrics.widths.main, + } + }) + .height += 1; + acc + }, + ) } -/// A lazy chip complex that is initialized on the first access -type LazyChipComplex = Option< - VmChipComplex< - BabyBear, - ExtendedVmConfigExecutor, - ExtendedVmConfigPeriphery, - >, +type ChipComplex = VmChipComplex< + BabyBearSC, + MatrixRecordArena>, + CpuBackend, + SystemChipInventory, >; +/// A lazy chip complex that is initialized on the first access +type LazyChipComplex = Option; + /// A shared and mutable reference to a `LazyChipComplex`. type CachedChipComplex = Arc>; @@ -149,11 +184,7 @@ pub struct ChipComplexGuard<'a> { } impl<'a> Deref for ChipComplexGuard<'a> { - type Target = VmChipComplex< - BabyBear, - ExtendedVmConfigExecutor, - ExtendedVmConfigPeriphery, - >; + type Target = ChipComplex; fn deref(&self) -> &Self::Target { // Unwrap is safe here because we ensure that the chip complex is initialized @@ -166,9 +197,41 @@ impl<'a> Deref for ChipComplexGuard<'a> { /// A wrapper around the `ExtendedVmConfig` that caches a chip complex. #[derive(Serialize, Deserialize, Clone)] pub struct OriginalVmConfig { - sdk_config: ExtendedVmConfig, + pub sdk_config: ExtendedVmConfig, #[serde(skip)] - chip_complex: CachedChipComplex, + pub chip_complex: CachedChipComplex, +} + +// TODO: derive `VmCircuitConfig`, currently not possible because we don't have SC/F everywhere +impl VmCircuitConfig for OriginalVmConfig +where + Val: PrimeField32, +{ + fn create_airs(&self) -> Result, AirInventoryError> { + self.sdk_config.create_airs() + } +} + +impl VmExecutionConfig for OriginalVmConfig { + type Executor = ExtendedVmConfigExecutor; + + fn create_executors( + &self, + ) -> Result, ExecutorInventoryError> { + self.sdk_config.create_executors() + } +} + +impl AsRef for OriginalVmConfig { + fn as_ref(&self) -> &SystemConfig { + self.sdk_config.as_ref() + } +} + +impl AsMut for OriginalVmConfig { + fn as_mut(&mut self) -> &mut SystemConfig { + self.sdk_config.as_mut() + } } impl OriginalVmConfig { @@ -190,14 +253,22 @@ impl OriginalVmConfig { } /// Returns a guard that provides access to the chip complex, initializing it if necessary. - fn chip_complex(&self) -> ChipComplexGuard { + fn chip_complex(&self) -> ChipComplexGuard<'_> { let mut guard = self.chip_complex.lock().expect("Mutex poisoned"); if guard.is_none() { // This is the expensive part that we want to run a single time: create the chip complex - let complex = self + let airs = self .sdk_config - .create_chip_complex() + .sdk + .create_airs() + .expect("Failed to create air inventory"); + let complex = + >::create_chip_complex( + &ExtendedVmConfigCpuBuilder, + &self.sdk_config, + airs, + ) .expect("Failed to create chip complex"); // Store the complex in the guard *guard = Some(complex); @@ -215,24 +286,33 @@ impl OriginalVmConfig { &self, max_degree: usize, ) -> Result, UnsupportedOpenVmReferenceError> { - let chip_complex = self.chip_complex(); + let chip_complex = &self.chip_complex(); + + let chip_inventory = &chip_complex.inventory; + + let executor_inventory: ExecutorInventory>> = + self.create_executors().unwrap(); let instruction_allowlist = instruction_allowlist(); - let res = chip_complex - .inventory - .available_opcodes() - .filter(|op| { - // Filter out the opcode that we are not interested in - instruction_allowlist.contains(op) + instruction_allowlist + .into_iter() + .filter_map(|op| { + executor_inventory + .instruction_lookup + .get(&op) + .map(|id| (op, *id as usize)) }) - .filter_map(|op| Some((op, chip_complex.inventory.get_executor(op)?))) - .try_fold(OriginalAirs::default(), |mut airs, (op, executor)| { - airs.insert_opcode(op, get_name::(executor.air()), || { - let air = executor.air(); - let columns = get_columns(air.clone()); - let constraints = get_constraints(air.clone()); - let metrics = get_air_metrics(air, max_degree); + .map(|(op, executor_id)| { + let insertion_index = chip_inventory.executor_idx_to_insertion_idx[executor_id]; + let air_ref = &chip_inventory.airs().ext_airs()[insertion_index]; + (op, air_ref) + }) // find executor for opcode + .try_fold(OriginalAirs::default(), |mut airs, (op, air_ref)| { + airs.insert_opcode(op, air_ref.name(), || { + let columns = get_columns(air_ref.clone()); + let constraints = get_constraints(air_ref.clone()); + let metrics = get_air_metrics(air_ref.clone(), max_degree); let powdr_exprs = constraints .constraints @@ -257,29 +337,46 @@ impl OriginalVmConfig { })?; Ok(airs) - }); - - res + }) } pub fn bus_map(&self) -> BusMap { let chip_complex = self.chip_complex(); - let builder = chip_complex.inventory_builder(); + let inventory = &chip_complex.inventory; - let shared_bitwise_lookup = - to_option(builder.find_chip::>()); - let shared_range_tuple_checker = - to_option(builder.find_chip::>()); + let shared_bitwise_lookup = inventory + .find_chip::>() + .next(); + let shared_range_tuple_checker = inventory + .find_chip::>() + .next(); + + let system_air_inventory = inventory.airs().system(); + let connector_air = system_air_inventory.connector; + let memory_air = &system_air_inventory.memory; BusMap::from_id_type_pairs( { - let base = &chip_complex.base; [ - (base.execution_bus().inner.index, BusType::ExecutionBridge), - (base.memory_bus().inner.index, BusType::Memory), - (base.program_bus().inner.index, BusType::PcLookup), ( - base.range_checker_bus().inner.index, + connector_air.execution_bus.index(), + BusType::ExecutionBridge, + ), + ( + // TODO: make getting memory bus index a helper function + match &memory_air.interface { + MemoryInterfaceAirs::Volatile { boundary } => { + boundary.memory_bus.inner.index + } + MemoryInterfaceAirs::Persistent { boundary, .. } => { + boundary.memory_bus.inner.index + } + }, + BusType::Memory, + ), + (connector_air.program_bus.index(), BusType::PcLookup), + ( + connector_air.range_bus.index(), BusType::Other(OpenVmBusType::VariableRangeChecker), ), ] @@ -301,39 +398,17 @@ impl OriginalVmConfig { ) } - pub fn create_chip_complex( - &self, - ) -> Result< - VmChipComplex< - BabyBear, - ExtendedVmConfigExecutor, - ExtendedVmConfigPeriphery, - >, - VmInventoryError, - > { - // Clear the cache - let mut guard = self.chip_complex.lock().expect("Mutex poisoned"); - *guard = None; // Invalidate cache - // Create a new chip complex - self.sdk_config.create_chip_complex() - } - pub fn chip_inventory_air_metrics(&self, max_degree: usize) -> HashMap { let inventory = &self.chip_complex().inventory; inventory - .executors() + .airs() + .ext_airs() .iter() - .map(|executor| executor.air()) - .chain( - inventory - .periphery() - .iter() - .map(|periphery| periphery.air()), - ) .map(|air| { - // both executors and periphery implement the same `air()` API - (air.name(), get_air_metrics(air, max_degree)) + let name = air.name(); + let metrics = get_air_metrics(air.clone(), max_degree); + (name, metrics) }) .collect() } @@ -341,17 +416,16 @@ impl OriginalVmConfig { pub fn export_pil(writer: &mut impl std::io::Write, vm_config: &SpecializedConfig) { let blacklist = ["KeccakVmAir"]; - let bus_map = vm_config.sdk_config.bus_map(); - let chip_complex: VmChipComplex<_, _, _> = vm_config.create_chip_complex().unwrap(); - - for executor in chip_complex.inventory.executors().iter() { - let air = executor.air(); - let name = match executor { - SpecializedExecutor::PowdrExecutor(powdr_executor) => { - powdr_executor.air_name() // name with opcode - } - _ => air.name(), - }; + let bus_map = vm_config.sdk.bus_map(); + let chip_complex = vm_config.sdk.chip_complex(); + + for air in chip_complex + .inventory + .executor_idx_to_insertion_idx + .iter() + .map(|insertion_idx| &chip_complex.inventory.airs().ext_airs()[*insertion_idx]) + { + let name = get_name(air.clone()); if blacklist.contains(&name.as_str()) { log::warn!("Skipping blacklisted AIR: {name}"); @@ -360,7 +434,7 @@ pub fn export_pil(writer: &mut impl std::io::Write, vm_config: &SpecializedConfi let columns = get_columns(air.clone()); - let constraints = get_constraints(air); + let constraints = get_constraints(air.clone()); let pil = get_pil(&name, &constraints, &columns, vec![], &bus_map); writeln!(writer, "{pil}\n").unwrap(); @@ -528,7 +602,8 @@ mod tests { use openvm_ecc_circuit::{WeierstrassExtension, SECP256K1_CONFIG}; use openvm_pairing_circuit::{PairingCurve, PairingExtension}; use openvm_rv32im_circuit::Rv32M; - use openvm_sdk::config::{SdkSystemConfig, SdkVmConfig}; + use openvm_sdk::config::SdkVmConfig; + use powdr_openvm_hints_circuit::HintsExtension; #[test] fn test_get_bus_map() { @@ -576,16 +651,19 @@ mod tests { .pairing(PairingExtension::new(supported_pairing_curves)) .build(); - let _ = OriginalVmConfig::new(ExtendedVmConfig { sdk_vm_config }).bus_map(); + let _ = OriginalVmConfig::new(ExtendedVmConfig { + sdk: sdk_vm_config, + hints: HintsExtension, + }) + .bus_map(); } #[test] fn test_export_pil() { let writer = &mut Vec::new(); let ext_config = ExtendedVmConfig { - sdk_vm_config: SdkVmConfig::builder() - .system(SdkSystemConfig::default()) - .build(), + sdk: SdkVmConfig::riscv32(), + hints: HintsExtension, }; let base_config = OriginalVmConfig::new(ext_config); let specialized_config = SpecializedConfig::new( diff --git a/openvm/src/lib.rs b/openvm/src/lib.rs index 357a15eb30..2c01e180c3 100644 --- a/openvm/src/lib.rs +++ b/openvm/src/lib.rs @@ -1,38 +1,54 @@ +#![cfg_attr(feature = "tco", allow(incomplete_features))] +#![cfg_attr(feature = "tco", feature(explicit_tail_calls))] + use derive_more::From; use eyre::Result; -use itertools::{multiunzip, Itertools}; +use itertools::Itertools; use openvm_build::{build_guest_package, find_unique_executable, get_package, TargetFilter}; -use openvm_circuit::arch::InitFileGenerator; +use openvm_circuit::arch::execution_mode::metered::segment_ctx::SegmentationLimits; +use openvm_circuit::arch::execution_mode::Segment; +use openvm_circuit::arch::instructions::exe::VmExe; +use openvm_circuit::arch::RowMajorMatrixArena; use openvm_circuit::arch::{ - instructions::exe::VmExe, segment::DefaultSegmentationStrategy, Streams, SystemConfig, - VirtualMachine, VmChipComplex, VmConfig, VmInventoryError, + debug_proving_ctx, AirInventory, AirInventoryError, ChipInventory, ChipInventoryError, + ExecutorInventory, ExecutorInventoryError, InitFileGenerator, MatrixRecordArena, + PreflightExecutionOutput, SystemConfig, VmBuilder, VmChipComplex, VmCircuitConfig, + VmCircuitExtension, VmExecutionConfig, VmInstance, VmProverExtension, }; +use openvm_circuit::system::SystemChipInventory; use openvm_circuit::{circuit_derive::Chip, derive::AnyEnum}; -use openvm_circuit_derive::InstructionExecutor; -use openvm_circuit_primitives_derive::ChipUsageGetter; -use openvm_instructions::program::Program; +use openvm_circuit_derive::{Executor, MeteredExecutor, PreflightExecutor}; +use openvm_circuit_primitives::bitwise_op_lookup::SharedBitwiseOperationLookupChip; +use openvm_circuit_primitives::range_tuple::SharedRangeTupleCheckerChip; +use openvm_circuit_primitives::var_range::SharedVariableRangeCheckerChip; +use openvm_sdk::config::SdkVmCpuBuilder; + +use openvm_instructions::program::{Program, DEFAULT_PC_STEP}; +use openvm_native_circuit::NativeCpuBuilder; +use openvm_sdk::config::TranspilerConfig; +use openvm_sdk::prover::vm::new_local_prover; +use openvm_sdk::prover::{verify_app_proof, AggStarkProver}; +use openvm_sdk::GenericSdk; use openvm_sdk::{ - config::{ - AggStarkConfig, AppConfig, SdkVmConfig, SdkVmConfigExecutor, SdkVmConfigPeriphery, - DEFAULT_APP_LOG_BLOWUP, - }, - keygen::AggStarkProvingKey, - prover::AggStarkProver, + config::{AppConfig, SdkVmConfig, SdkVmConfigExecutor, DEFAULT_APP_LOG_BLOWUP}, Sdk, StdIn, }; +use openvm_stark_backend::config::Val; use openvm_stark_backend::engine::StarkEngine; +use openvm_stark_backend::prover::cpu::{CpuBackend, CpuDevice}; use openvm_stark_sdk::config::{ baby_bear_poseidon2::{BabyBearPoseidon2Config, BabyBearPoseidon2Engine}, FriParameters, }; -use openvm_stark_sdk::engine::StarkFriEngine; use openvm_stark_sdk::openvm_stark_backend::p3_field::PrimeField32; use openvm_stark_sdk::p3_baby_bear::BabyBear; +use openvm_transpiler::transpiler::Transpiler; use powdr_autoprecompiles::evaluation::AirStats; use powdr_autoprecompiles::pgo::{CellPgo, InstructionPgo, NonePgo}; use powdr_autoprecompiles::{execution_profile::execution_profile, PowdrConfig}; -use powdr_extension::{PowdrExecutor, PowdrExtension, PowdrPeriphery}; -use powdr_openvm_hints_circuit::{HintsExecutor, HintsExtension, HintsPeriphery}; +use powdr_extension::PowdrExtension; +pub use powdr_openvm_hints_circuit::HintsExtension; +use powdr_openvm_hints_circuit::{HintsCpuProverExt, HintsExtensionExecutor}; use powdr_openvm_hints_transpiler::HintsTranspilerExtension; use serde::{Deserialize, Serialize}; use std::cmp::Reverse; @@ -48,13 +64,16 @@ use std::{ use crate::customize_exe::OpenVmApcCandidate; pub use crate::customize_exe::Prog; -use tracing::Level; +use crate::powdr_extension::chip::{PowdrAir, PowdrChip}; +use crate::powdr_extension::trace_generator::PowdrPeripheryInstances; +use crate::powdr_extension::PlonkAir; +use tracing::{info_span, Level}; #[cfg(test)] use crate::extraction_utils::AirWidthsDiff; use crate::extraction_utils::{export_pil, AirWidths, OriginalVmConfig}; use crate::instruction_formatter::openvm_opcode_formatter; -use crate::powdr_extension::PowdrPrecompile; +use crate::powdr_extension::{PlonkChip, PowdrExtensionExecutor, PowdrPrecompile}; mod air_builder; pub mod bus_map; @@ -107,63 +126,167 @@ mod plonk; /// A custom VmConfig that wraps the SdkVmConfig, adding our custom extension. #[derive(Serialize, Deserialize, Clone)] pub struct SpecializedConfig { - pub sdk_config: OriginalVmConfig, + pub sdk: OriginalVmConfig, pub powdr: PowdrExtension, } +#[derive(Default, Clone)] +pub struct SpecializedConfigCpuBuilder; + +impl VmBuilder for SpecializedConfigCpuBuilder +where + E: StarkEngine, PD = CpuDevice>, +{ + type VmConfig = SpecializedConfig; + type SystemChipInventory = SystemChipInventory; + type RecordArena = MatrixRecordArena>; + + fn create_chip_complex( + &self, + config: &SpecializedConfig, + circuit: AirInventory, + ) -> Result< + VmChipComplex, + ChipInventoryError, + > { + let mut chip_complex = VmBuilder::::create_chip_complex( + &SdkVmCpuBuilder, + &config.sdk.sdk_config.sdk, + circuit, + )?; + let inventory = &mut chip_complex.inventory; + VmProverExtension::::extend_prover(&PowdrCpuProverExt, &config.powdr, inventory)?; + Ok(chip_complex) + } +} + +struct PowdrCpuProverExt; + +impl VmProverExtension> for PowdrCpuProverExt +where + E: StarkEngine, PD = CpuDevice>, + RA: RowMajorMatrixArena, +{ + fn extend_prover( + &self, + extension: &PowdrExtension, + inventory: &mut ChipInventory<::SC, RA, ::PB>, + ) -> Result<(), ChipInventoryError> { + // TODO: here we make assumptions about the existence of some chips in the periphery. Make this more flexible + let bitwise_lookup = inventory + .find_chip::>() + .next() + .cloned(); + let range_checker = inventory + .find_chip::() + .next() + .unwrap(); + let tuple_range_checker = inventory + .find_chip::>() + .next() + .cloned(); + + // Create the shared chips and the dummy shared chips + let shared_chips_pair = PowdrPeripheryInstances::new( + range_checker.clone(), + bitwise_lookup, + tuple_range_checker, + ); + + for precompile in &extension.precompiles { + match extension.implementation { + PrecompileImplementation::SingleRowChip => { + inventory.next_air::>()?; + let chip = PowdrChip::new( + precompile.clone(), + extension.airs.clone(), + extension.base_config.clone(), + shared_chips_pair.clone(), + ); + inventory.add_executor_chip(chip); + } + PrecompileImplementation::PlonkChip => { + inventory.next_air::>()?; + let chip = PlonkChip::new( + precompile.clone(), + extension.airs.clone(), + extension.base_config.clone(), + shared_chips_pair.clone(), + extension.bus_map.clone(), + ); + inventory.add_executor_chip(chip); + } + }; + } + + Ok(()) + } +} + +impl TranspilerConfig for SpecializedConfig { + fn transpiler(&self) -> Transpiler { + self.sdk.config().transpiler() + } +} + // For generation of the init file, we delegate to the underlying SdkVmConfig. impl InitFileGenerator for SpecializedConfig { fn generate_init_file_contents(&self) -> Option { - self.sdk_config.config().generate_init_file_contents() + self.sdk.config().generate_init_file_contents() } fn write_to_init_file( &self, manifest_dir: &Path, init_file_name: Option<&str>, - ) -> eyre::Result<()> { - self.sdk_config + ) -> std::io::Result<()> { + self.sdk .config() .write_to_init_file(manifest_dir, init_file_name) } } -#[allow(clippy::large_enum_variant)] -#[derive(ChipUsageGetter, From, AnyEnum, InstructionExecutor, Chip)] -pub enum SpecializedExecutor { - #[any_enum] - SdkExecutor(ExtendedVmConfigExecutor), - #[any_enum] - PowdrExecutor(PowdrExecutor), +impl AsRef for SpecializedConfig { + fn as_ref(&self) -> &SystemConfig { + self.sdk.as_ref() + } } -#[derive(From, ChipUsageGetter, Chip, AnyEnum)] -pub enum MyPeriphery { +impl AsMut for SpecializedConfig { + fn as_mut(&mut self) -> &mut SystemConfig { + self.sdk.as_mut() + } +} + +#[allow(clippy::large_enum_variant)] +#[derive(From, AnyEnum, Chip, Executor, MeteredExecutor, PreflightExecutor)] +pub enum SpecializedExecutor { #[any_enum] - SdkPeriphery(ExtendedVmConfigPeriphery), + SdkExecutor(ExtendedVmConfigExecutor), #[any_enum] - PowdrPeriphery(PowdrPeriphery), + PowdrExecutor(PowdrExtensionExecutor), } -impl VmConfig for SpecializedConfig { - type Executor = SpecializedExecutor; - type Periphery = MyPeriphery; - - fn system(&self) -> &SystemConfig { - VmConfig::::system(self.sdk_config.config()) +// TODO: derive VmCircuitConfig, currently not possible because we don't have SC/F everywhere +// Also `start_new_extension` is normally only used in derive +impl VmCircuitConfig for SpecializedConfig { + fn create_airs(&self) -> Result, AirInventoryError> { + let mut inventory = self.sdk.create_airs()?; + inventory.start_new_extension(); + self.powdr.extend_circuit(&mut inventory)?; + Ok(inventory) } +} - fn system_mut(&mut self) -> &mut SystemConfig { - VmConfig::::system_mut(self.sdk_config.config_mut()) - } +impl VmExecutionConfig for SpecializedConfig { + type Executor = SpecializedExecutor; - fn create_chip_complex( + fn create_executors( &self, - ) -> Result, VmInventoryError> { - let chip = self.sdk_config.create_chip_complex()?; - let chip = chip.extend(&self.powdr)?; - - Ok(chip) + ) -> Result, ExecutorInventoryError> { + let mut inventory = self.sdk.create_executors()?.transmute(); + inventory = inventory.extend(&self.powdr)?; + Ok(inventory) } } @@ -178,13 +301,13 @@ impl SpecializedConfig { let bus_map = base_config.bus_map(); let powdr_extension = PowdrExtension::new( precompiles, - base_config.config().clone(), + base_config.clone(), implementation, bus_map, airs, ); Self { - sdk_config: base_config, + sdk: base_config, powdr: powdr_extension, } } @@ -216,7 +339,16 @@ pub fn compile_openvm( guest: &str, guest_opts: GuestOptions, ) -> Result> { - let sdk = Sdk::default(); + let mut sdk = Sdk::riscv32(); + + let transpiler = sdk.transpiler().unwrap(); + + // Add our custom transpiler extensions + sdk.set_transpiler( + transpiler + .clone() + .with_extension(HintsTranspilerExtension {}), + ); // Build the ELF with guest options and a target filter. // We need these extra Rust flags to get the labels. @@ -245,21 +377,18 @@ pub fn compile_openvm( let elf = sdk.build( guest_opts, - &sdk_vm_config, target_path, &Default::default(), Default::default(), )?; // Transpile the ELF into a VmExe. - let mut transpiler = sdk_vm_config.transpiler(); - - // Add our custom transpiler extensions - transpiler = transpiler.with_extension(HintsTranspilerExtension {}); + let exe = sdk.convert_to_exe(elf)?; - let exe = sdk.transpile(elf, transpiler)?; - - let vm_config = ExtendedVmConfig { sdk_vm_config }; + let vm_config = ExtendedVmConfig { + sdk: sdk_vm_config, + hints: HintsExtension, + }; Ok(OriginalCompiledProgram { exe, vm_config }) } @@ -300,7 +429,7 @@ pub fn compile_guest( } fn instruction_index_to_pc(program: &Program, idx: usize) -> u64 { - (program.pc_base + (idx as u32 * program.step)) as u64 + (program.pc_base + (idx as u32 * DEFAULT_PC_STEP)) as u64 } fn tally_opcode_frequency(pgo_config: &PgoConfig, exe: &VmExe) { @@ -382,7 +511,7 @@ pub fn compile_exe_with_elf( let max_total_apc_columns: Option = max_total_columns.map(|max_total_columns| { let original_config = OriginalVmConfig::new(original_program.vm_config.clone()); - let total_non_apc_columns = original_config + let total_non_apc_columns: usize = original_config .chip_inventory_air_metrics(config.degree_bound.identities) .values() .map(|m| m.total_width()) @@ -429,80 +558,76 @@ pub fn compile_exe_with_elf( #[derive(Serialize, Deserialize, Clone)] pub struct CompiledProgram { - pub exe: VmExe, + pub exe: Arc>, pub vm_config: SpecializedConfig, } // the original openvm program and config without powdr extension #[derive(Clone)] pub struct OriginalCompiledProgram { - pub exe: VmExe, + pub exe: Arc>, pub vm_config: ExtendedVmConfig, } -#[derive(Clone, Debug, Serialize, Deserialize)] +use openvm_circuit_derive::VmConfig; + +#[derive(Clone, Debug, Serialize, Deserialize, VmConfig)] // SdkVmConfig plus custom openvm extensions, before autoprecompile transformations. // For now, only includes custom hints. pub struct ExtendedVmConfig { - pub sdk_vm_config: SdkVmConfig, + #[config] + pub sdk: SdkVmConfig, + #[extension(executor = "HintsExtensionExecutor")] + pub hints: HintsExtension, } -impl VmConfig for ExtendedVmConfig { - type Executor = ExtendedVmConfigExecutor; - type Periphery = ExtendedVmConfigPeriphery; - - fn system(&self) -> &SystemConfig { - &self.sdk_vm_config.system.config +impl TranspilerConfig for ExtendedVmConfig { + fn transpiler(&self) -> Transpiler { + self.sdk.transpiler() } +} - fn system_mut(&mut self) -> &mut SystemConfig { - &mut self.sdk_vm_config.system.config - } +#[derive(Default, Clone)] +pub struct ExtendedVmConfigCpuBuilder; + +impl VmBuilder for ExtendedVmConfigCpuBuilder +where + E: StarkEngine, PD = CpuDevice>, +{ + type VmConfig = ExtendedVmConfig; + type SystemChipInventory = SystemChipInventory; + type RecordArena = MatrixRecordArena>; fn create_chip_complex( &self, - ) -> std::result::Result< - VmChipComplex, - VmInventoryError, + config: &ExtendedVmConfig, + circuit: AirInventory, + ) -> Result< + VmChipComplex, + ChipInventoryError, > { - let mut complex = self.sdk_vm_config.create_chip_complex()?.transmute(); - complex = complex.extend(&HintsExtension)?; - Ok(complex) + let mut chip_complex = + VmBuilder::::create_chip_complex(&SdkVmCpuBuilder, &config.sdk, circuit)?; + let inventory = &mut chip_complex.inventory; + VmProverExtension::::extend_prover(&HintsCpuProverExt, &config.hints, inventory)?; + Ok(chip_complex) } } impl InitFileGenerator for ExtendedVmConfig { fn generate_init_file_contents(&self) -> Option { - self.sdk_vm_config.generate_init_file_contents() + self.sdk.generate_init_file_contents() } fn write_to_init_file( &self, manifest_dir: &Path, init_file_name: Option<&str>, - ) -> eyre::Result<()> { - self.sdk_vm_config - .write_to_init_file(manifest_dir, init_file_name) + ) -> std::io::Result<()> { + self.sdk.write_to_init_file(manifest_dir, init_file_name) } } -#[derive(ChipUsageGetter, Chip, InstructionExecutor, From, AnyEnum)] -#[allow(clippy::large_enum_variant)] -pub enum ExtendedVmConfigExecutor { - #[any_enum] - Sdk(SdkVmConfigExecutor), - #[any_enum] - Hints(HintsExecutor), -} - -#[derive(From, ChipUsageGetter, Chip, AnyEnum)] -pub enum ExtendedVmConfigPeriphery { - #[any_enum] - Sdk(SdkVmConfigPeriphery), - #[any_enum] - Hints(HintsPeriphery), -} - #[derive(Clone, Serialize, Deserialize, Default, Debug, Eq, PartialEq)] pub struct AirMetrics { pub widths: AirWidths, @@ -551,11 +676,10 @@ impl CompiledProgram { &self, max_degree: usize, ) -> (Vec<(AirMetrics, Option)>, Vec) { - use openvm_stark_backend::Chip; - - use crate::extraction_utils::get_air_metrics; - - let inventory = self.vm_config.create_chip_complex().unwrap().inventory; + let air_inventory = self.vm_config.create_airs().unwrap(); + let builder = SpecializedConfigCpuBuilder; + let chip_complex = >::create_chip_complex(&builder, &self.vm_config, air_inventory).unwrap(); + let inventory = chip_complex.inventory; // Order of precompile is the same as that of Powdr executors in chip inventory let mut apc_stats = self @@ -565,45 +689,46 @@ impl CompiledProgram { .iter() .map(|precompile| precompile.apc_stats.clone()); - inventory - .executors() - .iter() - .map(|executor| executor.air()) - .chain( - inventory - .periphery() - .iter() - .map(|periphery| periphery.air()), - ) - .fold( - (Vec::new(), Vec::new()), - |(mut powdr_air_metrics, mut non_powdr_air_metrics), air| { - let name = air.name(); - - // We actually give name "powdr_air_for_opcode_" to the AIRs, - // but OpenVM uses the actual Rust type (PowdrAir) as the name in this method. - // TODO this is hacky but not sure how to do it better rn. - if name.starts_with("PowdrAir") || name.starts_with("PlonkAir") { - powdr_air_metrics.push(( - get_air_metrics(air, max_degree), - apc_stats.next().unwrap().map(|stats| stats.widths), - )); - } else { - non_powdr_air_metrics.push(get_air_metrics(air, max_degree)); - } + inventory.airs().ext_airs().iter().fold( + (Vec::new(), Vec::new()), + |(mut powdr_air_metrics, mut non_powdr_air_metrics), air| { + let name = air.name(); + // We actually give name "powdr_air_for_opcode_" to the AIRs, + // but OpenVM uses the actual Rust type (PowdrAir) as the name in this method. + // TODO this is hacky but not sure how to do it better rn. + if name.starts_with("PowdrAir") || name.starts_with("PlonkAir") { + use crate::extraction_utils::get_air_metrics; + + powdr_air_metrics.push(( + get_air_metrics(air.clone(), max_degree), + apc_stats.next().unwrap().map(|stats| stats.widths), + )); + } else { + use crate::extraction_utils::get_air_metrics; + + non_powdr_air_metrics.push(get_air_metrics(air.clone(), max_degree)); + } - (powdr_air_metrics, non_powdr_air_metrics) - }, - ) + (powdr_air_metrics, non_powdr_air_metrics) + }, + ) } } pub fn execute(program: CompiledProgram, inputs: StdIn) -> Result<(), Box> { let CompiledProgram { exe, vm_config } = program; - let sdk = Sdk::default(); + // Set app configuration + let app_fri_params = + FriParameters::standard_with_100_bits_conjectured_security(DEFAULT_APP_LOG_BLOWUP); + let app_config = AppConfig::new(app_fri_params, vm_config.clone()); + + // prepare for execute + let sdk: GenericSdk = + GenericSdk::new(app_config).unwrap(); + + let output = sdk.execute(exe.clone(), inputs.clone()).unwrap(); - let output = sdk.execute(exe.clone(), vm_config.clone(), inputs)?; tracing::info!("Public values output: {:?}", output); Ok(()) @@ -622,86 +747,97 @@ pub fn prove( // DefaultSegmentationStrategy { max_segment_len: 4194204, max_cells_per_chip_in_segment: 503304480 } if let Some(segment_height) = segment_height { vm_config - .sdk_config + .sdk .config_mut() - .sdk_vm_config + .sdk .system .config - .segmentation_strategy = Arc::new( - DefaultSegmentationStrategy::new_with_max_segment_len(segment_height), - ); + .segmentation_limits = + SegmentationLimits::default().with_max_trace_height(segment_height as u32); tracing::debug!("Setting max segment len to {}", segment_height); } - let sdk = Sdk::default(); - // Set app configuration let app_fri_params = FriParameters::standard_with_100_bits_conjectured_security(DEFAULT_APP_LOG_BLOWUP); let app_config = AppConfig::new(app_fri_params, vm_config.clone()); - // Commit the exe - let app_committed_exe = sdk.commit_app_exe(app_fri_params, exe.clone())?; - - // Generate an AppProvingKey - let app_pk = Arc::new(sdk.app_keygen(app_config)?); - + // Create the SDK + let sdk: GenericSdk<_, SpecializedConfigCpuBuilder, _> = GenericSdk::new(app_config).unwrap(); if mock { - tracing::info!("Checking constraints and witness in Mock prover..."); - let engine = BabyBearPoseidon2Engine::new(app_fri_params); - let vm = VirtualMachine::new(engine, vm_config.clone()); - let pk = vm.keygen(); - let streams = Streams::from(inputs); - let mut result = vm.execute_and_generate(exe.clone(), streams).unwrap(); - let _final_memory = Option::take(&mut result.final_memory); - let global_airs = vm.config().create_chip_complex().unwrap().airs(); - for proof_input in &result.per_segment { - let (airs, pks, air_proof_inputs): (Vec<_>, Vec<_>, Vec<_>) = - multiunzip(proof_input.per_air.iter().map(|(air_id, air_proof_input)| { - ( - global_airs[*air_id].clone(), - pk.per_air[*air_id].clone(), - air_proof_input.clone(), - ) - })); - vm.engine.debug(&airs, &pks, &air_proof_inputs); + // Build owned vm instance, so we can mutate it later + let vm_builder = sdk.app_vm_builder().clone(); + let vm_pk = sdk.app_pk().app_vm_pk.clone(); + let exe = sdk.convert_to_exe(exe.clone())?; + let mut vm_instance: VmInstance = + new_local_prover(vm_builder, &vm_pk, exe)?; + + vm_instance.reset_state(inputs.clone()); + let metered_ctx = vm_instance.vm.build_metered_ctx(); + let metered_interpreter = vm_instance.vm.metered_interpreter(vm_instance.exe())?; + let (segments, _) = metered_interpreter.execute_metered(inputs.clone(), metered_ctx)?; + let mut state = vm_instance.state_mut().take(); + + // Get reusable inputs for `debug_proving_ctx`, the mock prover API from OVM. + let vm = &mut vm_instance.vm; + let air_inv = vm.config().create_airs().unwrap(); + let pk = air_inv.keygen(&vm.engine); + + for (seg_idx, segment) in segments.into_iter().enumerate() { + let _segment_span = info_span!("prove_segment", segment = seg_idx).entered(); + // We need a separate span so the metric label includes "segment" from _segment_span + let _prove_span = info_span!("total_proof").entered(); + let Segment { + instret_start, + num_insns, + trace_heights, + } = segment; + assert_eq!(state.as_ref().unwrap().instret, instret_start); + let from_state = Option::take(&mut state).unwrap(); + vm.transport_init_memory_to_device(&from_state.memory); + let PreflightExecutionOutput { + system_records, + record_arenas, + to_state, + } = vm.execute_preflight( + &mut vm_instance.interpreter, + from_state, + Some(num_insns), + &trace_heights, + )?; + state = Some(to_state); + + // Generate proving context for each segment + let ctx = vm.generate_proving_ctx(system_records, record_arenas)?; + + // Run the mock prover for each segment + debug_proving_ctx(vm, &pk, &ctx); } } else { + let mut app_prover = sdk.app_prover(exe.clone())?; + // Generate a proof tracing::info!("Generating app proof..."); let start = std::time::Instant::now(); - let app_proof = - sdk.generate_app_proof(app_pk.clone(), app_committed_exe.clone(), inputs.clone())?; + let app_proof = app_prover.prove(inputs.clone())?; tracing::info!("App proof took {:?}", start.elapsed()); - tracing::info!( - "Public values: {:?}", - app_proof.user_public_values.public_values - ); + tracing::info!("Public values: {:?}", app_proof.user_public_values); // Verify - let app_vk = app_pk.get_app_vk(); - sdk.verify_app_proof(&app_vk, &app_proof)?; + let app_vk = sdk.app_pk().get_app_vk(); + verify_app_proof(&app_vk, &app_proof)?; tracing::info!("App proof verification done."); if recursion { - // Generate the aggregation proving key - tracing::info!("Generating aggregation proving key..."); - let (agg_stark_pk, _) = - AggStarkProvingKey::dummy_proof_and_keygen(AggStarkConfig::default()); + let mut agg_prover: AggStarkProver = + sdk.prover(exe.clone())?.agg_prover; - tracing::info!("Generating aggregation proof..."); - - let agg_prover = AggStarkProver::::new( - agg_stark_pk, - app_pk.leaf_committed_exe.clone(), - *sdk.agg_tree_config(), - ); // Note that this proof is not verified. We assume that any valid app proof // (verified above) also leads to a valid aggregation proof. // If this was not the case, it would be a completeness bug in OpenVM. let start = std::time::Instant::now(); - let _proof_with_publics = agg_prover.generate_root_verifier_input(app_proof); + let _ = agg_prover.generate_root_verifier_input(app_proof)?; tracing::info!("Agg proof (inner recursion) took {:?}", start.elapsed()); } @@ -720,12 +856,17 @@ pub fn execution_profile_from_guest( let OriginalCompiledProgram { exe, vm_config } = compile_openvm(guest, guest_opts).unwrap(); let program = Prog::from(&exe.program); + // Set app configuration + let app_fri_params = + FriParameters::standard_with_100_bits_conjectured_security(DEFAULT_APP_LOG_BLOWUP); + let app_config = AppConfig::new(app_fri_params, vm_config.clone()); + // prepare for execute - let sdk = Sdk::default(); + let sdk: GenericSdk = + GenericSdk::new(app_config).unwrap(); execution_profile::(&program, || { - sdk.execute(exe.clone(), vm_config.clone(), inputs.clone()) - .unwrap(); + sdk.execute(exe.clone(), inputs.clone()).unwrap(); }) } @@ -766,7 +907,7 @@ mod tests { pgo_config: PgoConfig, segment_height: Option, ) { - let result = compile_and_prove( + compile_and_prove( guest, config, implementation, @@ -775,8 +916,8 @@ mod tests { stdin, pgo_config, segment_height, - ); - assert!(result.is_ok()); + ) + .unwrap() } fn prove_mock( @@ -787,7 +928,7 @@ mod tests { pgo_config: PgoConfig, segment_height: Option, ) { - let result = compile_and_prove( + compile_and_prove( guest, config, implementation, @@ -796,8 +937,8 @@ mod tests { stdin, pgo_config, segment_height, - ); - assert!(result.is_ok()); + ) + .unwrap() } fn prove_recursion( @@ -808,7 +949,7 @@ mod tests { pgo_config: PgoConfig, segment_height: Option, ) { - let result = compile_and_prove( + compile_and_prove( guest, config, implementation, @@ -817,8 +958,8 @@ mod tests { stdin, pgo_config, segment_height, - ); - assert!(result.is_ok()); + ) + .unwrap() } const GUEST: &str = "guest"; @@ -902,6 +1043,7 @@ mod tests { // All gate constraints should be satisfied, but bus interactions are not implemented yet. #[test] + #[ignore = "TODO: fix"] fn guest_plonk_prove_mock() { let mut stdin = StdIn::default(); stdin.write(&GUEST_ITER); @@ -1061,6 +1203,7 @@ mod tests { // All gate constraints should be satisfied, but bus interactions are not implemented yet. #[test] + #[ignore = "Passes without debug assertions, but fails with it"] fn keccak_plonk_small_prove_mock() { let mut stdin = StdIn::default(); stdin.write(&GUEST_KECCAK_ITER_SMALL); @@ -1075,6 +1218,24 @@ mod tests { ); } + // TODO: fix this test. It works with `mock` (see above) but not with `prove_simple` + #[test] + #[ignore = "Panics with a verifier error like below without debug assertions, but fails with it"] + #[should_panic = "Verification(StarkError(InvalidProofShape)"] + fn keccak_plonk_small_prove_simple() { + let mut stdin = StdIn::default(); + stdin.write(&GUEST_KECCAK_ITER_SMALL); + let config = default_powdr_openvm_config(GUEST_KECCAK_APC, GUEST_KECCAK_SKIP); + prove_simple( + GUEST_KECCAK, + config, + PrecompileImplementation::PlonkChip, + stdin, + PgoConfig::None, + None, + ); + } + #[test] #[ignore = "Too long"] fn keccak_prove_mock() { @@ -1573,15 +1734,15 @@ mod tests { } } - const NON_POWDR_EXPECTED_MACHINE_COUNT: usize = 18; + const NON_POWDR_EXPECTED_MACHINE_COUNT: usize = 19; const NON_POWDR_EXPECTED_SUM: AirMetrics = AirMetrics { widths: AirWidths { - preprocessed: 5, - main: 797, - log_up: 676, + preprocessed: 7, + main: 798, + log_up: 684, }, constraints: 604, - bus_interactions: 252, + bus_interactions: 253, }; #[test] @@ -1991,11 +2152,11 @@ mod tests { AirMetrics { widths: AirWidths { preprocessed: 0, - main: 3268, - log_up: 5244, + main: 3254, + log_up: 5224, }, - constraints: 746, - bus_interactions: 2524, + constraints: 730, + bus_interactions: 2515, } "#]], powdr_expected_machine_count: expect![[r#" @@ -2008,13 +2169,13 @@ mod tests { AirWidthsDiff { before: AirWidths { preprocessed: 0, - main: 33079, - log_up: 42664, + main: 33065, + log_up: 42660, }, after: AirWidths { preprocessed: 0, - main: 3268, - log_up: 5244, + main: 3254, + log_up: 5224, }, } "#]]), diff --git a/openvm/src/powdr_extension/chip.rs b/openvm/src/powdr_extension/chip.rs index 3fd4f7641b..e7bde39679 100644 --- a/openvm/src/powdr_extension/chip.rs +++ b/openvm/src/powdr_extension/chip.rs @@ -1,125 +1,76 @@ // Mostly taken from [this openvm extension](https://github.com/openvm-org/openvm/blob/1b76fd5a900a7d69850ee9173969f70ef79c4c76/extensions/rv32im/circuit/src/auipc/core.rs#L1) -use std::{ - collections::BTreeMap, - sync::{Arc, Mutex}, -}; +use std::{cell::RefCell, collections::BTreeMap, rc::Rc, sync::Arc}; use crate::{ - extraction_utils::OriginalAirs, powdr_extension::executor::PowdrPeripheryInstances, - ExtendedVmConfig, Instr, + extraction_utils::{OriginalAirs, OriginalVmConfig}, + powdr_extension::{ + executor::OriginalArenas, + trace_generator::{PowdrPeripheryInstances, PowdrTraceGenerator}, + }, + Instr, }; -use super::{executor::PowdrExecutor, opcode::PowdrOpcode, PowdrPrecompile}; +use super::PowdrPrecompile; use itertools::Itertools; -use openvm_circuit::system::memory::MemoryController; -use openvm_circuit::{ - arch::{ExecutionState, InstructionExecutor, Result as ExecutionResult}, - system::memory::OfflineMemory, -}; -use openvm_instructions::{instruction::Instruction, LocalOpcode}; use openvm_stark_backend::{ p3_air::{Air, BaseAir}, + p3_matrix::dense::DenseMatrix, + prover::{hal::ProverBackend, types::AirProvingContext}, rap::ColumnsAir, }; use openvm_stark_backend::{ - config::{StarkGenericConfig, Val}, interaction::InteractionBuilder, p3_field::PrimeField32, p3_matrix::Matrix, - prover::types::AirProofInput, - rap::{AnyRap, BaseAirWithPublicValues, PartitionedBaseAir}, - Chip, ChipUsageGetter, + rap::{BaseAirWithPublicValues, PartitionedBaseAir}, + Chip, }; +use openvm_stark_sdk::p3_baby_bear::BabyBear; use powdr_autoprecompiles::{ expression::{AlgebraicEvaluator, AlgebraicReference, WitnessEvaluator}, Apc, }; -pub struct PowdrChip { +pub struct PowdrChip { pub name: String, - pub opcode: PowdrOpcode, - /// An "executor" for this chip, based on the original instructions in the basic block - pub executor: PowdrExecutor, - pub air: Arc>, + pub record_arena_by_air_name: Rc>, + pub trace_generator: PowdrTraceGenerator, } -impl PowdrChip { +impl PowdrChip { pub(crate) fn new( - precompile: PowdrPrecompile, - original_airs: OriginalAirs, - memory: Arc>>, - base_config: ExtendedVmConfig, + precompile: PowdrPrecompile, + original_airs: OriginalAirs, + base_config: OriginalVmConfig, periphery: PowdrPeripheryInstances, ) -> Self { let PowdrPrecompile { - name, opcode, apc, .. + name, + apc, + apc_record_arena, + .. } = precompile; - let air = Arc::new(PowdrAir::new(apc.clone())); - let executor = PowdrExecutor::new(original_airs, memory, base_config, periphery, apc); + let trace_generator = PowdrTraceGenerator::new(apc, original_airs, base_config, periphery); Self { name, - opcode, - executor, - air, + record_arena_by_air_name: apc_record_arena, + trace_generator, } } } -impl InstructionExecutor for PowdrChip { - fn execute( - &mut self, - memory: &mut MemoryController, - instruction: &Instruction, - from_state: ExecutionState, - ) -> ExecutionResult> { - let &Instruction { opcode, .. } = instruction; - assert_eq!(opcode.as_usize(), self.opcode.global_opcode().as_usize()); - - let execution_state = self.executor.execute(memory, from_state)?; - - Ok(execution_state) - } - - fn get_opcode_name(&self, _: usize) -> String { - self.name.clone() - } -} - -impl ChipUsageGetter for PowdrChip { - fn air_name(&self) -> String { - format!("powdr_air_for_opcode_{}", self.opcode.global_opcode()).to_string() - } - fn current_trace_height(&self) -> usize { - self.executor.number_of_calls() - } - - fn trace_width(&self) -> usize { - as BaseAir<_>>::width(&self.air) - } -} - -impl Chip for PowdrChip> -where - Val: PrimeField32, -{ - fn air(&self) -> Arc> { - self.air.clone() - } - - fn generate_air_proof_input(self) -> AirProofInput { +impl>>> Chip for PowdrChip { + fn generate_proving_ctx(&self, _: R) -> AirProvingContext { tracing::trace!("Generating air proof input for PowdrChip {}", self.name); - let width = self.trace_width(); - let labels = [("apc_opcode", self.opcode.global_opcode().to_string())]; - metrics::counter!("num_calls", &labels).absolute(self.executor.number_of_calls() as u64); - let trace = self.executor.generate_witness::(); - - assert_eq!(trace.width(), width); + let trace = self + .trace_generator + .generate_witness(self.record_arena_by_air_name.take()); - AirProofInput::simple(trace, vec![]) + AirProvingContext::simple(Arc::new(trace), vec![]) } } diff --git a/openvm/src/powdr_extension/executor/inventory.rs b/openvm/src/powdr_extension/executor/inventory.rs deleted file mode 100644 index ea91f6c21f..0000000000 --- a/openvm/src/powdr_extension/executor/inventory.rs +++ /dev/null @@ -1,144 +0,0 @@ -use derive_more::From; -use openvm_circuit::{ - arch::{SystemExecutor, SystemPeriphery, VmChipComplex, VmInventory}, - system::phantom::PhantomChip, -}; -use openvm_circuit_derive::{AnyEnum, InstructionExecutor}; -use openvm_circuit_primitives::{ - bitwise_op_lookup::SharedBitwiseOperationLookupChip, range_tuple::SharedRangeTupleCheckerChip, - var_range::SharedVariableRangeCheckerChip, Chip, ChipUsageGetter, -}; -use openvm_stark_backend::p3_field::PrimeField32; - -use crate::{ExtendedVmConfigExecutor, ExtendedVmConfigPeriphery}; - -/// A dummy inventory used for execution of autoprecompiles -/// It extends the `SdkVmConfigExecutor` and `SdkVmConfigPeriphery`, providing them with shared, pre-loaded periphery chips to avoid memory allocations by each SDK chip -pub type DummyInventory = VmInventory, DummyPeriphery>; -pub type DummyChipComplex = VmChipComplex, DummyPeriphery>; - -#[allow(clippy::large_enum_variant)] -#[derive(ChipUsageGetter, Chip, InstructionExecutor, AnyEnum, From)] -pub enum DummyExecutor { - #[any_enum] - Sdk(ExtendedVmConfigExecutor), - #[any_enum] - Shared(SharedExecutor), - #[any_enum] - /// We keep the `SystemExecutor` variant to allow for system-level operations. Failing to do this compiles, but at runtime since the `PhantomChip` cannot be found - /// This seems like a bug in openvm, as it breaks abstraction: wrapping an executor should not require the user to know about the system executor. - System(SystemExecutor), -} - -#[derive(ChipUsageGetter, Chip, AnyEnum, From)] -pub enum DummyPeriphery { - #[any_enum] - Sdk(ExtendedVmConfigPeriphery), - #[any_enum] - Shared(SharedPeriphery), - #[any_enum] - System(SystemPeriphery), -} - -#[derive(ChipUsageGetter, Chip, InstructionExecutor, From, AnyEnum)] -pub enum SharedExecutor { - Phantom(PhantomChip), -} - -#[derive(From, ChipUsageGetter, Chip, AnyEnum)] -pub enum SharedPeriphery { - BitwiseLookup8(SharedBitwiseOperationLookupChip<8>), - RangeChecker(SharedRangeTupleCheckerChip<2>), - VariableRangeChecker(SharedVariableRangeCheckerChip), - Phantom(PhantomChip), -} - -mod from_implementations { - - use super::{DummyExecutor, DummyPeriphery}; - use openvm_sdk::config::{SdkVmConfigExecutor, SdkVmConfigPeriphery}; - use openvm_stark_backend::p3_field::PrimeField32; - - // Import all the relevant executor and periphery types - use openvm_algebra_circuit::{ - Fp2ExtensionExecutor, Fp2ExtensionPeriphery, ModularExtensionExecutor, - ModularExtensionPeriphery, - }; - use openvm_bigint_circuit::{Int256Executor, Int256Periphery}; - use openvm_ecc_circuit::{WeierstrassExtensionExecutor, WeierstrassExtensionPeriphery}; - use openvm_keccak256_circuit::{Keccak256Executor, Keccak256Periphery}; - use openvm_native_circuit::{ - CastFExtensionExecutor, CastFExtensionPeriphery, NativeExecutor, NativePeriphery, - }; - use openvm_pairing_circuit::{PairingExtensionExecutor, PairingExtensionPeriphery}; - use openvm_rv32im_circuit::{ - Rv32IExecutor, Rv32IPeriphery, Rv32IoExecutor, Rv32IoPeriphery, Rv32MExecutor, - Rv32MPeriphery, - }; - use openvm_sha256_circuit::{Sha256Executor, Sha256Periphery}; - use powdr_openvm_hints_circuit::HintsExecutor; - use powdr_openvm_hints_circuit::HintsPeriphery; - - use crate::ExtendedVmConfigExecutor; - use crate::ExtendedVmConfigPeriphery; - - /// Defines `From for DummyExecutor` and `From for DummyPeriphery` - /// by mapping to the appropriate `SdkVmConfigExecutor` and `SdkVmConfigPeriphery` variant. - /// This cannot be derived because we have a custom implementation of this conversion for the `SystemExecutor`, avoiding this wrapping. - macro_rules! impl_zero_cost_conversions { - ($(($variant:ident, $executor_ty:ty, $periphery_ty:ty)),* $(,)?) => { - $( - impl From<$executor_ty> for DummyExecutor { - fn from(executor: $executor_ty) -> Self { - DummyExecutor::Sdk(ExtendedVmConfigExecutor::Sdk(SdkVmConfigExecutor::$variant(executor))) - } - } - - impl From<$periphery_ty> for DummyPeriphery { - fn from(periphery: $periphery_ty) -> Self { - DummyPeriphery::Sdk(ExtendedVmConfigPeriphery::Sdk(SdkVmConfigPeriphery::$variant(periphery))) - } - } - )* - }; - } - - impl From> for DummyExecutor { - fn from(executor: HintsExecutor) -> Self { - DummyExecutor::Sdk(ExtendedVmConfigExecutor::Hints(executor)) - } - } - - impl From> for DummyPeriphery { - fn from(executor: HintsPeriphery) -> Self { - DummyPeriphery::Sdk(ExtendedVmConfigPeriphery::Hints(executor)) - } - } - - impl_zero_cost_conversions!( - (Rv32i, Rv32IExecutor, Rv32IPeriphery), - (Io, Rv32IoExecutor, Rv32IoPeriphery), - (Keccak, Keccak256Executor, Keccak256Periphery), - (Sha256, Sha256Executor, Sha256Periphery), - (Native, NativeExecutor, NativePeriphery), - (Rv32m, Rv32MExecutor, Rv32MPeriphery), - (BigInt, Int256Executor, Int256Periphery), - ( - Modular, - ModularExtensionExecutor, - ModularExtensionPeriphery - ), - (Fp2, Fp2ExtensionExecutor, Fp2ExtensionPeriphery), - ( - Pairing, - PairingExtensionExecutor, - PairingExtensionPeriphery - ), - ( - Ecc, - WeierstrassExtensionExecutor, - WeierstrassExtensionPeriphery - ), - (CastF, CastFExtensionExecutor, CastFExtensionPeriphery), - ); -} diff --git a/openvm/src/powdr_extension/executor/mod.rs b/openvm/src/powdr_extension/executor/mod.rs index 3e4581e441..bd0cb0e312 100644 --- a/openvm/src/powdr_extension/executor/mod.rs +++ b/openvm/src/powdr_extension/executor/mod.rs @@ -1,375 +1,461 @@ use std::{ + borrow::{Borrow, BorrowMut}, + cell::RefCell, collections::HashMap, - sync::{Arc, Mutex}, + rc::Rc, + sync::Arc, }; use crate::{ - bus_map::DEFAULT_VARIABLE_RANGE_CHECKER, - extraction_utils::{get_name, OriginalAirs}, - powdr_extension::executor::{ - inventory::{DummyChipComplex, DummyInventory}, - periphery::SharedPeripheryChips, + extraction_utils::{ + record_arena_dimension_by_air_name_per_apc_call, OriginalAirs, OriginalVmConfig, }, - ExtendedVmConfig, Instr, + Instr, }; -use powdr_autoprecompiles::{ - expression::{AlgebraicEvaluator, ConcreteBusInteraction, MappingRowEvaluator, RowEvaluator}, - trace_handler::{generate_trace, TraceData, TraceTrait}, - Apc, +use openvm_circuit::arch::{ + execution_mode::{ExecutionCtx, MeteredCtx}, + E2PreCompute, PreflightExecutor, }; - -use itertools::Itertools; -use openvm_circuit::{ - arch::VmConfig, system::memory::MemoryController, utils::next_power_of_two_or_zero, -}; -use openvm_circuit::{ - arch::{ExecutionState, InstructionExecutor, Result as ExecutionResult, VmInventoryError}, - system::memory::{online::MemoryLogEntry, OfflineMemory}, -}; -use openvm_native_circuit::CastFExtension; +use openvm_circuit_derive::create_tco_handler; +use openvm_circuit_primitives::AlignedBytesBorrow; +use openvm_instructions::{instruction::Instruction, program::DEFAULT_PC_STEP}; +use openvm_sdk::config::SdkVmConfigExecutor; use openvm_stark_backend::{ - p3_field::FieldAlgebra, p3_matrix::dense::DenseMatrix, - p3_maybe_rayon::prelude::ParallelIterator, + p3_field::{Field, PrimeField32}, + p3_maybe_rayon::prelude::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}, }; +use openvm_stark_sdk::p3_baby_bear::BabyBear; +use powdr_autoprecompiles::Apc; -use openvm_stark_backend::p3_maybe_rayon::prelude::IndexedParallelIterator; -use openvm_stark_backend::{ - config::StarkGenericConfig, - p3_commit::{Pcs, PolynomialSpace}, - p3_maybe_rayon::prelude::ParallelSliceMut, - Chip, +use openvm_circuit::arch::{Arena, MatrixRecordArena}; +use openvm_circuit::{ + arch::{ + ExecuteFunc, ExecutionCtxTrait, ExecutionError, Executor, ExecutorInventory, + MeteredExecutionCtxTrait, MeteredExecutor, StaticProgramError, VmExecState, VmStateMut, + }, + system::memory::online::{GuestMemory, TracingMemory}, }; -use openvm_stark_backend::{p3_field::PrimeField32, p3_matrix::dense::RowMajorMatrix}; use powdr_autoprecompiles::InstructionHandler; -/// The inventory of the PowdrExecutor, which contains the executors for each opcode. -mod inventory; -/// The shared periphery chips used by the PowdrExecutor -mod periphery; - -pub use periphery::PowdrPeripheryInstances; -use powdr_constraint_solver::constraint_system::ComputationMethod; -use powdr_number::ExpressionConvertible; -use powdr_openvm_hints_circuit::HintsExtension; +/// A struct which holds the state of the execution based on the original instructions in this block and a dummy inventory. +pub struct PowdrExecutor { + pub air_by_opcode_id: OriginalAirs, + pub executor_inventory: ExecutorInventory>, + pub apc: Arc>>, + pub original_arenas: Rc>, + pub height_change: u32, +} -/// A wrapper around a DenseMatrix to implement `TraceTrait` which is required for `generate_trace`. -pub struct CpuTrace { - matrix: DenseMatrix, +/// A shared mutable reference to the arenas used to store the traces of the original instructions, accessed during preflight execution and trace generation. +/// The same reference is reused for all segments, under the assumption that segments are executed sequentially: preflight_0, tracegen_0, preflight_1, tracegen_1, ... +/// It goes through the following cycle for each segment: +/// - initialized at the beginning of preflight execution with the correct sizes for this segment +/// - written to during preflight execution +/// - read from during trace generation +/// - reset to uninitialized after trace generation +#[derive(Default)] +pub enum OriginalArenas { + #[default] + Uninitialized, + Initialized(InitializedOriginalArenas), } -impl TraceTrait for CpuTrace { - type Values = Vec; +impl OriginalArenas { + /// Given an estimate of how many times the APC is called in this segment, and the original airs and apc, + /// initializes the arenas iff not already initialized. + fn ensure_initialized( + &mut self, + apc_call_count_estimate: usize, + original_airs: &OriginalAirs, + apc: &Arc>>, + ) { + match self { + OriginalArenas::Uninitialized => { + *self = OriginalArenas::Initialized(InitializedOriginalArenas::new( + apc_call_count_estimate, + original_airs, + apc, + )); + } + OriginalArenas::Initialized(_) => {} + } + } - fn width(&self) -> usize { - self.matrix.width + /// Returns a mutable reference to the arenas. + /// - Panics if the arenas are not initialized. + pub fn arenas_mut(&mut self) -> &mut HashMap> { + match self { + OriginalArenas::Uninitialized => panic!("original arenas are uninitialized"), + OriginalArenas::Initialized(initialized) => &mut initialized.arenas, + } } - fn values(&self) -> &Self::Values { - &self.matrix.values + /// Returns a reference to the arenas. + /// - Panics if the arenas are not initialized. + pub fn arenas(&self) -> &HashMap> { + match self { + OriginalArenas::Uninitialized => panic!("original arenas are uninitialized"), + OriginalArenas::Initialized(initialized) => &initialized.arenas, + } + } + + /// Returns a mutable reference to the number of calls. + /// - Panics if the arenas are not initialized. + pub fn number_of_calls_mut(&mut self) -> &mut usize { + match self { + OriginalArenas::Uninitialized => panic!("original arenas are uninitialized"), + OriginalArenas::Initialized(initialized) => &mut initialized.number_of_calls, + } } -} -impl From> for CpuTrace { - fn from(matrix: DenseMatrix) -> Self { - Self { matrix } + /// Returns the number of calls. If not initialized, `Preflight::execute` is never called, and thus return 0. + pub fn number_of_calls(&self) -> usize { + match self { + OriginalArenas::Uninitialized => 0, + OriginalArenas::Initialized(initialized) => initialized.number_of_calls, + } } } -/// A struct which holds the state of the execution based on the original instructions in this block and a dummy inventory. -pub struct PowdrExecutor { - air_by_opcode_id: OriginalAirs, - inventory: DummyInventory, - number_of_calls: usize, - periphery: SharedPeripheryChips, - apc: Arc>>, +/// A collection of arenas used to store the records of the original instructions, one per air name. +/// Each arena is initialized with a capacity based on an estimate of how many times the APC is called in this segment +/// and how many calls to each air are made per APC call. +#[derive(Default)] +pub struct InitializedOriginalArenas { + pub arenas: HashMap>, + pub number_of_calls: usize, } -impl PowdrExecutor { +impl InitializedOriginalArenas { + /// Creates a new instance of `InitializedOriginalArenas`. pub fn new( - air_by_opcode_id: OriginalAirs, - memory: Arc>>, - base_config: ExtendedVmConfig, - periphery: PowdrPeripheryInstances, - apc: Arc>>, + apc_call_count_estimate: usize, + original_airs: &OriginalAirs, + apc: &Arc>>, ) -> Self { + let record_arena_dimensions = + record_arena_dimension_by_air_name_per_apc_call(apc, original_airs); Self { - air_by_opcode_id, - inventory: create_chip_complex_with_memory( - memory, - periphery.dummy, - base_config.clone(), - ) - .unwrap() - .inventory, + arenas: record_arena_dimensions + .iter() + .map( + |( + air_name, + RecordArenaDimension { + height: num_calls, + width: air_width, + }, + )| { + ( + air_name.clone(), + MatrixRecordArena::with_capacity( + *num_calls * apc_call_count_estimate, + *air_width, + ), + ) + }, + ) + .collect(), + // This is the actual number of calls, which we don't know yet. It will be updated during preflight execution. number_of_calls: 0, - periphery: periphery.real, - apc, } } +} - pub fn number_of_calls(&self) -> usize { - self.number_of_calls - } - - pub fn execute( - &mut self, - memory: &mut MemoryController, - from_state: ExecutionState, - ) -> ExecutionResult> { - // save the next available `RecordId` - let from_record_id = memory.get_memory_logs().len(); +/// The dimensions of a record arena for a given air name, used to initialize the arenas. +pub struct RecordArenaDimension { + pub height: usize, + pub width: usize, +} - // execute the original instructions one by one - let res = - self.apc - .instructions() - .iter() - .try_fold(from_state, |execution_state, instruction| { - let executor = self - .inventory - .get_mut_executor(&instruction.0.opcode) - .unwrap(); - executor.execute(memory, &instruction.0, execution_state) - }); +/// A struct to interpret the pre-compute data as for PowdrExecutor. +#[derive(AlignedBytesBorrow, Clone)] +#[repr(C)] +struct PowdrPreCompute { + height_change: u32, + original_instructions: Vec<(ExecuteFunc, Vec)>, +} - self.number_of_calls += 1; - let memory_logs = memory.get_memory_logs(); // exclusive range +impl Executor for PowdrExecutor { + fn pre_compute_size(&self) -> usize { + // TODO: do we know `ExecutionCtx` is correct? It's only one implementation of `ExecutionCtxTrait`. + // A clean fix would be to add `Ctx` as a generic parameter to this method in the `Executor` trait, but that would be a breaking change. + size_of::>() + } - let to_record_id = memory_logs.len(); + fn pre_compute( + &self, + pc: u32, + inst: &Instruction, + data: &mut [u8], + ) -> Result, StaticProgramError> + where + Ctx: ExecutionCtxTrait, + { + let pre_compute: &mut PowdrPreCompute = data.borrow_mut(); - let last_read_write = memory_logs[from_record_id..to_record_id] - .iter() - .rposition(|entry| { - matches!( - entry, - MemoryLogEntry::Read { .. } | MemoryLogEntry::Write { .. } - ) - }) - .map(|idx| idx + from_record_id); - - tracing::trace!( - "APC range (exclusive): {}..{} (last read/write at {})", - from_record_id, - to_record_id, - last_read_write.unwrap_or(to_record_id) - ); + self.pre_compute_impl::(pc, inst, pre_compute)?; - res + Ok(execute_e1_impl::) } - /// Generates the witness for the autoprecompile. The result will be a matrix of - /// size `next_power_of_two(number_of_calls) * width`, where `width` is the number of - /// nodes in the APC circuit. - pub fn generate_witness(self) -> RowMajorMatrix + #[cfg(feature = "tco")] + fn handler( + &self, + pc: u32, + inst: &Instruction, + data: &mut [u8], + ) -> Result, StaticProgramError> where - SC: StarkGenericConfig, - >::Domain: PolynomialSpace, + Ctx: ExecutionCtxTrait, { - let dummy_trace_by_air_name: HashMap<_, _> = self - .inventory - .executors - .into_iter() - .map(|executor| { - let air_name = get_name::(executor.air()); - let matrix = tracing::debug_span!("dummy trace", air_name = air_name.clone()) - .in_scope(|| { - Chip::::generate_air_proof_input(executor) - .raw - .common_main - .unwrap() - }); - (air_name.clone(), CpuTrace::from(matrix)) - }) - .collect(); - - let TraceData { - dummy_values, - dummy_trace_index_to_apc_index_by_instruction, - apc_poly_id_to_index, - columns_to_compute, - } = generate_trace( - &dummy_trace_by_air_name, - &self.air_by_opcode_id, - self.number_of_calls, - &self.apc, - ); - - // precompute the symbolic bus sends to the range checker for each original instruction - let range_checker_sends_per_original_instruction = self - .apc - .instructions() - .iter() - .map(|instruction| { - self.air_by_opcode_id - .get_instruction_air_and_id(instruction) - .1 - .bus_interactions - .iter() - .filter(|interaction| interaction.id == DEFAULT_VARIABLE_RANGE_CHECKER) - .collect_vec() - }) - .collect_vec(); - - // allocate for apc trace - let width = apc_poly_id_to_index.len(); - let height = next_power_of_two_or_zero(self.number_of_calls); - let mut values = ::zero_vec(height * width); - - // go through the final table and fill in the values - values - // a record is `width` values - .par_chunks_mut(width) - .zip(dummy_values) - .for_each(|(row_slice, dummy_values)| { - // map the dummy rows to the autoprecompile row - for ((dummy_row, range_checker_sends), dummy_trace_index_to_apc_index) in - dummy_values - .iter() - .map(|r| &r.data[r.start..r.start + r.length]) - .zip_eq(&range_checker_sends_per_original_instruction) - .zip_eq(&dummy_trace_index_to_apc_index_by_instruction) - { - let evaluator = RowEvaluator::new(dummy_row); - - range_checker_sends.iter().for_each(|interaction| { - let ConcreteBusInteraction { mult, mut args, .. } = - evaluator.eval_bus_interaction(interaction); - for _ in 0..mult.as_canonical_u32() { - self.periphery.range_checker.remove_count( - args.next().unwrap().as_canonical_u32(), - args.next().unwrap().as_canonical_u32() as usize, - ); - } - }); - - for (dummy_trace_index, apc_index) in dummy_trace_index_to_apc_index { - row_slice[*apc_index] = dummy_row[*dummy_trace_index]; - } - } - - // Fill in the columns we have to compute from other columns - // (these are either new columns or for example the "is_valid" column). - for (column, computation_method) in columns_to_compute { - let col_index = apc_poly_id_to_index[&column.id]; - row_slice[col_index] = match computation_method { - ComputationMethod::Constant(c) => *c, - ComputationMethod::InverseOrZero(expr) => { - let expr_val = expr.to_expression(&|n| *n, &|column_ref| { - row_slice[apc_poly_id_to_index[&column_ref.id]] - }); - if expr_val.is_zero() { - F::ZERO - } else { - expr_val.inverse() - } - } - }; - } - - let evaluator = MappingRowEvaluator::new(row_slice, &apc_poly_id_to_index); - // replay the side effects of this row on the main periphery - self.apc - .machine() - .bus_interactions - .iter() - .for_each(|interaction| { - let ConcreteBusInteraction { id, mult, args } = - evaluator.eval_bus_interaction(interaction); - self.periphery.apply( - id as u16, - mult.as_canonical_u32(), - args.map(|arg| arg.as_canonical_u32()), - ); - }); - }); - - RowMajorMatrix::new(values, width) + let pre_compute: &mut PowdrPreCompute = data.borrow_mut(); + self.pre_compute_impl::(pc, inst, pre_compute)?; + Ok(execute_e1_tco_handler::) } } -// Extracted from openvm, extended to create an inventory with the correct memory and periphery chips. -fn create_chip_complex_with_memory( - memory: Arc>>, - shared_chips: SharedPeripheryChips, - base_config: ExtendedVmConfig, -) -> std::result::Result, VmInventoryError> { - use openvm_keccak256_circuit::Keccak256; - use openvm_native_circuit::Native; - use openvm_rv32im_circuit::{Rv32I, Rv32Io}; - use openvm_sha256_circuit::Sha256; - - let this = base_config; - let mut complex: DummyChipComplex = this - .sdk_vm_config - .system - .config - .create_chip_complex()? - .transmute(); - - // CHANGE: inject the correct memory here to be passed to the chips, to be accessible in their get_proof_input - complex.base.memory_controller.offline_memory = memory.clone(); - complex.base.range_checker_chip = shared_chips.range_checker.clone(); - // END CHANGE - - // CHANGE: inject the periphery chips so that they are not created by the extensions. This is done for memory footprint: the dummy periphery chips are thrown away anyway, so we reuse a single one for all APCs. - complex = complex.extend(&shared_chips)?; - // END CHANGE - - if this.sdk_vm_config.rv32i.is_some() { - complex = complex.extend(&Rv32I)?; - } - if this.sdk_vm_config.io.is_some() { - complex = complex.extend(&Rv32Io)?; - } - if this.sdk_vm_config.keccak.is_some() { - complex = complex.extend(&Keccak256)?; - } - if this.sdk_vm_config.sha256.is_some() { - complex = complex.extend(&Sha256)?; +impl MeteredExecutor for PowdrExecutor { + fn metered_pre_compute_size(&self) -> usize { + // TODO: do we know `MeteredCtx` is correct? It's only one implementation of `MeteredExecutionCtxTrait`. + // A clean fix would be to add `Ctx` as a generic parameter to this method in the `MeteredExecutor` trait, but that would be a breaking change. + size_of::>>() } - if this.sdk_vm_config.native.is_some() { - complex = complex.extend(&Native)?; + + fn metered_pre_compute( + &self, + chip_idx: usize, + pc: u32, + inst: &Instruction, + data: &mut [u8], + ) -> Result, StaticProgramError> + where + Ctx: MeteredExecutionCtxTrait, + { + let pre_compute: &mut E2PreCompute> = data.borrow_mut(); + pre_compute.chip_idx = chip_idx as u32; + + self.pre_compute_impl::(pc, inst, &mut pre_compute.data)?; + + Ok(execute_e2_impl::) } - if this.sdk_vm_config.castf.is_some() { - complex = complex.extend(&CastFExtension)?; + + #[cfg(feature = "tco")] + fn metered_handler( + &self, + chip_idx: usize, + pc: u32, + inst: &Instruction, + data: &mut [u8], + ) -> Result, StaticProgramError> + where + Ctx: MeteredExecutionCtxTrait, + { + let pre_compute: &mut E2PreCompute> = data.borrow_mut(); + pre_compute.chip_idx = chip_idx as u32; + + self.pre_compute_impl::(pc, inst, &mut pre_compute.data)?; + + Ok(execute_e2_tco_handler::) } +} - if let Some(rv32m) = this.sdk_vm_config.rv32m { - let mut rv32m = rv32m; - if let Some(ref bigint) = this.sdk_vm_config.bigint { - rv32m.range_tuple_checker_sizes[0] = - rv32m.range_tuple_checker_sizes[0].max(bigint.range_tuple_checker_sizes[0]); - rv32m.range_tuple_checker_sizes[1] = - rv32m.range_tuple_checker_sizes[1].max(bigint.range_tuple_checker_sizes[1]); +impl PowdrExecutor { + /// The implementation of pre_compute, shared between Executor and MeteredExecutor. + #[inline] + fn pre_compute_impl( + &self, + pc: u32, + inst: &Instruction, + data: &mut PowdrPreCompute, + ) -> Result<(), StaticProgramError> + where + Ctx: ExecutionCtxTrait, + { + let &Instruction { + a, + b, + c, + d, + e, + f, + g, + .. + } = inst; + + // TODO: debug_assert that the opcode is the one we expect + + if !a.is_zero() + || !b.is_zero() + || !c.is_zero() + || !d.is_zero() + || !e.is_zero() + || !f.is_zero() + || !g.is_zero() + { + return Err(StaticProgramError::InvalidInstruction(pc)); } - complex = complex.extend(&rv32m)?; + + let executor_inventory = &self.executor_inventory; + // Set the data using the original instructions + *data = PowdrPreCompute { + height_change: self.height_change, + original_instructions: self + .apc + .block + .statements + .par_iter() + .enumerate() + .map(|(idx, instruction)| { + let executor = executor_inventory + .get_executor(instruction.0.opcode) + .ok_or(StaticProgramError::ExecutorNotFound { + opcode: instruction.0.opcode, + })?; + let pre_compute_size = executor.pre_compute_size(); + let mut pre_compute_data = vec![0u8; pre_compute_size]; + let execute_func = executor.pre_compute::( + pc + idx as u32 * DEFAULT_PC_STEP, + &instruction.0, + &mut pre_compute_data, + )?; + Ok((execute_func, pre_compute_data.to_vec())) + }) + .collect::, StaticProgramError>>()?, + }; + + Ok(()) } - if let Some(bigint) = this.sdk_vm_config.bigint { - let mut bigint = bigint; - if let Some(ref rv32m) = this.sdk_vm_config.rv32m { - bigint.range_tuple_checker_sizes[0] = - rv32m.range_tuple_checker_sizes[0].max(bigint.range_tuple_checker_sizes[0]); - bigint.range_tuple_checker_sizes[1] = - rv32m.range_tuple_checker_sizes[1].max(bigint.range_tuple_checker_sizes[1]); +} + +/// The implementation of the execute function, shared between Executor and MeteredExecutor. +#[inline(always)] +unsafe fn execute_e12_impl( + pre_compute: &PowdrPreCompute, + vm_state: &mut VmExecState, +) { + // Save the current instret, as we will overwrite it during execution of original instructions + let instret = vm_state.vm_state.instret; + let vm_state = + pre_compute + .original_instructions + .iter() + .fold(vm_state, |vm_state, (executor, data)| { + executor(data, vm_state); + vm_state + }); + // Restore the instret and increment it by one, since we executed a single apc instruction + vm_state.vm_state.instret = instret + 1; +} + +#[create_tco_handler] +unsafe fn execute_e1_impl( + pre_compute: &[u8], + vm_state: &mut VmExecState, +) { + let pre_compute: &PowdrPreCompute = pre_compute.borrow(); + execute_e12_impl::(pre_compute, vm_state); +} + +#[create_tco_handler] +unsafe fn execute_e2_impl( + pre_compute: &[u8], + vm_state: &mut VmExecState, +) { + let pre_compute: &E2PreCompute> = pre_compute.borrow(); + vm_state.ctx.on_height_change( + pre_compute.chip_idx as usize, + pre_compute.data.height_change, + ); + execute_e12_impl::(&pre_compute.data, vm_state); +} + +impl PreflightExecutor for PowdrExecutor { + fn execute( + &self, + state: VmStateMut>, + _: &Instruction, + ) -> Result<(), ExecutionError> { + // Extract the state components, since `execute` consumes the state but we need to pass it to each instruction execution + let VmStateMut { + pc, + memory, + streams, + rng, + custom_pvs, + ctx, + #[cfg(feature = "metrics")] + metrics, + } = state; + + // Initialize the original arenas if not already initialized + let mut original_arenas = self.original_arenas.as_ref().borrow_mut(); + + original_arenas.ensure_initialized( + // Recover an estimate of how many times the APC is called in this segment based on the current ctx height and width + ctx.trace_buffer.len() / ctx.width, + &self.air_by_opcode_id, + &self.apc, + ); + + let arenas = original_arenas.arenas_mut(); + + // execute the original instructions one by one + for instruction in self.apc.instructions() { + let executor = self + .executor_inventory + .get_executor(instruction.0.opcode) + .unwrap(); + + let air_name = self + .air_by_opcode_id + .get_instruction_air_and_id(instruction) + .0; + + let state = VmStateMut { + pc, + memory, + streams, + rng, + custom_pvs, + // We execute in the context of the relevant original table + ctx: arenas.get_mut(&air_name).unwrap(), + // TODO: should we pass around the same metrics object, or snapshot it at the beginning of this method and apply a single update at the end? + #[cfg(feature = "metrics")] + metrics, + }; + + executor.execute(state, &instruction.0)?; } - complex = complex.extend(&bigint)?; - } - if let Some(ref modular) = this.sdk_vm_config.modular { - complex = complex.extend(modular)?; - } - if let Some(ref fp2) = this.sdk_vm_config.fp2 { - complex = complex.extend(fp2)?; - } - if let Some(ref pairing) = this.sdk_vm_config.pairing { - complex = complex.extend(pairing)?; + + // Update the real number of calls to the APC + *original_arenas.number_of_calls_mut() += 1; + + Ok(()) } - if let Some(ref ecc) = this.sdk_vm_config.ecc { - complex = complex.extend(ecc)?; + + fn get_opcode_name(&self, opcode: usize) -> String { + format!("APC_{opcode}") } +} - // add custom extensions - complex = complex.extend(&HintsExtension)?; +use openvm_circuit::arch::VmExecutionConfig; - Ok(complex) +impl PowdrExecutor { + pub fn new( + air_by_opcode_id: OriginalAirs, + base_config: OriginalVmConfig, + apc: Arc>>, + record_arena_by_air_name: Rc>, + height_change: u32, + ) -> Self { + Self { + air_by_opcode_id, + executor_inventory: base_config.sdk_config.sdk.create_executors().unwrap(), + apc, + original_arenas: record_arena_by_air_name, + height_change, + } + } } diff --git a/openvm/src/powdr_extension/executor/trace_handler.rs b/openvm/src/powdr_extension/executor/trace_handler.rs deleted file mode 100644 index 4cc305e0f4..0000000000 --- a/openvm/src/powdr_extension/executor/trace_handler.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::Instr; -use openvm_stark_backend::p3_field::PrimeField32; -use powdr_autoprecompiles::trace_handler::{Trace, TraceHandler}; -use std::collections::HashMap; - -pub struct OpenVmTraceHandler<'a, F: PrimeField32> { - pub air_id_to_dummy_trace: &'a HashMap>, - pub original_instruction_air_ids: Vec, - pub apc_call_count: usize, -} - -impl<'a, F: PrimeField32> OpenVmTraceHandler<'a, F> { - pub fn new( - air_id_to_dummy_trace: &'a HashMap>, - original_instruction_air_ids: Vec, - apc_call_count: usize, - ) -> Self { - Self { - air_id_to_dummy_trace, - original_instruction_air_ids, - apc_call_count, - } - } -} - -impl<'a, F: PrimeField32> TraceHandler for OpenVmTraceHandler<'a, F> { - type AirId = String; - type Field = F; - type Instruction = Instr; - - fn original_instruction_air_ids(&self) -> Vec { - self.original_instruction_air_ids.clone() - } - - fn apc_call_count(&self) -> usize { - self.apc_call_count - } - - fn air_id_to_dummy_trace(&self) -> &'a HashMap> { - self.air_id_to_dummy_trace - } -} diff --git a/openvm/src/powdr_extension/mod.rs b/openvm/src/powdr_extension/mod.rs index 85200c82b3..cfa286af2d 100644 --- a/openvm/src/powdr_extension/mod.rs +++ b/openvm/src/powdr_extension/mod.rs @@ -2,6 +2,9 @@ pub mod chip; /// The executor for the powdr instructions pub mod executor; +/// The trace generator for the powdr instructions +pub mod trace_generator; + /// The opcodes for the powdr instructions, which is used in the chip implementation and contains the opcode ID pub mod opcode; /// The integration of our extension with the VM @@ -10,4 +13,5 @@ mod vm; mod plonk; pub use opcode::PowdrOpcode; -pub use vm::{PowdrExecutor, PowdrExtension, PowdrPeriphery, PowdrPrecompile}; +pub use plonk::{air::PlonkAir, chip::PlonkChip}; +pub use vm::{PowdrExtension, PowdrExtensionExecutor, PowdrPrecompile}; diff --git a/openvm/src/powdr_extension/plonk/chip.rs b/openvm/src/powdr_extension/plonk/chip.rs index 7d64dda6df..046d3e047e 100644 --- a/openvm/src/powdr_extension/plonk/chip.rs +++ b/openvm/src/powdr_extension/plonk/chip.rs @@ -1,130 +1,81 @@ use std::borrow::BorrowMut; +use std::cell::RefCell; use std::collections::BTreeMap; -use std::sync::{Arc, Mutex}; +use std::rc::Rc; +use std::sync::Arc; use crate::bus_map::BusMap; -use crate::extraction_utils::OriginalAirs; +use crate::extraction_utils::{OriginalAirs, OriginalVmConfig}; use crate::plonk::air_to_plonkish::build_circuit; use crate::plonk::{Gate, Variable}; -use crate::powdr_extension::executor::{PowdrExecutor, PowdrPeripheryInstances}; +use crate::powdr_extension::executor::OriginalArenas; use crate::powdr_extension::plonk::air::PlonkColumns; use crate::powdr_extension::plonk::copy_constraint::generate_permutation_columns; -use crate::powdr_extension::PowdrOpcode; +use crate::powdr_extension::trace_generator::{PowdrPeripheryInstances, PowdrTraceGenerator}; use crate::powdr_extension::PowdrPrecompile; -use crate::{ExtendedVmConfig, Instr}; +use crate::Instr; use itertools::Itertools; use openvm_circuit::utils::next_power_of_two_or_zero; -use openvm_circuit::{ - arch::{ExecutionState, InstructionExecutor, Result as ExecutionResult}, - system::memory::{MemoryController, OfflineMemory}, -}; -use openvm_instructions::instruction::Instruction; -use openvm_instructions::LocalOpcode; -use openvm_stark_backend::p3_air::BaseAir; -use openvm_stark_backend::p3_field::FieldAlgebra; -use openvm_stark_backend::p3_matrix::dense::RowMajorMatrix; -use openvm_stark_backend::p3_matrix::Matrix; use openvm_stark_backend::{ - config::{StarkGenericConfig, Val}, - p3_field::PrimeField32, - prover::types::AirProofInput, - rap::AnyRap, - Chip, ChipUsageGetter, + p3_field::{FieldAlgebra, PrimeField32}, + p3_matrix::{ + dense::{DenseMatrix, RowMajorMatrix}, + Matrix, + }, + prover::{hal::ProverBackend, types::AirProvingContext}, + Chip, }; +use openvm_stark_sdk::p3_baby_bear::BabyBear; use powdr_autoprecompiles::expression::AlgebraicReference; use powdr_autoprecompiles::Apc; -use super::air::PlonkAir; - -pub struct PlonkChip { +pub struct PlonkChip { name: String, - opcode: PowdrOpcode, - air: Arc>, - executor: PowdrExecutor, - apc: Arc>>, + apc: Arc>>, bus_map: BusMap, + trace_generator: PowdrTraceGenerator, + record_arena_by_air_name: Rc>, } -impl PlonkChip { - #[allow(dead_code)] +impl PlonkChip { pub(crate) fn new( - precompile: PowdrPrecompile, - original_airs: OriginalAirs, - memory: Arc>>, - base_config: ExtendedVmConfig, + precompile: PowdrPrecompile, + original_airs: OriginalAirs, + base_config: OriginalVmConfig, periphery: PowdrPeripheryInstances, bus_map: BusMap, - copy_constraint_bus_id: u16, ) -> Self { let PowdrPrecompile { - name, opcode, apc, .. + name, + apc, + apc_record_arena, + .. } = precompile; - let air = Arc::new(PlonkAir { - copy_constraint_bus_id, - bus_map: bus_map.clone(), - _marker: std::marker::PhantomData, - }); - let executor = - PowdrExecutor::new(original_airs, memory, base_config, periphery, apc.clone()); + let trace_generator = PowdrTraceGenerator::new( + apc.clone(), + original_airs.clone(), + base_config.clone(), + periphery.clone(), + ); Self { name, - opcode, - air, - executor, bus_map, apc, + trace_generator, + record_arena_by_air_name: apc_record_arena, } } } -impl InstructionExecutor for PlonkChip { - fn execute( - &mut self, - memory: &mut MemoryController, - instruction: &Instruction, - from_state: ExecutionState, - ) -> ExecutionResult> { - let &Instruction { opcode, .. } = instruction; - assert_eq!(opcode.as_usize(), self.opcode.global_opcode().as_usize()); - - let execution_state = self.executor.execute(memory, from_state)?; - - Ok(execution_state) - } - - fn get_opcode_name(&self, _opcode: usize) -> String { - self.name.clone() - } -} - -impl ChipUsageGetter for PlonkChip { - fn air_name(&self) -> String { - format!("powdr_plonk_air_for_opcode_{}", self.opcode.global_opcode()).to_string() - } - fn current_trace_height(&self) -> usize { - self.executor.number_of_calls() - } - - fn trace_width(&self) -> usize { - self.air.width() - } -} - -impl Chip for PlonkChip> -where - Val: PrimeField32, -{ - fn air(&self) -> Arc> { - self.air.clone() - } - - fn generate_air_proof_input(self) -> AirProofInput { +impl>>> Chip for PlonkChip { + fn generate_proving_ctx(&self, _: R) -> AirProvingContext { tracing::debug!("Generating air proof input for PlonkChip {}", self.name); let plonk_circuit = build_circuit(self.apc.machine(), &self.bus_map); - let number_of_calls = self.executor.number_of_calls(); - let width = self.trace_width(); + let record_arena_by_air_name = self.record_arena_by_air_name.take(); + let number_of_calls = record_arena_by_air_name.number_of_calls(); + let width = self.apc.machine().main_columns().count(); let height = next_power_of_two_or_zero(number_of_calls * plonk_circuit.len()); tracing::debug!(" Number of calls: {number_of_calls}"); tracing::debug!(" Plonk gates: {}", plonk_circuit.len()); @@ -141,10 +92,12 @@ where .enumerate() .map(|(index, c)| (c.id, index)) .collect(); - let witness = self.executor.generate_witness::(); + let witness = self + .trace_generator + .generate_witness(record_arena_by_air_name); // TODO: This should be parallelized. - let mut values = >::zero_vec(height * width); + let mut values = BabyBear::zero_vec(height * width); let num_tmp_vars = plonk_circuit.num_tmp_vars(); for (call_index, witness) in witness.rows().take(number_of_calls).enumerate() { // Computing the trace values for the current call (starting at row call_index * circuit_length). @@ -212,7 +165,7 @@ where generate_permutation_columns(&mut values, &plonk_circuit, number_of_calls, width); - AirProofInput::simple(RowMajorMatrix::new(values, width), vec![]) + AirProvingContext::simple(Arc::new(RowMajorMatrix::new(values, width)), vec![]) } } diff --git a/openvm/src/powdr_extension/plonk/mod.rs b/openvm/src/powdr_extension/plonk/mod.rs index fded230cbc..54c8018172 100644 --- a/openvm/src/powdr_extension/plonk/mod.rs +++ b/openvm/src/powdr_extension/plonk/mod.rs @@ -1,3 +1,3 @@ -mod air; +pub mod air; pub mod chip; pub mod copy_constraint; diff --git a/openvm/src/powdr_extension/trace_generator/inventory.rs b/openvm/src/powdr_extension/trace_generator/inventory.rs new file mode 100644 index 0000000000..9fbb60db55 --- /dev/null +++ b/openvm/src/powdr_extension/trace_generator/inventory.rs @@ -0,0 +1,269 @@ +use derive_more::From; +use openvm_algebra_circuit::AlgebraCpuProverExt; +use openvm_bigint_circuit::Int256CpuProverExt; +use openvm_circuit::{ + arch::{ + AirInventory, AirInventoryError, ChipInventoryError, MatrixRecordArena, VmBuilder, + VmChipComplex, VmCircuitConfig, VmCircuitExtension, VmProverExtension, + }, + system::{phantom::PhantomExecutor, SystemChipInventory, SystemCpuBuilder, SystemExecutor}, +}; +use openvm_circuit_derive::{AnyEnum, Executor, MeteredExecutor, PreflightExecutor}; +use openvm_circuit_primitives::Chip; +use openvm_ecc_circuit::EccCpuProverExt; +use openvm_keccak256_circuit::Keccak256CpuProverExt; +use openvm_native_circuit::NativeCpuProverExt; +use openvm_pairing_circuit::PairingProverExt; +use openvm_rv32im_circuit::Rv32ImCpuProverExt; +use openvm_sdk::config::SdkVmConfig; +use openvm_sha256_circuit::Sha2CpuProverExt; +use openvm_stark_backend::{config::Val, p3_field::PrimeField32, prover::cpu::CpuBackend}; + +use crate::{ + powdr_extension::trace_generator::periphery::{ + SharedPeripheryChips, SharedPeripheryChipsCpuProverExt, + }, + BabyBearSC, ExtendedVmConfigExecutor, +}; + +use openvm_stark_sdk::config::baby_bear_poseidon2::BabyBearPoseidon2Engine; + +/// A dummy inventory used for execution of autoprecompiles +/// It extends the `SdkVmConfigExecutor` and `SdkVmConfigPeriphery`, providing them with shared, pre-loaded periphery chips to avoid memory allocations by each SDK chip +pub type DummyChipComplex = + VmChipComplex>, CpuBackend, SystemChipInventory>; + +#[allow(clippy::large_enum_variant)] +#[derive(Chip, PreflightExecutor, Executor, MeteredExecutor, From, AnyEnum)] +pub enum DummyExecutor { + #[any_enum] + Sdk(ExtendedVmConfigExecutor), + #[any_enum] + Shared(SharedExecutor), + #[any_enum] + /// We keep the `SystemExecutor` variant to allow for system-level operations. Failing to do this compiles, but at runtime since the `PhantomChip` cannot be found + /// This seems like a bug in openvm, as it breaks abstraction: wrapping an executor should not require the user to know about the system executor. + System(SystemExecutor), +} + +#[derive(Chip, PreflightExecutor, Executor, MeteredExecutor, From, AnyEnum)] +pub enum SharedExecutor { + Phantom(PhantomExecutor), +} + +mod from_implementations { + + use super::DummyExecutor; + use openvm_sdk::config::SdkVmConfigExecutor; + use openvm_stark_backend::p3_field::PrimeField32; + + // Import all the relevant executor types + use openvm_algebra_circuit::{Fp2ExtensionExecutor, ModularExtensionExecutor}; + + use openvm_bigint_circuit::Int256Executor; + use openvm_ecc_circuit::WeierstrassExtensionExecutor; + use openvm_keccak256_circuit::Keccak256Executor; + use openvm_native_circuit::{CastFExtensionExecutor, NativeExecutor}; + use openvm_pairing_circuit::PairingExtensionExecutor; + use openvm_rv32im_circuit::{Rv32IExecutor, Rv32IoExecutor, Rv32MExecutor}; + use openvm_sha256_circuit::Sha256Executor; + use powdr_openvm_hints_circuit::HintsExtensionExecutor; + + use crate::ExtendedVmConfigExecutor; + + /// Defines `From for DummyExecutor` and `From for DummyPeriphery` + /// by mapping to the appropriate `SdkVmConfigExecutor` and `SdkVmConfigPeriphery` variant. + /// This cannot be derived because we have a custom implementation of this conversion for the `SystemExecutor`, avoiding this wrapping. + macro_rules! impl_zero_cost_conversions { + ($(($variant:ident, $executor_ty:ty)),* $(,)?) => { + $( + impl From<$executor_ty> for DummyExecutor { + fn from(executor: $executor_ty) -> Self { + DummyExecutor::Sdk(ExtendedVmConfigExecutor::Sdk(SdkVmConfigExecutor::$variant(executor))) + } + } + )* + }; + } + + impl From> for DummyExecutor { + fn from(executor: HintsExtensionExecutor) -> Self { + DummyExecutor::Sdk(ExtendedVmConfigExecutor::Hints(executor)) + } + } + + impl_zero_cost_conversions!( + (Rv32i, Rv32IExecutor), + (Io, Rv32IoExecutor), + (Keccak, Keccak256Executor), + (Sha256, Sha256Executor), + (Native, NativeExecutor), + (Rv32m, Rv32MExecutor), + (Bigint, Int256Executor), + (Modular, ModularExtensionExecutor), + (Fp2, Fp2ExtensionExecutor), + (Pairing, PairingExtensionExecutor), + (Ecc, WeierstrassExtensionExecutor), + (Castf, CastFExtensionExecutor), + ); +} + +pub fn create_dummy_airs( + config: &SdkVmConfig, + shared_chips: SharedPeripheryChips, +) -> Result, AirInventoryError> { + let config = config.to_inner(); + let mut inventory = config.system.create_airs()?; + + // CHANGE: add dummy periphery + inventory.start_new_extension(); + VmCircuitExtension::extend_circuit(&shared_chips, &mut inventory)?; + // END CHANGE + + if let Some(rv32i) = &config.rv32i { + VmCircuitExtension::extend_circuit(rv32i, &mut inventory)?; + } + if let Some(io) = &config.io { + VmCircuitExtension::extend_circuit(io, &mut inventory)?; + } + if let Some(keccak) = &config.keccak { + VmCircuitExtension::extend_circuit(keccak, &mut inventory)?; + } + if let Some(sha256) = &config.sha256 { + VmCircuitExtension::extend_circuit(sha256, &mut inventory)?; + } + if let Some(native) = &config.native { + VmCircuitExtension::extend_circuit(native, &mut inventory)?; + } + if let Some(castf) = &config.castf { + VmCircuitExtension::extend_circuit(castf, &mut inventory)?; + } + if let Some(rv32m) = &config.rv32m { + VmCircuitExtension::extend_circuit(rv32m, &mut inventory)?; + } + if let Some(bigint) = &config.bigint { + VmCircuitExtension::extend_circuit(bigint, &mut inventory)?; + } + if let Some(modular) = &config.modular { + VmCircuitExtension::extend_circuit(modular, &mut inventory)?; + } + if let Some(fp2) = &config.fp2 { + VmCircuitExtension::extend_circuit(fp2, &mut inventory)?; + } + if let Some(pairing) = &config.pairing { + VmCircuitExtension::extend_circuit(pairing, &mut inventory)?; + } + if let Some(ecc) = &config.ecc { + VmCircuitExtension::extend_circuit(ecc, &mut inventory)?; + } + Ok(inventory) +} + +pub fn create_dummy_chip_complex( + config: &SdkVmConfig, + circuit: AirInventory, + shared_chips: SharedPeripheryChips, +) -> Result, ChipInventoryError> { + let config = config.to_inner(); + let mut chip_complex = VmBuilder::::create_chip_complex( + &SystemCpuBuilder, + &config.system, + circuit, + )?; + let inventory = &mut chip_complex.inventory; + + // CHANGE: inject the periphery chips so that they are not created by the extensions. This is done for memory footprint: the dummy periphery chips are thrown away anyway, so we reuse a single one for all APCs. + VmProverExtension::::extend_prover( + &SharedPeripheryChipsCpuProverExt, + &shared_chips, + inventory, + )?; + // END CHANGE + + if let Some(rv32i) = &config.rv32i { + VmProverExtension::::extend_prover( + &Rv32ImCpuProverExt, + rv32i, + inventory, + )?; + } + if let Some(io) = &config.io { + VmProverExtension::::extend_prover( + &Rv32ImCpuProverExt, + io, + inventory, + )?; + } + if let Some(keccak) = &config.keccak { + VmProverExtension::::extend_prover( + &Keccak256CpuProverExt, + keccak, + inventory, + )?; + } + if let Some(sha256) = &config.sha256 { + VmProverExtension::::extend_prover( + &Sha2CpuProverExt, + sha256, + inventory, + )?; + } + if let Some(native) = &config.native { + VmProverExtension::::extend_prover( + &NativeCpuProverExt, + native, + inventory, + )?; + } + if let Some(castf) = &config.castf { + VmProverExtension::::extend_prover( + &NativeCpuProverExt, + castf, + inventory, + )?; + } + if let Some(rv32m) = &config.rv32m { + VmProverExtension::::extend_prover( + &Rv32ImCpuProverExt, + rv32m, + inventory, + )?; + } + if let Some(bigint) = &config.bigint { + VmProverExtension::::extend_prover( + &Int256CpuProverExt, + bigint, + inventory, + )?; + } + if let Some(modular) = &config.modular { + VmProverExtension::::extend_prover( + &AlgebraCpuProverExt, + modular, + inventory, + )?; + } + if let Some(fp2) = &config.fp2 { + VmProverExtension::::extend_prover( + &AlgebraCpuProverExt, + fp2, + inventory, + )?; + } + if let Some(pairing) = &config.pairing { + VmProverExtension::::extend_prover( + &PairingProverExt, + pairing, + inventory, + )?; + } + if let Some(ecc) = &config.ecc { + VmProverExtension::::extend_prover( + &EccCpuProverExt, + ecc, + inventory, + )?; + } + + Ok(chip_complex) +} diff --git a/openvm/src/powdr_extension/trace_generator/mod.rs b/openvm/src/powdr_extension/trace_generator/mod.rs new file mode 100644 index 0000000000..bda7347ab6 --- /dev/null +++ b/openvm/src/powdr_extension/trace_generator/mod.rs @@ -0,0 +1,204 @@ +use std::{collections::HashMap, sync::Arc}; + +use itertools::Itertools; +use openvm_circuit::{arch::AirInventory, utils::next_power_of_two_or_zero}; +use openvm_stark_backend::{ + p3_field::{Field, FieldAlgebra, PrimeField32}, + p3_matrix::dense::{DenseMatrix, RowMajorMatrix}, +}; +use openvm_stark_sdk::p3_baby_bear::BabyBear; +use powdr_autoprecompiles::{ + expression::{AlgebraicEvaluator, ConcreteBusInteraction, MappingRowEvaluator}, + trace_handler::{generate_trace, TraceData, TraceTrait}, + Apc, +}; +use powdr_constraint_solver::constraint_system::ComputationMethod; +use powdr_number::ExpressionConvertible; + +use crate::{ + extraction_utils::{OriginalAirs, OriginalVmConfig}, + powdr_extension::{ + executor::OriginalArenas, + trace_generator::inventory::{create_dummy_airs, create_dummy_chip_complex}, + }, + BabyBearSC, Instr, +}; + +/// The inventory of the PowdrExecutor, which contains the executors for each opcode. +mod inventory; +/// The shared periphery chips used by the PowdrTraceGenerator +mod periphery; + +pub use periphery::PowdrPeripheryInstances; + +/// A wrapper around a DenseMatrix to implement `TraceTrait` which is required for `generate_trace`. +pub struct SharedCpuTrace { + matrix: Arc>, +} + +impl TraceTrait for SharedCpuTrace { + type Values = Vec; + + fn width(&self) -> usize { + self.matrix.width + } + + fn values(&self) -> &Self::Values { + &self.matrix.values + } +} + +impl From>> for SharedCpuTrace { + fn from(matrix: Arc>) -> Self { + Self { matrix } + } +} + +pub struct PowdrTraceGenerator { + pub apc: Arc>>, + pub original_airs: OriginalAirs, + pub config: OriginalVmConfig, + pub periphery: PowdrPeripheryInstances, +} + +impl PowdrTraceGenerator { + pub fn new( + apc: Arc>>, + original_airs: OriginalAirs, + config: OriginalVmConfig, + periphery: PowdrPeripheryInstances, + ) -> Self { + Self { + apc, + original_airs, + config, + periphery, + } + } + + /// Generates the witness for the autoprecompile. The result will be a matrix of + /// size `next_power_of_two(number_of_calls) * width`, where `width` is the number of + /// nodes in the APC circuit. + pub fn generate_witness( + &self, + mut original_arenas: OriginalArenas, + ) -> RowMajorMatrix { + let num_apc_calls = original_arenas.number_of_calls(); + if num_apc_calls == 0 { + // If the APC isn't called, early return with an empty trace. + let width = self.apc.machine().main_columns().count(); + return RowMajorMatrix::new(vec![], width); + } + + let chip_inventory = { + let airs: AirInventory = + create_dummy_airs(&self.config.sdk_config.sdk, self.periphery.dummy.clone()) + .expect("Failed to create dummy airs"); + + create_dummy_chip_complex( + &self.config.sdk_config.sdk, + airs, + self.periphery.dummy.clone(), + ) + .expect("Failed to create chip complex") + .inventory + }; + + let arenas = original_arenas.arenas_mut(); + + let dummy_trace_by_air_name: HashMap> = chip_inventory + .chips() + .iter() + .enumerate() + .rev() + .filter_map(|(insertion_idx, chip)| { + let air_name = chip_inventory.airs().ext_airs()[insertion_idx].name(); + + let record_arena = { + match arenas.remove(&air_name) { + Some(ra) => ra, + None => return None, // skip this iteration, because we only have record arena for chips that are used + } + }; + + let shared_trace = chip.generate_proving_ctx(record_arena).common_main.unwrap(); + + Some((air_name, SharedCpuTrace::from(shared_trace))) + }) + .collect(); + + let TraceData { + dummy_values, + dummy_trace_index_to_apc_index_by_instruction, + apc_poly_id_to_index, + columns_to_compute, + } = generate_trace( + &dummy_trace_by_air_name, + &self.original_airs, + num_apc_calls, + &self.apc, + ); + + // allocate for apc trace + let width = apc_poly_id_to_index.len(); + let height = next_power_of_two_or_zero(num_apc_calls); + let mut values = ::zero_vec(height * width); + + // go through the final table and fill in the values + values + // a record is `width` values + // TODO: optimize by parallelizing on chunks of rows, currently fails because `dyn AnyChip>>` is not `Send` + .chunks_mut(width) + .zip(dummy_values) + .for_each(|(row_slice, dummy_values)| { + // map the dummy rows to the autoprecompile row + for (dummy_row, dummy_trace_index_to_apc_index) in dummy_values + .iter() + .map(|r| &r.data[r.start..r.start + r.length]) + .zip_eq(&dummy_trace_index_to_apc_index_by_instruction) + { + for (dummy_trace_index, apc_index) in dummy_trace_index_to_apc_index { + row_slice[*apc_index] = dummy_row[*dummy_trace_index]; + } + } + + // Fill in the columns we have to compute from other columns + // (these are either new columns or for example the "is_valid" column). + for (column, computation_method) in columns_to_compute { + let col_index = apc_poly_id_to_index[&column.id]; + row_slice[col_index] = match computation_method { + ComputationMethod::Constant(c) => *c, + ComputationMethod::InverseOrZero(expr) => { + let expr_val = expr.to_expression(&|n| *n, &|column_ref| { + row_slice[apc_poly_id_to_index[&column_ref.id]] + }); + if expr_val.is_zero() { + BabyBear::ZERO + } else { + expr_val.inverse() + } + } + }; + } + + let evaluator = MappingRowEvaluator::new(row_slice, &apc_poly_id_to_index); + + // replay the side effects of this row on the main periphery + self.apc + .machine() + .bus_interactions + .iter() + .for_each(|interaction| { + let ConcreteBusInteraction { id, mult, args } = + evaluator.eval_bus_interaction(interaction); + self.periphery.real.apply( + id as u16, + mult.as_canonical_u32(), + args.map(|arg| arg.as_canonical_u32()), + ); + }); + }); + + RowMajorMatrix::new(values, width) + } +} diff --git a/openvm/src/powdr_extension/executor/periphery.rs b/openvm/src/powdr_extension/trace_generator/periphery.rs similarity index 54% rename from openvm/src/powdr_extension/executor/periphery.rs rename to openvm/src/powdr_extension/trace_generator/periphery.rs index bc285ab91d..1a6472b659 100644 --- a/openvm/src/powdr_extension/executor/periphery.rs +++ b/openvm/src/powdr_extension/trace_generator/periphery.rs @@ -1,11 +1,24 @@ -use crate::powdr_extension::executor::inventory::{SharedExecutor, SharedPeriphery}; use itertools::Itertools; -use openvm_circuit::arch::VmExtension; +use openvm_circuit::arch::{ + AirInventory, AirInventoryError, ChipInventory, ChipInventoryError, ExecutorInventoryBuilder, + ExecutorInventoryError, RowMajorMatrixArena, VmCircuitExtension, VmExecutionExtension, + VmProverExtension, +}; use openvm_circuit_primitives::{ - bitwise_op_lookup::SharedBitwiseOperationLookupChip, range_tuple::SharedRangeTupleCheckerChip, - var_range::SharedVariableRangeCheckerChip, + bitwise_op_lookup::{ + BitwiseOperationLookupAir, BitwiseOperationLookupChip, SharedBitwiseOperationLookupChip, + }, + range_tuple::{RangeTupleCheckerAir, RangeTupleCheckerChip, SharedRangeTupleCheckerChip}, + var_range::{SharedVariableRangeCheckerChip, VariableRangeCheckerAir}, +}; +use openvm_stark_backend::{ + config::{StarkGenericConfig, Val}, + p3_field::PrimeField32, + prover::cpu::{CpuBackend, CpuDevice}, }; -use openvm_stark_backend::p3_field::PrimeField32; +use openvm_stark_sdk::engine::StarkEngine; + +use crate::powdr_extension::trace_generator::inventory::DummyExecutor; /// The shared chips which can be used by the PowdrChip. #[derive(Clone)] @@ -25,71 +38,122 @@ pub struct SharedPeripheryChips { impl PowdrPeripheryInstances { pub(crate) fn new( - range_checker: &SharedVariableRangeCheckerChip, - bitwise_8: Option<&SharedBitwiseOperationLookupChip<8>>, - tuple_range_checker: Option<&SharedRangeTupleCheckerChip<2>>, + range_checker: SharedVariableRangeCheckerChip, + bitwise_8: Option>, + tuple_range_checker: Option>, ) -> Self { Self { real: SharedPeripheryChips { - bitwise_lookup_8: bitwise_8.cloned(), + bitwise_lookup_8: bitwise_8.clone(), range_checker: range_checker.clone(), - tuple_range_checker: tuple_range_checker.cloned(), + tuple_range_checker: tuple_range_checker.clone(), }, // Bitwise lookup and tuple range checker do not need to be shared with the main execution: // If we did share, we'd have to roll back the side effects of execution and apply the side effects from the apc air onto the main periphery. // By not sharing them, we can throw away the dummy ones after execution and only apply the side effects from the apc air onto the main periphery. dummy: SharedPeripheryChips { - bitwise_lookup_8: bitwise_8 - .map(|bitwise_8| SharedBitwiseOperationLookupChip::new(bitwise_8.bus())), + bitwise_lookup_8: bitwise_8.map(|bitwise_8| { + SharedBitwiseOperationLookupChip::new(BitwiseOperationLookupChip::new( + bitwise_8.bus(), + )) + }), range_checker: range_checker.clone(), tuple_range_checker: tuple_range_checker.map(|tuple_range_checker| { - SharedRangeTupleCheckerChip::new(*tuple_range_checker.bus()) + SharedRangeTupleCheckerChip::new(RangeTupleCheckerChip::new( + *tuple_range_checker.bus(), + )) }), }, } } } +impl VmExecutionExtension for SharedPeripheryChips { + type Executor = DummyExecutor; + + fn extend_execution( + &self, + _: &mut ExecutorInventoryBuilder, + ) -> Result<(), ExecutorInventoryError> { + // No executor to add for periphery chips + Ok(()) + } +} + +impl VmCircuitExtension for SharedPeripheryChips { + fn extend_circuit(&self, inventory: &mut AirInventory) -> Result<(), AirInventoryError> { + // create dummy airs + if let Some(bitwise_lookup_8) = &self.bitwise_lookup_8 { + assert!(inventory + .find_air::>() + .next() + .is_none()); + inventory.add_air(BitwiseOperationLookupAir::<8>::new( + bitwise_lookup_8.air.bus, + )); + } + + if let Some(tuple_range_checker) = &self.tuple_range_checker { + assert!(inventory + .find_air::>() + .next() + .is_none()); + inventory.add_air(RangeTupleCheckerAir::<2> { + bus: tuple_range_checker.air.bus, + }); + } + + // The range checker is already present in the builder because it's is used by the system, so we don't add it again. + assert!(inventory + .find_air::() + .next() + .is_some()); + + Ok(()) + } +} + +pub struct SharedPeripheryChipsCpuProverExt; + /// We implement an extension to make it easy to pre-load the shared chips into the VM inventory. -impl VmExtension for SharedPeripheryChips +// This implementation is specific to CpuBackend because the lookup chips (VariableRangeChecker, +// BitwiseOperationLookupChip) are specific to CpuBackend. +impl VmProverExtension for SharedPeripheryChipsCpuProverExt where - F: PrimeField32, + SC: StarkGenericConfig, + E: StarkEngine, PD = CpuDevice>, + RA: RowMajorMatrixArena>, + Val: PrimeField32, { - type Executor = SharedExecutor; - - type Periphery = SharedPeriphery; - - fn build( + fn extend_prover( &self, - builder: &mut openvm_circuit::arch::VmInventoryBuilder, - ) -> Result< - openvm_circuit::arch::VmInventory, - openvm_circuit::arch::VmInventoryError, - > { - let mut inventory = openvm_circuit::arch::VmInventory::new(); - + extension: &SharedPeripheryChips, + inventory: &mut ChipInventory>, + ) -> Result<(), ChipInventoryError> { // Sanity check that the shared chips are not already present in the builder. - if let Some(bitwise_lookup_8) = &self.bitwise_lookup_8 { - assert!(builder + if let Some(bitwise_lookup_8) = &extension.bitwise_lookup_8 { + assert!(inventory .find_chip::>() - .is_empty()); + .next() + .is_none()); inventory.add_periphery_chip(bitwise_lookup_8.clone()); } - if let Some(tuple_checker) = &self.tuple_range_checker { - assert!(builder + if let Some(tuple_checker) = &extension.tuple_range_checker { + assert!(inventory .find_chip::>() - .is_empty()); + .next() + .is_none()); inventory.add_periphery_chip(tuple_checker.clone()); } // The range checker is already present in the builder because it's is used by the system, so we don't add it again. - assert_eq!( - builder.find_chip::().len(), - 1 - ); + assert!(inventory + .find_chip::() + .next() + .is_some()); - Ok(inventory) + Ok(()) } } diff --git a/openvm/src/powdr_extension/vm.rs b/openvm/src/powdr_extension/vm.rs index d3ca208d16..f42add6435 100644 --- a/openvm/src/powdr_extension/vm.rs +++ b/openvm/src/powdr_extension/vm.rs @@ -1,43 +1,43 @@ // Mostly taken from [this openvm extension](https://github.com/openvm-org/openvm/blob/1b76fd5a900a7d69850ee9173969f70ef79c4c76/extensions/rv32im/circuit/src/extension.rs#L185) and simplified to only handle a single opcode with its necessary dependencies +use std::cell::RefCell; use std::iter::once; +use std::rc::Rc; use std::sync::Arc; use derive_more::From; -use openvm_circuit_derive::InstructionExecutor; +use openvm_circuit_derive::{Executor, MeteredExecutor, PreflightExecutor}; +use openvm_instructions::LocalOpcode; +use openvm_stark_sdk::p3_baby_bear::BabyBear; use crate::bus_map::BusMap; use crate::customize_exe::OvmApcStats; -use crate::extraction_utils::OriginalAirs; -use crate::powdr_extension::executor::PowdrPeripheryInstances; -use openvm_circuit::arch::VmInventoryError; +use crate::extraction_utils::{OriginalAirs, OriginalVmConfig}; +use crate::plonk::air_to_plonkish::build_circuit; +use crate::powdr_extension::chip::PowdrAir; +use crate::powdr_extension::executor::{OriginalArenas, PowdrExecutor}; +use crate::powdr_extension::PlonkAir; use openvm_circuit::{ - arch::{VmExtension, VmInventory}, - circuit_derive::{Chip, ChipUsageGetter}, + arch::{AirInventory, AirInventoryError, VmCircuitExtension, VmExecutionExtension}, + circuit_derive::Chip, derive::AnyEnum, - system::phantom::PhantomChip, }; -use openvm_circuit_primitives::bitwise_op_lookup::SharedBitwiseOperationLookupChip; -use openvm_circuit_primitives::range_tuple::SharedRangeTupleCheckerChip; -use openvm_circuit_primitives::var_range::SharedVariableRangeCheckerChip; -use openvm_instructions::LocalOpcode; use openvm_stark_backend::{ + config::{StarkGenericConfig, Val}, p3_field::{Field, PrimeField32}, - ChipUsageGetter, }; use powdr_autoprecompiles::Apc; use serde::{Deserialize, Serialize}; -use crate::{ExtendedVmConfig, ExtendedVmConfigPeriphery, Instr, PrecompileImplementation}; +use crate::{Instr, PrecompileImplementation}; -use super::plonk::chip::PlonkChip; -use super::{chip::PowdrChip, PowdrOpcode}; +use super::PowdrOpcode; #[derive(Clone, Deserialize, Serialize)] #[serde(bound = "F: Field")] pub struct PowdrExtension { pub precompiles: Vec>, - pub base_config: ExtendedVmConfig, + pub base_config: OriginalVmConfig, pub implementation: PrecompileImplementation, pub bus_map: BusMap, pub airs: OriginalAirs, @@ -50,6 +50,8 @@ pub struct PowdrPrecompile { pub opcode: PowdrOpcode, pub apc: Arc>>, pub apc_stats: Option, + #[serde(skip)] + pub apc_record_arena: Rc>, } impl PowdrPrecompile { @@ -64,6 +66,8 @@ impl PowdrPrecompile { opcode, apc, apc_stats, + // Initialize with empty Rc (default to OriginalArenas::Uninitialized) for each APC + apc_record_arena: Default::default(), } } } @@ -71,7 +75,7 @@ impl PowdrPrecompile { impl PowdrExtension { pub fn new( precompiles: Vec>, - base_config: ExtendedVmConfig, + base_config: OriginalVmConfig, implementation: PrecompileImplementation, bus_map: BusMap, airs: OriginalAirs, @@ -86,88 +90,64 @@ impl PowdrExtension { } } -#[derive(ChipUsageGetter, From, AnyEnum, InstructionExecutor, Chip)] +#[derive(From, AnyEnum, PreflightExecutor, Executor, MeteredExecutor, Chip)] #[allow(clippy::large_enum_variant)] -pub enum PowdrExecutor { - Powdr(PowdrChip), - Plonk(PlonkChip), +pub enum PowdrExtensionExecutor { + Powdr(PowdrExecutor), } -impl PowdrExecutor { - pub fn air_name(&self) -> String { - match self { - PowdrExecutor::Powdr(powdr_chip) => powdr_chip.air_name(), - PowdrExecutor::Plonk(plonk_chip) => plonk_chip.air_name(), - } - } -} +impl VmExecutionExtension for PowdrExtension { + type Executor = PowdrExtensionExecutor; -#[derive(From, ChipUsageGetter, Chip, AnyEnum)] -pub enum PowdrPeriphery { - Sdk(ExtendedVmConfigPeriphery), - Phantom(PhantomChip), -} - -impl VmExtension for PowdrExtension { - type Executor = PowdrExecutor; - - type Periphery = PowdrPeriphery; - - fn build( + fn extend_execution( &self, - builder: &mut openvm_circuit::arch::VmInventoryBuilder, - ) -> Result, VmInventoryError> { - let mut inventory = VmInventory::new(); - - let offline_memory = builder.system_base().offline_memory(); + inventory: &mut openvm_circuit::arch::ExecutorInventoryBuilder, + ) -> Result<(), openvm_circuit::arch::ExecutorInventoryError> { + for precompile in &self.precompiles { + let height_change = match self.implementation { + PrecompileImplementation::SingleRowChip => 1, + PrecompileImplementation::PlonkChip => { + let plonk_circuit = build_circuit(precompile.apc.machine(), &self.bus_map); + plonk_circuit.len() as u32 + } + }; - // TODO: here we make assumptions about the existence of some chips in the periphery. Make this more flexible - let bitwise_lookup = builder - .find_chip::>() - .first() - .cloned(); - let range_checker = *builder - .find_chip::() - .first() - .unwrap(); - let tuple_range_checker = builder - .find_chip::>() - .first() - .cloned(); + let powdr_executor = PowdrExtensionExecutor::Powdr(PowdrExecutor::new( + self.airs.clone(), + self.base_config.clone(), + precompile.apc.clone(), + precompile.apc_record_arena.clone(), + height_change, + )); + inventory.add_executor(powdr_executor, once(precompile.opcode.global_opcode()))?; + } - // Create the shared chips and the dummy shared chips - let shared_chips_pair = - PowdrPeripheryInstances::new(range_checker, bitwise_lookup, tuple_range_checker); + Ok(()) + } +} +impl VmCircuitExtension for PowdrExtension> +where + SC: StarkGenericConfig, + Val: PrimeField32, +{ + fn extend_circuit(&self, inventory: &mut AirInventory) -> Result<(), AirInventoryError> { for precompile in &self.precompiles { - let powdr_chip: PowdrExecutor = match self.implementation { - PrecompileImplementation::SingleRowChip => PowdrChip::new( - precompile.clone(), - self.airs.clone(), - offline_memory.clone(), - self.base_config.clone(), - shared_chips_pair.clone(), - ) - .into(), + match self.implementation { + PrecompileImplementation::SingleRowChip => { + inventory.add_air(PowdrAir::new(precompile.apc.clone())); + } PrecompileImplementation::PlonkChip => { - let copy_constraint_bus_id = builder.new_bus_idx(); - - PlonkChip::new( - precompile.clone(), - self.airs.clone(), - offline_memory.clone(), - self.base_config.clone(), - shared_chips_pair.clone(), - self.bus_map.clone(), + let copy_constraint_bus_id = inventory.new_bus_idx(); + let plonk_air = PlonkAir { copy_constraint_bus_id, - ) - .into() + bus_map: self.bus_map.clone(), + _marker: std::marker::PhantomData, + }; + inventory.add_air(plonk_air); } - }; - - inventory.add_executor(powdr_chip, once(precompile.opcode.global_opcode()))?; + } } - - Ok(inventory) + Ok(()) } } diff --git a/openvm/tests/common/mod.rs b/openvm/tests/common/mod.rs index 86a2d0ac2d..c1b4f76a97 100644 --- a/openvm/tests/common/mod.rs +++ b/openvm/tests/common/mod.rs @@ -1,5 +1,6 @@ use openvm_sdk::config::SdkVmConfig; use powdr_openvm::{extraction_utils::OriginalVmConfig, ExtendedVmConfig}; +use powdr_openvm_hints_circuit::HintsExtension; pub fn original_vm_config() -> OriginalVmConfig { let sdk_vm_config = SdkVmConfig::builder() @@ -9,7 +10,10 @@ pub fn original_vm_config() -> OriginalVmConfig { .io(Default::default()) .build(); - let ext_vm_config = ExtendedVmConfig { sdk_vm_config }; + let ext_vm_config = ExtendedVmConfig { + sdk: sdk_vm_config, + hints: HintsExtension, + }; OriginalVmConfig::new(ext_vm_config) } diff --git a/riscv-elf/src/debug_info.rs b/riscv-elf/src/debug_info.rs index 56dbcde9f5..9401ecebff 100644 --- a/riscv-elf/src/debug_info.rs +++ b/riscv-elf/src/debug_info.rs @@ -457,7 +457,7 @@ impl SymbolTable { } /// Get a symbol, or a default label formed from the address value. - pub fn get_one(&self, addr: u32) -> Cow { + pub fn get_one(&self, addr: u32) -> Cow<'_, str> { match self.try_get_one(addr) { Some(s) => Cow::Borrowed(s), None => Self::default_label(addr), @@ -465,7 +465,7 @@ impl SymbolTable { } /// Get all symbol, or a default label formed from the address value. - pub fn get_all(&self, addr: u32) -> impl Iterator> { + pub fn get_all(&self, addr: u32) -> impl Iterator> { static EMPTY: Vec = Vec::new(); let elems = self.0.get(&addr).unwrap_or(&EMPTY); let default = if elems.is_empty() { diff --git a/riscv-elf/src/lib.rs b/riscv-elf/src/lib.rs index fd594943f2..dc177e30e9 100644 --- a/riscv-elf/src/lib.rs +++ b/riscv-elf/src/lib.rs @@ -286,7 +286,7 @@ impl ElfProgram { } impl RiscVProgram for ElfProgram { - fn take_source_files_info(&mut self) -> impl Iterator { + fn take_source_files_info(&mut self) -> impl Iterator> { self.dbg .file_list .iter() @@ -326,7 +326,7 @@ impl RiscVProgram for ElfProgram { fn take_executable_statements( &mut self, - ) -> impl Iterator, impl InstructionArgs>> { + ) -> impl Iterator, impl InstructionArgs>> { // In the output, the precedence is labels, locations, and then instructions. // We merge the 3 iterators with this operations: merge(labels, merge(locs, instructions)), where each is sorted by address. @@ -1095,7 +1095,7 @@ struct RiscVInstructionIterator<'a> { } impl RiscVInstructionIterator<'_> { - fn new(base_addr: u32, data: &[u8]) -> RiscVInstructionIterator { + fn new(base_addr: u32, data: &[u8]) -> RiscVInstructionIterator<'_> { RiscVInstructionIterator { curr_address: base_addr, remaining_data: data, diff --git a/riscv-types/src/lib.rs b/riscv-types/src/lib.rs index c7af10a9f6..e03f8abc42 100644 --- a/riscv-types/src/lib.rs +++ b/riscv-types/src/lib.rs @@ -102,7 +102,7 @@ pub struct SourceFileInfo<'a> { /// A RISC-V program that can be translated to POWDR ASM. pub trait RiscVProgram { /// Takes the listing of source files, to be used in the debug statements. - fn take_source_files_info(&mut self) -> impl Iterator; + fn take_source_files_info(&mut self) -> impl Iterator>; /// Takes the initial memory snapshot. fn take_initial_mem(&mut self) -> impl Iterator; @@ -110,7 +110,7 @@ pub trait RiscVProgram { /// Takes the executable statements and labels. fn take_executable_statements( &mut self, - ) -> impl Iterator, impl InstructionArgs>>; + ) -> impl Iterator, impl InstructionArgs>>; /// Returns the addresses of the start and end of prover data. fn prover_data_bounds(&self) -> (u32, u32); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 7ee67e8480..58b88a3894 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "nightly-2025-05-14" +channel = "nightly-2025-10-01"