From fa5afc8d5929d23fba7896283e94b17d535d72b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Thu, 5 Dec 2024 14:50:34 +0100 Subject: [PATCH 01/21] ecall-keccak: introduce syscall handlers and SyscallEvent --- ceno_emul/src/lib.rs | 2 + ceno_emul/src/syscalls.rs | 54 +++++++++++++++++++++++++++ ceno_emul/src/tracer.rs | 19 ++++++++++ ceno_emul/src/vm_state.rs | 34 ++++++++++++----- ceno_emul/tests/test_elf.rs | 21 +++++++++++ examples-builder/build.rs | 1 + examples/examples/ceno_rt_syscalls.rs | 47 +++++++++++++++++++++++ 7 files changed, 169 insertions(+), 9 deletions(-) create mode 100644 ceno_emul/src/syscalls.rs create mode 100644 examples/examples/ceno_rt_syscalls.rs diff --git a/ceno_emul/src/lib.rs b/ceno_emul/src/lib.rs index c734b1794..9970633b5 100644 --- a/ceno_emul/src/lib.rs +++ b/ceno_emul/src/lib.rs @@ -19,3 +19,5 @@ pub use elf::Program; mod rv32im_encode; pub use rv32im_encode::encode_rv32; + +mod syscalls; \ No newline at end of file diff --git a/ceno_emul/src/syscalls.rs b/ceno_emul/src/syscalls.rs new file mode 100644 index 000000000..70d1e2911 --- /dev/null +++ b/ceno_emul/src/syscalls.rs @@ -0,0 +1,54 @@ +use crate::{Change, EmuContext, VMState, WORD_SIZE, WordAddr, WriteOp}; +use anyhow::Result; +use itertools::{Itertools, izip}; + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct SyscallEvent { + pub mem_writes: Vec, +} + +pub const KECCAK_PERMUTE: u32 = 0x00_01_01_09; + +/// Trace the inputs and effects of a syscall. +pub fn handle_syscall(vm: &VMState, function_code: u32, arg0: u32) -> Result { + match function_code { + KECCAK_PERMUTE => Ok(keccak_permute(vm, arg0)), + _ => Err(anyhow::anyhow!("Unknown syscall: {}", function_code)), + } +} + +const KECCAK_WORDS: usize = 25 * 2; + +/// Trace the execution of a Keccak permutation. +/// +/// Compatible with: +/// https://github.com/succinctlabs/sp1/blob/013c24ea2fa15a0e7ed94f7d11a7ada4baa39ab9/crates/core/executor/src/syscalls/precompiles/keccak256/permute.rs +/// +/// TODO: test compatibility. +fn keccak_permute(vm: &VMState, state_ptr: u32) -> SyscallEvent { + let addrs = (state_ptr..) + .step_by(WORD_SIZE as usize) + .take(KECCAK_WORDS) + .map(WordAddr::from) + .collect_vec(); + + // Read Keccak state. + let input = addrs + .iter() + .map(|&addr| vm.peek_memory(addr)) + .collect::>(); + + // TODO: Compute Keccak permutation. + let output = input.clone(); + + // Write permuted state. + let mem_writes = izip!(addrs, input, output) + .map(|(addr, before, after)| WriteOp { + addr, + value: Change { before, after }, + previous_cycle: 0, // Set later by Tracer. + }) + .collect_vec(); + + SyscallEvent { mem_writes } +} diff --git a/ceno_emul/src/tracer.rs b/ceno_emul/src/tracer.rs index bbb1e6a51..617540e31 100644 --- a/ceno_emul/src/tracer.rs +++ b/ceno_emul/src/tracer.rs @@ -5,6 +5,7 @@ use crate::{ addr::{ByteAddr, Cycle, RegIdx, Word, WordAddr}, encode_rv32, rv32im::DecodedInstruction, + syscalls::SyscallEvent, }; /// An instruction and its context in an execution trace. That is concrete values of registers and memory. @@ -30,6 +31,8 @@ pub struct StepRecord { rd: Option, memory_op: Option, + + syscall: Option, } #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -242,6 +245,7 @@ impl StepRecord { previous_cycle, }), memory_op, + syscall: None, } } @@ -383,6 +387,21 @@ impl Tracer { }); } + pub fn track_syscall(&mut self, mut event: SyscallEvent) { + let cycle = self.record.cycle + Self::SUBCYCLE_MEM; + for op in &mut event.mem_writes { + op.previous_cycle = self.track_access(op.addr, Self::SUBCYCLE_MEM); + assert_ne!( + op.previous_cycle, cycle, + "Memory address {:?} was accessed twice in the same cycle", + op.addr + ); + } + + assert!(self.record.syscall.is_none(), "Only one syscall per step"); + self.record.syscall = Some(event); + } + /// - Return the cycle when an address was last accessed. /// - Return 0 if this is the first access. /// - Record the current instruction as the origin of the latest access. diff --git a/ceno_emul/src/vm_state.rs b/ceno_emul/src/vm_state.rs index d8eff9701..101b11f43 100644 --- a/ceno_emul/src/vm_state.rs +++ b/ceno_emul/src/vm_state.rs @@ -6,6 +6,7 @@ use crate::{ addr::{ByteAddr, RegIdx, Word, WordAddr}, platform::Platform, rv32im::{DecodedInstruction, Emulator, TrapCause}, + syscalls::{KECCAK_PERMUTE, SyscallEvent, handle_syscall}, tracer::{Change, StepRecord, Tracer}, }; use anyhow::{Result, anyhow}; @@ -106,6 +107,13 @@ impl VMState { self.set_pc(0.into()); self.halted = true; } + + fn apply_syscall(&mut self, event: SyscallEvent) { + for write_op in &event.mem_writes { + self.memory.insert(write_op.addr, write_op.value.after); + } + self.tracer.track_syscall(event); + } } impl EmuContext for VMState { @@ -119,15 +127,23 @@ impl EmuContext for VMState { self.halt(); Ok(true) } else if self.platform.unsafe_ecall_nop { - // Treat unknown ecalls as all powerful instructions: - // Read two registers, write one register, write one memory word, and branch. - tracing::warn!("ecall ignored: syscall_id={}", function); - self.store_register(DecodedInstruction::RD_NULL as RegIdx, 0)?; - // Example ecall effect - any writable address will do. - let addr = (self.platform.stack_top - WORD_SIZE as u32).into(); - self.store_memory(addr, self.peek_memory(addr))?; - self.set_pc(ByteAddr(self.pc) + PC_STEP_SIZE); - Ok(true) + if function == KECCAK_PERMUTE { + let event = handle_syscall(self, function, arg0)?; + self.apply_syscall(event); + self.set_pc(ByteAddr(self.pc) + PC_STEP_SIZE); + Ok(true) + } else { + // TODO: remove this example. + // Treat unknown ecalls as all powerful instructions: + // Read two registers, write one register, write one memory word, and branch. + tracing::warn!("ecall ignored: syscall_id={}", function); + self.store_register(DecodedInstruction::RD_NULL as RegIdx, 0)?; + // Example ecall effect - any writable address will do. + let addr = (self.platform.stack_top - WORD_SIZE as u32).into(); + self.store_memory(addr, self.peek_memory(addr))?; + self.set_pc(ByteAddr(self.pc) + PC_STEP_SIZE); + Ok(true) + } } else { self.trap(TrapCause::EcallError) } diff --git a/ceno_emul/tests/test_elf.rs b/ceno_emul/tests/test_elf.rs index ca7a14c1c..8ba16632d 100644 --- a/ceno_emul/tests/test_elf.rs +++ b/ceno_emul/tests/test_elf.rs @@ -1,5 +1,6 @@ use anyhow::Result; use ceno_emul::{ByteAddr, CENO_PLATFORM, EmuContext, InsnKind, Platform, StepRecord, VMState}; +use itertools::Itertools; #[test] fn test_ceno_rt_mini() -> Result<()> { @@ -72,6 +73,26 @@ fn test_ceno_rt_io() -> Result<()> { Ok(()) } +#[test] +fn test_ceno_rt_syscalls() -> Result<()> { + let program_elf = ceno_examples::ceno_rt_syscalls; + let mut state = VMState::new_from_elf(unsafe_platform(), program_elf)?; + let _steps = run(&mut state)?; + + for (&addr, &cycle) in state.tracer().final_accesses().iter().sorted() { + let value = state.peek_memory(addr); + println!("{:?} = {:08x} (cycle {})", addr, value, cycle); + } + + Ok(()) +} + +fn unsafe_platform() -> Platform { + let mut platform = CENO_PLATFORM; + platform.unsafe_ecall_nop = true; + platform +} + fn run(state: &mut VMState) -> Result> { let steps = state.iter_until_halt().collect::>>()?; eprintln!("Emulator ran for {} steps.", steps.len()); diff --git a/examples-builder/build.rs b/examples-builder/build.rs index 7ea9b9628..fd5cca76e 100644 --- a/examples-builder/build.rs +++ b/examples-builder/build.rs @@ -14,6 +14,7 @@ const EXAMPLES: &[&str] = &[ "ceno_rt_mem", "ceno_rt_mini", "ceno_rt_panic", + "ceno_rt_syscalls", ]; const CARGO_MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR"); diff --git a/examples/examples/ceno_rt_syscalls.rs b/examples/examples/ceno_rt_syscalls.rs new file mode 100644 index 000000000..ba3336a4c --- /dev/null +++ b/examples/examples/ceno_rt_syscalls.rs @@ -0,0 +1,47 @@ +#![no_main] +#![no_std] +use core::ptr::{addr_of, read_volatile}; + +extern crate ceno_rt; + +static mut OUTPUT: u32 = 0; + +ceno_rt::entry!(main); +fn main() { + let mut state = [0_u64; 25]; + + syscalls::syscall_keccak_permute(&mut state); +} + +/// Prevent compiler optimizations. +fn black_box(x: *const T) -> T { + unsafe { read_volatile(x) } +} + +mod syscalls { + + // Based on https://github.com/succinctlabs/sp1/blob/013c24ea2fa15a0e7ed94f7d11a7ada4baa39ab9/crates/zkvm/entrypoint/src/syscalls/keccak_permute.rs + + const KECCAK_PERMUTE: u32 = 0x00_01_01_09; + + use core::arch::asm; + + /// Executes the Keccak256 permutation on the given state. + /// + /// ### Safety + /// + /// The caller must ensure that `state` is valid pointer to data that is aligned along a four + /// byte boundary. + #[allow(unused_variables)] + #[no_mangle] + pub extern "C" fn syscall_keccak_permute(state: &mut [u64; 25]) { + unsafe { + asm!( + "ecall", + in("t0") KECCAK_PERMUTE, + in("a0") state as *mut [u64; 25], + in("a1") 0 + ); + } + } +} From 31760af11135a154317b42cd59a6d8ce9e09fbb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Thu, 5 Dec 2024 18:05:30 +0100 Subject: [PATCH 02/21] ecall-keccak: support return value --- ceno_emul/src/syscalls.rs | 19 +++++++++++++++---- ceno_emul/src/tracer.rs | 10 +++++----- ceno_emul/src/vm_state.rs | 14 +++++++++----- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/ceno_emul/src/syscalls.rs b/ceno_emul/src/syscalls.rs index 70d1e2911..c2be069a9 100644 --- a/ceno_emul/src/syscalls.rs +++ b/ceno_emul/src/syscalls.rs @@ -2,15 +2,23 @@ use crate::{Change, EmuContext, VMState, WORD_SIZE, WordAddr, WriteOp}; use anyhow::Result; use itertools::{Itertools, izip}; +/// A syscall event, available to the circuit witness generators. #[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct SyscallEvent { +pub struct SyscallWitness { pub mem_writes: Vec, } +/// The effects of a syscall to apply on the VM. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct SyscallEffects { + pub witness: SyscallWitness, + pub return_value: Option, +} + pub const KECCAK_PERMUTE: u32 = 0x00_01_01_09; /// Trace the inputs and effects of a syscall. -pub fn handle_syscall(vm: &VMState, function_code: u32, arg0: u32) -> Result { +pub fn handle_syscall(vm: &VMState, function_code: u32, arg0: u32) -> Result { match function_code { KECCAK_PERMUTE => Ok(keccak_permute(vm, arg0)), _ => Err(anyhow::anyhow!("Unknown syscall: {}", function_code)), @@ -25,7 +33,7 @@ const KECCAK_WORDS: usize = 25 * 2; /// https://github.com/succinctlabs/sp1/blob/013c24ea2fa15a0e7ed94f7d11a7ada4baa39ab9/crates/core/executor/src/syscalls/precompiles/keccak256/permute.rs /// /// TODO: test compatibility. -fn keccak_permute(vm: &VMState, state_ptr: u32) -> SyscallEvent { +fn keccak_permute(vm: &VMState, state_ptr: u32) -> SyscallEffects { let addrs = (state_ptr..) .step_by(WORD_SIZE as usize) .take(KECCAK_WORDS) @@ -50,5 +58,8 @@ fn keccak_permute(vm: &VMState, state_ptr: u32) -> SyscallEvent { }) .collect_vec(); - SyscallEvent { mem_writes } + SyscallEffects { + witness: SyscallWitness { mem_writes }, + return_value: None, + } } diff --git a/ceno_emul/src/tracer.rs b/ceno_emul/src/tracer.rs index 617540e31..e9b9ba96c 100644 --- a/ceno_emul/src/tracer.rs +++ b/ceno_emul/src/tracer.rs @@ -5,7 +5,7 @@ use crate::{ addr::{ByteAddr, Cycle, RegIdx, Word, WordAddr}, encode_rv32, rv32im::DecodedInstruction, - syscalls::SyscallEvent, + syscalls::{SyscallEffects, SyscallWitness}, }; /// An instruction and its context in an execution trace. That is concrete values of registers and memory. @@ -32,7 +32,7 @@ pub struct StepRecord { memory_op: Option, - syscall: Option, + syscall: Option, } #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -387,9 +387,9 @@ impl Tracer { }); } - pub fn track_syscall(&mut self, mut event: SyscallEvent) { + pub fn track_syscall(&mut self, mut effects: SyscallEffects) { let cycle = self.record.cycle + Self::SUBCYCLE_MEM; - for op in &mut event.mem_writes { + for op in &mut effects.witness.mem_writes { op.previous_cycle = self.track_access(op.addr, Self::SUBCYCLE_MEM); assert_ne!( op.previous_cycle, cycle, @@ -399,7 +399,7 @@ impl Tracer { } assert!(self.record.syscall.is_none(), "Only one syscall per step"); - self.record.syscall = Some(event); + self.record.syscall = Some(effects.witness); } /// - Return the cycle when an address was last accessed. diff --git a/ceno_emul/src/vm_state.rs b/ceno_emul/src/vm_state.rs index 101b11f43..9560a3412 100644 --- a/ceno_emul/src/vm_state.rs +++ b/ceno_emul/src/vm_state.rs @@ -6,7 +6,7 @@ use crate::{ addr::{ByteAddr, RegIdx, Word, WordAddr}, platform::Platform, rv32im::{DecodedInstruction, Emulator, TrapCause}, - syscalls::{KECCAK_PERMUTE, SyscallEvent, handle_syscall}, + syscalls::{KECCAK_PERMUTE, SyscallEffects, handle_syscall}, tracer::{Change, StepRecord, Tracer}, }; use anyhow::{Result, anyhow}; @@ -108,11 +108,15 @@ impl VMState { self.halted = true; } - fn apply_syscall(&mut self, event: SyscallEvent) { - for write_op in &event.mem_writes { + fn apply_syscall(&mut self, effects: SyscallEffects) -> Result<()> { + for write_op in &effects.witness.mem_writes { self.memory.insert(write_op.addr, write_op.value.after); } - self.tracer.track_syscall(event); + if let Some(return_value) = effects.return_value { + self.store_register(Platform::reg_arg0(), return_value)?; + } + self.tracer.track_syscall(effects); + Ok(()) } } @@ -129,7 +133,7 @@ impl EmuContext for VMState { } else if self.platform.unsafe_ecall_nop { if function == KECCAK_PERMUTE { let event = handle_syscall(self, function, arg0)?; - self.apply_syscall(event); + self.apply_syscall(event)?; self.set_pc(ByteAddr(self.pc) + PC_STEP_SIZE); Ok(true) } else { From 60da95c68f9a47aebefb2d740286d12c234626ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Thu, 5 Dec 2024 18:27:24 +0100 Subject: [PATCH 03/21] ecall-keccak: support next_pc --- ceno_emul/src/lib.rs | 2 +- ceno_emul/src/syscalls.rs | 4 +++- ceno_emul/src/vm_state.rs | 10 +++++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/ceno_emul/src/lib.rs b/ceno_emul/src/lib.rs index 9970633b5..536887b7d 100644 --- a/ceno_emul/src/lib.rs +++ b/ceno_emul/src/lib.rs @@ -20,4 +20,4 @@ pub use elf::Program; mod rv32im_encode; pub use rv32im_encode::encode_rv32; -mod syscalls; \ No newline at end of file +mod syscalls; diff --git a/ceno_emul/src/syscalls.rs b/ceno_emul/src/syscalls.rs index c2be069a9..72242c7ad 100644 --- a/ceno_emul/src/syscalls.rs +++ b/ceno_emul/src/syscalls.rs @@ -13,6 +13,7 @@ pub struct SyscallWitness { pub struct SyscallEffects { pub witness: SyscallWitness, pub return_value: Option, + pub next_pc: Option, } pub const KECCAK_PERMUTE: u32 = 0x00_01_01_09; @@ -35,7 +36,7 @@ const KECCAK_WORDS: usize = 25 * 2; /// TODO: test compatibility. fn keccak_permute(vm: &VMState, state_ptr: u32) -> SyscallEffects { let addrs = (state_ptr..) - .step_by(WORD_SIZE as usize) + .step_by(WORD_SIZE) .take(KECCAK_WORDS) .map(WordAddr::from) .collect_vec(); @@ -61,5 +62,6 @@ fn keccak_permute(vm: &VMState, state_ptr: u32) -> SyscallEffects { SyscallEffects { witness: SyscallWitness { mem_writes }, return_value: None, + next_pc: None, } } diff --git a/ceno_emul/src/vm_state.rs b/ceno_emul/src/vm_state.rs index 9560a3412..e39e6613d 100644 --- a/ceno_emul/src/vm_state.rs +++ b/ceno_emul/src/vm_state.rs @@ -112,9 +112,14 @@ impl VMState { for write_op in &effects.witness.mem_writes { self.memory.insert(write_op.addr, write_op.value.after); } + if let Some(return_value) = effects.return_value { self.store_register(Platform::reg_arg0(), return_value)?; } + + let next_pc = effects.next_pc.unwrap_or(self.pc + PC_STEP_SIZE as u32); + self.set_pc(next_pc.into()); + self.tracer.track_syscall(effects); Ok(()) } @@ -132,9 +137,8 @@ impl EmuContext for VMState { Ok(true) } else if self.platform.unsafe_ecall_nop { if function == KECCAK_PERMUTE { - let event = handle_syscall(self, function, arg0)?; - self.apply_syscall(event)?; - self.set_pc(ByteAddr(self.pc) + PC_STEP_SIZE); + let effects = handle_syscall(self, function, arg0)?; + self.apply_syscall(effects)?; Ok(true) } else { // TODO: remove this example. From 206f1dd399841d06f43422de4776d528df2030c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Mon, 9 Dec 2024 13:29:16 +0100 Subject: [PATCH 04/21] ecall-keccak: Compute actual Keccak-f --- Cargo.lock | 1 + ceno_emul/Cargo.toml | 1 + ceno_emul/src/syscalls.rs | 18 +++++++++++++++--- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52d969efd..4952a9896 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,6 +246,7 @@ dependencies = [ "num-traits", "strum", "strum_macros", + "tiny-keccak", "tracing", ] diff --git a/ceno_emul/Cargo.toml b/ceno_emul/Cargo.toml index 38f0a8bfd..40197036a 100644 --- a/ceno_emul/Cargo.toml +++ b/ceno_emul/Cargo.toml @@ -17,6 +17,7 @@ num-derive.workspace = true num-traits.workspace = true strum.workspace = true strum_macros.workspace = true +tiny-keccak = { version = "2.0.2", features = ["keccak"] } tracing.workspace = true [dev-dependencies] diff --git a/ceno_emul/src/syscalls.rs b/ceno_emul/src/syscalls.rs index 72242c7ad..5c2ddf3b7 100644 --- a/ceno_emul/src/syscalls.rs +++ b/ceno_emul/src/syscalls.rs @@ -1,6 +1,7 @@ use crate::{Change, EmuContext, VMState, WORD_SIZE, WordAddr, WriteOp}; use anyhow::Result; use itertools::{Itertools, izip}; +use tiny_keccak::keccakf; /// A syscall event, available to the circuit witness generators. #[derive(Clone, Debug, Default, PartialEq, Eq)] @@ -26,7 +27,8 @@ pub fn handle_syscall(vm: &VMState, function_code: u32, arg0: u32) -> Result SyscallEffects { .map(|&addr| vm.peek_memory(addr)) .collect::>(); - // TODO: Compute Keccak permutation. - let output = input.clone(); + // Compute Keccak permutation. + let output = { + let mut state = [0_u64; KECCAK_CELLS]; + izip!(state.iter_mut(), input.chunks_exact(2)).for_each(|(cell, chunk)| { + let lo = chunk[0] as u64; + let hi = chunk[1] as u64; + *cell = lo | hi << 32; + }); + keccakf(&mut state); + state.into_iter().flat_map(|c| [c as u32, (c >> 32) as u32]) + }; // Write permuted state. let mem_writes = izip!(addrs, input, output) @@ -59,6 +70,7 @@ fn keccak_permute(vm: &VMState, state_ptr: u32) -> SyscallEffects { }) .collect_vec(); + assert_eq!(mem_writes.len(), KECCAK_WORDS); SyscallEffects { witness: SyscallWitness { mem_writes }, return_value: None, From 1cf67b2407513868bc176ee733665dc45aed6c12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Mon, 9 Dec 2024 14:21:02 +0100 Subject: [PATCH 05/21] ecall-keccak: test actual Keccak computation --- ceno_emul/tests/test_elf.rs | 28 +++++++++++++++++++++++---- examples/examples/ceno_rt_syscalls.rs | 19 +++++++++++------- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/ceno_emul/tests/test_elf.rs b/ceno_emul/tests/test_elf.rs index 8ba16632d..ca4891511 100644 --- a/ceno_emul/tests/test_elf.rs +++ b/ceno_emul/tests/test_elf.rs @@ -1,6 +1,7 @@ use anyhow::Result; use ceno_emul::{ByteAddr, CENO_PLATFORM, EmuContext, InsnKind, Platform, StepRecord, VMState}; -use itertools::Itertools; +use itertools::{Itertools, izip}; +use tiny_keccak::keccakf; #[test] fn test_ceno_rt_mini() -> Result<()> { @@ -79,9 +80,16 @@ fn test_ceno_rt_syscalls() -> Result<()> { let mut state = VMState::new_from_elf(unsafe_platform(), program_elf)?; let _steps = run(&mut state)?; - for (&addr, &cycle) in state.tracer().final_accesses().iter().sorted() { - let value = state.peek_memory(addr); - println!("{:?} = {:08x} (cycle {})", addr, value, cycle); + const ITERATIONS: usize = 3; + let keccak_outs = sample_keccak_f(ITERATIONS); + + let all_messages = read_all_messages(&state); + for (got, expect) in izip!(&all_messages, keccak_outs) { + let got = got + .chunks_exact(8) + .map(|chunk| u64::from_le_bytes(chunk.try_into().unwrap())) + .collect_vec(); + assert_eq!(got, expect); } Ok(()) @@ -93,6 +101,18 @@ fn unsafe_platform() -> Platform { platform } +fn sample_keccak_f(count: usize) -> Vec> { + let input = [0_u64; 25]; + let mut state = input.clone(); + + (0..count) + .map(|_| { + keccakf(&mut state); + state.into() + }) + .collect_vec() +} + fn run(state: &mut VMState) -> Result> { let steps = state.iter_until_halt().collect::>>()?; eprintln!("Emulator ran for {} steps.", steps.len()); diff --git a/examples/examples/ceno_rt_syscalls.rs b/examples/examples/ceno_rt_syscalls.rs index ba3336a4c..ed3621b2b 100644 --- a/examples/examples/ceno_rt_syscalls.rs +++ b/examples/examples/ceno_rt_syscalls.rs @@ -1,21 +1,26 @@ #![no_main] #![no_std] -use core::ptr::{addr_of, read_volatile}; - extern crate ceno_rt; +use ceno_rt::info_out; +use core::{ptr::read_volatile, slice}; -static mut OUTPUT: u32 = 0; +const ITERATIONS: usize = 3; ceno_rt::entry!(main); fn main() { let mut state = [0_u64; 25]; - syscalls::syscall_keccak_permute(&mut state); + for _ in 0..ITERATIONS { + syscalls::syscall_keccak_permute(&mut state); + log_state(&state); + } } -/// Prevent compiler optimizations. -fn black_box(x: *const T) -> T { - unsafe { read_volatile(x) } +fn log_state(state: &[u64; 25]) { + let out = unsafe { + slice::from_raw_parts_mut(state.as_ptr() as *mut u8, state.len() * size_of::()) + }; + info_out().write_frame(out); } mod syscalls { From 762bb5dbbc83e05a929f95404c9689ec7b27a4c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Mon, 9 Dec 2024 14:24:29 +0100 Subject: [PATCH 06/21] ecall-keccak: rename --- ceno_emul/tests/test_elf.rs | 5 +++-- examples-builder/build.rs | 2 +- examples/examples/{ceno_rt_syscalls.rs => ceno_rt_keccak.rs} | 4 ++++ 3 files changed, 8 insertions(+), 3 deletions(-) rename examples/examples/{ceno_rt_syscalls.rs => ceno_rt_keccak.rs} (91%) diff --git a/ceno_emul/tests/test_elf.rs b/ceno_emul/tests/test_elf.rs index ca4891511..7f5c328d6 100644 --- a/ceno_emul/tests/test_elf.rs +++ b/ceno_emul/tests/test_elf.rs @@ -75,11 +75,12 @@ fn test_ceno_rt_io() -> Result<()> { } #[test] -fn test_ceno_rt_syscalls() -> Result<()> { - let program_elf = ceno_examples::ceno_rt_syscalls; +fn test_ceno_rt_keccak() -> Result<()> { + let program_elf = ceno_examples::ceno_rt_keccak; let mut state = VMState::new_from_elf(unsafe_platform(), program_elf)?; let _steps = run(&mut state)?; + // Expect the program to have written successive states between Keccak permutations. const ITERATIONS: usize = 3; let keccak_outs = sample_keccak_f(ITERATIONS); diff --git a/examples-builder/build.rs b/examples-builder/build.rs index fd5cca76e..9b29b90d2 100644 --- a/examples-builder/build.rs +++ b/examples-builder/build.rs @@ -14,7 +14,7 @@ const EXAMPLES: &[&str] = &[ "ceno_rt_mem", "ceno_rt_mini", "ceno_rt_panic", - "ceno_rt_syscalls", + "ceno_rt_keccak", ]; const CARGO_MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR"); diff --git a/examples/examples/ceno_rt_syscalls.rs b/examples/examples/ceno_rt_keccak.rs similarity index 91% rename from examples/examples/ceno_rt_syscalls.rs rename to examples/examples/ceno_rt_keccak.rs index ed3621b2b..f436b9432 100644 --- a/examples/examples/ceno_rt_syscalls.rs +++ b/examples/examples/ceno_rt_keccak.rs @@ -1,3 +1,7 @@ +//! Compute the Keccak permutation using a syscall. +//! +//! Iterate multiple times and log the state after each iteration. + #![no_main] #![no_std] extern crate ceno_rt; From 8adf10095e1a815ec9e25afdfc8ecb9d0976445c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Mon, 9 Dec 2024 14:26:57 +0100 Subject: [PATCH 07/21] ecall-keccak: move syscall to ceno_rt --- Cargo.lock | 180 ++++++++++++++-------------- ceno_rt/src/lib.rs | 3 + ceno_rt/src/syscalls.rs | 24 ++++ examples/examples/ceno_rt_keccak.rs | 32 +---- 4 files changed, 119 insertions(+), 120 deletions(-) create mode 100644 ceno_rt/src/syscalls.rs diff --git a/Cargo.lock b/Cargo.lock index 4952a9896..7e4928c15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,9 +59,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -74,43 +74,43 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "ark-std" @@ -205,9 +205,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" [[package]] name = "byteorder" @@ -223,9 +223,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.31" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" +checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" dependencies = [ "shlex", ] @@ -340,9 +340,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.21" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", @@ -350,9 +350,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.21" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -369,20 +369,20 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" @@ -431,9 +431,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -645,19 +645,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "fastrand" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "ff" @@ -819,9 +819,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" @@ -849,12 +849,12 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.2", ] [[package]] @@ -939,16 +939,17 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -973,9 +974,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.161" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libredox" @@ -1148,7 +1149,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1274,9 +1275,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "plonky2" @@ -1433,9 +1434,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.88" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -1537,13 +1538,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -1558,9 +1559,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1609,7 +1610,7 @@ checksum = "f265be5d634272320a7de94cea15c22a3bfdd4eb42eb43edc528415f066a1f25" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1626,9 +1627,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.40" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", "errno", @@ -1681,7 +1682,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1763,7 +1764,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1792,9 +1793,9 @@ dependencies = [ [[package]] name = "symbolic-common" -version = "12.12.0" +version = "12.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "366f1b4c6baf6cfefc234bbd4899535fca0b06c74443039a73f6dfb2fad88d77" +checksum = "e5ba5365997a4e375660bed52f5b42766475d5bc8ceb1bb13fea09c469ea0f49" dependencies = [ "debugid", "memmap2", @@ -1804,9 +1805,9 @@ dependencies = [ [[package]] name = "symbolic-demangle" -version = "12.12.0" +version = "12.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aba05ba5b9962ea5617baf556293720a8b2d0a282aa14ee4bf10e22efc7da8c8" +checksum = "beff338b2788519120f38c59ff4bb15174f52a183e547bac3d6072c2c0aa48aa" dependencies = [ "cpp_demangle", "rustc-demangle", @@ -1826,9 +1827,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.82" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -1867,22 +1868,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1933,7 +1934,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -2019,9 +2020,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-width" @@ -2081,9 +2082,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -2092,24 +2093,23 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2117,28 +2117,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -2360,5 +2360,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index 8de456c41..6e2623ee0 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -12,6 +12,9 @@ pub use io::info_out; mod params; pub use params::*; +mod syscalls; +pub use syscalls::*; + #[cfg(not(test))] mod panic_handler { use core::panic::PanicInfo; diff --git a/ceno_rt/src/syscalls.rs b/ceno_rt/src/syscalls.rs new file mode 100644 index 000000000..90ace85da --- /dev/null +++ b/ceno_rt/src/syscalls.rs @@ -0,0 +1,24 @@ +// Based on https://github.com/succinctlabs/sp1/blob/013c24ea2fa15a0e7ed94f7d11a7ada4baa39ab9/crates/zkvm/entrypoint/src/syscalls/keccak_permute.rs + +const KECCAK_PERMUTE: u32 = 0x00_01_01_09; + +use core::arch::asm; + +/// Executes the Keccak256 permutation on the given state. +/// +/// ### Safety +/// +/// The caller must ensure that `state` is valid pointer to data that is aligned along a four +/// byte boundary. +#[allow(unused_variables)] +#[no_mangle] +pub extern "C" fn syscall_keccak_permute(state: &mut [u64; 25]) { + unsafe { + asm!( + "ecall", + in("t0") KECCAK_PERMUTE, + in("a0") state as *mut [u64; 25], + in("a1") 0 + ); + } +} diff --git a/examples/examples/ceno_rt_keccak.rs b/examples/examples/ceno_rt_keccak.rs index f436b9432..07fb7b9ca 100644 --- a/examples/examples/ceno_rt_keccak.rs +++ b/examples/examples/ceno_rt_keccak.rs @@ -5,7 +5,7 @@ #![no_main] #![no_std] extern crate ceno_rt; -use ceno_rt::info_out; +use ceno_rt::{info_out, syscall_keccak_permute}; use core::{ptr::read_volatile, slice}; const ITERATIONS: usize = 3; @@ -15,7 +15,7 @@ fn main() { let mut state = [0_u64; 25]; for _ in 0..ITERATIONS { - syscalls::syscall_keccak_permute(&mut state); + syscall_keccak_permute(&mut state); log_state(&state); } } @@ -26,31 +26,3 @@ fn log_state(state: &[u64; 25]) { }; info_out().write_frame(out); } - -mod syscalls { - - // Based on https://github.com/succinctlabs/sp1/blob/013c24ea2fa15a0e7ed94f7d11a7ada4baa39ab9/crates/zkvm/entrypoint/src/syscalls/keccak_permute.rs - - const KECCAK_PERMUTE: u32 = 0x00_01_01_09; - - use core::arch::asm; - - /// Executes the Keccak256 permutation on the given state. - /// - /// ### Safety - /// - /// The caller must ensure that `state` is valid pointer to data that is aligned along a four - /// byte boundary. - #[allow(unused_variables)] - #[no_mangle] - pub extern "C" fn syscall_keccak_permute(state: &mut [u64; 25]) { - unsafe { - asm!( - "ecall", - in("t0") KECCAK_PERMUTE, - in("a0") state as *mut [u64; 25], - in("a1") 0 - ); - } - } -} From d854fa5963648bc7b201ee4641958f6f2e8ec210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Mon, 9 Dec 2024 14:56:21 +0100 Subject: [PATCH 08/21] ecall-keccak: test memory records --- ceno_emul/src/tracer.rs | 4 ++++ ceno_emul/tests/test_elf.rs | 32 +++++++++++++++++++++++++---- examples/examples/ceno_rt_keccak.rs | 4 ++-- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/ceno_emul/src/tracer.rs b/ceno_emul/src/tracer.rs index 54dfafe78..8aa55cf79 100644 --- a/ceno_emul/src/tracer.rs +++ b/ceno_emul/src/tracer.rs @@ -286,6 +286,10 @@ impl StepRecord { pub fn is_busy_loop(&self) -> bool { self.pc.before == self.pc.after } + + pub fn syscall(&self) -> Option<&SyscallWitness> { + self.syscall.as_ref() + } } #[derive(Debug)] diff --git a/ceno_emul/tests/test_elf.rs b/ceno_emul/tests/test_elf.rs index 7f5c328d6..afbd67a12 100644 --- a/ceno_emul/tests/test_elf.rs +++ b/ceno_emul/tests/test_elf.rs @@ -78,18 +78,43 @@ fn test_ceno_rt_io() -> Result<()> { fn test_ceno_rt_keccak() -> Result<()> { let program_elf = ceno_examples::ceno_rt_keccak; let mut state = VMState::new_from_elf(unsafe_platform(), program_elf)?; - let _steps = run(&mut state)?; + let steps = run(&mut state)?; // Expect the program to have written successive states between Keccak permutations. const ITERATIONS: usize = 3; let keccak_outs = sample_keccak_f(ITERATIONS); let all_messages = read_all_messages(&state); - for (got, expect) in izip!(&all_messages, keccak_outs) { + assert_eq!(all_messages.len(), ITERATIONS); + for (got, expect) in izip!(&all_messages, &keccak_outs) { let got = got .chunks_exact(8) .map(|chunk| u64::from_le_bytes(chunk.try_into().unwrap())) .collect_vec(); + assert_eq!(&got, expect); + } + + // Find the syscall records. + let syscalls = steps.iter().filter_map(|step| step.syscall()).collect_vec(); + assert_eq!(syscalls.len(), ITERATIONS); + + // Check the syscall effects. + for (witness, expect) in izip!(syscalls, keccak_outs) { + assert_eq!(witness.mem_writes.len(), expect.len() * 2); + + let got = witness + .mem_writes + .chunks_exact(2) + .map(|write_ops| { + assert_eq!( + write_ops[1].addr.baddr(), + write_ops[0].addr.baddr() + WORD_SIZE as u32 + ); + let lo = write_ops[0].value.after as u64; + let hi = write_ops[1].value.after as u64; + lo | (hi << 32) + }) + .collect_vec(); assert_eq!(got, expect); } @@ -103,8 +128,7 @@ fn unsafe_platform() -> Platform { } fn sample_keccak_f(count: usize) -> Vec> { - let input = [0_u64; 25]; - let mut state = input.clone(); + let mut state = [0_u64; 25]; (0..count) .map(|_| { diff --git a/examples/examples/ceno_rt_keccak.rs b/examples/examples/ceno_rt_keccak.rs index 07fb7b9ca..cd68d17db 100644 --- a/examples/examples/ceno_rt_keccak.rs +++ b/examples/examples/ceno_rt_keccak.rs @@ -6,7 +6,7 @@ #![no_std] extern crate ceno_rt; use ceno_rt::{info_out, syscall_keccak_permute}; -use core::{ptr::read_volatile, slice}; +use core::slice; const ITERATIONS: usize = 3; @@ -22,7 +22,7 @@ fn main() { fn log_state(state: &[u64; 25]) { let out = unsafe { - slice::from_raw_parts_mut(state.as_ptr() as *mut u8, state.len() * size_of::()) + slice::from_raw_parts(state.as_ptr() as *const u8, state.len() * size_of::()) }; info_out().write_frame(out); } From 4dd9b39b051521f901dfe5d8eb5572c41c6a2e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Mon, 9 Dec 2024 18:46:56 +0100 Subject: [PATCH 09/21] dummy-keccak: draft ecall circuit --- .../instructions/riscv/dummy/dummy_circuit.rs | 8 +- .../instructions/riscv/dummy/dummy_ecall.rs | 89 +++++++++++++++++++ ceno_zkvm/src/instructions/riscv/dummy/mod.rs | 3 + ceno_zkvm/src/instructions/riscv/insn_base.rs | 24 +++-- 4 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 ceno_zkvm/src/instructions/riscv/dummy/dummy_ecall.rs diff --git a/ceno_zkvm/src/instructions/riscv/dummy/dummy_circuit.rs b/ceno_zkvm/src/instructions/riscv/dummy/dummy_circuit.rs index bb85ad7c1..06562133c 100644 --- a/ceno_zkvm/src/instructions/riscv/dummy/dummy_circuit.rs +++ b/ceno_zkvm/src/instructions/riscv/dummy/dummy_circuit.rs @@ -94,7 +94,7 @@ pub struct DummyConfig { impl DummyConfig { #[allow(clippy::too_many_arguments)] - fn construct_circuit( + pub(super) fn construct_circuit( circuit_builder: &mut CircuitBuilder, kind: InsnKind, with_rs1: bool, @@ -212,7 +212,7 @@ impl DummyConfig { }) } - fn assign_instance( + pub(super) fn assign_instance( &self, instance: &mut [MaybeUninit], lk_multiplicity: &mut LkMultiplicity, @@ -263,4 +263,8 @@ impl DummyConfig { Ok(()) } + + pub(super) fn ts(&self) -> WitIn { + self.vm_state.ts + } } diff --git a/ceno_zkvm/src/instructions/riscv/dummy/dummy_ecall.rs b/ceno_zkvm/src/instructions/riscv/dummy/dummy_ecall.rs new file mode 100644 index 000000000..21b51ab7d --- /dev/null +++ b/ceno_zkvm/src/instructions/riscv/dummy/dummy_ecall.rs @@ -0,0 +1,89 @@ +use std::marker::PhantomData; + +use ceno_emul::{InsnKind, StepRecord}; +use ff_ext::ExtensionField; +use itertools::izip; + +use super::{super::insn_base::WriteMEM, dummy_circuit::DummyConfig}; +use crate::{ + circuit_builder::CircuitBuilder, error::ZKVMError, expression::Expression, + instructions::Instruction, witness::LkMultiplicity, +}; +use core::mem::MaybeUninit; + +trait EcallSpec { + const NAME: &'static str; + + const MEM_WRITE_COUNT: usize; +} + +/// DummyEcall can handle any instruction and produce its side-effects. +pub struct DummyEcall(PhantomData<(E, I)>); + +impl Instruction for DummyEcall { + type InstructionConfig = DummyEcallConfig; + + fn name() -> String { + format!("{}_DUMMY", I::NAME) + } + + fn construct_circuit( + circuit_builder: &mut CircuitBuilder, + ) -> Result { + let dummy_insn = DummyConfig::construct_circuit( + circuit_builder, + InsnKind::EANY, + true, + true, + false, + false, + false, + false, + )?; + + // TODO. + let mem_addr = Expression::ZERO; + let val_before = Expression::ZERO; + let val_after = Expression::ZERO; + + let mem_writes = (0..I::MEM_WRITE_COUNT) + .map(|_| { + WriteMEM::construct_circuit( + circuit_builder, + mem_addr.clone(), // TODO: + offset. + val_before.clone(), + val_after.clone(), + dummy_insn.ts(), + ) + }) + .collect::, _>>()?; + + Ok(DummyEcallConfig { + dummy_insn, + mem_writes, + }) + } + + fn assign_instance( + config: &Self::InstructionConfig, + instance: &mut [MaybeUninit], + lk_multiplicity: &mut LkMultiplicity, + step: &StepRecord, + ) -> Result<(), ZKVMError> { + let ops = &step.syscall().unwrap().mem_writes; + for (mem_write, op) in izip!(&config.mem_writes, ops) { + mem_write.assign_op(instance, lk_multiplicity, step.cycle(), op)?; + } + + config + .dummy_insn + .assign_instance(instance, lk_multiplicity, step) + } +} + +#[derive(Debug)] +pub struct DummyEcallConfig { + dummy_insn: DummyConfig, + + mem_writes: Vec, +} diff --git a/ceno_zkvm/src/instructions/riscv/dummy/mod.rs b/ceno_zkvm/src/instructions/riscv/dummy/mod.rs index 9c912f422..e37276de7 100644 --- a/ceno_zkvm/src/instructions/riscv/dummy/mod.rs +++ b/ceno_zkvm/src/instructions/riscv/dummy/mod.rs @@ -12,5 +12,8 @@ mod dummy_circuit; pub use dummy_circuit::DummyInstruction; +mod dummy_ecall; +pub use dummy_ecall::DummyEcall; + #[cfg(test)] mod test; diff --git a/ceno_zkvm/src/instructions/riscv/insn_base.rs b/ceno_zkvm/src/instructions/riscv/insn_base.rs index bcdae575f..f3ab2ca06 100644 --- a/ceno_zkvm/src/instructions/riscv/insn_base.rs +++ b/ceno_zkvm/src/instructions/riscv/insn_base.rs @@ -1,6 +1,7 @@ -use ceno_emul::{StepRecord, Word}; +use ceno_emul::{Cycle, StepRecord, Word, WriteOp}; use ff::Field; use ff_ext::ExtensionField; +use goldilocks::SmallField; use itertools::Itertools; use super::constants::{PC_STEP_SIZE, UINT_LIMBS, UInt}; @@ -332,17 +333,24 @@ impl WriteMEM { lk_multiplicity: &mut LkMultiplicity, step: &StepRecord, ) -> Result<(), ZKVMError> { - set_val!( - instance, - self.prev_ts, - step.memory_op().unwrap().previous_cycle - ); + let op = step.memory_op().unwrap(); + self.assign_op(instance, lk_multiplicity, step.cycle(), &op) + } + + pub fn assign_op( + &self, + instance: &mut [MaybeUninit], + lk_multiplicity: &mut LkMultiplicity, + cycle: Cycle, + op: &WriteOp, + ) -> Result<(), ZKVMError> { + set_val!(instance, self.prev_ts, op.previous_cycle); self.lt_cfg.assign_instance( instance, lk_multiplicity, - step.memory_op().unwrap().previous_cycle, - step.cycle() + Tracer::SUBCYCLE_MEM, + op.previous_cycle, + cycle + Tracer::SUBCYCLE_MEM, )?; Ok(()) From 5be0bc101394a9c995109c1cdfb3eacff7c7c18b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Tue, 10 Dec 2024 09:50:07 +0100 Subject: [PATCH 10/21] ecall-keccak: reorganize ecall cases --- ceno_emul/src/vm_state.rs | 42 +++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/ceno_emul/src/vm_state.rs b/ceno_emul/src/vm_state.rs index d2ca1991d..27d6b12c4 100644 --- a/ceno_emul/src/vm_state.rs +++ b/ceno_emul/src/vm_state.rs @@ -6,7 +6,7 @@ use crate::{ addr::{ByteAddr, RegIdx, Word, WordAddr}, platform::Platform, rv32im::{DecodedInstruction, Emulator, TrapCause}, - syscalls::{KECCAK_PERMUTE, SyscallEffects, handle_syscall}, + syscalls::{SyscallEffects, handle_syscall}, tracer::{Change, StepRecord, Tracer}, }; use anyhow::{Result, anyhow}; @@ -134,25 +134,29 @@ impl EmuContext for VMState { self.halt(); Ok(true) - } else if self.platform.unsafe_ecall_nop { - if function == KECCAK_PERMUTE { - let effects = handle_syscall(self, function, arg0)?; - self.apply_syscall(effects)?; - Ok(true) - } else { - // TODO: remove this example. - // Treat unknown ecalls as all powerful instructions: - // Read two registers, write one register, write one memory word, and branch. - tracing::warn!("ecall ignored: syscall_id={}", function); - self.store_register(DecodedInstruction::RD_NULL as RegIdx, 0)?; - // Example ecall effect - any writable address will do. - let addr = (self.platform.stack_top - WORD_SIZE as u32).into(); - self.store_memory(addr, self.peek_memory(addr))?; - self.set_pc(ByteAddr(self.pc) + PC_STEP_SIZE); - Ok(true) - } } else { - self.trap(TrapCause::EcallError) + match handle_syscall(self, function, arg0) { + Ok(effects) => { + self.apply_syscall(effects)?; + Ok(true) + } + Err(err) if self.platform.unsafe_ecall_nop => { + tracing::warn!("ecall ignored with unsafe_ecall_nop: {:?}", err); + // TODO: remove this example. + // Treat unknown ecalls as all powerful instructions: + // Read two registers, write one register, write one memory word, and branch. + self.store_register(DecodedInstruction::RD_NULL as RegIdx, 0)?; + // Example ecall effect - any writable address will do. + let addr = (self.platform.stack_top - WORD_SIZE as u32).into(); + self.store_memory(addr, self.peek_memory(addr))?; + self.set_pc(ByteAddr(self.pc) + PC_STEP_SIZE); + Ok(true) + } + Err(err) => { + tracing::error!("ecall error: {:?}", err); + self.trap(TrapCause::EcallError) + } + } } } From 241452b4eea2434ad319673d5ae4c22ac2a34d01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Tue, 10 Dec 2024 10:51:20 +0100 Subject: [PATCH 11/21] ecall-keccak: move register accesses to syscall module --- ceno_emul/src/syscalls.rs | 25 ++++++++++++++++++------- ceno_emul/src/tracer.rs | 24 +++++++++++++++++++----- ceno_emul/src/vm_state.rs | 12 ++++++------ ceno_emul/tests/test_elf.rs | 7 ++++++- 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/ceno_emul/src/syscalls.rs b/ceno_emul/src/syscalls.rs index 5c2ddf3b7..99b764364 100644 --- a/ceno_emul/src/syscalls.rs +++ b/ceno_emul/src/syscalls.rs @@ -1,4 +1,4 @@ -use crate::{Change, EmuContext, VMState, WORD_SIZE, WordAddr, WriteOp}; +use crate::{Change, EmuContext, Platform, VMState, WORD_SIZE, WordAddr, WriteOp}; use anyhow::Result; use itertools::{Itertools, izip}; use tiny_keccak::keccakf; @@ -7,22 +7,22 @@ use tiny_keccak::keccakf; #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct SyscallWitness { pub mem_writes: Vec, + pub reg_accesses: Vec, } /// The effects of a syscall to apply on the VM. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct SyscallEffects { pub witness: SyscallWitness, - pub return_value: Option, pub next_pc: Option, } pub const KECCAK_PERMUTE: u32 = 0x00_01_01_09; /// Trace the inputs and effects of a syscall. -pub fn handle_syscall(vm: &VMState, function_code: u32, arg0: u32) -> Result { +pub fn handle_syscall(vm: &VMState, function_code: u32) -> Result { match function_code { - KECCAK_PERMUTE => Ok(keccak_permute(vm, arg0)), + KECCAK_PERMUTE => Ok(keccak_permute(vm)), _ => Err(anyhow::anyhow!("Unknown syscall: {}", function_code)), } } @@ -36,7 +36,16 @@ const KECCAK_WORDS: usize = KECCAK_CELLS * 2; // u32 words /// https://github.com/succinctlabs/sp1/blob/013c24ea2fa15a0e7ed94f7d11a7ada4baa39ab9/crates/core/executor/src/syscalls/precompiles/keccak256/permute.rs /// /// TODO: test compatibility. -fn keccak_permute(vm: &VMState, state_ptr: u32) -> SyscallEffects { +fn keccak_permute(vm: &VMState) -> SyscallEffects { + let state_ptr = vm.peek_register(Platform::reg_arg0()); + + // Read the argument `state_ptr`. + let reg_accesses = vec![WriteOp::new_register_op( + Platform::reg_arg0(), + Change::new(state_ptr, state_ptr), + 0, // Set later by Tracer. + )]; + let addrs = (state_ptr..) .step_by(WORD_SIZE) .take(KECCAK_WORDS) @@ -72,8 +81,10 @@ fn keccak_permute(vm: &VMState, state_ptr: u32) -> SyscallEffects { assert_eq!(mem_writes.len(), KECCAK_WORDS); SyscallEffects { - witness: SyscallWitness { mem_writes }, - return_value: None, + witness: SyscallWitness { + mem_writes, + reg_accesses, + }, next_pc: None, } } diff --git a/ceno_emul/src/tracer.rs b/ceno_emul/src/tracer.rs index 8aa55cf79..88ef444cc 100644 --- a/ceno_emul/src/tracer.rs +++ b/ceno_emul/src/tracer.rs @@ -1,5 +1,7 @@ use std::{collections::HashMap, fmt, mem}; +use itertools::chain; + use crate::{ CENO_PLATFORM, InsnKind, PC_STEP_SIZE, Platform, addr::{ByteAddr, Cycle, RegIdx, Word, WordAddr}, @@ -47,6 +49,15 @@ pub struct MemOp { } impl MemOp { + pub fn new_register_op(idx: RegIdx, value: T, previous_cycle: Cycle) -> MemOp { + let addr = Platform::register_vma(idx).into(); + MemOp { + addr, + value, + previous_cycle, + } + } + /// Get the register index of this operation. pub fn register_index(&self) -> RegIdx { Platform::register_index(self.addr.into()) @@ -395,12 +406,15 @@ impl Tracer { } pub fn track_syscall(&mut self, mut effects: SyscallEffects) { - let cycle = self.record.cycle + Self::SUBCYCLE_MEM; - for op in &mut effects.witness.mem_writes { - op.previous_cycle = self.track_access(op.addr, Self::SUBCYCLE_MEM); + // Keep track of the cycles of registers and memory accesses. + for op in chain( + &mut effects.witness.reg_accesses, + &mut effects.witness.mem_writes, + ) { + op.previous_cycle = self.track_access(op.addr, 0); assert_ne!( - op.previous_cycle, cycle, - "Memory address {:?} was accessed twice in the same cycle", + op.previous_cycle, self.record.cycle, + "Address {:?} was accessed twice in the same cycle", op.addr ); } diff --git a/ceno_emul/src/vm_state.rs b/ceno_emul/src/vm_state.rs index 27d6b12c4..0eba5fe9c 100644 --- a/ceno_emul/src/vm_state.rs +++ b/ceno_emul/src/vm_state.rs @@ -112,8 +112,8 @@ impl VMState { self.memory.insert(write_op.addr, write_op.value.after); } - if let Some(return_value) = effects.return_value { - self.store_register(Platform::reg_arg0(), return_value)?; + for reg_access in &effects.witness.reg_accesses { + self.registers[reg_access.register_index()] = reg_access.value.after; } let next_pc = effects.next_pc.unwrap_or(self.pc + PC_STEP_SIZE as u32); @@ -128,14 +128,13 @@ impl EmuContext for VMState { // Expect an ecall to terminate the program: function HALT with argument exit_code. fn ecall(&mut self) -> Result { let function = self.load_register(Platform::reg_ecall())?; - let arg0 = self.load_register(Platform::reg_arg0())?; if function == Platform::ecall_halt() { - tracing::debug!("halt with exit_code={}", arg0); - + let exit_code = self.load_register(Platform::reg_arg0())?; + tracing::debug!("halt with exit_code={}", exit_code); self.halt(); Ok(true) } else { - match handle_syscall(self, function, arg0) { + match handle_syscall(self, function) { Ok(effects) => { self.apply_syscall(effects)?; Ok(true) @@ -145,6 +144,7 @@ impl EmuContext for VMState { // TODO: remove this example. // Treat unknown ecalls as all powerful instructions: // Read two registers, write one register, write one memory word, and branch. + let _arg0 = self.load_register(Platform::reg_arg0())?; self.store_register(DecodedInstruction::RD_NULL as RegIdx, 0)?; // Example ecall effect - any writable address will do. let addr = (self.platform.stack_top - WORD_SIZE as u32).into(); diff --git a/ceno_emul/tests/test_elf.rs b/ceno_emul/tests/test_elf.rs index afbd67a12..0aa3da539 100644 --- a/ceno_emul/tests/test_elf.rs +++ b/ceno_emul/tests/test_elf.rs @@ -100,8 +100,13 @@ fn test_ceno_rt_keccak() -> Result<()> { // Check the syscall effects. for (witness, expect) in izip!(syscalls, keccak_outs) { - assert_eq!(witness.mem_writes.len(), expect.len() * 2); + assert_eq!(witness.reg_accesses.len(), 1); + assert_eq!( + witness.reg_accesses[0].register_index(), + Platform::reg_arg0() + ); + assert_eq!(witness.mem_writes.len(), expect.len() * 2); let got = witness .mem_writes .chunks_exact(2) From 62737a3f2b1a0db99e76c9c5161f33d5759d0b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Tue, 10 Dec 2024 11:17:31 +0100 Subject: [PATCH 12/21] ecall-keccak: encapsulate construction details --- ceno_emul/src/syscalls.rs | 37 ++++++++++++++++++++++++++++++++++--- ceno_emul/src/tracer.rs | 21 ++++----------------- ceno_emul/src/vm_state.rs | 8 ++++---- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/ceno_emul/src/syscalls.rs b/ceno_emul/src/syscalls.rs index 99b764364..3c37b117a 100644 --- a/ceno_emul/src/syscalls.rs +++ b/ceno_emul/src/syscalls.rs @@ -1,6 +1,8 @@ -use crate::{Change, EmuContext, Platform, VMState, WORD_SIZE, WordAddr, WriteOp}; +use crate::{ + Change, EmuContext, Platform, RegIdx, Tracer, VMState, WORD_SIZE, Word, WordAddr, WriteOp, +}; use anyhow::Result; -use itertools::{Itertools, izip}; +use itertools::{Itertools, chain, izip}; use tiny_keccak::keccakf; /// A syscall event, available to the circuit witness generators. @@ -13,10 +15,39 @@ pub struct SyscallWitness { /// The effects of a syscall to apply on the VM. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct SyscallEffects { - pub witness: SyscallWitness, + /// The witness being built. Get it with `finalize`. + witness: SyscallWitness, + + /// The next PC after the syscall. Defaults to the next instruction. pub next_pc: Option, } +impl SyscallEffects { + /// Iterate over the register values after the syscall. + pub fn iter_reg_values(&self) -> impl Iterator + '_ { + self.witness + .reg_accesses + .iter() + .map(|op| (op.register_index(), op.value.after)) + } + + /// Iterate over the memory values after the syscall. + pub fn iter_mem_values(&self) -> impl Iterator + '_ { + self.witness + .mem_writes + .iter() + .map(|op| (op.addr, op.value.after)) + } + + /// Keep track of the cycles of registers and memory accesses. + pub fn finalize(mut self, tracer: &mut Tracer) -> SyscallWitness { + for op in chain(&mut self.witness.reg_accesses, &mut self.witness.mem_writes) { + op.previous_cycle = tracer.track_access(op.addr, 0); + } + self.witness + } +} + pub const KECCAK_PERMUTE: u32 = 0x00_01_01_09; /// Trace the inputs and effects of a syscall. diff --git a/ceno_emul/src/tracer.rs b/ceno_emul/src/tracer.rs index 88ef444cc..fd9a12cf0 100644 --- a/ceno_emul/src/tracer.rs +++ b/ceno_emul/src/tracer.rs @@ -1,7 +1,5 @@ use std::{collections::HashMap, fmt, mem}; -use itertools::chain; - use crate::{ CENO_PLATFORM, InsnKind, PC_STEP_SIZE, Platform, addr::{ByteAddr, Cycle, RegIdx, Word, WordAddr}, @@ -405,29 +403,18 @@ impl Tracer { }); } - pub fn track_syscall(&mut self, mut effects: SyscallEffects) { - // Keep track of the cycles of registers and memory accesses. - for op in chain( - &mut effects.witness.reg_accesses, - &mut effects.witness.mem_writes, - ) { - op.previous_cycle = self.track_access(op.addr, 0); - assert_ne!( - op.previous_cycle, self.record.cycle, - "Address {:?} was accessed twice in the same cycle", - op.addr - ); - } + pub fn track_syscall(&mut self, effects: SyscallEffects) { + let witness = effects.finalize(self); assert!(self.record.syscall.is_none(), "Only one syscall per step"); - self.record.syscall = Some(effects.witness); + self.record.syscall = Some(witness); } /// - Return the cycle when an address was last accessed. /// - Return 0 if this is the first access. /// - Record the current instruction as the origin of the latest access. /// - Accesses within the same instruction are distinguished by `subcycle ∈ [0, 3]`. - fn track_access(&mut self, addr: WordAddr, subcycle: Cycle) -> Cycle { + pub fn track_access(&mut self, addr: WordAddr, subcycle: Cycle) -> Cycle { self.latest_accesses .insert(addr, self.record.cycle + subcycle) .unwrap_or(0) diff --git a/ceno_emul/src/vm_state.rs b/ceno_emul/src/vm_state.rs index 0eba5fe9c..536d77d3e 100644 --- a/ceno_emul/src/vm_state.rs +++ b/ceno_emul/src/vm_state.rs @@ -108,12 +108,12 @@ impl VMState { } fn apply_syscall(&mut self, effects: SyscallEffects) -> Result<()> { - for write_op in &effects.witness.mem_writes { - self.memory.insert(write_op.addr, write_op.value.after); + for (addr, value) in effects.iter_mem_values() { + self.memory.insert(addr, value); } - for reg_access in &effects.witness.reg_accesses { - self.registers[reg_access.register_index()] = reg_access.value.after; + for (idx, value) in effects.iter_reg_values() { + self.registers[idx] = value; } let next_pc = effects.next_pc.unwrap_or(self.pc + PC_STEP_SIZE as u32); From e987066e85adea6d39011a2711afdc12d28079f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Tue, 10 Dec 2024 11:29:58 +0100 Subject: [PATCH 13/21] ecall-keccak: move Keccak to its own module --- ceno_emul/src/syscalls.rs | 91 ++++-------------------- ceno_emul/src/syscalls/keccak_permute.rs | 68 ++++++++++++++++++ ceno_emul/src/tracer.rs | 3 +- 3 files changed, 83 insertions(+), 79 deletions(-) create mode 100644 ceno_emul/src/syscalls/keccak_permute.rs diff --git a/ceno_emul/src/syscalls.rs b/ceno_emul/src/syscalls.rs index 3c37b117a..e5b3413cd 100644 --- a/ceno_emul/src/syscalls.rs +++ b/ceno_emul/src/syscalls.rs @@ -1,9 +1,18 @@ -use crate::{ - Change, EmuContext, Platform, RegIdx, Tracer, VMState, WORD_SIZE, Word, WordAddr, WriteOp, -}; +use crate::{RegIdx, Tracer, VMState, Word, WordAddr, WriteOp}; use anyhow::Result; -use itertools::{Itertools, chain, izip}; -use tiny_keccak::keccakf; +use itertools::chain; + +mod keccak_permute; + +pub const KECCAK_PERMUTE: u32 = 0x00_01_01_09; + +/// Trace the inputs and effects of a syscall. +pub fn handle_syscall(vm: &VMState, function_code: u32) -> Result { + match function_code { + KECCAK_PERMUTE => Ok(keccak_permute::keccak_permute(vm)), + _ => Err(anyhow::anyhow!("Unknown syscall: {}", function_code)), + } +} /// A syscall event, available to the circuit witness generators. #[derive(Clone, Debug, Default, PartialEq, Eq)] @@ -47,75 +56,3 @@ impl SyscallEffects { self.witness } } - -pub const KECCAK_PERMUTE: u32 = 0x00_01_01_09; - -/// Trace the inputs and effects of a syscall. -pub fn handle_syscall(vm: &VMState, function_code: u32) -> Result { - match function_code { - KECCAK_PERMUTE => Ok(keccak_permute(vm)), - _ => Err(anyhow::anyhow!("Unknown syscall: {}", function_code)), - } -} - -const KECCAK_CELLS: usize = 25; // u64 cells -const KECCAK_WORDS: usize = KECCAK_CELLS * 2; // u32 words - -/// Trace the execution of a Keccak permutation. -/// -/// Compatible with: -/// https://github.com/succinctlabs/sp1/blob/013c24ea2fa15a0e7ed94f7d11a7ada4baa39ab9/crates/core/executor/src/syscalls/precompiles/keccak256/permute.rs -/// -/// TODO: test compatibility. -fn keccak_permute(vm: &VMState) -> SyscallEffects { - let state_ptr = vm.peek_register(Platform::reg_arg0()); - - // Read the argument `state_ptr`. - let reg_accesses = vec![WriteOp::new_register_op( - Platform::reg_arg0(), - Change::new(state_ptr, state_ptr), - 0, // Set later by Tracer. - )]; - - let addrs = (state_ptr..) - .step_by(WORD_SIZE) - .take(KECCAK_WORDS) - .map(WordAddr::from) - .collect_vec(); - - // Read Keccak state. - let input = addrs - .iter() - .map(|&addr| vm.peek_memory(addr)) - .collect::>(); - - // Compute Keccak permutation. - let output = { - let mut state = [0_u64; KECCAK_CELLS]; - izip!(state.iter_mut(), input.chunks_exact(2)).for_each(|(cell, chunk)| { - let lo = chunk[0] as u64; - let hi = chunk[1] as u64; - *cell = lo | hi << 32; - }); - keccakf(&mut state); - state.into_iter().flat_map(|c| [c as u32, (c >> 32) as u32]) - }; - - // Write permuted state. - let mem_writes = izip!(addrs, input, output) - .map(|(addr, before, after)| WriteOp { - addr, - value: Change { before, after }, - previous_cycle: 0, // Set later by Tracer. - }) - .collect_vec(); - - assert_eq!(mem_writes.len(), KECCAK_WORDS); - SyscallEffects { - witness: SyscallWitness { - mem_writes, - reg_accesses, - }, - next_pc: None, - } -} diff --git a/ceno_emul/src/syscalls/keccak_permute.rs b/ceno_emul/src/syscalls/keccak_permute.rs new file mode 100644 index 000000000..05c04c872 --- /dev/null +++ b/ceno_emul/src/syscalls/keccak_permute.rs @@ -0,0 +1,68 @@ +use itertools::{Itertools, izip}; +use tiny_keccak::keccakf; + +use crate::{Change, EmuContext, Platform, VMState, WORD_SIZE, WordAddr, WriteOp}; + +use super::{SyscallEffects, SyscallWitness}; + +const KECCAK_CELLS: usize = 25; // u64 cells +const KECCAK_WORDS: usize = KECCAK_CELLS * 2; // u32 words + +/// Trace the execution of a Keccak permutation. +/// +/// Compatible with: +/// https://github.com/succinctlabs/sp1/blob/013c24ea2fa15a0e7ed94f7d11a7ada4baa39ab9/crates/core/executor/src/syscalls/precompiles/keccak256/permute.rs +/// +/// TODO: test compatibility. +pub fn keccak_permute(vm: &VMState) -> SyscallEffects { + let state_ptr = vm.peek_register(Platform::reg_arg0()); + + // Read the argument `state_ptr`. + let reg_accesses = vec![WriteOp::new_register_op( + Platform::reg_arg0(), + Change::new(state_ptr, state_ptr), + 0, // Cycle set later in finalize(). + )]; + + let addrs = (state_ptr..) + .step_by(WORD_SIZE) + .take(KECCAK_WORDS) + .map(WordAddr::from) + .collect_vec(); + + // Read Keccak state. + let input = addrs + .iter() + .map(|&addr| vm.peek_memory(addr)) + .collect::>(); + + // Compute Keccak permutation. + let output = { + let mut state = [0_u64; KECCAK_CELLS]; + izip!(state.iter_mut(), input.chunks_exact(2)).for_each(|(cell, chunk)| { + let lo = chunk[0] as u64; + let hi = chunk[1] as u64; + *cell = lo | hi << 32; + }); + keccakf(&mut state); + state.into_iter().flat_map(|c| [c as u32, (c >> 32) as u32]) + }; + + // Write permuted state. + let mem_writes = izip!(addrs, input, output) + .map(|(addr, before, after)| WriteOp { + addr, + value: Change { before, after }, + previous_cycle: 0, // Cycle set later in finalize(). + }) + .collect_vec(); + + assert_eq!(mem_writes.len(), KECCAK_WORDS); + SyscallEffects { + witness: SyscallWitness { + mem_writes, + reg_accesses, + }, + next_pc: None, + } +} diff --git a/ceno_emul/src/tracer.rs b/ceno_emul/src/tracer.rs index fd9a12cf0..a7e5b6fd6 100644 --- a/ceno_emul/src/tracer.rs +++ b/ceno_emul/src/tracer.rs @@ -48,9 +48,8 @@ pub struct MemOp { impl MemOp { pub fn new_register_op(idx: RegIdx, value: T, previous_cycle: Cycle) -> MemOp { - let addr = Platform::register_vma(idx).into(); MemOp { - addr, + addr: Platform::register_vma(idx).into(), value, previous_cycle, } From 9b941114c9e0a0ed5fb0c3465475fa5f9641d04f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Wed, 11 Dec 2024 18:40:03 +0100 Subject: [PATCH 14/21] dummy-keccak: Register and Memory ops. Test. --- ceno_emul/src/lib.rs | 3 + ceno_emul/src/rv32im_encode.rs | 7 ++ ceno_emul/src/syscalls.rs | 18 +-- ceno_emul/src/syscalls/keccak_permute.rs | 13 +- ceno_emul/src/test_utils.rs | 25 ++++ ceno_emul/tests/test_elf.rs | 8 +- .../instructions/riscv/dummy/dummy_ecall.rs | 117 +++++++++++++----- ceno_zkvm/src/instructions/riscv/dummy/mod.rs | 2 +- .../src/instructions/riscv/dummy/test.rs | 25 ++++ ceno_zkvm/src/instructions/riscv/insn_base.rs | 12 +- 10 files changed, 174 insertions(+), 56 deletions(-) create mode 100644 ceno_emul/src/test_utils.rs diff --git a/ceno_emul/src/lib.rs b/ceno_emul/src/lib.rs index 536887b7d..ee1b51a7a 100644 --- a/ceno_emul/src/lib.rs +++ b/ceno_emul/src/lib.rs @@ -21,3 +21,6 @@ mod rv32im_encode; pub use rv32im_encode::encode_rv32; mod syscalls; +pub use syscalls::{KECCAK_PERMUTE, keccak_permute::KECCAK_WORDS}; + +pub mod test_utils; diff --git a/ceno_emul/src/rv32im_encode.rs b/ceno_emul/src/rv32im_encode.rs index b6a80f32b..fc9d9e172 100644 --- a/ceno_emul/src/rv32im_encode.rs +++ b/ceno_emul/src/rv32im_encode.rs @@ -26,6 +26,13 @@ pub const fn encode_rv32(kind: InsnKind, rs1: u32, rs2: u32, rd: u32, imm: u32) } } +pub const fn load_immediate(rd: u32, imm: u32) -> [u32; 2] { + [ + encode_rv32(InsnKind::LUI, 0, 0, rd, imm), + encode_rv32(InsnKind::ORI, rd, 0, rd, imm), + ] +} + // R-Type // 25 20 15 12 7 0 // +------+-----+-----+--------+----+-------+ diff --git a/ceno_emul/src/syscalls.rs b/ceno_emul/src/syscalls.rs index e5b3413cd..7b80c1d53 100644 --- a/ceno_emul/src/syscalls.rs +++ b/ceno_emul/src/syscalls.rs @@ -1,8 +1,7 @@ use crate::{RegIdx, Tracer, VMState, Word, WordAddr, WriteOp}; use anyhow::Result; -use itertools::chain; -mod keccak_permute; +pub mod keccak_permute; pub const KECCAK_PERMUTE: u32 = 0x00_01_01_09; @@ -17,8 +16,8 @@ pub fn handle_syscall(vm: &VMState, function_code: u32) -> Result, - pub reg_accesses: Vec, + pub mem_ops: Vec, + pub reg_ops: Vec, } /// The effects of a syscall to apply on the VM. @@ -35,7 +34,7 @@ impl SyscallEffects { /// Iterate over the register values after the syscall. pub fn iter_reg_values(&self) -> impl Iterator + '_ { self.witness - .reg_accesses + .reg_ops .iter() .map(|op| (op.register_index(), op.value.after)) } @@ -43,15 +42,18 @@ impl SyscallEffects { /// Iterate over the memory values after the syscall. pub fn iter_mem_values(&self) -> impl Iterator + '_ { self.witness - .mem_writes + .mem_ops .iter() .map(|op| (op.addr, op.value.after)) } /// Keep track of the cycles of registers and memory accesses. pub fn finalize(mut self, tracer: &mut Tracer) -> SyscallWitness { - for op in chain(&mut self.witness.reg_accesses, &mut self.witness.mem_writes) { - op.previous_cycle = tracer.track_access(op.addr, 0); + for op in &mut self.witness.reg_ops { + op.previous_cycle = tracer.track_access(op.addr, Tracer::SUBCYCLE_RD); + } + for op in &mut self.witness.mem_ops { + op.previous_cycle = tracer.track_access(op.addr, Tracer::SUBCYCLE_MEM); } self.witness } diff --git a/ceno_emul/src/syscalls/keccak_permute.rs b/ceno_emul/src/syscalls/keccak_permute.rs index 05c04c872..f34acff7a 100644 --- a/ceno_emul/src/syscalls/keccak_permute.rs +++ b/ceno_emul/src/syscalls/keccak_permute.rs @@ -6,7 +6,7 @@ use crate::{Change, EmuContext, Platform, VMState, WORD_SIZE, WordAddr, WriteOp} use super::{SyscallEffects, SyscallWitness}; const KECCAK_CELLS: usize = 25; // u64 cells -const KECCAK_WORDS: usize = KECCAK_CELLS * 2; // u32 words +pub const KECCAK_WORDS: usize = KECCAK_CELLS * 2; // u32 words /// Trace the execution of a Keccak permutation. /// @@ -18,7 +18,7 @@ pub fn keccak_permute(vm: &VMState) -> SyscallEffects { let state_ptr = vm.peek_register(Platform::reg_arg0()); // Read the argument `state_ptr`. - let reg_accesses = vec![WriteOp::new_register_op( + let reg_ops = vec![WriteOp::new_register_op( Platform::reg_arg0(), Change::new(state_ptr, state_ptr), 0, // Cycle set later in finalize(). @@ -49,7 +49,7 @@ pub fn keccak_permute(vm: &VMState) -> SyscallEffects { }; // Write permuted state. - let mem_writes = izip!(addrs, input, output) + let mem_ops = izip!(addrs, input, output) .map(|(addr, before, after)| WriteOp { addr, value: Change { before, after }, @@ -57,12 +57,9 @@ pub fn keccak_permute(vm: &VMState) -> SyscallEffects { }) .collect_vec(); - assert_eq!(mem_writes.len(), KECCAK_WORDS); + assert_eq!(mem_ops.len(), KECCAK_WORDS); SyscallEffects { - witness: SyscallWitness { - mem_writes, - reg_accesses, - }, + witness: SyscallWitness { mem_ops, reg_ops }, next_pc: None, } } diff --git a/ceno_emul/src/test_utils.rs b/ceno_emul/src/test_utils.rs new file mode 100644 index 000000000..812e2fc88 --- /dev/null +++ b/ceno_emul/src/test_utils.rs @@ -0,0 +1,25 @@ +use crate::{ + CENO_PLATFORM, InsnKind, Platform, Program, StepRecord, VMState, encode_rv32, + rv32im_encode::load_immediate, syscalls::KECCAK_PERMUTE, +}; +use anyhow::Result; + +pub fn keccak_step() -> (StepRecord, Vec) { + let instructions = [ + // Call Keccak-f. + &load_immediate(Platform::reg_arg0() as u32, CENO_PLATFORM.ram.start)[..], + &load_immediate(Platform::reg_ecall() as u32, KECCAK_PERMUTE)[..], + &[encode_rv32(InsnKind::EANY, 0, 0, 0, 0)], + // Halt. + &load_immediate(Platform::reg_ecall() as u32, Platform::ecall_halt())[..], + &[encode_rv32(InsnKind::EANY, 0, 0, 0, 0)], + ] + .concat(); + + let pc = CENO_PLATFORM.pc_base(); + let program = Program::new(pc, pc, instructions.clone(), Default::default()); + let mut vm = VMState::new(CENO_PLATFORM, program.into()); + let steps = vm.iter_until_halt().collect::>>().unwrap(); + + (steps[4].clone(), instructions) +} diff --git a/ceno_emul/tests/test_elf.rs b/ceno_emul/tests/test_elf.rs index 0aa3da539..6e97ffcbc 100644 --- a/ceno_emul/tests/test_elf.rs +++ b/ceno_emul/tests/test_elf.rs @@ -100,15 +100,15 @@ fn test_ceno_rt_keccak() -> Result<()> { // Check the syscall effects. for (witness, expect) in izip!(syscalls, keccak_outs) { - assert_eq!(witness.reg_accesses.len(), 1); + assert_eq!(witness.reg_ops.len(), 1); assert_eq!( - witness.reg_accesses[0].register_index(), + witness.reg_ops[0].register_index(), Platform::reg_arg0() ); - assert_eq!(witness.mem_writes.len(), expect.len() * 2); + assert_eq!(witness.mem_ops.len(), expect.len() * 2); let got = witness - .mem_writes + .mem_ops .chunks_exact(2) .map(|write_ops| { assert_eq!( diff --git a/ceno_zkvm/src/instructions/riscv/dummy/dummy_ecall.rs b/ceno_zkvm/src/instructions/riscv/dummy/dummy_ecall.rs index 21b51ab7d..4b1b4f15e 100644 --- a/ceno_zkvm/src/instructions/riscv/dummy/dummy_ecall.rs +++ b/ceno_zkvm/src/instructions/riscv/dummy/dummy_ecall.rs @@ -1,65 +1,96 @@ use std::marker::PhantomData; -use ceno_emul::{InsnKind, StepRecord}; +use ceno_emul::{Change, InsnKind, KECCAK_WORDS, StepRecord, WORD_SIZE}; use ff_ext::ExtensionField; -use itertools::izip; +use itertools::Itertools; use super::{super::insn_base::WriteMEM, dummy_circuit::DummyConfig}; use crate::{ - circuit_builder::CircuitBuilder, error::ZKVMError, expression::Expression, - instructions::Instruction, witness::LkMultiplicity, + Value, + circuit_builder::CircuitBuilder, + error::ZKVMError, + expression::{ToExpr, WitIn}, + instructions::{ + Instruction, + riscv::{constants::UInt, insn_base::WriteRD}, + }, + set_val, + witness::LkMultiplicity, }; use core::mem::MaybeUninit; trait EcallSpec { const NAME: &'static str; - const MEM_WRITE_COUNT: usize; + const REG_OPS_COUNT: usize; + const MEM_OPS_COUNT: usize; } -/// DummyEcall can handle any instruction and produce its side-effects. -pub struct DummyEcall(PhantomData<(E, I)>); +pub struct KeccakSpec; -impl Instruction for DummyEcall { - type InstructionConfig = DummyEcallConfig; +impl EcallSpec for KeccakSpec { + const NAME: &'static str = "KECCAK"; + + const REG_OPS_COUNT: usize = 1; + const MEM_OPS_COUNT: usize = KECCAK_WORDS; +} + +/// LargeEcallDummy can handle any instruction and produce its effects, +/// including multiple memory operations. +/// +/// Unsafe: The content is not constrained. +pub struct LargeEcallDummy(PhantomData<(E, S)>); + +impl Instruction for LargeEcallDummy { + type InstructionConfig = LargeEcallConfig; fn name() -> String { - format!("{}_DUMMY", I::NAME) + format!("{}_DUMMY", S::NAME) } - fn construct_circuit( - circuit_builder: &mut CircuitBuilder, - ) -> Result { + fn construct_circuit(cb: &mut CircuitBuilder) -> Result { let dummy_insn = DummyConfig::construct_circuit( - circuit_builder, + cb, InsnKind::EANY, - true, - true, + true, // Read the ecall function code. + false, false, false, false, false, )?; - // TODO. - let mem_addr = Expression::ZERO; - let val_before = Expression::ZERO; - let val_after = Expression::ZERO; + let start_addr = cb.create_witin(|| "mem_addr"); + + let reg_writes = (0..S::REG_OPS_COUNT) + .map(|i| { + let val_after = UInt::new_unchecked(|| format!("reg_after_{}", i), cb)?; + + WriteRD::construct_circuit(cb, val_after.register_expr(), dummy_insn.ts()) + .map(|writer| (val_after, writer)) + }) + .collect::, _>>()?; + + let mem_writes = (0..S::MEM_OPS_COUNT) + .map(|i| { + let val_before = cb.create_witin(|| format!("mem_before_{}", i)); + let val_after = cb.create_witin(|| format!("mem_after_{}", i)); - let mem_writes = (0..I::MEM_WRITE_COUNT) - .map(|_| { WriteMEM::construct_circuit( - circuit_builder, - mem_addr.clone(), // TODO: + offset. - val_before.clone(), - val_after.clone(), + cb, + start_addr.expr() + (i * WORD_SIZE) as u64, + val_before.expr(), + val_after.expr(), dummy_insn.ts(), ) + .map(|writer| (Change::new(val_before, val_after), writer)) }) .collect::, _>>()?; - Ok(DummyEcallConfig { + Ok(LargeEcallConfig { dummy_insn, + start_addr, + reg_writes, mem_writes, }) } @@ -70,20 +101,38 @@ impl Instruction for DummyEcall { lk_multiplicity: &mut LkMultiplicity, step: &StepRecord, ) -> Result<(), ZKVMError> { - let ops = &step.syscall().unwrap().mem_writes; - for (mem_write, op) in izip!(&config.mem_writes, ops) { - mem_write.assign_op(instance, lk_multiplicity, step.cycle(), op)?; - } + let ops = &step.syscall().expect("syscall step"); + // Assign instruction. config .dummy_insn - .assign_instance(instance, lk_multiplicity, step) + .assign_instance(instance, lk_multiplicity, step)?; + + set_val!(instance, config.start_addr, u64::from(ops.mem_ops[0].addr)); + + // Assign registers. + for ((value, writer), op) in config.reg_writes.iter().zip_eq(&ops.reg_ops) { + value.assign_value(instance, Value::new_unchecked(op.value.after)); + writer.assign_op(instance, lk_multiplicity, step.cycle(), op)?; + } + + // Assign memory. + for ((value, writer), op) in config.mem_writes.iter().zip_eq(&ops.mem_ops) { + set_val!(instance, value.before, op.value.before as u64); + set_val!(instance, value.after, op.value.after as u64); + writer.assign_op(instance, lk_multiplicity, step.cycle(), op)?; + } + + Ok(()) } } #[derive(Debug)] -pub struct DummyEcallConfig { +pub struct LargeEcallConfig { dummy_insn: DummyConfig, - mem_writes: Vec, + reg_writes: Vec<(UInt, WriteRD)>, + + start_addr: WitIn, + mem_writes: Vec<(Change, WriteMEM)>, } diff --git a/ceno_zkvm/src/instructions/riscv/dummy/mod.rs b/ceno_zkvm/src/instructions/riscv/dummy/mod.rs index e37276de7..02114e737 100644 --- a/ceno_zkvm/src/instructions/riscv/dummy/mod.rs +++ b/ceno_zkvm/src/instructions/riscv/dummy/mod.rs @@ -13,7 +13,7 @@ mod dummy_circuit; pub use dummy_circuit::DummyInstruction; mod dummy_ecall; -pub use dummy_ecall::DummyEcall; +pub use dummy_ecall::LargeEcallDummy; #[cfg(test)] mod test; diff --git a/ceno_zkvm/src/instructions/riscv/dummy/test.rs b/ceno_zkvm/src/instructions/riscv/dummy/test.rs index df1eb0572..e46f8d68f 100644 --- a/ceno_zkvm/src/instructions/riscv/dummy/test.rs +++ b/ceno_zkvm/src/instructions/riscv/dummy/test.rs @@ -1,4 +1,5 @@ use ceno_emul::{Change, InsnKind, StepRecord, encode_rv32}; +use dummy_ecall::KeccakSpec; use goldilocks::GoldilocksExt2; use super::*; @@ -37,6 +38,30 @@ fn test_dummy_ecall() { MockProver::assert_satisfied_raw(&cb, raw_witin, &[insn_code], None, Some(lkm)); } +#[test] +fn test_dummy_keccak() { + type KeccakDummy = LargeEcallDummy; + + let mut cs = ConstraintSystem::::new(|| "riscv"); + let mut cb = CircuitBuilder::new(&mut cs); + let config = cb + .namespace( + || "keccak_dummy", + |cb| { + let config = KeccakDummy::construct_circuit(cb); + Ok(config) + }, + ) + .unwrap() + .unwrap(); + + let (step, program) = ceno_emul::test_utils::keccak_step(); + let (raw_witin, lkm) = + KeccakDummy::assign_instances(&config, cb.cs.num_witin as usize, vec![step]).unwrap(); + + MockProver::assert_satisfied_raw(&cb, raw_witin, &program, None, Some(lkm)); +} + #[test] fn test_dummy_r() { let mut cs = ConstraintSystem::::new(|| "riscv"); diff --git a/ceno_zkvm/src/instructions/riscv/insn_base.rs b/ceno_zkvm/src/instructions/riscv/insn_base.rs index f3ab2ca06..72c5edd79 100644 --- a/ceno_zkvm/src/instructions/riscv/insn_base.rs +++ b/ceno_zkvm/src/instructions/riscv/insn_base.rs @@ -223,6 +223,16 @@ impl WriteRD { step: &StepRecord, ) -> Result<(), ZKVMError> { let op = step.rd().expect("rd op"); + self.assign_op(instance, lk_multiplicity, step.cycle(), &op) + } + + pub fn assign_op( + &self, + instance: &mut [MaybeUninit], + lk_multiplicity: &mut LkMultiplicity, + cycle: Cycle, + op: &WriteOp, + ) -> Result<(), ZKVMError> { set_val!(instance, self.id, op.register_index() as u64); set_val!(instance, self.prev_ts, op.previous_cycle); @@ -237,7 +247,7 @@ impl WriteRD { instance, lk_multiplicity, op.previous_cycle, - step.cycle() + Tracer::SUBCYCLE_RD, + cycle + Tracer::SUBCYCLE_RD, )?; Ok(()) From f88cf8e18c5a0a3a44e19047b2ea81de412d632a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Fri, 13 Dec 2024 08:08:45 +0100 Subject: [PATCH 15/21] ecall-keccak: for_each to for --- ceno_emul/src/syscalls/keccak_permute.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ceno_emul/src/syscalls/keccak_permute.rs b/ceno_emul/src/syscalls/keccak_permute.rs index 05c04c872..63decd3eb 100644 --- a/ceno_emul/src/syscalls/keccak_permute.rs +++ b/ceno_emul/src/syscalls/keccak_permute.rs @@ -39,12 +39,12 @@ pub fn keccak_permute(vm: &VMState) -> SyscallEffects { // Compute Keccak permutation. let output = { let mut state = [0_u64; KECCAK_CELLS]; - izip!(state.iter_mut(), input.chunks_exact(2)).for_each(|(cell, chunk)| { - let lo = chunk[0] as u64; - let hi = chunk[1] as u64; - *cell = lo | hi << 32; - }); + for (cell, (&lo, &hi)) in izip!(&mut state, input.iter().tuples()) { + *cell = lo as u64 | (hi as u64) << 32; + } + keccakf(&mut state); + state.into_iter().flat_map(|c| [c as u32, (c >> 32) as u32]) }; From ff9bf415da2867e38db39971e4c60e430d8a2cc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Fri, 13 Dec 2024 08:33:34 +0100 Subject: [PATCH 16/21] ecall-keccak: add reference --- ceno_emul/src/syscalls.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ceno_emul/src/syscalls.rs b/ceno_emul/src/syscalls.rs index e5b3413cd..5980506cb 100644 --- a/ceno_emul/src/syscalls.rs +++ b/ceno_emul/src/syscalls.rs @@ -4,6 +4,9 @@ use itertools::chain; mod keccak_permute; +// Using the same function codes as sp1: +// https://github.com/succinctlabs/sp1/blob/013c24ea2fa15a0e7ed94f7d11a7ada4baa39ab9/crates/core/executor/src/syscalls/code.rs + pub const KECCAK_PERMUTE: u32 = 0x00_01_01_09; /// Trace the inputs and effects of a syscall. From 5d429a62bb6d60408319bd82dbf665caec8f60e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Fri, 13 Dec 2024 09:09:51 +0100 Subject: [PATCH 17/21] dummy-keccak: fix after merge --- ceno_emul/src/test_utils.rs | 30 +++++++++++++----------------- ceno_emul/tests/test_elf.rs | 5 +---- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/ceno_emul/src/test_utils.rs b/ceno_emul/src/test_utils.rs index c3aef2d01..0343d9534 100644 --- a/ceno_emul/src/test_utils.rs +++ b/ceno_emul/src/test_utils.rs @@ -1,32 +1,28 @@ use crate::{ CENO_PLATFORM, InsnKind, Instruction, Platform, Program, StepRecord, VMState, encode_rv32, - rv32im_encode::load_immediate, syscalls::KECCAK_PERMUTE, + encode_rv32u, syscalls::KECCAK_PERMUTE, }; use anyhow::Result; -pub const fn load_immediate(rd: u32, imm: u32) -> [u32; 2] { - [ - encode_rv32(InsnKind::LUI, 0, 0, rd, imm), - encode_rv32(InsnKind::ORI, rd, 0, rd, imm), - ] -} - pub fn keccak_step() -> (StepRecord, Vec) { - let instructions = [ + let instructions = vec![ // Call Keccak-f. - &load_immediate(Platform::reg_arg0() as u32, CENO_PLATFORM.ram.start)[..], - &load_immediate(Platform::reg_ecall() as u32, KECCAK_PERMUTE)[..], - &[encode_rv32(InsnKind::EANY, 0, 0, 0, 0)], + load_immediate(Platform::reg_arg0() as u32, CENO_PLATFORM.ram.start), + load_immediate(Platform::reg_ecall() as u32, KECCAK_PERMUTE), + encode_rv32(InsnKind::ECALL, 0, 0, 0, 0), // Halt. - &load_immediate(Platform::reg_ecall() as u32, Platform::ecall_halt())[..], - &[encode_rv32(InsnKind::EANY, 0, 0, 0, 0)], - ] - .concat(); + load_immediate(Platform::reg_ecall() as u32, Platform::ecall_halt()), + encode_rv32(InsnKind::ECALL, 0, 0, 0, 0), + ]; let pc = CENO_PLATFORM.pc_base(); let program = Program::new(pc, pc, instructions.clone(), Default::default()); let mut vm = VMState::new(CENO_PLATFORM, program.into()); let steps = vm.iter_until_halt().collect::>>().unwrap(); - (steps[4].clone(), instructions) + (steps[2].clone(), instructions) +} + +const fn load_immediate(rd: u32, imm: u32) -> Instruction { + encode_rv32u(InsnKind::ADDI, 0, 0, rd, imm) } diff --git a/ceno_emul/tests/test_elf.rs b/ceno_emul/tests/test_elf.rs index 80310f43c..ce979f089 100644 --- a/ceno_emul/tests/test_elf.rs +++ b/ceno_emul/tests/test_elf.rs @@ -101,10 +101,7 @@ fn test_ceno_rt_keccak() -> Result<()> { // Check the syscall effects. for (witness, expect) in izip!(syscalls, keccak_outs) { assert_eq!(witness.reg_ops.len(), 1); - assert_eq!( - witness.reg_ops[0].register_index(), - Platform::reg_arg0() - ); + assert_eq!(witness.reg_ops[0].register_index(), Platform::reg_arg0()); assert_eq!(witness.mem_ops.len(), expect.len() * 2); let got = witness From e76767cefc3c8ffe196cd3e7b3f15fdd64e9a102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Mon, 16 Dec 2024 11:56:23 +0100 Subject: [PATCH 18/21] ecall-keccak-merge: restore test after merge --- Cargo.lock | 1 + Cargo.toml | 1 + ceno_emul/Cargo.toml | 2 +- ceno_host/Cargo.toml | 1 + ceno_host/tests/test_elf.rs | 73 ++++++++++++++++++++++++++++++++++++- 5 files changed, 76 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5d10243f..3cec0f9e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -277,6 +277,7 @@ dependencies = [ "itertools 0.13.0", "rand", "rkyv", + "tiny-keccak", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5a8c224d4..bd5c8187f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" strum = "0.26" strum_macros = "0.26" +tiny-keccak = { version = "2.0.2", features = ["keccak"] } tracing = { version = "0.1", features = [ "attributes", ] } diff --git a/ceno_emul/Cargo.toml b/ceno_emul/Cargo.toml index f1885e883..4f4f4a7c7 100644 --- a/ceno_emul/Cargo.toml +++ b/ceno_emul/Cargo.toml @@ -18,7 +18,7 @@ num-traits.workspace = true rrs_lib = { package = "rrs-succinct", version = "0.1.0" } strum.workspace = true strum_macros.workspace = true -tiny-keccak = { version = "2.0.2", features = ["keccak"] } +tiny-keccak.workspace = true tracing.workspace = true [features] diff --git a/ceno_host/Cargo.toml b/ceno_host/Cargo.toml index e589e5854..7f69843b5 100644 --- a/ceno_host/Cargo.toml +++ b/ceno_host/Cargo.toml @@ -14,6 +14,7 @@ anyhow.workspace = true ceno_emul = { path = "../ceno_emul" } itertools.workspace = true rkyv = { version = "0.8", default-features = false, features = ["alloc", "bytecheck"] } +tiny-keccak.workspace = true [dev-dependencies] ceno-examples = { path = "../examples-builder" } diff --git a/ceno_host/tests/test_elf.rs b/ceno_host/tests/test_elf.rs index 7448d4508..f06d07f48 100644 --- a/ceno_host/tests/test_elf.rs +++ b/ceno_host/tests/test_elf.rs @@ -1,5 +1,7 @@ use anyhow::Result; use ceno_emul::{ByteAddr, CENO_PLATFORM, EmuContext, InsnKind, Platform, StepRecord, VMState}; +use itertools::{Itertools, izip}; +use tiny_keccak::keccakf; #[test] fn test_ceno_rt_mini() -> Result<()> { @@ -27,7 +29,7 @@ fn test_ceno_rt_mem() -> Result<()> { let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; let _steps = run(&mut state)?; - let value = state.peek_memory(CENO_PLATFORM.ram.start.into()); + let value = state.peek_memory(CENO_PLATFORM.heap.start.into()); assert_eq!(value, 6765, "Expected Fibonacci 20, got {}", value); Ok(()) } @@ -72,6 +74,75 @@ fn test_ceno_rt_io() -> Result<()> { Ok(()) } +#[test] +fn test_ceno_rt_keccak() -> Result<()> { + let program_elf = ceno_examples::ceno_rt_keccak; + let mut state = VMState::new_from_elf(unsafe_platform(), program_elf)?; + let steps = run(&mut state)?; + + // Expect the program to have written successive states between Keccak permutations. + const ITERATIONS: usize = 3; + let keccak_outs = sample_keccak_f(ITERATIONS); + + let all_messages = read_all_messages(&state); + assert_eq!(all_messages.len(), ITERATIONS); + for (got, expect) in izip!(&all_messages, &keccak_outs) { + let got = got + .chunks_exact(8) + .map(|chunk| u64::from_le_bytes(chunk.try_into().unwrap())) + .collect_vec(); + assert_eq!(&got, expect); + } + + // Find the syscall records. + let syscalls = steps.iter().filter_map(|step| step.syscall()).collect_vec(); + assert_eq!(syscalls.len(), ITERATIONS); + + // Check the syscall effects. + for (witness, expect) in izip!(syscalls, keccak_outs) { + assert_eq!(witness.reg_accesses.len(), 1); + assert_eq!( + witness.reg_accesses[0].register_index(), + Platform::reg_arg0() + ); + + assert_eq!(witness.mem_writes.len(), expect.len() * 2); + let got = witness + .mem_writes + .chunks_exact(2) + .map(|write_ops| { + assert_eq!( + write_ops[1].addr.baddr(), + write_ops[0].addr.baddr() + WORD_SIZE as u32 + ); + let lo = write_ops[0].value.after as u64; + let hi = write_ops[1].value.after as u64; + lo | (hi << 32) + }) + .collect_vec(); + assert_eq!(got, expect); + } + + Ok(()) +} + +fn unsafe_platform() -> Platform { + let mut platform = CENO_PLATFORM; + platform.unsafe_ecall_nop = true; + platform +} + +fn sample_keccak_f(count: usize) -> Vec> { + let mut state = [0_u64; 25]; + + (0..count) + .map(|_| { + keccakf(&mut state); + state.into() + }) + .collect_vec() +} + fn run(state: &mut VMState) -> Result> { let steps = state.iter_until_halt().collect::>>()?; eprintln!("Emulator ran for {} steps.", steps.len()); From 823ec5ee395d6142928bd7e5bf17e8bad2409520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Mon, 16 Dec 2024 12:27:53 +0100 Subject: [PATCH 19/21] ecall-keccak-merge: put back test after merge --- ceno_host/tests/test_elf.rs | 69 +++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/ceno_host/tests/test_elf.rs b/ceno_host/tests/test_elf.rs index f06d07f48..1a7354e07 100644 --- a/ceno_host/tests/test_elf.rs +++ b/ceno_host/tests/test_elf.rs @@ -1,6 +1,11 @@ use anyhow::Result; -use ceno_emul::{ByteAddr, CENO_PLATFORM, EmuContext, InsnKind, Platform, StepRecord, VMState}; -use itertools::{Itertools, izip}; +use ceno_emul::{ + CENO_PLATFORM, EmuContext, InsnKind, Platform, Program, StepRecord, VMState, WORD_SIZE, + host_utils::read_all_messages, +}; +use ceno_host::CenoStdin; +use itertools::{Itertools, enumerate, izip}; +use std::{collections::HashSet, sync::Arc}; use tiny_keccak::keccakf; #[test] @@ -62,15 +67,36 @@ fn test_ceno_rt_alloc() -> Result<()> { #[test] fn test_ceno_rt_io() -> Result<()> { let program_elf = ceno_examples::ceno_rt_io; - let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; + let program = Program::load_elf(program_elf, u32::MAX)?; + let platform = Platform { + prog_data: Some(program.image.keys().copied().collect::>()), + ..CENO_PLATFORM + }; + let mut state = VMState::new(platform, Arc::new(program)); let _steps = run(&mut state)?; let all_messages = read_all_messages(&state); for msg in &all_messages { - print!("{}", String::from_utf8_lossy(msg)); + print!("{msg}"); + } + assert_eq!(&all_messages[0], "📜📜📜 Hello, World!\n"); + assert_eq!(&all_messages[1], "🌏🌍🌎\n"); + Ok(()) +} + +#[test] +fn test_hints() -> Result<()> { + let mut hints = CenoStdin::default(); + hints.write(&true)?; + hints.write(&"This is my hint string.".to_string())?; + hints.write(&1997_u32)?; + hints.write(&1999_u32)?; + + let all_messages = ceno_host::run(CENO_PLATFORM, ceno_examples::hints, &hints); + for (i, msg) in enumerate(&all_messages) { + println!("{i}: {msg}"); } - assert_eq!(&all_messages[0], "📜📜📜 Hello, World!\n".as_bytes()); - assert_eq!(&all_messages[1], "🌏🌍🌎\n".as_bytes()); + assert_eq!(all_messages[0], "3992003"); Ok(()) } @@ -148,34 +174,3 @@ fn run(state: &mut VMState) -> Result> { eprintln!("Emulator ran for {} steps.", steps.len()); Ok(steps) } - -const WORD_SIZE: usize = 4; -const INFO_OUT_ADDR: u32 = 0xC000_0000; - -fn read_all_messages(state: &VMState) -> Vec> { - let mut all_messages = Vec::new(); - let mut word_offset = 0; - loop { - let out = read_message(state, word_offset); - if out.is_empty() { - break; - } - word_offset += out.len().div_ceil(WORD_SIZE) as u32 + 1; - all_messages.push(out); - } - all_messages -} - -fn read_message(state: &VMState, word_offset: u32) -> Vec { - let out_addr = ByteAddr(INFO_OUT_ADDR).waddr() + word_offset; - let byte_len = state.peek_memory(out_addr); - let word_len_up = byte_len.div_ceil(4); - - let mut info_out = Vec::with_capacity(WORD_SIZE * word_len_up as usize); - for i in 1..1 + word_len_up { - let value = state.peek_memory(out_addr + i); - info_out.extend_from_slice(&value.to_le_bytes()); - } - info_out.truncate(byte_len as usize); - info_out -} From 9b0ac19e580a5cc01acc62b7f55666bdd5b9c11f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Mon, 16 Dec 2024 12:37:42 +0100 Subject: [PATCH 20/21] ecall-keccak-merge: generalize read_all_messages again --- ceno_emul/src/host_utils.rs | 17 +++++++---------- ceno_host/src/lib.rs | 2 +- ceno_host/tests/test_elf.rs | 12 ++++++++++-- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/ceno_emul/src/host_utils.rs b/ceno_emul/src/host_utils.rs index 43ef7b06b..5a5956317 100644 --- a/ceno_emul/src/host_utils.rs +++ b/ceno_emul/src/host_utils.rs @@ -5,7 +5,7 @@ use crate::{ByteAddr, EmuContext, VMState, WordAddr}; const WORD_SIZE: usize = 4; const INFO_OUT_ADDR: WordAddr = ByteAddr(0xC000_0000).waddr(); -pub fn read_all_messages(state: &VMState) -> Vec { +pub fn read_all_messages(state: &VMState) -> Vec> { let mut offset: WordAddr = WordAddr::from(0); from_fn(move || match read_message(state, offset) { out if out.is_empty() => None, @@ -17,16 +17,13 @@ pub fn read_all_messages(state: &VMState) -> Vec { .collect() } -fn read_message(state: &VMState, offset: WordAddr) -> String { +fn read_message(state: &VMState, offset: WordAddr) -> Vec { let out_addr = INFO_OUT_ADDR + offset; let byte_len = state.peek_memory(out_addr) as usize; - String::from_utf8_lossy( - &(out_addr + 1_usize..) - .map(|address| state.peek_memory(address)) - .flat_map(u32::to_le_bytes) - .take(byte_len) - .collect::>(), - ) - .to_string() + (out_addr + 1_usize..) + .map(|address| state.peek_memory(address)) + .flat_map(u32::to_le_bytes) + .take(byte_len) + .collect::>() } diff --git a/ceno_host/src/lib.rs b/ceno_host/src/lib.rs index 5e309de74..22afc4b98 100644 --- a/ceno_host/src/lib.rs +++ b/ceno_host/src/lib.rs @@ -123,7 +123,7 @@ impl CenoStdin { } } -pub fn run(platform: Platform, elf: &[u8], hints: &CenoStdin) -> Vec { +pub fn run(platform: Platform, elf: &[u8], hints: &CenoStdin) -> Vec> { let program = Program::load_elf(elf, u32::MAX).unwrap(); let platform = Platform { prog_data: Some(program.image.keys().copied().collect::>()), diff --git a/ceno_host/tests/test_elf.rs b/ceno_host/tests/test_elf.rs index 1a7354e07..7b665ddff 100644 --- a/ceno_host/tests/test_elf.rs +++ b/ceno_host/tests/test_elf.rs @@ -75,7 +75,7 @@ fn test_ceno_rt_io() -> Result<()> { let mut state = VMState::new(platform, Arc::new(program)); let _steps = run(&mut state)?; - let all_messages = read_all_messages(&state); + let all_messages = messages_to_strings(&read_all_messages(&state)); for msg in &all_messages { print!("{msg}"); } @@ -92,7 +92,8 @@ fn test_hints() -> Result<()> { hints.write(&1997_u32)?; hints.write(&1999_u32)?; - let all_messages = ceno_host::run(CENO_PLATFORM, ceno_examples::hints, &hints); + let all_messages = + messages_to_strings(&ceno_host::run(CENO_PLATFORM, ceno_examples::hints, &hints)); for (i, msg) in enumerate(&all_messages) { println!("{i}: {msg}"); } @@ -169,6 +170,13 @@ fn sample_keccak_f(count: usize) -> Vec> { .collect_vec() } +fn messages_to_strings(messages: &[Vec]) -> Vec { + messages + .iter() + .map(|msg| String::from_utf8_lossy(msg).to_string()) + .collect() +} + fn run(state: &mut VMState) -> Result> { let steps = state.iter_until_halt().collect::>>()?; eprintln!("Emulator ran for {} steps.", steps.len()); From 48081aee9a24b1cde1c657c07e0c9b5f5b55cfff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Nicolas?= Date: Mon, 16 Dec 2024 12:55:42 +0100 Subject: [PATCH 21/21] dummy-keccak: fix after merge --- ceno_emul/src/rv32im_encode.rs | 123 ------------------ ceno_emul/src/test_utils.rs | 2 +- .../instructions/riscv/dummy/dummy_ecall.rs | 5 +- ceno_zkvm/src/instructions/riscv/insn_base.rs | 4 +- 4 files changed, 5 insertions(+), 129 deletions(-) delete mode 100644 ceno_emul/src/rv32im_encode.rs diff --git a/ceno_emul/src/rv32im_encode.rs b/ceno_emul/src/rv32im_encode.rs deleted file mode 100644 index fc9d9e172..000000000 --- a/ceno_emul/src/rv32im_encode.rs +++ /dev/null @@ -1,123 +0,0 @@ -use crate::{InsnKind, rv32im::InsnFormat}; - -const MASK_4_BITS: u32 = 0xF; -const MASK_5_BITS: u32 = 0x1F; -const MASK_6_BITS: u32 = 0x3F; -const MASK_7_BITS: u32 = 0x7F; -const MASK_8_BITS: u32 = 0xFF; -const MASK_10_BITS: u32 = 0x3FF; -const MASK_12_BITS: u32 = 0xFFF; - -/// Generate bit encoding of a RISC-V instruction. -/// -/// Values `rs1`, `rs2` and `rd1` are 5-bit register indices, and `imm` is of -/// bit length depending on the requirements of the instruction format type. -/// -/// Fields not required by the instruction's format type are ignored, so one can -/// safely pass an arbitrary value for these, say 0. -pub const fn encode_rv32(kind: InsnKind, rs1: u32, rs2: u32, rd: u32, imm: u32) -> u32 { - match kind.codes().format { - InsnFormat::R => encode_r(kind, rs1, rs2, rd), - InsnFormat::I => encode_i(kind, rs1, rd, imm), - InsnFormat::S => encode_s(kind, rs1, rs2, imm), - InsnFormat::B => encode_b(kind, rs1, rs2, imm), - InsnFormat::U => encode_u(kind, rd, imm), - InsnFormat::J => encode_j(kind, rd, imm), - } -} - -pub const fn load_immediate(rd: u32, imm: u32) -> [u32; 2] { - [ - encode_rv32(InsnKind::LUI, 0, 0, rd, imm), - encode_rv32(InsnKind::ORI, rd, 0, rd, imm), - ] -} - -// R-Type -// 25 20 15 12 7 0 -// +------+-----+-----+--------+----+-------+ -// funct7 | rs2 | rs1 | funct3 | rd | opcode -const fn encode_r(kind: InsnKind, rs1: u32, rs2: u32, rd: u32) -> u32 { - let rs2 = rs2 & MASK_5_BITS; // 5-bits mask - let rs1 = rs1 & MASK_5_BITS; - let rd = rd & MASK_5_BITS; - let func7 = kind.codes().func7; - let func3 = kind.codes().func3; - let opcode = kind.codes().opcode; - func7 << 25 | rs2 << 20 | rs1 << 15 | func3 << 12 | rd << 7 | opcode -} - -// I-Type -// 20 15 12 7 0 -// +---------+-----+--------+----+-------+ -// imm[0:11] | rs1 | funct3 | rd | opcode -const fn encode_i(kind: InsnKind, rs1: u32, rd: u32, imm: u32) -> u32 { - let rs1 = rs1 & MASK_5_BITS; - let rd = rd & MASK_5_BITS; - let func3 = kind.codes().func3; - let opcode = kind.codes().opcode; - // SRLI/SRAI use a specialization of the I-type format with the shift type in imm[10]. - let is_arithmetic_right_shift = (matches!(kind, InsnKind::SRAI) as u32) << 10; - let imm = imm & MASK_12_BITS | is_arithmetic_right_shift; - imm << 20 | rs1 << 15 | func3 << 12 | rd << 7 | opcode -} - -// S-Type -// 25 20 15 12 7 0 -// +---------+-----+-----+--------+----------+-------+ -// imm[5:11] | rs2 | rs1 | funct3 | imm[0:4] | opcode -const fn encode_s(kind: InsnKind, rs1: u32, rs2: u32, imm: u32) -> u32 { - let rs2 = rs2 & MASK_5_BITS; - let rs1 = rs1 & MASK_5_BITS; - let func3 = kind.codes().func3; - let opcode = kind.codes().opcode; - let imm_lo = imm & MASK_5_BITS; - let imm_hi = (imm >> 5) & MASK_7_BITS; // 7-bits mask - imm_hi << 25 | rs2 << 20 | rs1 << 15 | func3 << 12 | imm_lo << 7 | opcode -} - -// B-Type -// 31 25 20 15 12 8 7 0 -// +-------+-----------+-----+-----+--------+----------+---------+-------+ -// imm[12] | imm[5:10] | rs2 | rs1 | funct3 | imm[1:4] | imm[11] | opcode -const fn encode_b(kind: InsnKind, rs1: u32, rs2: u32, imm: u32) -> u32 { - let rs2 = rs2 & MASK_5_BITS; - let rs1 = rs1 & MASK_5_BITS; - let func3 = kind.codes().func3; - let opcode = kind.codes().opcode; - let imm_1_4 = (imm >> 1) & MASK_4_BITS; // skip imm[0] - let imm_5_10 = (imm >> 5) & MASK_6_BITS; - ((imm >> 12) & 1) << 31 - | imm_5_10 << 25 - | rs2 << 20 - | rs1 << 15 - | func3 << 12 - | imm_1_4 << 8 - | ((imm >> 11) & 1) << 7 - | opcode -} - -// J-Type -// 31 21 20 12 7 0 -// +-------+-----------+---------+------------+----+-------+ -// imm[20] | imm[1:10] | imm[11] | imm[12:19] | rd | opcode -const fn encode_j(kind: InsnKind, rd: u32, imm: u32) -> u32 { - let rd = rd & MASK_5_BITS; - let opcode = kind.codes().opcode; - let imm_1_10 = (imm >> 1) & MASK_10_BITS; // skip imm[0] - let imm_12_19 = (imm >> 12) & MASK_8_BITS; - ((imm >> 20) & 1) << 31 - | imm_1_10 << 21 - | ((imm >> 11) & 1) << 20 - | imm_12_19 << 12 - | rd << 7 - | opcode -} - -// U-Type -// 12 7 0 -// +----------+----+--------+ -// imm[12:31] | rd | opcode -const fn encode_u(kind: InsnKind, rd: u32, imm: u32) -> u32 { - (imm >> 12) << 12 | (rd & MASK_5_BITS) << 7 | kind.codes().opcode -} diff --git a/ceno_emul/src/test_utils.rs b/ceno_emul/src/test_utils.rs index 0343d9534..07a5a817c 100644 --- a/ceno_emul/src/test_utils.rs +++ b/ceno_emul/src/test_utils.rs @@ -7,7 +7,7 @@ use anyhow::Result; pub fn keccak_step() -> (StepRecord, Vec) { let instructions = vec![ // Call Keccak-f. - load_immediate(Platform::reg_arg0() as u32, CENO_PLATFORM.ram.start), + load_immediate(Platform::reg_arg0() as u32, CENO_PLATFORM.heap.start), load_immediate(Platform::reg_ecall() as u32, KECCAK_PERMUTE), encode_rv32(InsnKind::ECALL, 0, 0, 0, 0), // Halt. diff --git a/ceno_zkvm/src/instructions/riscv/dummy/dummy_ecall.rs b/ceno_zkvm/src/instructions/riscv/dummy/dummy_ecall.rs index 4b1b4f15e..cd3156387 100644 --- a/ceno_zkvm/src/instructions/riscv/dummy/dummy_ecall.rs +++ b/ceno_zkvm/src/instructions/riscv/dummy/dummy_ecall.rs @@ -17,7 +17,6 @@ use crate::{ set_val, witness::LkMultiplicity, }; -use core::mem::MaybeUninit; trait EcallSpec { const NAME: &'static str; @@ -51,7 +50,7 @@ impl Instruction for LargeEcallDummy { fn construct_circuit(cb: &mut CircuitBuilder) -> Result { let dummy_insn = DummyConfig::construct_circuit( cb, - InsnKind::EANY, + InsnKind::ECALL, true, // Read the ecall function code. false, false, @@ -97,7 +96,7 @@ impl Instruction for LargeEcallDummy { fn assign_instance( config: &Self::InstructionConfig, - instance: &mut [MaybeUninit], + instance: &mut [E::BaseField], lk_multiplicity: &mut LkMultiplicity, step: &StepRecord, ) -> Result<(), ZKVMError> { diff --git a/ceno_zkvm/src/instructions/riscv/insn_base.rs b/ceno_zkvm/src/instructions/riscv/insn_base.rs index cc10f0ea7..057a27bb5 100644 --- a/ceno_zkvm/src/instructions/riscv/insn_base.rs +++ b/ceno_zkvm/src/instructions/riscv/insn_base.rs @@ -227,7 +227,7 @@ impl WriteRD { pub fn assign_op( &self, - instance: &mut [MaybeUninit], + instance: &mut [E::BaseField], lk_multiplicity: &mut LkMultiplicity, cycle: Cycle, op: &WriteOp, @@ -348,7 +348,7 @@ impl WriteMEM { pub fn assign_op( &self, - instance: &mut [MaybeUninit], + instance: &mut [F], lk_multiplicity: &mut LkMultiplicity, cycle: Cycle, op: &WriteOp,