diff --git a/ceno_emul/src/addr.rs b/ceno_emul/src/addr.rs index 0ce39f56b..200739ce7 100644 --- a/ceno_emul/src/addr.rs +++ b/ceno_emul/src/addr.rs @@ -17,6 +17,7 @@ use std::{fmt, ops}; pub const WORD_SIZE: usize = 4; +pub const PC_WORD_SIZE: usize = 4; pub const PC_STEP_SIZE: usize = 4; // Type aliases to clarify the code without wrapper types. @@ -26,10 +27,10 @@ pub type Addr = u32; pub type Cycle = u64; pub type RegIdx = usize; -#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ByteAddr(pub u32); -#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WordAddr(u32); impl From for WordAddr { diff --git a/ceno_emul/src/elf.rs b/ceno_emul/src/elf.rs index f6a28f743..21d12779f 100644 --- a/ceno_emul/src/elf.rs +++ b/ceno_emul/src/elf.rs @@ -20,21 +20,47 @@ use alloc::collections::BTreeMap; use crate::addr::WORD_SIZE; use anyhow::{Context, Result, anyhow, bail}; -use elf::{ElfBytes, endian::LittleEndian, file::Class}; +use elf::{ + ElfBytes, + abi::{PF_R, PF_W, PF_X}, + endian::LittleEndian, + file::Class, +}; /// A RISC Zero program +#[derive(Clone, Debug)] pub struct Program { /// The entrypoint of the program pub entry: u32, - + /// This is the lowest address of the program's executable code + pub base_address: u32, + /// The instructions of the program + pub instructions: Vec, /// The initial memory image pub image: BTreeMap, } impl Program { + /// Create program + pub fn new( + entry: u32, + base_address: u32, + instructions: Vec, + image: BTreeMap, + ) -> Program { + Self { + entry, + base_address, + instructions, + image, + } + } /// Initialize a RISC Zero Program from an appropriate ELF file pub fn load_elf(input: &[u8], max_mem: u32) -> Result { + let mut instructions: Vec = Vec::new(); let mut image: BTreeMap = BTreeMap::new(); + let mut base_address = None; + let elf = ElfBytes::::minimal_parse(input) .map_err(|err| anyhow!("Elf parse error: {err}"))?; if elf.ehdr.class != Class::ELF32 { @@ -58,7 +84,18 @@ impl Program { if segments.len() > 256 { bail!("Too many program headers"); } - for segment in segments.iter().filter(|x| x.p_type == elf::abi::PT_LOAD) { + for (idx, segment) in segments + .iter() + .filter(|x| x.p_type == elf::abi::PT_LOAD) + .enumerate() + { + tracing::debug!( + "loadable segement {}: PF_R={}, PF_W={}, PF_X={}", + idx, + segment.p_flags & PF_R != 0, + segment.p_flags & PF_W != 0, + segment.p_flags & PF_X != 0, + ); let file_size: u32 = segment .p_filesz .try_into() @@ -77,6 +114,13 @@ impl Program { .p_vaddr .try_into() .map_err(|err| anyhow!("vaddr is larger than 32 bits. {err}"))?; + if (segment.p_flags & PF_X) != 0 { + if base_address.is_none() { + base_address = Some(vaddr); + } else { + return Err(anyhow!("only support one executable segment")); + } + } if vaddr % WORD_SIZE as u32 != 0 { bail!("vaddr {vaddr:08x} is unaligned"); } @@ -104,9 +148,25 @@ impl Program { word |= (*byte as u32) << (j * 8); } image.insert(addr, word); + if (segment.p_flags & PF_X) != 0 { + instructions.push(word); + } } } } - Ok(Program { entry, image }) + + if base_address.is_none() { + return Err(anyhow!("does not have executable segment")); + } + let base_address = base_address.unwrap(); + assert!(entry >= base_address); + assert!((entry - base_address) as usize <= instructions.len() * WORD_SIZE); + + Ok(Program { + entry, + base_address, + image, + instructions, + }) } } diff --git a/ceno_emul/src/platform.rs b/ceno_emul/src/platform.rs index 264d810f5..163757057 100644 --- a/ceno_emul/src/platform.rs +++ b/ceno_emul/src/platform.rs @@ -5,19 +5,30 @@ use crate::addr::{Addr, RegIdx}; /// - the layout of virtual memory, /// - special addresses, such as the initial PC, /// - codes of environment calls. -pub struct Platform; +#[derive(Copy, Clone)] +pub struct Platform { + pub rom_start: Addr, + pub rom_end: Addr, + pub ram_start: Addr, + pub ram_end: Addr, +} -pub const CENO_PLATFORM: Platform = Platform; +pub const CENO_PLATFORM: Platform = Platform { + rom_start: 0x2000_0000, + rom_end: 0x3000_0000 - 1, + ram_start: 0x8000_0000, + ram_end: 0xFFFF_FFFF, +}; impl Platform { // Virtual memory layout. pub const fn rom_start(&self) -> Addr { - 0x2000_0000 + self.rom_start } pub const fn rom_end(&self) -> Addr { - 0x3000_0000 - 1 + self.rom_end } pub fn is_rom(&self, addr: Addr) -> bool { @@ -25,18 +36,17 @@ impl Platform { } pub const fn ram_start(&self) -> Addr { - let ram_start = 0x8000_0000; if cfg!(feature = "forbid_overflow") { // -1<<11 == 0x800 is the smallest negative 'immediate' // offset we can have in memory instructions. // So if we stay away from it, we are safe. - assert!(ram_start >= 0x800); + assert!(self.ram_start >= 0x800); } - ram_start + self.ram_start } pub const fn ram_end(&self) -> Addr { - 0xFFFF_FFFF + self.ram_end - if cfg!(feature = "forbid_overflow") { // (1<<11) - 1 == 0x7ff is the largest positive 'immediate' // offset we can have in memory instructions. @@ -69,7 +79,7 @@ impl Platform { // Startup. - pub const fn pc_start(&self) -> Addr { + pub const fn pc_base(&self) -> Addr { self.rom_start() } @@ -122,7 +132,7 @@ mod tests { #[test] fn test_no_overlap() { let p = CENO_PLATFORM; - assert!(p.can_execute(p.pc_start())); + assert!(p.can_execute(p.pc_base())); // ROM and RAM do not overlap. assert!(!p.is_rom(p.ram_start())); assert!(!p.is_rom(p.ram_end())); diff --git a/ceno_emul/src/rv32im.rs b/ceno_emul/src/rv32im.rs index 25a13f22b..2deb4598a 100644 --- a/ceno_emul/src/rv32im.rs +++ b/ceno_emul/src/rv32im.rs @@ -556,6 +556,7 @@ impl Emulator { let decoded = DecodedInstruction::new(word); let insn = self.table.lookup(&decoded); ctx.on_insn_decoded(&decoded); + tracing::trace!("pc: {:x}, kind: {:?}", pc.0, insn.kind); if match insn.category { InsnCategory::Compute => self.step_compute(ctx, insn.kind, &decoded)?, @@ -775,6 +776,7 @@ impl Emulator { let addr = ByteAddr(rs1.wrapping_add(decoded.imm_s())); let shift = 8 * (addr.0 & 3); if !ctx.check_data_store(addr) { + tracing::error!("mstore: addr={:x?},rs1={:x}", addr, rs1); return ctx.trap(TrapCause::StoreAccessFault); } let mut data = ctx.peek_memory(addr.waddr()); diff --git a/ceno_emul/src/vm_state.rs b/ceno_emul/src/vm_state.rs index 5d43a3865..03a1f7926 100644 --- a/ceno_emul/src/vm_state.rs +++ b/ceno_emul/src/vm_state.rs @@ -9,10 +9,11 @@ use crate::{ tracer::{Change, StepRecord, Tracer}, }; use anyhow::{Result, anyhow}; -use std::iter::from_fn; +use std::{iter::from_fn, ops::Deref, sync::Arc}; /// An implementation of the machine state and of the side-effects of operations. pub struct VMState { + program: Arc, platform: Platform, pc: Word, /// Map a word-address (addr/4) to a word. @@ -24,28 +25,39 @@ pub struct VMState { } impl VMState { - pub fn new(platform: Platform) -> Self { - let pc = platform.pc_start(); - Self { - platform, + pub fn new(platform: Platform, program: Program) -> Self { + let pc = program.entry; + let program = Arc::new(program); + + let mut vm = Self { pc, + platform, + program: program.clone(), memory: HashMap::new(), registers: [0; 32], halted: false, tracer: Tracer::new(), + }; + + // init memory from program.image + for (&addr, &value) in program.image.iter() { + vm.init_memory(ByteAddr(addr).waddr(), value); } + + vm } pub fn new_from_elf(platform: Platform, elf: &[u8]) -> Result { - let mut state = Self::new(platform); let program = Program::load_elf(elf, u32::MAX).unwrap(); - for (addr, word) in program.image.iter() { - let addr = ByteAddr(*addr).waddr(); - state.init_memory(addr, *word); - } - if program.entry != state.platform.pc_start() { - return Err(anyhow!("Invalid entrypoint {:x}", program.entry)); + let state = Self::new(platform, program); + + if state.program.base_address != state.platform.rom_start() { + return Err(anyhow!( + "Invalid base_address {:x}", + state.program.base_address + )); } + Ok(state) } @@ -57,7 +69,11 @@ impl VMState { &self.tracer } - /// Set a word in memory without side-effects. + pub fn program(&self) -> &Program { + self.program.deref() + } + + /// Set a word in memory without side effects. pub fn init_memory(&mut self, addr: WordAddr, value: Word) { self.memory.insert(addr, value); } diff --git a/ceno_emul/tests/test_vm_trace.rs b/ceno_emul/tests/test_vm_trace.rs index 57069fd75..c01977823 100644 --- a/ceno_emul/tests/test_vm_trace.rs +++ b/ceno_emul/tests/test_vm_trace.rs @@ -1,19 +1,30 @@ #![allow(clippy::unusual_byte_groupings)] use anyhow::Result; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use ceno_emul::{ - ByteAddr, CENO_PLATFORM, Cycle, EmuContext, InsnKind, StepRecord, Tracer, VMState, WordAddr, + CENO_PLATFORM, Cycle, EmuContext, InsnKind, Program, StepRecord, Tracer, VMState, WORD_SIZE, + WordAddr, }; #[test] fn test_vm_trace() -> Result<()> { - let mut ctx = VMState::new(CENO_PLATFORM); - - let pc_start = ByteAddr(CENO_PLATFORM.pc_start()).waddr(); - for (i, &inst) in PROGRAM_FIBONACCI_20.iter().enumerate() { - ctx.init_memory(pc_start + i as u32, inst); - } + let program = Program::new( + CENO_PLATFORM.pc_base(), + CENO_PLATFORM.pc_base(), + PROGRAM_FIBONACCI_20.to_vec(), + PROGRAM_FIBONACCI_20 + .iter() + .enumerate() + .map(|(insn_idx, &insn)| { + ( + CENO_PLATFORM.pc_base() + (WORD_SIZE * insn_idx) as u32, + insn, + ) + }) + .collect(), + ); + let mut ctx = VMState::new(CENO_PLATFORM, program); let steps = run(&mut ctx)?; @@ -35,7 +46,13 @@ fn test_vm_trace() -> Result<()> { #[test] fn test_empty_program() -> Result<()> { - let mut ctx = VMState::new(CENO_PLATFORM); + let empty_program = Program::new( + CENO_PLATFORM.pc_base(), + CENO_PLATFORM.pc_base(), + vec![], + BTreeMap::new(), + ); + let mut ctx = VMState::new(CENO_PLATFORM, empty_program); let res = run(&mut ctx); assert!(matches!(res, Err(e) if e.to_string().contains("IllegalInstruction(0)"))); Ok(()) diff --git a/ceno_zkvm/examples/riscv_opcodes.rs b/ceno_zkvm/examples/riscv_opcodes.rs index b979975d2..2ba51773d 100644 --- a/ceno_zkvm/examples/riscv_opcodes.rs +++ b/ceno_zkvm/examples/riscv_opcodes.rs @@ -10,9 +10,9 @@ use ceno_zkvm::{ use clap::Parser; use ceno_emul::{ - ByteAddr, CENO_PLATFORM, EmuContext, + CENO_PLATFORM, EmuContext, InsnKind::{ADD, BLTU, EANY, JAL, LUI, LW}, - StepRecord, Tracer, VMState, WordAddr, encode_rv32, + PC_WORD_SIZE, Program, StepRecord, Tracer, VMState, WordAddr, encode_rv32, }; use ceno_zkvm::{ scheme::{PublicValues, constants::MAX_NUM_VARIABLES, verifier::ZKVMVerifier}, @@ -76,6 +76,21 @@ fn main() { type E = GoldilocksExt2; type Pcs = Basefold; + let program = Program::new( + CENO_PLATFORM.pc_base(), + CENO_PLATFORM.pc_base(), + PROGRAM_CODE.to_vec(), + PROGRAM_CODE + .iter() + .enumerate() + .map(|(insn_idx, &insn)| { + ( + (insn_idx * PC_WORD_SIZE) as u32 + CENO_PLATFORM.pc_base(), + insn, + ) + }) + .collect(), + ); let (flame_layer, _guard) = FlameLayer::with_file("./tracing.folded").unwrap(); let subscriber = Registry::default() .with( @@ -108,7 +123,7 @@ fn main() { zkvm_fixed_traces.register_table_circuit::>( &zkvm_cs, prog_config.clone(), - &PROGRAM_CODE, + &program, ); let reg_init = initial_registers(); @@ -126,12 +141,8 @@ fn main() { let prover = ZKVMProver::new(pk); let verifier = ZKVMVerifier::new(vk); - let mut vm = VMState::new(CENO_PLATFORM); - let pc_start = ByteAddr(CENO_PLATFORM.pc_start()).waddr(); + let mut vm = VMState::new(CENO_PLATFORM, program.clone()); - for (i, inst) in PROGRAM_CODE.iter().enumerate() { - vm.init_memory(pc_start + i, *inst); - } for record in &mem_init { vm.init_memory(record.addr.into(), record.value); } @@ -201,11 +212,7 @@ fn main() { // assign program circuit zkvm_witness - .assign_table_circuit::>( - &zkvm_cs, - &prog_config, - &PROGRAM_CODE.len(), - ) + .assign_table_circuit::>(&zkvm_cs, &prog_config, &program) .unwrap(); let timer = Instant::now(); diff --git a/ceno_zkvm/src/scheme/mock_prover.rs b/ceno_zkvm/src/scheme/mock_prover.rs index 74cc15de8..ff257809a 100644 --- a/ceno_zkvm/src/scheme/mock_prover.rs +++ b/ceno_zkvm/src/scheme/mock_prover.rs @@ -12,7 +12,7 @@ use crate::{ }; use ark_std::test_rng; use base64::{Engine, engine::general_purpose::STANDARD_NO_PAD}; -use ceno_emul::{ByteAddr, CENO_PLATFORM}; +use ceno_emul::{ByteAddr, CENO_PLATFORM, PC_WORD_SIZE, Program}; use ff::Field; use ff_ext::ExtensionField; use generic_static::StaticTypeMap; @@ -20,7 +20,7 @@ use goldilocks::SmallField; use itertools::{Itertools, izip}; use multilinear_extensions::{mle::IntoMLEs, virtual_poly_v2::ArcMultilinearExtension}; use std::{ - collections::HashSet, + collections::{BTreeMap, HashSet}, fs::File, hash::Hash, io::{BufReader, ErrorKind}, @@ -31,7 +31,7 @@ use std::{ use strum::IntoEnumIterator; const MOCK_PROGRAM_SIZE: usize = 32; -pub const MOCK_PC_START: ByteAddr = ByteAddr(CENO_PLATFORM.pc_start()); +pub const MOCK_PC_START: ByteAddr = ByteAddr(CENO_PLATFORM.pc_base()); #[allow(clippy::enum_variant_names)] #[derive(Debug, Clone)] @@ -389,10 +389,28 @@ impl<'a, E: ExtensionField + Hash> MockProver { lkm: Option, ) -> Result<(), Vec>> { // fix the program table - let mut programs = [0u32; MOCK_PROGRAM_SIZE]; - for (i, &program) in input_programs.iter().enumerate() { - programs[i] = program; - } + let instructions = input_programs + .iter() + .cloned() + .chain(std::iter::repeat(0)) + .take(MOCK_PROGRAM_SIZE) + .collect_vec(); + let image = instructions + .iter() + .enumerate() + .map(|(insn_idx, &insn)| { + ( + CENO_PLATFORM.pc_base() + (insn_idx * PC_WORD_SIZE) as u32, + insn, + ) + }) + .collect::>(); + let program = Program::new( + CENO_PLATFORM.pc_base(), + CENO_PLATFORM.pc_base(), + instructions, + image, + ); // load tables let (challenge, mut table) = if let Some(challenge) = challenge { @@ -401,7 +419,7 @@ impl<'a, E: ExtensionField + Hash> MockProver { load_once_tables(cb) }; let mut prog_table = vec![]; - Self::load_program_table(&mut prog_table, &programs, challenge); + Self::load_program_table(&mut prog_table, &program, challenge); for prog in prog_table { table.insert(prog); } @@ -589,11 +607,7 @@ impl<'a, E: ExtensionField + Hash> MockProver { } } - fn load_program_table( - t_vec: &mut Vec>, - programs: &[u32; MOCK_PROGRAM_SIZE], - challenge: [E; 2], - ) { + fn load_program_table(t_vec: &mut Vec>, program: &Program, challenge: [E; 2]) { let mut cs = ConstraintSystem::::new(|| "mock_program"); let mut cb = CircuitBuilder::new(&mut cs); let config = @@ -601,7 +615,7 @@ impl<'a, E: ExtensionField + Hash> MockProver { let fixed = ProgramTableCircuit::::generate_fixed_traces( &config, cs.num_fixed, - programs, + program, ); for table_expr in &cs.lk_table_expressions { for row in fixed.iter_rows() { diff --git a/ceno_zkvm/src/scheme/tests.rs b/ceno_zkvm/src/scheme/tests.rs index f928a8918..d745917c9 100644 --- a/ceno_zkvm/src/scheme/tests.rs +++ b/ceno_zkvm/src/scheme/tests.rs @@ -1,9 +1,9 @@ use std::{marker::PhantomData, mem::MaybeUninit}; use ceno_emul::{ - ByteAddr, CENO_PLATFORM, + CENO_PLATFORM, InsnKind::{ADD, EANY}, - StepRecord, VMState, + PC_WORD_SIZE, Program, StepRecord, VMState, }; use ff::Field; use ff_ext::ExtensionField; @@ -202,6 +202,23 @@ fn test_single_add_instance_e2e() { type E = GoldilocksExt2; type Pcs = Basefold; + // set up program + let program = Program::new( + CENO_PLATFORM.pc_base(), + CENO_PLATFORM.pc_base(), + PROGRAM_CODE.to_vec(), + PROGRAM_CODE + .iter() + .enumerate() + .map(|(insn_idx, &insn)| { + ( + (insn_idx * PC_WORD_SIZE) as u32 + CENO_PLATFORM.pc_base(), + insn, + ) + }) + .collect(), + ); + let pcs_param = Pcs::setup(1 << MAX_NUM_VARIABLES).expect("Basefold PCS setup"); let (pp, vp) = Pcs::trim(&pcs_param, 1 << MAX_NUM_VARIABLES).expect("Basefold trim"); let mut zkvm_cs = ZKVMConstraintSystem::default(); @@ -225,7 +242,7 @@ fn test_single_add_instance_e2e() { zkvm_fixed_traces.register_table_circuit::>( &zkvm_cs, prog_config.clone(), - &PROGRAM_CODE, + &program, ); let pk = zkvm_cs @@ -235,11 +252,7 @@ fn test_single_add_instance_e2e() { let vk = pk.get_vk(); // single instance - let mut vm = VMState::new(CENO_PLATFORM); - let pc_start = ByteAddr(CENO_PLATFORM.pc_start()).waddr(); - for (i, insn) in PROGRAM_CODE.iter().enumerate() { - vm.init_memory(pc_start + i, *insn); - } + let mut vm = VMState::new(CENO_PLATFORM, program.clone()); let all_records = vm .iter_until_halt() .collect::, _>>() @@ -282,7 +295,7 @@ fn test_single_add_instance_e2e() { .assign_table_circuit::>( &zkvm_cs, &prog_config, - &PROGRAM_CODE.len(), + &program, ) .unwrap(); diff --git a/ceno_zkvm/src/tables/program.rs b/ceno_zkvm/src/tables/program.rs index 3514365c8..f901fbfd9 100644 --- a/ceno_zkvm/src/tables/program.rs +++ b/ceno_zkvm/src/tables/program.rs @@ -10,7 +10,7 @@ use crate::{ tables::TableCircuit, witness::RowMajorMatrix, }; -use ceno_emul::{CENO_PLATFORM, DecodedInstruction, PC_STEP_SIZE, WORD_SIZE}; +use ceno_emul::{DecodedInstruction, PC_STEP_SIZE, Program, WORD_SIZE}; use ff_ext::ExtensionField; use goldilocks::SmallField; use itertools::Itertools; @@ -117,8 +117,8 @@ impl TableCircuit for ProgramTableCircuit { type TableConfig = ProgramTableConfig; - type FixedInput = [u32; PROGRAM_SIZE]; - type WitnessInput = usize; + type FixedInput = Program; + type WitnessInput = Program; fn name() -> String { "PROGRAM".into() @@ -153,9 +153,8 @@ impl TableCircuit num_fixed: usize, program: &Self::FixedInput, ) -> RowMajorMatrix { - // TODO: get bytecode of the program. - let num_instructions = program.len(); - let pc_start = CENO_PLATFORM.pc_start(); + let num_instructions = program.instructions.len(); + let pc_base = program.base_address; let mut fixed = RowMajorMatrix::::new(num_instructions, num_fixed); @@ -164,8 +163,8 @@ impl TableCircuit .with_min_len(MIN_PAR_SIZE) .zip((0..num_instructions).into_par_iter()) .for_each(|(row, i)| { - let pc = pc_start + (i * PC_STEP_SIZE) as u32; - let insn = DecodedInstruction::new(program[i]); + let pc = pc_base + (i * PC_STEP_SIZE) as u32; + let insn = DecodedInstruction::new(program.instructions[i]); let values = InsnRecord::from_decoded(pc, &insn); // Copy all the fields except immediate. @@ -192,13 +191,13 @@ impl TableCircuit config: &Self::TableConfig, num_witin: usize, multiplicity: &[HashMap], - num_instructions: &usize, + program: &Program, ) -> Result, ZKVMError> { let multiplicity = &multiplicity[ROMType::Instruction as usize]; - let mut prog_mlt = vec![0_usize; *num_instructions]; + let mut prog_mlt = vec![0_usize; program.instructions.len()]; for (pc, mlt) in multiplicity { - let i = (*pc as usize - CENO_PLATFORM.pc_start() as usize) / WORD_SIZE; + let i = (*pc as usize - program.base_address as usize) / WORD_SIZE; prog_mlt[i] = *mlt; }