-
Notifications
You must be signed in to change notification settings - Fork 368
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Convert NVM Back to RV32 Base #164
Changes from all commits
9458b66
3399b29
249e609
09c9ff3
d0031d9
a2ce1e4
4827f98
c6583dc
ec91bd5
686289a
569e68a
b408268
2cf87da
02690ad
f237a3d
b789e96
05bf956
68e7640
3dfa8a6
4767256
b7c1a1f
6b621bf
c97369c
e841e81
4a88a75
b90d2ad
d813f75
d062c7b
38edd94
0a39c97
90c051f
5375f76
447f228
d685995
d4aa98e
0d07baa
556a4f5
48d9aa2
506f9c1
9090eae
0323018
bc82783
d130aa2
b922877
adc3886
b623018
f60ef84
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
mod nvm; | ||
mod r1cs; | ||
mod riscv; | ||
mod step; | ||
|
||
#[cfg(test)] | ||
mod test; | ||
|
||
pub use nvm::ARITY; | ||
pub use r1cs::F; | ||
pub use riscv::ARITY; | ||
pub use step::build_constraints; |
This file was deleted.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,8 +16,8 @@ use crate::{ | |
}; | ||
|
||
use super::{ | ||
nvm::{step, ARITY}, | ||
r1cs::{R1CS, V, ZERO}, | ||
riscv::{step, ARITY}, | ||
F, | ||
}; | ||
|
||
|
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,188 @@ | ||
#![allow(dead_code)] | ||
//! A RISC-V virtual machine designed for verified computing | ||
#![allow(non_snake_case)] | ||
#![allow(clippy::needless_range_loop)] | ||
#![allow(clippy::field_reassign_with_default)] | ||
|
||
// We rely on this in cacheline.rs | ||
#[cfg(not(target_endian = "little"))] | ||
compile_error!("Host must be little-endian"); | ||
|
||
pub mod error; | ||
pub mod eval; | ||
pub mod instructions; | ||
pub mod machines; | ||
pub mod rv32; | ||
|
||
pub mod syscalls; | ||
pub mod trace; | ||
|
||
mod ark_serde; | ||
pub mod memory; | ||
|
||
pub mod circuit; | ||
|
||
use clap::Args; | ||
use elf::{abi::PT_LOAD, endian::LittleEndian, ElfBytes}; | ||
use std::fs::read; | ||
use std::path::PathBuf; | ||
use std::time::Instant; | ||
|
||
pub use error::*; | ||
use eval::*; | ||
use memory::*; | ||
use rv32::*; | ||
use trace::*; | ||
|
||
// don't break API | ||
pub use machines::{loop_vm, nop_vm}; | ||
|
||
// re-export | ||
#[doc(hidden)] | ||
pub use elf; | ||
|
||
/// Load a VM state from an ELF file | ||
pub fn load_elf<M: Memory>(path: &PathBuf) -> Result<NexusVM<M>> { | ||
let file_data = read(path)?; | ||
let slice = file_data.as_slice(); | ||
parse_elf(slice) | ||
} | ||
|
||
#[doc(hidden)] | ||
pub fn parse_elf_bytes(bytes: &[u8]) -> Result<ElfBytes<LittleEndian>> { | ||
let file = ElfBytes::<LittleEndian>::minimal_parse(bytes)?; | ||
Ok(file) | ||
} | ||
|
||
#[doc(hidden)] | ||
pub fn init_vm<M: Memory>(elf: &ElfBytes<LittleEndian>, data: &[u8]) -> Result<NexusVM<M>> { | ||
let e_entry = elf.ehdr.e_entry as u32; | ||
|
||
let load_phdrs = elf | ||
.segments() | ||
.unwrap() | ||
.iter() | ||
.filter(|phdr| phdr.p_type == PT_LOAD); | ||
|
||
let mut vm = NexusVM::new(e_entry); | ||
for p in load_phdrs { | ||
let s = p.p_offset as usize; | ||
let e = (p.p_offset + p.p_filesz) as usize; | ||
let bytes = &data[s..e]; | ||
vm.init_memory(p.p_vaddr as u32, bytes)?; | ||
} | ||
Ok(vm) | ||
} | ||
|
||
pub fn parse_elf<M: Memory>(bytes: &[u8]) -> Result<NexusVM<M>> { | ||
let file = parse_elf_bytes(bytes)?; | ||
init_vm(&file, bytes) | ||
} | ||
|
||
/// A structure describing a VM to load. | ||
/// This structure can be used with clap. | ||
#[derive(Default, Debug, Args)] | ||
pub struct VMOpts { | ||
/// Instructions per step | ||
#[arg(short, name = "k", default_value = "1")] | ||
pub k: usize, | ||
|
||
/// Use a named test machine | ||
#[arg(group = "vm", long, long_help(list_machines()))] | ||
pub machine: Option<String>, | ||
|
||
/// Input file, RISC-V 32i ELF | ||
#[arg(group = "vm", required = true)] | ||
pub file: Option<std::path::PathBuf>, | ||
} | ||
|
||
fn list_machines() -> String { | ||
let ms = machines::MACHINES | ||
.iter() | ||
.map(|m| m.0.to_string()) | ||
.collect::<Vec<String>>() | ||
.join(", "); | ||
"Use a named machine: ".to_string() + &ms | ||
} | ||
|
||
/// Load the VM described by `opts` | ||
pub fn load_vm<M: Memory>(opts: &VMOpts) -> Result<NexusVM<M>> { | ||
if let Some(m) = &opts.machine { | ||
if let Some(vm) = machines::lookup_test_machine(m) { | ||
Ok(vm) | ||
} else { | ||
Err(NexusVMError::UnknownMachine(m.clone())) | ||
} | ||
} else { | ||
load_elf(opts.file.as_ref().unwrap()) | ||
} | ||
} | ||
|
||
/// Evaluate a program starting from a given machine state | ||
pub fn eval(vm: &mut NexusVM<impl Memory>, show: bool) -> Result<()> { | ||
if show { | ||
println!("\nExecution:"); | ||
println!( | ||
"{:7} {:8} {:32} {:>8} {:>8}", | ||
"pc", "mem[pc]", "inst", "Z", "PC" | ||
); | ||
} | ||
let t = std::time::Instant::now(); | ||
let mut count = 0; | ||
|
||
loop { | ||
eval_inst(vm)?; | ||
count += 1; | ||
if show { | ||
println!("{:50} {:8x} {:8x}", vm.inst, vm.Z, vm.regs.pc); | ||
} | ||
if vm.inst.inst == RV32::UNIMP { | ||
break; | ||
} | ||
} | ||
|
||
fn table(name: &str, mem: &[u32]) { | ||
for (i, w) in mem.iter().enumerate() { | ||
print!(" {}{:02}: {:8x}", name, i, w); | ||
if (i % 8) == 7 { | ||
println!(); | ||
} | ||
} | ||
println!(); | ||
} | ||
|
||
if show { | ||
println!("\nFinal Machine State: pc: {:x}", vm.regs.pc); | ||
table("x", &vm.regs.x); | ||
|
||
println!("Executed {count} instructions in {:?}", t.elapsed()); | ||
} | ||
Ok(()) | ||
} | ||
|
||
/// Load and run an ELF file | ||
pub fn run_vm<M: Memory>(vm: &VMOpts, show: bool) -> Result<()> { | ||
let mut vm: NexusVM<M> = load_vm(vm)?; | ||
eval(&mut vm, show) | ||
} | ||
|
||
/// Load and run an ELF file, then return the execution trace | ||
pub fn trace_vm<M: Memory>( | ||
opts: &VMOpts, | ||
pow: bool, | ||
show: bool, | ||
) -> Result<Trace<M::Proof>, NexusVMError> { | ||
let mut vm = load_vm::<M>(opts)?; | ||
|
||
if show { | ||
println!("Executing program..."); | ||
} | ||
|
||
let start = Instant::now(); | ||
let trace = trace::<M>(&mut vm, opts.k, pow)?; | ||
|
||
if show { | ||
println!( | ||
"Executed {} instructions in {:?}. {} bytes used by trace.", | ||
trace.k * trace.blocks.len(), | ||
start.elapsed(), | ||
&trace.estimate_size(), | ||
); | ||
} | ||
|
||
Ok(trace) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on a discussion with @sjudson, I anticipated the Jolt prover wouldn't fully work yet, at least not with
ECALL
s, but I also wanted to test and document the latest behavior.I tried out the Jolt prover using the steps described at #159 (comment), except with
--impl jolt
since it's no longer the default, and on the HEAD of this branch instead, currently adc3886.The default "Hello, World" program fails with the following error:
Based on the error message, it must be coming from the highlighted line. Again, this isn't surprising and on its own doesn't necessarily warrant blocking the PR since Jolt isn't the default prover.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I removed any source of
ECALL
s in the program to prove/verify, thejolt
impl worked as expected. I've not audited to see what, if anything, the optimizer elided as dead code, but it's good to see there's no apparent regression here.Prove then verify with
--impl jolt
: