diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a995c65e..d0f4bd33 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -201,3 +201,54 @@ jobs: RUST_LOG=info FRI_QUERIES=1 cargo run --release --example test_riscv -- --field m31 RUST_LOG=info FRI_QUERIES=1 cargo run --release --example test_riscv -- --elf precompile --field m31 RUST_LOG=info FRI_QUERIES=1 cargo run --release --example test_riscv -- --elf poseidon2 --field m31 --n 2118082624 + + riscof: + if: true + name: RISCOF Arch Tests + runs-on: ubuntu-latest + steps: + - name: Configure git for private repo + run: | + git config --global url."https://${{ secrets.GH_TOKEN }}:@github.com/".insteadOf "https://github.com/" + - name: Checkout pico sources + uses: actions/checkout@v4 + with: + path: pico + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2025-08-04 + override: true + - name: Rust cache + uses: Swatinem/rust-cache@v2 + with: + workspaces: pico + - name: Build pico-cli + run: | + cd pico + cargo build --release -p pico-cli + - name: Checkout zkevm-test-monitor + uses: actions/checkout@v4 + with: + repository: eth-act/zkevm-test-monitor + path: zkevm-test-monitor + - name: Setup pico binary for RISCOF + run: | + mkdir -p zkevm-test-monitor/binaries + cp pico/target/release/cargo-pico zkevm-test-monitor/binaries/pico-binary + chmod +x zkevm-test-monitor/binaries/pico-binary + - name: Run RISCOF arch tests + run: | + cd zkevm-test-monitor + ./src/test.sh --arch pico + - name: Display test results + if: always() + run: | + cd zkevm-test-monitor + if [ -f "test-results/pico/summary-arch.json" ]; then + echo "📊 RISCOF Test Results:" + cat test-results/pico/summary-arch.json | jq '.' + else + echo "⚠️ No test results found" + fi diff --git a/sdk/cli/src/bin/cargo-pico.rs b/sdk/cli/src/bin/cargo-pico.rs index d47ad90a..d639a5fe 100644 --- a/sdk/cli/src/bin/cargo-pico.rs +++ b/sdk/cli/src/bin/cargo-pico.rs @@ -1,6 +1,8 @@ use anyhow::Result; use clap::{crate_version, Parser, Subcommand}; -use pico_cli::subcommand::{build::BuildCmd, new::NewCmd, prove::ProveCmd}; +use pico_cli::subcommand::{ + build::BuildCmd, new::NewCmd, prove::ProveCmd, test_emulator::TestEmulatorCmd, +}; use pico_sdk::init_logger; #[derive(Parser)] @@ -21,6 +23,7 @@ pub enum SubCommands { Build(BuildCmd), Prove(ProveCmd), New(NewCmd), + TestEmulator(TestEmulatorCmd), } fn main() -> Result<()> { @@ -32,5 +35,6 @@ fn main() -> Result<()> { SubCommands::Build(cmd) => cmd.run(), SubCommands::Prove(cmd) => cmd.run(), SubCommands::New(cmd) => cmd.run(), + SubCommands::TestEmulator(cmd) => cmd.run(), } } diff --git a/sdk/cli/src/subcommand/mod.rs b/sdk/cli/src/subcommand/mod.rs index ed861296..22e838b3 100644 --- a/sdk/cli/src/subcommand/mod.rs +++ b/sdk/cli/src/subcommand/mod.rs @@ -1,3 +1,4 @@ pub mod build; pub mod new; pub mod prove; +pub mod test_emulator; diff --git a/sdk/cli/src/subcommand/test_emulator.rs b/sdk/cli/src/subcommand/test_emulator.rs new file mode 100644 index 00000000..62cdd3c4 --- /dev/null +++ b/sdk/cli/src/subcommand/test_emulator.rs @@ -0,0 +1,57 @@ +use anyhow::Result; +use clap::Parser; +use log::debug; +use std::{fs::File, io::Write, path::PathBuf}; + +use pico_vm::compiler::riscv::disassembler::find_signature_region; + +#[derive(Parser)] +#[command( + name = "test-emulator", + about = "Execute ELF execution and collect RISCOF signatures" +)] +pub struct TestEmulatorCmd { + #[clap(long, help = "ELF file path")] + elf: String, + + #[clap(long, help = "Output signature file path")] + signatures: PathBuf, +} + +impl TestEmulatorCmd { + pub fn run(&self) -> Result<()> { + // Read ELF bytes + let elf_bytes = std::fs::read(&self.elf)?; + + // Parse symbols to find begin_signature and end_signature + let (begin, end) = find_signature_region(&elf_bytes) + .map_err(|e| anyhow::anyhow!("Failed to parse ELF symbols: {}", e))?; + + debug!( + "Found signature region: begin=0x{:08x}, end=0x{:08x}", + begin, end + ); + + // Create RiscvProver directly (hard-coded to KoalaBear for RISCOF) + use pico_vm::{ + configs::{config::StarkGenericConfig, stark_config::KoalaBearPoseidon2}, + proverchain::{InitialProverSetup, RiscvProver}, + }; + let prover = RiscvProver::new_initial_prover( + (KoalaBearPoseidon2::new(), &elf_bytes), + Default::default(), + None, + ); + + // Collect signatures + let signatures = prover.test_emulator(begin, end); + + // Write signatures to file + let mut file = File::create(&self.signatures)?; + for sig in signatures { + writeln!(file, "{:08x}", sig)?; + } + + Ok(()) + } +} diff --git a/vm/src/compiler/riscv/disassembler/elf.rs b/vm/src/compiler/riscv/disassembler/elf.rs index 21b15366..d91ef3ac 100644 --- a/vm/src/compiler/riscv/disassembler/elf.rs +++ b/vm/src/compiler/riscv/disassembler/elf.rs @@ -34,6 +34,32 @@ pub struct Elf { pub(crate) memory_image: Arc>, } +/// Parse symbol table from ELF to extract signature region boundary for RISCOF testing. +pub fn find_signature_region(elf_bytes: &[u8]) -> eyre::Result<(u32, u32)> { + let mut begin_signature = None; + let mut end_signature = None; + + let file = ElfBytes::::minimal_parse(elf_bytes)?; + + if let Some((symbol_table, string_table)) = file.symbol_table()? { + for sym in symbol_table.iter() { + if let Ok(name) = string_table.get(sym.st_name as usize) { + match name { + "begin_signature" => begin_signature = Some(sym.st_value as u32), + "end_signature" => end_signature = Some(sym.st_value as u32), + _ => {} + } + } + } + } + + let begin = + begin_signature.ok_or_else(|| eyre::eyre!("Missing begin_signature symbol in ELF"))?; + let end = end_signature.ok_or_else(|| eyre::eyre!("Missing end_signature symbol in ELF"))?; + + Ok((begin, end)) +} + impl Elf { /// Create a new [Elf]. /// diff --git a/vm/src/compiler/riscv/disassembler/mod.rs b/vm/src/compiler/riscv/disassembler/mod.rs index 81918ea5..e258a64b 100644 --- a/vm/src/compiler/riscv/disassembler/mod.rs +++ b/vm/src/compiler/riscv/disassembler/mod.rs @@ -4,3 +4,6 @@ mod rrs; pub(crate) use elf::*; pub(crate) use rrs::*; + +// Public re-export for RISCOF testing +pub use elf::find_signature_region; diff --git a/vm/src/emulator/riscv/emulator/mod.rs b/vm/src/emulator/riscv/emulator/mod.rs index 448631a1..fe5b1d18 100644 --- a/vm/src/emulator/riscv/emulator/mod.rs +++ b/vm/src/emulator/riscv/emulator/mod.rs @@ -1540,6 +1540,24 @@ impl RiscvEmulator { )); } } + + /// Collect signatures from memory between the given begin and end addresses + pub fn collect_signatures(&mut self, begin: u32, end: u32) -> Vec { + if begin >= end { + return Vec::new(); + } + + let size = (end - begin) as usize; + let mut signatures = Vec::with_capacity(size / 4); + + for offset in (0..size).step_by(4) { + let addr = begin + offset as u32; + let word = self.word(addr); + signatures.push(word); + } + + signatures + } } #[cfg(test)] diff --git a/vm/src/proverchain/riscv.rs b/vm/src/proverchain/riscv.rs index 316f9604..780d38ce 100644 --- a/vm/src/proverchain/riscv.rs +++ b/vm/src/proverchain/riscv.rs @@ -377,6 +377,40 @@ where (reports, pv_stream) } + /// Emulate and collect RISCOF signatures from memory between the given addresses + pub fn test_emulator(&self, begin: u32, end: u32) -> Vec { + use crate::emulator::riscv::riscv_emulator::RiscvEmulator; + + let mut emulator = + RiscvEmulator::new_single::>(self.program.clone(), self.opts, None); + + loop { + let mut batch_records = vec![]; + let result = emulator.emulate_batch(&mut |record| batch_records.push(record)); + + match result { + Ok(report) => { + if report.done { + // Normal completion + break; + } else { + // Continue execution + continue; + } + } + Err(err) => { + tracing::error!( + ?err, + "test_emulator: emulate_batch failed, will still try to collect signatures" + ); + break; + } + } + } + + emulator.collect_signatures(begin, end) + } + pub fn get_program(&self) -> Arc { self.program.clone() }